diff --git a/services/site/src/lib/resume-pdf.ts b/services/site/src/lib/resume-pdf.ts index 5ab616b..4e5fff6 100644 --- a/services/site/src/lib/resume-pdf.ts +++ b/services/site/src/lib/resume-pdf.ts @@ -55,7 +55,7 @@ interface Body { * (and line spacing by a little less), so a short resume can be opened up to * fill its one page instead of leaving the bottom empty. */ -function renderBody(r: ResumeData, spread = 1): Promise
{ +export function renderBody(r: ResumeData, spread = 1): Promise { const gapDown = (n: number) => doc.moveDown(n * spread); const lg = (n: number) => n + (spread - 1) * 1.2; const doc = new PDFDocument({ @@ -281,11 +281,20 @@ const LIMIT = 792 - PAGE.bottom; /** * A one-page resume that ends well above the footer is laid out again with * wider gaps, up to twice as wide, so the page reads as full; the widest - * spread that still fits on one page wins. Longer resumes are left as they are. + * spread that still fits on one page wins. One that just spills onto a second + * page is laid out with gaps down to 70% when that brings it back to one. + * Longer resumes are left as they are. */ export async function fitBody(r: ResumeData): Promise { const first = await renderBody(r); - if (first.pages > 1 || LIMIT - first.end < 24) return first; + if (first.pages > 1) { + for (const spread of [0.9, 0.8, 0.7]) { + const tight = await renderBody(r, spread); + if (tight.pages === 1) return tight; + } + return first; + } + if (LIMIT - first.end < 24) return first; let best = first; for (let spread = 1.1; spread <= 2.001; spread += 0.1) { const next = await renderBody(r, spread); diff --git a/services/site/test/resume-pdf.test.ts b/services/site/test/resume-pdf.test.ts index 9186638..29cc2ed 100644 --- a/services/site/test/resume-pdf.test.ts +++ b/services/site/test/resume-pdf.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { fitBody } from "../src/lib/resume-pdf.ts"; +import { fitBody, renderBody } from "../src/lib/resume-pdf.ts"; import type { ResumeData } from "../src/lib/resume.ts"; const role = (n: number) => @@ -31,7 +31,15 @@ describe("resume PDF fit", () => { expect(fitted.end).toBeGreaterThan(560); }); - it("leaves a resume that runs past one page alone", async () => { + it("tightens a resume that just spills onto a second page", async () => { + const ten = { ...base, roles: Array.from({ length: 10 }, (_, i) => role(i)) }; + expect((await renderBody(ten)).pages).toBe(2); + const fitted = await fitBody(ten); + expect(fitted.pages).toBe(1); + expect(fitted.end).toBeLessThanOrEqual(792 - 70); + }); + + it("leaves a resume that runs well past one page alone", async () => { const long = await fitBody({ ...base, roles: Array.from({ length: 12 }, (_, i) => role(i)) }); expect(long.pages).toBeGreaterThan(1); }); diff --git a/tools/migrate/src/seed.ts b/tools/migrate/src/seed.ts index 401d923..394fd97 100644 --- a/tools/migrate/src/seed.ts +++ b/tools/migrate/src/seed.ts @@ -278,7 +278,7 @@ export const skills = { */ export const experience: Record