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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 ----
|
||||||
|
|||||||
Reference in New Issue
Block a user