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.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 32f0ec38-20de-4de2-916d-1b5b97281170
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/Ow4s0aa
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
riyadhafraa
2026-05-04 06:19:52 +00:00
parent d308b0a5ac
commit f9d05421f6
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",
latinRegular: "DejaVuSans.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;
type FontFileKey = keyof typeof FONT_FILES;
@@ -163,15 +173,9 @@ type FamilyMapping = {
latinBold: FontFileKey;
};
// Web fonts (DIN Next LT Arabic, Tajawal, Helvetica Neue LT Arabic,
// Helvetica Neue, Majalla) are not bundled into the PDF renderer
// because we do not ship their font files server-side. Instead we map
// 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.
// Every user-facing font family now maps to its actual bundled font
// files. The user uploaded all font assets so the PDF renders with the
// exact typeface chosen in settings — no more Noto stand-ins.
const FAMILY_MAP: Record<string, FamilyMapping> = {
system: {
arabicRegular: "naskhRegular",
@@ -180,34 +184,34 @@ const FAMILY_MAP: Record<string, FamilyMapping> = {
latinBold: "latinBold",
},
"DIN Next LT Arabic": {
arabicRegular: "arabicSansRegular",
arabicBold: "arabicSansBold",
latinRegular: "latinRegular",
latinBold: "latinBold",
arabicRegular: "dinNextRegular",
arabicBold: "dinNextBold",
latinRegular: "dinNextRegular",
latinBold: "dinNextBold",
},
Tajawal: {
arabicRegular: "arabicSansRegular",
arabicBold: "arabicSansBold",
latinRegular: "latinRegular",
latinBold: "latinBold",
arabicRegular: "tajawalRegular",
arabicBold: "tajawalBold",
latinRegular: "tajawalRegular",
latinBold: "tajawalBold",
},
"Helvetica Neue LT Arabic": {
arabicRegular: "arabicSansRegular",
arabicBold: "arabicSansBold",
latinRegular: "latinRegular",
latinBold: "latinBold",
arabicRegular: "helvNeueLTArRegular",
arabicBold: "helvNeueLTArBold",
latinRegular: "helvNeueLTArRegular",
latinBold: "helvNeueLTArBold",
},
"Helvetica Neue": {
arabicRegular: "naskhRegular",
arabicBold: "naskhBold",
latinRegular: "latinRegular",
latinBold: "latinBold",
latinRegular: "helvNeueRegular",
latinBold: "helvNeueBold",
},
Majalla: {
arabicRegular: "naskhRegular",
arabicBold: "naskhBold",
latinRegular: "latinRegular",
latinBold: "latinBold",
arabicRegular: "majallaRegular",
arabicBold: "majallaBold",
latinRegular: "majallaRegular",
latinBold: "majallaBold",
},
};
@@ -658,36 +662,46 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
function drawFooter(): void {
const longDate = formatLongDate(input.date);
const labelSize = Math.max(11, Math.round(input.font.fontSize * 0.95));
const dateSize = Math.max(11, Math.round(input.font.fontSize * 0.95));
const footerHeight = labelSize * 1.4 + dateSize * 1.4;
const sz = Math.max(11, Math.round(input.font.fontSize * 0.95));
const lineStep = sz * 1.5;
const footerHeight = lineStep * 2 + 4;
const footerWidth =
doc.page.width - doc.page.margins.left - doc.page.margins.right;
const footerY = doc.page.height - doc.page.margins.bottom - footerHeight;
const footerAlign: "left" | "right" = baseDir === "rtl" ? "right" : "left";
drawMixedLine(doc, input.labels.recordedBy, {
x: doc.page.margins.left,
y: footerY,
// Temporarily zero-out the bottom margin AND save/restore doc.y so
// 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,
fontSize: labelSize,
weight: "bold",
align: footerAlign,
baseDirection: baseDir,
mapping,
lineBreak: false,
...(features ? { features } : {}),
});
drawMixedLine(doc, longDate, {
x: doc.page.margins.left,
y: footerY + labelSize * 1.4,
// --- Date line ("3 May 2026") ---
// 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,
fontSize: dateSize,
weight: "regular",
align: footerAlign,
// Force LTR for the date — the long-form date is always Latin
// ("3 May 2026"), so an RTL base direction would visually reorder
// the runs ("May 2026 3"). Alignment still mirrors the locale.
baseDirection: "ltr",
mapping,
lineBreak: false,
});
doc.page.margins.bottom = savedBottom;
doc.y = savedY;
}
// ---- Table layout ----