Add font import for Now, Gotham and Open Sauce One overrides; package fonts for Void

Font import:
- Migration M6 adds user_fonts; files are stored content-addressed under
  the local data dir. New Rust commands inspect, import, list, read and
  remove fonts, refusing WOFF2, collections, variable fonts and
  restricted fsType, and unpacking WOFF1 to plain sfnt.
- Imported fonts register in the render worker as user:<Face> families
  with an italic alias, and are pinned in the frozen render prefs so
  issued invoices can still re-render. Fonts referenced by issued
  invoices are hidden, never deleted.
- Size and tracking compensations tuned for the substitutes apply only
  while the face is not overridden; default output and fingerprint
  goldens are unchanged.
- Settings gets a Fonts section with a licence acknowledgement.

Void packaging:
- Install fonts to /usr/share/fonts/voiced with per-family OFL licences,
  set font_dirs, and add INSTALL/REMOVE scripts that run fc-cache.
  xbps-create carries both scripts (verified in a void-glibc container).
- Fix xbps-create dependency specs in build-xbps.sh.
This commit is contained in:
2026-10-04 13:58:06 +05:30
parent 4d18a50753
commit afbc0aa56f
37 changed files with 2436 additions and 72 deletions
+17
View File
@@ -10,6 +10,7 @@ import type {
Settings,
} from "./types";
import type { LogoAsset } from "./logo";
import type { FontInspection, ImportMeta, RemoveOutcome, UserFontRow } from "./fontImport";
export interface ArchiveStatus {
archived: boolean;
@@ -70,6 +71,22 @@ export const api = {
readArchive: async (invoiceId: number): Promise<Uint8Array> =>
new Uint8Array(await invoke<ArrayBuffer>("read_archive", { invoiceId })),
archiveStatus: (invoiceId: number) => invoke<ArchiveStatus>("archive_status", { invoiceId }),
// Imported fonts (Now, Gotham, Open Sauce One). The font bytes are the raw body; metadata goes in headers.
inspectFont: (bytes: Uint8Array) => invoke<FontInspection>("inspect_font", bytes),
importFont: (bytes: Uint8Array, meta: ImportMeta) =>
invoke<UserFontRow>("import_font", bytes, {
headers: {
"x-face": encodeURIComponent(meta.face),
"x-file-name": encodeURIComponent(meta.fileName),
"x-licence-ack": String(meta.licenceAcknowledged),
...(meta.weight !== undefined ? { "x-weight": String(meta.weight) } : {}),
...(meta.italic !== undefined ? { "x-style": meta.italic ? "italic" : "normal" } : {}),
},
}),
listUserFonts: (includeHidden = false) => invoke<UserFontRow[]>("list_user_fonts", { includeHidden }),
getUserFontBytes: async (sha256: string): Promise<Uint8Array> =>
new Uint8Array(await invoke<ArrayBuffer>("get_user_font_bytes", { sha256 })),
removeUserFont: (id: number) => invoke<RemoveOutcome>("remove_user_font", { id }),
writeExportFile: (path: string, bytes: Uint8Array) =>
invoke<string>("write_export_file", bytes, { headers: { "x-path": encodeURIComponent(path) } }),
getLastExportDir: () => invoke<string>("get_last_export_dir"),