From bbc38d2f099446f56695f323aacd4c5d54ea32ed Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sun, 26 Apr 2026 00:57:50 +0200 Subject: [PATCH] implement logic from #2853, thanks to @trigger-xyz --- .../orpc/services/printer-styles.test.ts | 21 ++++++++++++++++ .../orpc/services/printer-styles.ts | 9 +++++++ src/integrations/orpc/services/printer.ts | 24 +++++++++++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/integrations/orpc/services/printer-styles.test.ts create mode 100644 src/integrations/orpc/services/printer-styles.ts diff --git a/src/integrations/orpc/services/printer-styles.test.ts b/src/integrations/orpc/services/printer-styles.test.ts new file mode 100644 index 000000000..e9d338ade --- /dev/null +++ b/src/integrations/orpc/services/printer-styles.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { getSubsequentPageTopMarginStyle } from "./printer-styles"; + +describe("getSubsequentPageTopMarginStyle", () => { + it("uses print padding when the template applies page padding", () => { + expect(getSubsequentPageTopMarginStyle(12, 24)).toContain("@page { margin-top: 12pt; }"); + }); + + it("falls back to the resume page margin when no print padding is applied", () => { + expect(getSubsequentPageTopMarginStyle(0, 24)).toContain("@page { margin-top: 24pt; }"); + }); + + it("leaves the first PDF page unchanged", () => { + expect(getSubsequentPageTopMarginStyle(12, 24)).toContain("@page :first { margin-top: 0; }"); + }); + + it("returns null when there is no vertical spacing", () => { + expect(getSubsequentPageTopMarginStyle(0, 0)).toBeNull(); + }); +}); diff --git a/src/integrations/orpc/services/printer-styles.ts b/src/integrations/orpc/services/printer-styles.ts new file mode 100644 index 000000000..a0d80fcd5 --- /dev/null +++ b/src/integrations/orpc/services/printer-styles.ts @@ -0,0 +1,9 @@ +export function getSubsequentPageTopMarginStyle(pagePaddingY: number, marginY: number): string | null { + const effectivePaddingY = pagePaddingY > 0 ? pagePaddingY : marginY; + if (effectivePaddingY <= 0) return null; + + return ` + @page { margin-top: ${effectivePaddingY}pt; } + @page :first { margin-top: 0; } + `; +} diff --git a/src/integrations/orpc/services/printer.ts b/src/integrations/orpc/services/printer.ts index d53a3b031..cbd3ca20a 100644 --- a/src/integrations/orpc/services/printer.ts +++ b/src/integrations/orpc/services/printer.ts @@ -12,6 +12,7 @@ import { printMarginTemplates } from "@/schema/templates"; import { env } from "@/utils/env"; import { generatePrinterToken } from "@/utils/printer-token"; +import { getSubsequentPageTopMarginStyle } from "./printer-styles"; import { getStorageService, uploadFile } from "./storage"; const SCREENSHOT_TTL = 1000 * 60 * 60 * 6; // 6 hours @@ -195,10 +196,17 @@ async function doPrintResumeAsPDF( ), ); + const subsequentPageTopMarginStyle = getSubsequentPageTopMarginStyle(pagePaddingY, data.metadata.page.marginY); + // Step 5a: Prepare the DOM for PDF rendering (background colors, reset margins, print padding) await page .evaluate( - (pagePaddingX: number, pagePaddingY: number, backgroundColor: string) => { + ( + pagePaddingX: number, + pagePaddingY: number, + subsequentPageTopMarginStyle: string | null, + backgroundColor: string, + ) => { const root = document.documentElement; const body = document.body; const pageElements = document.querySelectorAll("[data-page-index]"); @@ -222,14 +230,13 @@ async function doPrintResumeAsPDF( if (pageSurface) pageSurface.style.backgroundColor = backgroundColor; } - // Apply print-only margins as padding inside each page's content surface. + // Apply print-only horizontal margins as padding inside each page's content surface. + // This is only needed for printMarginTemplates which use print:p-0 to remove CSS padding. 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) { @@ -242,9 +249,18 @@ async function doPrintResumeAsPDF( } } } + + // Add top margin to PDF pages 2+ so content doesn't start flush at the top. + // The html/body background colors above ensure the margin area is colored. + if (subsequentPageTopMarginStyle) { + const style = document.createElement("style"); + style.textContent = subsequentPageTopMarginStyle; + document.head.appendChild(style); + } }, pagePaddingX, pagePaddingY, + subsequentPageTopMarginStyle, data.metadata.design.colors.background, ) .catch((error) =>