Use actual font files in PDF generation and fix footer display

Update pdf-renderer.ts to map specific font families to their corresponding files, and add the font files to the assets directory. Adjust footer height calculation to ensure proper display.
This commit is contained in:
Riyadh
2026-05-04 06:19:52 +00:00
parent 47385d43d3
commit 64583d09af
11 changed files with 61 additions and 47 deletions
Binary file not shown.
Binary file not shown.
+61 -47
View File
@@ -145,6 +145,16 @@ const FONT_FILES = {
arabicSansBold: "NotoSansArabic-Bold.ttf", arabicSansBold: "NotoSansArabic-Bold.ttf",
latinRegular: "DejaVuSans.ttf", latinRegular: "DejaVuSans.ttf",
latinBold: "DejaVuSans-Bold.ttf", latinBold: "DejaVuSans-Bold.ttf",
dinNextRegular: "DINNextLTArabic-Regular.ttf",
dinNextBold: "DINNextLTArabic-Bold.ttf",
tajawalRegular: "Tajawal-Regular.ttf",
tajawalBold: "Tajawal-Bold.ttf",
helvNeueLTArRegular: "HelveticaNeueLTArabic-Regular.ttf",
helvNeueLTArBold: "HelveticaNeueLTArabic-Bold.ttf",
helvNeueRegular: "HelveticaNeue-Regular.ttf",
helvNeueBold: "HelveticaNeue-Bold.ttf",
majallaRegular: "Majalla-Regular.ttf",
majallaBold: "Majalla-Bold.ttf",
} as const; } as const;
type FontFileKey = keyof typeof FONT_FILES; type FontFileKey = keyof typeof FONT_FILES;
@@ -163,15 +173,9 @@ type FamilyMapping = {
latinBold: FontFileKey; latinBold: FontFileKey;
}; };
// Web fonts (DIN Next LT Arabic, Tajawal, Helvetica Neue LT Arabic, // Every user-facing font family now maps to its actual bundled font
// Helvetica Neue, Majalla) are not bundled into the PDF renderer // files. The user uploaded all font assets so the PDF renders with the
// because we do not ship their font files server-side. Instead we map // exact typeface chosen in settings — no more Noto stand-ins.
// each web family to the closest visual stand-in already bundled here:
// sans-serif Arabic families render with NotoSansArabic; serif/Naskh
// stand-ins use NotoNaskhArabic; pure-Latin families (Helvetica Neue)
// fall back to the Naskh pair so Arabic glyphs in mixed strings still
// render correctly. This keeps PDFs readable without ballooning the
// server bundle.
const FAMILY_MAP: Record<string, FamilyMapping> = { const FAMILY_MAP: Record<string, FamilyMapping> = {
system: { system: {
arabicRegular: "naskhRegular", arabicRegular: "naskhRegular",
@@ -180,34 +184,34 @@ const FAMILY_MAP: Record<string, FamilyMapping> = {
latinBold: "latinBold", latinBold: "latinBold",
}, },
"DIN Next LT Arabic": { "DIN Next LT Arabic": {
arabicRegular: "arabicSansRegular", arabicRegular: "dinNextRegular",
arabicBold: "arabicSansBold", arabicBold: "dinNextBold",
latinRegular: "latinRegular", latinRegular: "dinNextRegular",
latinBold: "latinBold", latinBold: "dinNextBold",
}, },
Tajawal: { Tajawal: {
arabicRegular: "arabicSansRegular", arabicRegular: "tajawalRegular",
arabicBold: "arabicSansBold", arabicBold: "tajawalBold",
latinRegular: "latinRegular", latinRegular: "tajawalRegular",
latinBold: "latinBold", latinBold: "tajawalBold",
}, },
"Helvetica Neue LT Arabic": { "Helvetica Neue LT Arabic": {
arabicRegular: "arabicSansRegular", arabicRegular: "helvNeueLTArRegular",
arabicBold: "arabicSansBold", arabicBold: "helvNeueLTArBold",
latinRegular: "latinRegular", latinRegular: "helvNeueLTArRegular",
latinBold: "latinBold", latinBold: "helvNeueLTArBold",
}, },
"Helvetica Neue": { "Helvetica Neue": {
arabicRegular: "naskhRegular", arabicRegular: "naskhRegular",
arabicBold: "naskhBold", arabicBold: "naskhBold",
latinRegular: "latinRegular", latinRegular: "helvNeueRegular",
latinBold: "latinBold", latinBold: "helvNeueBold",
}, },
Majalla: { Majalla: {
arabicRegular: "naskhRegular", arabicRegular: "majallaRegular",
arabicBold: "naskhBold", arabicBold: "majallaBold",
latinRegular: "latinRegular", latinRegular: "majallaRegular",
latinBold: "latinBold", latinBold: "majallaBold",
}, },
}; };
@@ -658,36 +662,46 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
function drawFooter(): void { function drawFooter(): void {
const longDate = formatLongDate(input.date); const longDate = formatLongDate(input.date);
const labelSize = Math.max(11, Math.round(input.font.fontSize * 0.95)); const sz = Math.max(11, Math.round(input.font.fontSize * 0.95));
const dateSize = Math.max(11, Math.round(input.font.fontSize * 0.95)); const lineStep = sz * 1.5;
const footerHeight = labelSize * 1.4 + dateSize * 1.4; const footerHeight = lineStep * 2 + 4;
const footerWidth = const footerWidth =
doc.page.width - doc.page.margins.left - doc.page.margins.right; doc.page.width - doc.page.margins.left - doc.page.margins.right;
const footerY = doc.page.height - doc.page.margins.bottom - footerHeight; const footerY = doc.page.height - doc.page.margins.bottom - footerHeight;
const footerAlign: "left" | "right" = baseDir === "rtl" ? "right" : "left"; const footerAlign: "left" | "right" = baseDir === "rtl" ? "right" : "left";
drawMixedLine(doc, input.labels.recordedBy, {
x: doc.page.margins.left, // Temporarily zero-out the bottom margin AND save/restore doc.y so
y: footerY, // PDFKit never auto-paginates when we render near the page bottom.
const savedBottom = doc.page.margins.bottom;
const savedY = doc.y;
doc.page.margins.bottom = 0;
// --- Label line ("مقيد" / "Recorded by") ---
const labelIsArabic = [...input.labels.recordedBy].filter(isArabicChar).length * 2 > input.labels.recordedBy.length;
const labelFont = fontKeyFor(labelIsArabic, "bold", mapping);
const features = featuresFor(labelIsArabic);
doc.font(labelFont).fontSize(sz).fillColor("black");
doc.text(input.labels.recordedBy, doc.page.margins.left, footerY, {
width: footerWidth, width: footerWidth,
fontSize: labelSize,
weight: "bold",
align: footerAlign, align: footerAlign,
baseDirection: baseDir, lineBreak: false,
mapping, ...(features ? { features } : {}),
}); });
drawMixedLine(doc, longDate, {
x: doc.page.margins.left, // --- Date line ("3 May 2026") ---
y: footerY + labelSize * 1.4, // Reset doc.y BEFORE the second call so PDFKit doesn't think
// we've exceeded the page boundary.
doc.y = footerY;
const dateFont = fontKeyFor(false, "regular", mapping);
doc.font(dateFont).fontSize(sz).fillColor("black");
doc.text(longDate, doc.page.margins.left, footerY + lineStep, {
width: footerWidth, width: footerWidth,
fontSize: dateSize,
weight: "regular",
align: footerAlign, align: footerAlign,
// Force LTR for the date — the long-form date is always Latin lineBreak: false,
// ("3 May 2026"), so an RTL base direction would visually reorder
// the runs ("May 2026 3"). Alignment still mirrors the locale.
baseDirection: "ltr",
mapping,
}); });
doc.page.margins.bottom = savedBottom;
doc.y = savedY;
} }
// ---- Table layout ---- // ---- Table layout ----