fix(docx): print pages the way the PDF does for one-column templates and full-width pages

This commit is contained in:
Amruth Pillai
2026-09-28 21:35:39 +02:00
parent 4fbc0d7b1d
commit 25306630e7
2 changed files with 41 additions and 5 deletions
+19 -4
View File
@@ -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<Template, TemplateConfig> = {
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 {
+22 -1
View File
@@ -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"] });
});
});