Add Rust logo pipeline: trim, metrics, knockout and Branding settings

Logos (PNG, JPEG, WebP) are decoded, trimmed to their ink box, downscaled
to 1200 px and re-encoded as a content-addressed print PNG plus a white
knockout variant when the logo has alpha; aspect, ink density, mean colour
and kind (wordmark, mark, tall) are stored with the settings and frozen in
the issue snapshot. Legacy logos are derived at startup. The Test Vendor logo
PDF shrinks from 334 KB to 92 KB. Settings gains a Branding section with
white and dark previews and a business-name toggle (migration M5).
This commit is contained in:
2026-10-04 06:31:30 +05:30
parent 36aeaa6bce
commit bd83326d54
27 changed files with 1615 additions and 37 deletions
+5 -8
View File
@@ -5,13 +5,10 @@ import { vendorFromSnapshot } from "../pdf/model/vendor";
import { Superseded } from "../pdf/render/client";
import type { RenderResult } from "../pdf/render/jobs";
import { api } from "./api";
import { imageSizeFromDataUri } from "./imageSize";
import { loadLogoImages, logoForModel, type LogoImages } from "./logo";
import { getRenderClient, nextRenderJobId } from "./renderClient";
import type { BankAccount, Invoice, Settings } from "./types";
/** A logo with no readable size is fitted as if it filled Classic's 170 x 54 box. */
const UNKNOWN_LOGO_SIZE = { width: 170, height: 54 };
/**
* The render model for an invoice whose images are already loaded (the live preview caches them).
* Supplier and bank come from the snapshots frozen at issue; live settings are only a fallback.
@@ -20,14 +17,14 @@ export function modelFromAssets(
invoice: Invoice,
settings: Settings,
bank: BankAccount | null,
logoUri: string | null,
logoImages: LogoImages | null,
signatureDataUri: string | null,
): RenderModel {
return buildRenderModel({
invoice,
vendor: settings,
bank,
logo: logoUri ? { dataUri: logoUri, ...(imageSizeFromDataUri(logoUri) ?? UNKNOWN_LOGO_SIZE) } : null,
logo: logoForModel(vendorFromSnapshot(invoice.vendorSnapshot, settings), logoImages),
signatureDataUri,
appVersion: pkg.version,
});
@@ -36,9 +33,9 @@ export function modelFromAssets(
/** Loads the invoice's logo and signature, then builds its render model. */
export async function buildModelForInvoice(invoice: Invoice, settings: Settings, bank: BankAccount | null): Promise<RenderModel> {
const vendor = vendorFromSnapshot(invoice.vendorSnapshot, settings);
const logoUri = vendor.logoPath ? await api.readAssetDataUri(vendor.logoPath) : null;
const logoImages = await loadLogoImages(vendor, api.readAssetDataUri);
const signatureDataUri = invoice.signaturePath ? await api.readAssetDataUri(invoice.signaturePath) : null;
return modelFromAssets(invoice, settings, bank, logoUri, signatureDataUri);
return modelFromAssets(invoice, settings, bank, logoImages, signatureDataUri);
}
/** Page setup for an invoice: what was frozen at issue, else the user's current default. */