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.
46 lines
2.5 KiB
TypeScript
46 lines
2.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { DEFAULT_PREFS } from "../pdf/model/prefs";
|
|
import { clientRefusal, selectionFrom, withUserFonts, type UserFontRow } from "./fontImport";
|
|
|
|
const row = (id: number, face: UserFontRow["face"], sha: string, over: Partial<UserFontRow> = {}): UserFontRow => ({
|
|
id, face, familyName: "F", fullName: "F", styleName: "", weight: 400, style: "normal", sha256: sha, fileName: "f.ttf",
|
|
format: "ttf", size: 1, fsType: 0, hasRupee: true, licenceAckAt: "t", importedAt: "t", hidden: false, ...over,
|
|
});
|
|
const A = "a".repeat(64);
|
|
const B = "b".repeat(64);
|
|
const C = "c".repeat(64);
|
|
|
|
describe("clientRefusal", () => {
|
|
const bytes = (magic: string) => new Uint8Array([...magic].map((c) => c.charCodeAt(0)).concat(new Array(20).fill(0)));
|
|
it("passes TTF, OTF and WOFF and refuses the rest with a reason", () => {
|
|
expect(clientRefusal(new Uint8Array([0, 1, 0, 0, 0, 0, 0, 0]))).toBeNull();
|
|
expect(clientRefusal(bytes("OTTO"))).toBeNull();
|
|
expect(clientRefusal(bytes("wOFF"))).toBeNull();
|
|
expect(clientRefusal(bytes("wOF2"))).toMatch(/WOFF2/);
|
|
expect(clientRefusal(bytes("ttcf"))).toMatch(/collections/);
|
|
expect(clientRefusal(bytes("<htm"))).toMatch(/not a TrueType/);
|
|
});
|
|
});
|
|
|
|
describe("withUserFonts", () => {
|
|
const fonts = [row(1, "Gotham", B), row(2, "Gotham", A, { weight: 700 }), row(3, "Now", C), row(4, "Now", "d".repeat(64), { hidden: true })];
|
|
|
|
it("lists visible fonts per face", () => {
|
|
expect(selectionFrom(fonts)).toEqual({ Gotham: [B, A], Now: [C] });
|
|
});
|
|
|
|
it("adds only the faces the template draws, sorted, and nothing for a template without them", () => {
|
|
expect(withUserFonts({ ...DEFAULT_PREFS, templateId: "purple-pop" }, fonts).fonts).toEqual({ Gotham: [A, B] });
|
|
expect(withUserFonts({ ...DEFAULT_PREFS, templateId: "citrus-split" }, fonts).fonts).toEqual({ Now: [C], Gotham: [A, B] });
|
|
expect(withUserFonts({ ...DEFAULT_PREFS, templateId: "marble" }, fonts).fonts).toEqual({ Now: [C] });
|
|
expect(withUserFonts({ ...DEFAULT_PREFS, templateId: "classic" }, fonts)).toEqual({ ...DEFAULT_PREFS, templateId: "classic" });
|
|
expect(withUserFonts({ ...DEFAULT_PREFS, templateId: "marble" }, [])).toEqual({ ...DEFAULT_PREFS, templateId: "marble" });
|
|
});
|
|
|
|
it("replaces fonts already in the prefs rather than merging", () => {
|
|
const stale = { ...DEFAULT_PREFS, templateId: "marble", fonts: { Now: ["e".repeat(64)] } };
|
|
expect(withUserFonts(stale, fonts).fonts).toEqual({ Now: [C] });
|
|
expect(withUserFonts(stale, []).fonts).toBeUndefined();
|
|
});
|
|
});
|