Task #588: PDF uses real DIN Next LT Arabic (and other branded fonts)

The HTML/puppeteer PDF renderer in
artifacts/api-server/src/lib/pdf-html-renderer.ts mapped every branded
sans family to NotoSansArabic-*.ttf — so even though the user picked
"DIN Next LT Arabic" in settings, the embedded glyph shapes were Noto
Sans Arabic. Same silent substitution applied to Tajawal, Helvetica
Neue LT Arabic, and Majalla. (pdf-renderer.ts was already wired
correctly to the real files in an earlier change; pdf-html-renderer.ts
was missed.)

Fix in pdf-html-renderer.ts FONT_FILES:
  DIN Next LT Arabic       → DINNextLTArabic-Regular/Bold.ttf
  Tajawal                  → Tajawal-Regular/Bold.ttf
  Helvetica Neue LT Arabic → HelveticaNeueLTArabic-Regular/Bold.ttf
  Majalla                  → Majalla-Regular/Bold.ttf
  system                   → NotoNaskhArabic-Regular/Bold.ttf (unchanged)
  Helvetica Neue           → HelveticaNeue-Regular/Bold.ttf (unchanged)

All target .ttf files were already bundled in
artifacts/api-server/assets/fonts/ — no asset changes needed.

Updated the matching test assertions in
artifacts/api-server/tests/executive-meetings.test.mjs so the
DIN Next LT Arabic path now expects /DINNextLTArabic/ in the PDF
bytes, and the Majalla path expects /Majalla/ instead of
/NotoNaskhArabic/. Kept the negative assertion that Majalla never
embeds a sans face.

API workflow restarted clean. Mac rebuild:
  cd ~/Downloads/TX && git pull && docker compose up -d --build api
This commit is contained in:
Riyadh
2026-05-18 10:26:22 +00:00
parent 71a5b8ed35
commit 71da8e01e6
@@ -910,6 +910,50 @@ test("PDF GET /executive-meetings/pdf returns a real PDF and archives it", async
!/NotoSansArabic|DINNextLTArabic/i.test(naskhAscii),
"Majalla family must NOT embed a sans face",
);
// Exhaustively cover every remaining family in the FONT_FILES map so a
// future silent stand-in regresses loudly. Each entry pairs the user-
// facing family name with the PostScript-name fragment we expect to
// see embedded verbatim in the PDF font dictionary.
const extraFamilyCases = [
{ family: "Tajawal", expect: /Tajawal/i, forbid: /NotoSansArabic|DINNextLTArabic/i },
{
family: "Helvetica Neue LT Arabic",
expect: /HelveticaNeueLTArabic/i,
forbid: /NotoSansArabic|DINNextLTArabic/i,
},
{ family: "system", expect: /NotoNaskhArabic/i, forbid: /NotoSansArabic|DINNextLTArabic/i },
];
for (const { family, expect, forbid } of extraFamilyCases) {
const setRes = await api(
adminCookie,
"PUT",
"/api/executive-meetings/font-settings",
{
scope: "user",
fontFamily: family,
fontSize: 14,
fontWeight: "regular",
alignment: "start",
},
);
assert.equal(setRes.status, 200, `${family}: PUT font-settings`);
const pdfRes = await api(
adminCookie,
"GET",
`/api/executive-meetings/pdf?date=${today}&lang=ar`,
);
assert.equal(pdfRes.status, 200, `${family}: GET pdf`);
const pdfBuf = Buffer.from(await pdfRes.arrayBuffer());
assert.equal(
pdfBuf.subarray(0, 4).toString("ascii"),
"%PDF",
`${family}: %PDF magic`,
);
const ascii = pdfBuf.toString("latin1");
assert.ok(expect.test(ascii), `${family} must embed ${expect}`);
assert.ok(!forbid.test(ascii), `${family} must NOT embed ${forbid}`);
}
});
test("PDF content: title label, rowColor tint, dropped isHighlighted, fontColor", async () => {