From 25306630e72c0c83cccd383c2943c969edbc926f Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Mon, 28 Sep 2026 21:35:39 +0200 Subject: [PATCH] fix(docx): print pages the way the PDF does for one-column templates and full-width pages --- packages/docx/src/builder.ts | 23 ++++++++++++++++++---- packages/docx/src/template-layouts.test.ts | 23 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/docx/src/builder.ts b/packages/docx/src/builder.ts index aa6e146ec..bb392c3c4 100644 --- a/packages/docx/src/builder.ts +++ b/packages/docx/src/builder.ts @@ -1,4 +1,4 @@ -import type { ResumeData, SectionType } from "@reactive-resume/schema/resume/data"; +import type { LayoutPage, ResumeData, SectionType } from "@reactive-resume/schema/resume/data"; import type { Template } from "@reactive-resume/schema/templates"; import { BorderStyle, @@ -14,6 +14,7 @@ import { TextRun, WidthType, } from "docx"; +import { templateLayouts } from "@reactive-resume/schema/templates"; import { parseColorString } from "@reactive-resume/utils/color"; import { isRTL } from "@reactive-resume/utils/locale"; import { shouldShowResumeHeader } from "./cover-letter"; @@ -86,6 +87,20 @@ export const TEMPLATE_CONFIGS: Record = { scizor: { sidebarSide: "left", sidebarBackground: "none", headerPosition: "full-width" }, }; +type PagePlan = { kind: "single"; sections: string[] } | { kind: "split" }; + +/** + * How a layout page prints, as in the PDF: a full-width page prints no sidebar, a one-column template prints its + * sidebar sections after the main ones, and only a two-column template with sidebar sections splits the page. + */ +export function planPageColumns(page: LayoutPage, template: Template): PagePlan { + const sidebar = page.fullWidth ? [] : page.sidebar; + if (templateLayouts[template].columns === 1 || sidebar.length === 0) { + return { kind: "single", sections: [...page.main, ...sidebar] }; + } + return { kind: "split" }; +} + /** * Blends a hex color toward white at the given opacity (0-1). * Used to approximate CSS `background-color: rgba(r,g,b, 0.2)` on a white background. @@ -401,11 +416,11 @@ export function buildDocument(data: ResumeData, resolveTitle?: SectionTitleResol // Process each page in the layout for (const layoutPage of data.metadata.layout.pages) { - const isFullWidth = layoutPage.fullWidth || layoutPage.sidebar.length === 0; + const plan = planPageColumns(layoutPage, data.metadata.template); - if (isFullWidth) { + if (plan.kind === "single") { setRenderConfig(mainConfig); - for (const sectionId of [...layoutPage.main, ...layoutPage.sidebar]) { + for (const sectionId of plan.sections) { documentChildren.push(...renderSection(sectionId, data, colorHex, resolveTitle)); } } else { diff --git a/packages/docx/src/template-layouts.test.ts b/packages/docx/src/template-layouts.test.ts index 5904783c9..998532b0a 100644 --- a/packages/docx/src/template-layouts.test.ts +++ b/packages/docx/src/template-layouts.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { templateLayouts } from "@reactive-resume/schema/templates"; -import { TEMPLATE_CONFIGS } from "./builder"; +import { planPageColumns, TEMPLATE_CONFIGS } from "./builder"; describe("DOCX template configs", () => { it("put the sidebar and header where the PDF does for two-column templates", () => { @@ -14,3 +14,24 @@ describe("DOCX template configs", () => { } }); }); + +describe("planPageColumns", () => { + const page = { fullWidth: false, main: ["experience"], sidebar: ["skills"] }; + + it("splits the page only for a two-column template with sidebar sections", () => { + expect(planPageColumns(page, "azurill")).toEqual({ kind: "split" }); + expect(planPageColumns({ ...page, sidebar: [] }, "azurill")).toEqual({ kind: "single", sections: ["experience"] }); + }); + + it("prints a one-column template's sidebar after the main sections, as the PDF does", () => { + expect(planPageColumns(page, "onyx")).toEqual({ kind: "single", sections: ["experience", "skills"] }); + }); + + it("prints no sidebar on a full-width page, as the PDF does", () => { + expect(planPageColumns({ ...page, fullWidth: true }, "azurill")).toEqual({ + kind: "single", + sections: ["experience"], + }); + expect(planPageColumns({ ...page, fullWidth: true }, "onyx")).toEqual({ kind: "single", sections: ["experience"] }); + }); +});