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 d955130cf3
commit 43afdaf4bf
37 changed files with 2436 additions and 72 deletions
+8 -2
View File
@@ -64,6 +64,8 @@ import { issueAndArchive, renderAndArchive } from "../lib/exportFlow";
import { loadExportMode } from "../lib/exportMode";
import type { ExportMode } from "../lib/filename";
import { parsePrefs, type RenderPrefsV1 } from "../pdf/model/prefs";
import { useUserFonts } from "../hooks/useUserFonts";
import { withUserFonts } from "../lib/fontImport";
import { ImagePicker } from "../components/ImagePicker";
import { loadLogoImages, type LogoImages } from "../lib/logo";
@@ -354,7 +356,11 @@ export default function NewInvoice({ settings, onSettingsChange, active, onActiv
// Page setup for the invoice being edited; an issued invoice shows what was frozen at issue.
const [prefs, setPrefs] = useState<RenderPrefsV1>(() => parsePrefs(settings.renderPrefs));
useEffect(() => setPrefs(parsePrefs(settings.renderPrefs)), [settings.renderPrefs]);
const activePrefs = useMemo(() => (issued ? prefsForInvoice(issued, settings) : prefs), [issued, prefs, settings]);
// setupPrefs is the page setup as edited (and saved as the default); activePrefs adds the imported fonts the template
// draws, which is what renders and what is frozen on the invoice at issue. An issued invoice keeps its frozen fonts.
const userFonts = useUserFonts();
const setupPrefs = useMemo(() => (issued ? prefsForInvoice(issued, settings) : prefs), [issued, prefs, settings]);
const activePrefs = useMemo(() => (issued ? setupPrefs : withUserFonts(setupPrefs, userFonts)), [issued, setupPrefs, userFonts]);
// The preview renders the real PDF from a draft-status Invoice built from the form. It is skipped
// while this view is hidden (it stays mounted) so typing elsewhere never starts a render.
@@ -1038,7 +1044,7 @@ export default function NewInvoice({ settings, onSettingsChange, active, onActiv
<Column sm={4} md={8} lg={7}>
<div style={{ position: "sticky", top: "3rem" }}>
<h3 style={{ marginBottom: "0.75rem" }}>Preview</h3>
<PageSetupControls prefs={activePrefs} onChange={setPrefs} settings={settings} onSaved={onSettingsChange} disabled={locked} />
<PageSetupControls prefs={setupPrefs} onChange={setPrefs} settings={settings} onSaved={onSettingsChange} disabled={locked} />
<Suspense fallback={<InlineLoading description="Loading preview…" />}>
<PdfPreview
bytes={pdf.bytes}