Files
Voiced/src/pdf/render/render.worker.ts
T
xavierk afbc0aa56f 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.
2026-10-04 13:58:06 +05:30

66 lines
2.3 KiB
TypeScript

// Browser-only module worker: the one place react-pdf runs in the app. Not importable from Node tests.
import { installStreamAsyncIterator } from "../../lib/streamPolyfill";
import { initBrowserRendering } from "./browserInit";
import { runAssembleJob } from "./assembleJob";
import { renderInvoicePdf } from "./invoice";
import { registerJobFonts } from "./userFontJob";
import type { WorkerMessage, WorkerRequest } from "./jobs";
interface WorkerScope {
onmessage: ((event: MessageEvent<WorkerRequest>) => void) | null;
postMessage(message: WorkerMessage, options?: { transfer: Transferable[] }): void;
}
const scope = self as unknown as WorkerScope;
installStreamAsyncIterator();
const post = (message: WorkerMessage, transfer: Transferable[] = []) => scope.postMessage(message, { transfer });
const failure = (id: number | null, err: unknown): WorkerMessage => ({
type: "error",
id,
message: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,
});
// Handing a buffer over instead of copying it only works when the view covers all of it.
const whole = (b: Uint8Array) => b.byteOffset === 0 && b.byteLength === b.buffer.byteLength;
const started = initBrowserRendering();
scope.onmessage = async (event) => {
const request = event.data;
if (request?.type === "assemble-images") {
const { job } = request;
try {
const out = await runAssembleJob(job);
const bytes = whole(out.bytes) ? out.bytes : out.bytes.slice();
post({ type: "assembled", result: { ...out, bytes } }, [bytes.buffer]);
} catch (err) {
post(failure(job.id, err));
}
return;
}
if (request?.type !== "render") return;
const { job } = request;
try {
await started.catch(() => initBrowserRendering());
await registerJobFonts(job.prefs, job.userFonts);
const t0 = performance.now();
const out = await renderInvoicePdf({ model: job.model, prefs: job.prefs });
const ms = Math.round(performance.now() - t0);
const bytes = whole(out.bytes) ? out.bytes : out.bytes.slice();
post(
{ type: "result", result: { id: job.id, bytes, pages: out.pages, issues: out.issues, fingerprint: out.fingerprint, ms } },
[bytes.buffer],
);
} catch (err) {
post(failure(job.id, err));
}
};
started.then(
() => post({ type: "ready" }),
(err) => post(failure(null, err)),
);