From ee0cbff037e1cd190d88084814b7f209e5ed3e6e Mon Sep 17 00:00:00 2001 From: Marcel <62351477+lezram@users.noreply.github.com> Date: Sat, 12 Dec 2020 15:01:50 +0100 Subject: [PATCH] feat(table): add customizable page break handler for tables --- examples/tables.js | 40 +++++++++++++++++++++++ src/LayoutBuilder.js | 54 +++++++++++++++++++++----------- src/TableProcessor.js | 7 +++++ tests/unit/LayoutBuilder.spec.js | 29 +++++++++++++++++ 4 files changed, 112 insertions(+), 18 deletions(-) diff --git a/examples/tables.js b/examples/tables.js index 3e9dc3332..bb52fa024 100644 --- a/examples/tables.js +++ b/examples/tables.js @@ -671,6 +671,46 @@ var docDefinition = { ], ], }, + }, + { text: 'onPageBreak action', pageBreak: 'before', style: 'subheader' }, + { + table: { + dontBreakRows: true, // important for the current implementation -> onPageBreak only called once + onPageBreak: function (pageInfo, tableInfo, processor, layoutBuilder) { + var tableNode = processor.tableNode; + + var additionalRowWrapper = layoutBuilder.docMeasure.measureTable({ + table: { + widths: ['*', '*', '*'], + body: [ + [{ + text: 'Additional row on break', + colSpan: 3, + }] + ] + } + }); + + var additionalRow = additionalRowWrapper.table.body[0]; + + tableNode.table.body.splice(tableInfo.rowPosition, 0, additionalRow); + + layoutBuilder.insertTableRow(tableInfo.rowPosition, additionalRow, processor); + tableInfo.rowPosition++; + }, + headerRows: 1, + widths: ['*', '*', '*'], + body: [ + ['Title', 'Title', 'Title'], + ...(function () { + var rows = []; + for (var i = 0; i < 40; i++) { + rows.push(['Row ' + i, 'Row ' + i, 'Row ' + i]); + } + return rows; + })() + ], + }, } ], styles: { diff --git a/src/LayoutBuilder.js b/src/LayoutBuilder.js index 7f25a9391..2a85d9c4e 100644 --- a/src/LayoutBuilder.js +++ b/src/LayoutBuilder.js @@ -619,32 +619,50 @@ class LayoutBuilder { processor.beginTable(this.writer); - let rowHeights = tableNode.table.heights; - for (let i = 0, l = tableNode.table.body.length; i < l; i++) { - processor.beginRow(i, this.writer); - - let height; - if (isFunction(rowHeights)) { - height = rowHeights(i); - } else if (isArray(rowHeights)) { - height = rowHeights[i]; - } else { - height = rowHeights; - } + const tableInfo = { + rowPosition: 0 + }; - if (height === 'auto') { - height = undefined; - } + const pageBreakAction = (prevPage) => { + processor.onPageBreak(prevPage, tableInfo, processor, this); + }; - let result = this.processRow(tableNode.table.body[i], tableNode.table.widths, tableNode._offsets.offsets, tableNode.table.body, i, height); - addAll(tableNode.positions, result.positions); + this.writer.addListener("pageChanged", pageBreakAction); - processor.endRow(i, this.writer, result.pageBreaks); + while (tableInfo.rowPosition < tableNode.table.body.length) { + let rowContent = tableNode.table.body[tableInfo.rowPosition]; + this.insertTableRow(tableInfo.rowPosition, rowContent, processor); + tableInfo.rowPosition++; } + this.writer.removeListener("pageChanged", pageBreakAction); processor.endTable(this.writer); } + insertTableRow(rowPosition, rowContent, processor) { + processor.beginRow(rowPosition, this.writer); + + let rowHeights = processor.tableNode.table.heights; + + let height; + if (isFunction(rowHeights)) { + height = rowHeights(rowPosition); + } else if (isArray(rowHeights)) { + height = rowHeights[rowPosition]; + } else { + height = rowHeights; + } + + if (height === 'auto') { + height = undefined; + } + + let result = this.processRow(rowContent, processor.tableNode.table.widths, processor.tableNode._offsets.offsets, processor.tableNode.table.body, rowPosition, height); + addAll(processor.tableNode.positions, result.positions); + + processor.endRow(rowPosition, this.writer, result.pageBreaks); + } + // leafs (texts) processLeaf(node) { let line = this.buildNextLine(node); diff --git a/src/TableProcessor.js b/src/TableProcessor.js index 5476c220a..4b38f9222 100644 --- a/src/TableProcessor.js +++ b/src/TableProcessor.js @@ -105,6 +105,12 @@ class TableProcessor { this.rowsWithoutPageBreak = this.headerRows + (tableNode.table.keepWithHeaderRows || 0); this.dontBreakRows = tableNode.table.dontBreakRows || false; + this.onPageBreak = () => { + }; + if (tableNode.table && tableNode.table.onPageBreak && isFunction(tableNode.table.onPageBreak)) { + this.onPageBreak = tableNode.table.onPageBreak; + } + if (this.rowsWithoutPageBreak) { writer.beginUnbreakableBlock(); } @@ -123,6 +129,7 @@ class TableProcessor { }; } + beginRow(rowIndex, writer) { this.topLineWidth = this.layout.hLineWidth(rowIndex, this.tableNode); this.rowPaddingTop = this.layout.paddingTop(rowIndex, this.tableNode); diff --git a/tests/unit/LayoutBuilder.spec.js b/tests/unit/LayoutBuilder.spec.js index c98a7f21c..962da1cc6 100644 --- a/tests/unit/LayoutBuilder.spec.js +++ b/tests/unit/LayoutBuilder.spec.js @@ -1209,6 +1209,35 @@ describe('LayoutBuilder', function () { }); }); + it('should repeat be able to handle table page breaks externally', function () { + const pageBreaks = []; + + var desc = [{ + table: { + headerRows: 1, + widths: 'auto', + onPageBreak: function (pageInfo) { + pageBreaks.push(pageInfo); + }, + body: [ + ['h1', 'h2', 'h3'] + ] + }, + layout: emptyTableLayout + }]; + + for (var i = 0; i < 590; i++) { + desc[0].table.body.push(['a', 'b', 'c']); + } + + var pages = builder.layoutDocument(desc, sampleTestProvider); + + assert.equal(pages.length, 10); + const expectedPageChanges = 9; + const columns = 3; + assert.equal(pageBreaks.length, expectedPageChanges * columns); + }); + it('should not change x positions of repeated table headers, if context.x has changed (bugfix)', function () { var desc = [{ table: {