Skip to content

Commit 40d74b2

Browse files
NadeemShadanNadeem Shadanliborm85
authored
Fix extra blank page when using headerRows + dontBreakRows + cell pageBreak (#2929)
* Fix extra blank page when using headerRows + dontBreakRows + cell pageBreak (fixes #2730) * ci - build examples * close initial dontBreakRows unbreakable block to avoid NaN page height * revert pdfs * Update CHANGELOG.md --------- Co-authored-by: Nadeem Shadan <nadeem.s@iinerds.com> Co-authored-by: Libor M. <liborm85@gmail.com>
1 parent 66ed750 commit 40d74b2

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Fixed extra blank page when using headerRows, dontBreakRows and cell pageBreak together
6+
37
## 0.3.7 - 2026-03-17
48

59
- Updated pdfkit to 0.18.0

src/TableProcessor.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
import ColumnCalculator from './columnCalculator';
22
import { isNumber, isPositiveInteger } from './helpers/variableType';
33

4+
const PAGE_BREAK_VALUES = new Set(['before', 'beforeOdd', 'beforeEven', 'after', 'afterOdd', 'afterEven']);
5+
6+
const hasExplicitPageBreak = cell => {
7+
if (!cell || typeof cell !== 'object') {
8+
return false;
9+
}
10+
11+
return PAGE_BREAK_VALUES.has(cell.pageBreak);
12+
};
13+
414
class TableProcessor {
515
constructor(tableNode) {
616
this.tableNode = tableNode;
17+
this._isCurrentRowUnbreakable = false;
718
}
819

920
beginTable(writer) {
@@ -163,7 +174,11 @@ class TableProcessor {
163174

164175
this.rowTopPageY = writer.context().y + this.rowPaddingTop;
165176

166-
if (this.dontBreakRows && rowIndex > 0) {
177+
const rowCells = this.tableNode.table.body[rowIndex] || [];
178+
const rowHasPageBreak = rowCells.some(hasExplicitPageBreak);
179+
this._isCurrentRowUnbreakable = this.dontBreakRows && rowIndex > 0 && !rowHasPageBreak;
180+
181+
if (this._isCurrentRowUnbreakable) {
167182
writer.beginUnbreakableBlock();
168183
}
169184
this.rowTopY = writer.context().y;
@@ -589,7 +604,9 @@ class TableProcessor {
589604
this.headerRepeatable = writer.currentBlockToRepeatable();
590605
}
591606

592-
if (this.dontBreakRows) {
607+
const shouldCommitCurrentRowUnbreakable = this.dontBreakRows && (rowIndex === 0 || this._isCurrentRowUnbreakable);
608+
609+
if (shouldCommitCurrentRowUnbreakable) {
593610
const pageChangedCallback = () => {
594611
if (rowIndex > 0 && !this.headerRows && this.layout.hLineWhenBroken !== false) {
595612
// Draw the top border of the row after a page break
@@ -604,6 +621,8 @@ class TableProcessor {
604621
writer.removeListener('pageChanged', pageChangedCallback);
605622
}
606623

624+
this._isCurrentRowUnbreakable = false;
625+
607626
if (this.headerRepeatable && (rowIndex === (this.rowsWithoutPageBreak - 1) || rowIndex === this.tableNode.table.body.length - 1)) {
608627
writer.commitUnbreakableBlock();
609628
writer.pushToRepeatables(this.headerRepeatable);

tests/integration/tables.spec.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,54 @@ describe('Integration test: tables', function () {
288288
assert.deepEqual(getColumnText(lines, { cell: 1 }), 'Row 2');
289289
});
290290

291+
it('does not insert an extra page when combining headerRows, dontBreakRows and cell pageBreak', function () {
292+
var dd = {
293+
content: {
294+
table: {
295+
dontBreakRows: true,
296+
headerRows: 1,
297+
body: [
298+
['row Header', 'column B'],
299+
['row 1', 'column B'],
300+
['row 2', 'column B'],
301+
['row 3', 'column B'],
302+
[{ text: '', pageBreak: 'after' }, ''],
303+
['row 4', 'column B'],
304+
['row 5', 'column B']
305+
]
306+
}
307+
}
308+
};
309+
310+
var pages = testHelper.renderPages('A6', dd);
311+
var page1Texts = getCells(pages, { pageNumber: 0 }).map(node => node.item.inlines.map(inline => inline.text).join(''));
312+
var page2Texts = getCells(pages, { pageNumber: 1 }).map(node => node.item.inlines.map(inline => inline.text).join(''));
313+
314+
assert.equal(pages.length, 2);
315+
assert.deepEqual(page1Texts, ['row Header', 'column B', 'row 1', 'column B', 'row 2', 'column B', 'row 3', 'column B', '', '']);
316+
assert.deepEqual(page2Texts, ['row Header', 'column B', 'row 4', 'column B', 'row 5', 'column B']);
317+
});
318+
319+
it('keeps finite page dimensions with dontBreakRows tables without headers', function () {
320+
var dd = {
321+
content: {
322+
table: {
323+
dontBreakRows: true,
324+
body: [
325+
['row 1', 'column B'],
326+
['row 2', 'column B'],
327+
['row 3', 'column B']
328+
]
329+
}
330+
}
331+
};
332+
333+
var pages = testHelper.renderPages('A6', dd);
334+
335+
pages.forEach(page => {
336+
assert.equal(Number.isFinite(page.pageSize.width), true);
337+
assert.equal(Number.isFinite(page.pageSize.height), true);
338+
});
339+
});
340+
291341
});

0 commit comments

Comments
 (0)