Fix glyph cache poisoning across renders; add CLAUDE.md and handoff.md
fontkit caches glyphs per font for the whole process and pdfkit embeds composite components with no code points, so setting ':' before '.' in Poppins corrupted every later render in the same process (text layer showed '1,200;00', line breaks shifted). primeFontGlyphs() now creates the component glyphs with their cmap code point at font registration, awaited by renderCore and browser init. Regression test and Quirk 13 added; the warm-up workarounds in the template tests are removed.
This commit is contained in:
+16
-4
@@ -4,7 +4,9 @@
|
||||
* fails and the design rule behind it must be revisited.
|
||||
*/
|
||||
import { createElement as h, type ReactElement, type ReactNode } from "react";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { create as createFont } from "fontkit";
|
||||
import { Font, renderToBuffer, Text, View } from "@react-pdf/renderer";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { auditLayout } from "./engine/audit";
|
||||
@@ -302,12 +304,22 @@ describe("react-pdf 4.9.0 quirks the engine relies on", () => {
|
||||
r.layout.forEach((p) => expect(byId(p, "label")[0].lines).toHaveLength(0));
|
||||
});
|
||||
});
|
||||
it("13. fontkit caches glyph objects per font for the whole process, and pdfkit creates a composite's component glyphs at embed time with no code points: Poppins' colon makes the period glyph keep codePoints [] (so a later document's '.' is not extracted and textkit's glyph-to-string mapping slips)", async () => {
|
||||
// Must stay the first test that sets Poppins Regular in this file: any earlier '.' in that face would create the period glyph normally.
|
||||
it("13. fontkit caches glyph objects per font for the whole process, and a subset encode (pdfkit's embed) creates a composite's component glyphs with no code points: Poppins' colon makes the period glyph keep codePoints [] unless the glyphs were created first, which primeFontGlyphs does for every registered face", async () => {
|
||||
// The mechanism, on a font object of our own so no earlier render in this file matters.
|
||||
const fresh = createFont(new Uint8Array(readFileSync(path.join(FONTS_DIR, "poppins/Poppins-Regular.ttf")))) as unknown as {
|
||||
glyphForCodePoint(cp: number): { codePoints: number[] };
|
||||
createSubset(): { includeGlyph(g: unknown): void; encode(): unknown };
|
||||
};
|
||||
const subset = fresh.createSubset();
|
||||
subset.includeGlyph(fresh.glyphForCodePoint(0x3a));
|
||||
subset.encode();
|
||||
expect(fresh.glyphForCodePoint(0x2e).codePoints).toEqual([]);
|
||||
|
||||
// The fix: rendering primes the registered faces first, so the colon in the first document leaves the period intact for the next one.
|
||||
const poppins = { fontFamily: stack("Poppins"), fontSize: 10, lineHeight: "12pt", fontWeight: 400 };
|
||||
await render(doc(h(Text, { style: poppins }, "Bank : State")));
|
||||
const data = (Font as unknown as { getFont: (d: object) => { data: { glyphForCodePoint: (cp: number) => { codePoints: number[] } } } }).getFont({ fontFamily: "Poppins", fontWeight: 400, fontStyle: "normal" }).data;
|
||||
expect(data.glyphForCodePoint(0x2e).codePoints).toEqual([]);
|
||||
// The Highlighter and Marble tests and the standalone sweep warm Poppins with a document that has amounts (period and comma) first, for this reason.
|
||||
expect(data.glyphForCodePoint(0x2e).codePoints).toEqual([0x2e]);
|
||||
// Text-layer extraction and every other face: src/pdf/fonts/glyphPriming.test.tsx.
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user