fix(printer): make PDF margins match resume background color (#2770)

* fix(printer): match PDF margins to resume background color

* fix(printer): preserve vertical margins when content wraps pages
This commit is contained in:
François Martin
2026-03-04 20:38:02 +01:00
committed by GitHub
parent 4825eed22c
commit df81d0342f
+68 -19
View File
@@ -99,15 +99,15 @@ export const printerService = {
const token = generatePrinterToken(id);
const url = `${baseUrl}/printer/${id}?token=${token}`;
// Step 3: Calculate PDF margins
// Some templates require margins to be applied via PDF (they use print:p-0 to remove CSS padding)
// Convert from CSS pixels to PDF points (divide by 0.75 since 1pt = 0.75px at 72dpi)
let marginX = 0;
let marginY = 0;
// Step 3: Calculate print paddings for templates that disable CSS padding in print mode.
// We render these margins inside the page (not via Puppeteer's PDF margins), so the margin
// area matches the resume background color instead of staying white.
let pagePaddingX = 0;
let pagePaddingY = 0;
if (printMarginTemplates.includes(template)) {
marginX = Math.round(data.metadata.page.marginX / 0.75);
marginY = Math.round(data.metadata.page.marginY / 0.75);
pagePaddingX = data.metadata.page.marginX;
pagePaddingY = data.metadata.page.marginY;
}
let browser: Browser | null = null;
@@ -135,21 +135,65 @@ export const printerService = {
const isFreeForm = format === "free-form";
const contentHeight = await page.evaluate(
(marginY: number, isFreeForm: boolean, minPageHeight: number) => {
(
pagePaddingX: number,
pagePaddingY: number,
isFreeForm: boolean,
minPageHeight: number,
backgroundColor: string,
) => {
const root = document.documentElement;
const body = document.body;
const pageElements = document.querySelectorAll("[data-page-index]");
const pageContentElements = document.querySelectorAll(".page-content");
const container = document.querySelector(".resume-preview-container") as HTMLElement | null;
// Ensure PDF margins inherit the resume background color instead of defaulting to white.
root.style.backgroundColor = backgroundColor;
body.style.backgroundColor = backgroundColor;
root.style.margin = "0";
body.style.margin = "0";
root.style.padding = "0";
body.style.padding = "0";
for (const el of pageElements) {
const pageWrapper = el as HTMLElement;
const pageSurface = pageWrapper.querySelector(".page") as HTMLElement | null;
pageWrapper.style.backgroundColor = backgroundColor;
pageWrapper.style.breakInside = "auto";
if (pageSurface) pageSurface.style.backgroundColor = backgroundColor;
}
// Apply print-only margins as padding inside each page's content surface.
if (pagePaddingX > 0 || pagePaddingY > 0) {
for (const el of pageContentElements) {
const pageContent = el as HTMLElement;
pageContent.style.boxSizing = "border-box";
// Ensure padding is repeated on every printed fragment when content
// flows across physical PDF pages (not just the first fragment).
pageContent.style.boxDecorationBreak = "clone";
pageContent.style.setProperty("-webkit-box-decoration-break", "clone");
if (pagePaddingX > 0) {
pageContent.style.paddingLeft = `${pagePaddingX}pt`;
pageContent.style.paddingRight = `${pagePaddingX}pt`;
}
if (pagePaddingY > 0) {
pageContent.style.paddingTop = `${pagePaddingY}pt`;
pageContent.style.paddingBottom = `${pagePaddingY}pt`;
}
}
}
if (isFreeForm) {
// For free-form: add visual gaps between pages, then measure total height
// Convert marginY from PDF points to CSS pixels (1pt = 0.75px)
const marginYAsPixels = marginY * 0.75;
const numberOfPages = pageElements.length;
// Add margin between pages (except the last one)
for (let i = 0; i < numberOfPages - 1; i++) {
const pageEl = pageElements[i] as HTMLElement;
pageEl.style.marginBottom = `${marginYAsPixels}px`;
if (pagePaddingY > 0) pageEl.style.marginBottom = `${pagePaddingY}pt`;
}
// Now measure the total height (margins are now part of the DOM)
@@ -169,8 +213,8 @@ export const printerService = {
// For A4/Letter
const heightValue = minPageHeight;
// Subtract top + bottom margins from page height
const newHeight = `${heightValue - marginY}px`;
// Keep page height fixed and let in-page padding (if any) define content bounds.
const newHeight = `${heightValue}px`;
if (container) container.style.setProperty("--page-height", newHeight);
root.style.setProperty("--page-height", newHeight);
@@ -181,7 +225,10 @@ export const printerService = {
const index = Number.parseInt(element.getAttribute("data-page-index") ?? "0", 10);
// Force a page break before each page except the first
if (index > 0) element.style.breakBefore = "page";
if (index > 0) {
element.style.breakBefore = "page";
element.style.pageBreakBefore = "always";
}
// Allow content within a page to break naturally if it overflows
// (e.g., if a single page has more content than fits on one PDF page)
@@ -190,9 +237,11 @@ export const printerService = {
return null; // Fixed height from pageDimensionsAsPixels for A4/Letter
},
marginY,
pagePaddingX,
pagePaddingY,
isFreeForm,
pageDimensionsAsPixels[format].height,
data.metadata.design.colors.background,
);
// Step 6: Generate the PDF with the specified dimensions and margins
@@ -208,9 +257,9 @@ export const printerService = {
printBackground: true, // Includes background colors and images
margin: {
bottom: 0,
top: marginY,
right: marginX,
left: marginX,
top: 0,
right: 0,
left: 0,
},
});