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.
66 lines
2.3 KiB
TypeScript
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)),
|
|
);
|