Skip to content

Commit df81d03

Browse files
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
1 parent 4825eed commit df81d03

File tree

1 file changed

+68
-19
lines changed

1 file changed

+68
-19
lines changed

src/integrations/orpc/services/printer.ts

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,15 @@ export const printerService = {
9999
const token = generatePrinterToken(id);
100100
const url = `${baseUrl}/printer/${id}?token=${token}`;
101101

102-
// Step 3: Calculate PDF margins
103-
// Some templates require margins to be applied via PDF (they use print:p-0 to remove CSS padding)
104-
// Convert from CSS pixels to PDF points (divide by 0.75 since 1pt = 0.75px at 72dpi)
105-
let marginX = 0;
106-
let marginY = 0;
102+
// Step 3: Calculate print paddings for templates that disable CSS padding in print mode.
103+
// We render these margins inside the page (not via Puppeteer's PDF margins), so the margin
104+
// area matches the resume background color instead of staying white.
105+
let pagePaddingX = 0;
106+
let pagePaddingY = 0;
107107

108108
if (printMarginTemplates.includes(template)) {
109-
marginX = Math.round(data.metadata.page.marginX / 0.75);
110-
marginY = Math.round(data.metadata.page.marginY / 0.75);
109+
pagePaddingX = data.metadata.page.marginX;
110+
pagePaddingY = data.metadata.page.marginY;
111111
}
112112

113113
let browser: Browser | null = null;
@@ -135,21 +135,65 @@ export const printerService = {
135135
const isFreeForm = format === "free-form";
136136

137137
const contentHeight = await page.evaluate(
138-
(marginY: number, isFreeForm: boolean, minPageHeight: number) => {
138+
(
139+
pagePaddingX: number,
140+
pagePaddingY: number,
141+
isFreeForm: boolean,
142+
minPageHeight: number,
143+
backgroundColor: string,
144+
) => {
139145
const root = document.documentElement;
146+
const body = document.body;
140147
const pageElements = document.querySelectorAll("[data-page-index]");
148+
const pageContentElements = document.querySelectorAll(".page-content");
141149
const container = document.querySelector(".resume-preview-container") as HTMLElement | null;
142150

151+
// Ensure PDF margins inherit the resume background color instead of defaulting to white.
152+
root.style.backgroundColor = backgroundColor;
153+
body.style.backgroundColor = backgroundColor;
154+
root.style.margin = "0";
155+
body.style.margin = "0";
156+
root.style.padding = "0";
157+
body.style.padding = "0";
158+
159+
for (const el of pageElements) {
160+
const pageWrapper = el as HTMLElement;
161+
const pageSurface = pageWrapper.querySelector(".page") as HTMLElement | null;
162+
163+
pageWrapper.style.backgroundColor = backgroundColor;
164+
pageWrapper.style.breakInside = "auto";
165+
166+
if (pageSurface) pageSurface.style.backgroundColor = backgroundColor;
167+
}
168+
169+
// Apply print-only margins as padding inside each page's content surface.
170+
if (pagePaddingX > 0 || pagePaddingY > 0) {
171+
for (const el of pageContentElements) {
172+
const pageContent = el as HTMLElement;
173+
174+
pageContent.style.boxSizing = "border-box";
175+
// Ensure padding is repeated on every printed fragment when content
176+
// flows across physical PDF pages (not just the first fragment).
177+
pageContent.style.boxDecorationBreak = "clone";
178+
pageContent.style.setProperty("-webkit-box-decoration-break", "clone");
179+
if (pagePaddingX > 0) {
180+
pageContent.style.paddingLeft = `${pagePaddingX}pt`;
181+
pageContent.style.paddingRight = `${pagePaddingX}pt`;
182+
}
183+
if (pagePaddingY > 0) {
184+
pageContent.style.paddingTop = `${pagePaddingY}pt`;
185+
pageContent.style.paddingBottom = `${pagePaddingY}pt`;
186+
}
187+
}
188+
}
189+
143190
if (isFreeForm) {
144-
// For free-form: add visual gaps between pages, then measure total height
145-
// Convert marginY from PDF points to CSS pixels (1pt = 0.75px)
146-
const marginYAsPixels = marginY * 0.75;
147191
const numberOfPages = pageElements.length;
148192

149193
// Add margin between pages (except the last one)
150194
for (let i = 0; i < numberOfPages - 1; i++) {
151195
const pageEl = pageElements[i] as HTMLElement;
152-
pageEl.style.marginBottom = `${marginYAsPixels}px`;
196+
if (pagePaddingY > 0) pageEl.style.marginBottom = `${pagePaddingY}pt`;
153197
}
154198

155199
// Now measure the total height (margins are now part of the DOM)
@@ -169,8 +213,8 @@ export const printerService = {
169213
// For A4/Letter
170214
const heightValue = minPageHeight;
171215

172-
// Subtract top + bottom margins from page height
173-
const newHeight = `${heightValue - marginY}px`;
216+
// Keep page height fixed and let in-page padding (if any) define content bounds.
217+
const newHeight = `${heightValue}px`;
174218
if (container) container.style.setProperty("--page-height", newHeight);
175219
root.style.setProperty("--page-height", newHeight);
176220

@@ -181,7 +225,10 @@ export const printerService = {
181225
const index = Number.parseInt(element.getAttribute("data-page-index") ?? "0", 10);
182226

183227
// Force a page break before each page except the first
184-
if (index > 0) element.style.breakBefore = "page";
228+
if (index > 0) {
229+
element.style.breakBefore = "page";
230+
element.style.pageBreakBefore = "always";
231+
}
185232

186233
// Allow content within a page to break naturally if it overflows
187234
// (e.g., if a single page has more content than fits on one PDF page)
@@ -190,9 +237,11 @@ export const printerService = {
190237

191238
return null; // Fixed height from pageDimensionsAsPixels for A4/Letter
192239
},
193-
marginY,
240+
pagePaddingX,
241+
pagePaddingY,
194242
isFreeForm,
195243
pageDimensionsAsPixels[format].height,
244+
data.metadata.design.colors.background,
196245
);
197246

198247
// Step 6: Generate the PDF with the specified dimensions and margins
@@ -208,9 +257,9 @@ export const printerService = {
208257
printBackground: true, // Includes background colors and images
209258
margin: {
210259
bottom: 0,
211-
top: marginY,
212-
right: marginX,
213-
left: marginX,
260+
top: 0,
261+
right: 0,
262+
left: 0,
214263
},
215264
});
216265

0 commit comments

Comments
 (0)