From 9a6b786c8372027ae48384bf4c62f358abf2a7e0 Mon Sep 17 00:00:00 2001 From: Pratik Dulal Date: Wed, 29 Jul 2026 16:05:49 +0545 Subject: [PATCH 1/3] fix: stop bare addPage() from re-forcing the document's default font PDFPage's constructor applies options.font/options.fontSize whenever they're set, but addPage() reuses the exact same options object passed to the PDFDocument constructor when called with no arguments. So if a default font was set via `new PDFDocument({ font: ... })`, every subsequent bare addPage() call silently re-applied it, clobbering any doc.font()/doc.fontSize() call made since the previous page (#1739). initFonts() already applies the constructor's default font/fontSize once, before the first page is even created, so PDFPage re-deriving them from options was only ever necessary for two cases: the very first page (harmless no-op re-application) and an explicit addPage({ font, fontSize }) call, which is a documented way to override for a specific page. Only apply them in those two cases now, identified by checking whether the options object is the exact one reused from document.options and whether a page already exists. --- lib/page.js | 17 +++++++++++++++-- tests/unit/page.spec.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/page.js b/lib/page.js index 9a8b0be39..87d93db98 100644 --- a/lib/page.js +++ b/lib/page.js @@ -85,8 +85,21 @@ class PDFPage { this.content = this.document.ref(); - if (options.font) document.font(options.font, options.fontFamily); - if (options.fontSize) document.fontSize(options.fontSize); + // `options` defaults to `document.options` (the PDFDocument constructor options) when + // addPage() is called without arguments, so it's the same object for every page. Applying + // font/fontSize from it unconditionally would silently override any doc.font()/doc.fontSize() + // call made since the previous page. Only (re-)apply them here when they were explicitly + // passed to this addPage() call, or for the very first page (document.page is still null), + // where they're the intended initial default - already applied by initFonts(), so this is + // just keeping fontSize/margin calculation below consistent. + const isInheritedDefaultOptions = options === document.options; + const isFirstPage = document.page == null; + if (options.font && (!isInheritedDefaultOptions || isFirstPage)) { + document.font(options.font, options.fontFamily); + } + if (options.fontSize && (!isInheritedDefaultOptions || isFirstPage)) { + document.fontSize(options.fontSize); + } // process margins // Margin calculation must occur after font assignment to ensure any dynamic sizes are calculated correctly diff --git a/tests/unit/page.spec.js b/tests/unit/page.spec.js index 8164517f4..1aff920e4 100644 --- a/tests/unit/page.spec.js +++ b/tests/unit/page.spec.js @@ -28,4 +28,34 @@ describe('page', function () { expect(page.margins).toEqual({ top: 0, right: 0, bottom: 0, left: 0 }); }); }); + + // https://github.com/foliojs/pdfkit/issues/1739 + describe('font persistence across addPage()', function () { + test('a font set with doc.font() persists onto a bare addPage() call', function () { + const doc = new PDFDocument({ font: 'Helvetica' }); + doc.font('Helvetica-Bold'); + doc.addPage(); + expect(doc._font.name).toBe('Helvetica-Bold'); + }); + + test('a font size set with doc.fontSize() persists onto a bare addPage() call', function () { + const doc = new PDFDocument({ font: 'Helvetica' }); + doc.fontSize(30); + doc.addPage(); + expect(doc._fontSize).toBe(30); + }); + + test('an explicit font passed to addPage() still overrides the current font', function () { + const doc = new PDFDocument({ font: 'Helvetica' }); + doc.font('Helvetica-Bold'); + doc.addPage({ font: 'Times-Roman' }); + expect(doc._font.name).toBe('Times-Roman'); + }); + + test('an explicit fontSize passed to addPage() still applies', function () { + const doc = new PDFDocument({ font: 'Helvetica' }); + doc.addPage({ fontSize: 20 }); + expect(doc._fontSize).toBe(20); + }); + }); }); From fc9129232efa352ce774af451edd8e3bc0ca968c Mon Sep 17 00:00:00 2001 From: Luiz Americo Date: Sat, 1 Aug 2026 11:36:13 -0300 Subject: [PATCH 2/3] move font setting from PDFPage constructor to PDFDocument#addPage --- lib/document.js | 16 +++++++++++----- lib/page.js | 18 +----------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/lib/document.js b/lib/document.js index b3296f4cd..7d649d8c6 100644 --- a/lib/document.js +++ b/lib/document.js @@ -142,17 +142,23 @@ class PDFDocument extends stream.Readable { } addPage(options) { - if (options == null) { - ({ options } = this); - } + const documentOptions = this.options; // end the current page if needed - if (!this.options.bufferPages) { + if (!documentOptions.bufferPages) { this.flushPages(); } + const font = options?.font; + if (font) { + this.font(font, options.fontFamily); + } + + const fontSize = options?.fontSize; + if (fontSize) this.fontSize(fontSize); + // create a page object - this.page = new PDFPage(this, options); + this.page = new PDFPage(this, options || documentOptions); this._pageBuffer.push(this.page); // add the page to the object store diff --git a/lib/page.js b/lib/page.js index 87d93db98..f3c24b56b 100644 --- a/lib/page.js +++ b/lib/page.js @@ -69,7 +69,7 @@ const SIZES = { }; class PDFPage { - constructor(document, options = {}) { + constructor(document, options) { this.document = document; this._options = options; this.size = options.size || 'letter'; @@ -85,22 +85,6 @@ class PDFPage { this.content = this.document.ref(); - // `options` defaults to `document.options` (the PDFDocument constructor options) when - // addPage() is called without arguments, so it's the same object for every page. Applying - // font/fontSize from it unconditionally would silently override any doc.font()/doc.fontSize() - // call made since the previous page. Only (re-)apply them here when they were explicitly - // passed to this addPage() call, or for the very first page (document.page is still null), - // where they're the intended initial default - already applied by initFonts(), so this is - // just keeping fontSize/margin calculation below consistent. - const isInheritedDefaultOptions = options === document.options; - const isFirstPage = document.page == null; - if (options.font && (!isInheritedDefaultOptions || isFirstPage)) { - document.font(options.font, options.fontFamily); - } - if (options.fontSize && (!isInheritedDefaultOptions || isFirstPage)) { - document.fontSize(options.fontSize); - } - // process margins // Margin calculation must occur after font assignment to ensure any dynamic sizes are calculated correctly this.margins = normalizeSides( From 91f47710679b49249d50be3091e3fc5cb1c74882 Mon Sep 17 00:00:00 2001 From: Luiz Americo Date: Sat, 1 Aug 2026 11:40:08 -0300 Subject: [PATCH 3/3] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8155b4e35..a0201e600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [BREAKING CHANGE] Restrict AcroForm options to documented mappings and explicit escape hatches. - [BREAKING CHANGE] Stop automatically uppercasing annotation option keys. - Do not mutate options passed to `doc.annotate()` and its convenience methods (link, note, strike, lineAnnotation, rectAnnotation, ellipseAnnotation, textAnnotation, fileAnnotation) +- Persist font options when adding a new page. Fixes #1739 ### [v0.19.1] - 2026-06-10