diff --git a/src/layoutBuilder.js b/src/layoutBuilder.js index fbb203fa1..d95210031 100644 --- a/src/layoutBuilder.js +++ b/src/layoutBuilder.js @@ -576,19 +576,40 @@ LayoutBuilder.prototype.processList = function (orderedList, node) { // tables LayoutBuilder.prototype.processTable = function (tableNode) { + var self = this; + + tableNode.table.insertAdditionHeaderRow = insertAdditionHeaderRow; + var processor = new TableProcessor(tableNode); processor.beginTable(this.writer); var rowHeights = tableNode.table.heights; for (var i = 0, l = tableNode.table.body.length; i < l; i++) { - processor.beginRow(i, this.writer); + insertRow(tableNode.table.body[i], i, rowHeights, false); + } + + processor.endTable(this.writer); + + function insertAdditionHeaderRow(additionalHeaderRowFn, rowIndex) { + var _self = this; + var additionRowNode = additionalHeaderRowFn(rowIndex); + if (additionRowNode && additionRowNode.table) { + var additionalTableNode = self.docMeasure.measureTable(additionRowNode); + var additionalRow = additionalTableNode.table.body[0]; + + insertRow(additionalRow, rowIndex, _self.heights, true); + } + } + + function insertRow(row, rowIndex, rowHeights, isAdditionalHeaderRow) { + processor.beginRow(rowIndex, self.writer); var height; if (isFunction(rowHeights)) { - height = rowHeights(i); + height = rowHeights(rowIndex); } else if (isArray(rowHeights)) { - height = rowHeights[i]; + height = rowHeights[rowIndex]; } else { height = rowHeights; } @@ -597,13 +618,11 @@ LayoutBuilder.prototype.processTable = function (tableNode) { height = undefined; } - var result = this.processRow(tableNode.table.body[i], tableNode.table.widths, tableNode._offsets.offsets, tableNode.table.body, i, height); + var result = self.processRow(row, tableNode.table.widths, tableNode._offsets.offsets, tableNode.table.body, rowIndex, height); addAll(tableNode.positions, result.positions); - processor.endRow(i, this.writer, result.pageBreaks); + processor.endRow(rowIndex, self.writer, result.pageBreaks, isAdditionalHeaderRow); } - - processor.endTable(this.writer); }; // leafs (texts) diff --git a/src/pageElementWriter.js b/src/pageElementWriter.js index 88f91298b..c275e320d 100644 --- a/src/pageElementWriter.js +++ b/src/pageElementWriter.js @@ -76,6 +76,10 @@ PageElementWriter.prototype.moveToNextPage = function (pageOrientation) { this.repeatables.forEach(function (rep) { this.writer.addFragment(rep, true); }, this); + + if (this.repeatables) { + this.writer.tracker.emit('afterAddRepeatedTable', {}); + } } else { this.repeatables.forEach(function (rep) { this.writer.context.moveDown(rep.height); diff --git a/src/tableProcessor.js b/src/tableProcessor.js index e99ba3cb5..64fd53c87 100644 --- a/src/tableProcessor.js +++ b/src/tableProcessor.js @@ -26,6 +26,9 @@ TableProcessor.prototype.beginTable = function (writer) { this.headerRows = tableNode.table.headerRows || 0; this.rowsWithoutPageBreak = this.headerRows + (tableNode.table.keepWithHeaderRows || 0); this.dontBreakRows = tableNode.table.dontBreakRows || false; + + this.additionalHeaderRowFn = tableNode.table.additionalHeaderRow; + this.currentRowIndex = 0; if (this.rowsWithoutPageBreak) { writer.beginUnbreakableBlock(); @@ -126,7 +129,17 @@ TableProcessor.prototype.onRowBreak = function (rowIndex, writer) { }; }; +TableProcessor.prototype.onAfterAddRepeatedTableHandler = function() { + var self = this; + return function () { + if (isFunction(self.additionalHeaderRowFn)) { + self.tableNode.table.insertAdditionHeaderRow(self.additionalHeaderRowFn, self.currentRowIndex); + } + } +}; + TableProcessor.prototype.beginRow = function (rowIndex, writer) { + this.currentRowIndex = rowIndex; this.topLineWidth = this.layout.hLineWidth(rowIndex, this.tableNode); this.rowPaddingTop = this.layout.paddingTop(rowIndex, this.tableNode); this.bottomLineWidth = this.layout.hLineWidth(rowIndex + 1, this.tableNode); @@ -227,9 +240,13 @@ TableProcessor.prototype.endTable = function (writer) { writer.popFromRepeatables(); this.headerRepeatableHeight = null; } + + if (this.insertAdditionHeaderRow) { + writer.tracker.stopTracking('afterAddRepeatedTable', this.insertAdditionHeaderRow); + } }; -TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks) { +TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks, isAdditionalHeaderRow) { var l, i; var self = this; writer.tracker.stopTracking('pageChanged', this.rowCallback); @@ -358,7 +375,7 @@ TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks) { } this.drawHorizontalLine(rowIndex + 1, writer); - + if (this.headerRows && rowIndex === this.headerRows - 1) { this.headerRepeatable = writer.currentBlockToRepeatable(); } @@ -375,11 +392,19 @@ TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks) { } ); } + + if (isAdditionalHeaderRow) { + return; + } if (this.headerRepeatable && (rowIndex === (this.rowsWithoutPageBreak - 1) || rowIndex === this.tableNode.table.body.length - 1)) { this.headerRepeatableHeight = this.headerRepeatable.height; writer.commitUnbreakableBlock(); writer.pushToRepeatables(this.headerRepeatable); + + this.insertAdditionHeaderRow = this.onAfterAddRepeatedTableHandler(); + writer.tracker.startTracking('afterAddRepeatedTable', this.insertAdditionHeaderRow); + this.cleanUpRepeatables = true; this.headerRepeatable = null; } diff --git a/tests/layoutBuilder.js b/tests/layoutBuilder.js index 5eec0074c..3fa2936ae 100644 --- a/tests/layoutBuilder.js +++ b/tests/layoutBuilder.js @@ -1208,6 +1208,60 @@ describe('LayoutBuilder', function () { }); }); + it('should repeat table headers and additional row', function () { + var additionalRowTxt = 'Cont..'; + var tableRows = []; + tableRows.push(['h1', 'h2', 'h3']); + + for (var i = 0; i < 590; i++) { + tableRows.push(['a', 'b', 'c']); + } + + var desc = [{ + table: { + headerRows: 1, + widths: 'auto', + body: tableRows, + additionalHeaderRow: function(rowIdx) { + return { + table: { + widths: 'auto', + body: [ + [ + { + text: additionalRowTxt, + colSpan: 3 + }, + {}, {} + ] + ] + } + }; + } + }, + layout: emptyTableLayout + }]; + + var pages = builder.layoutDocument(desc, sampleTestProvider); + + assert.equal(pages.length, 11); + for (var pIdx = 0; pIdx < pages.length; pIdx++) { + var page = pages[pIdx]; + + // Repeated header is rendered + assert.equal(page.items[0].item.inlines[0].text, 'h1'); + assert.equal(page.items[0].item.y, 40); + assert.equal(page.items[0].item.x, 40); + + // First page doesn't have additional row + if (pIdx === 0) { + assert.equal(page.items[3].item.inlines[0].text, 'a'); + } else { + assert.equal(page.items[3].item.inlines[0].text, additionalRowTxt); + } + } + }); + it('should not change x positions of repeated table headers, if context.x has changed (bugfix)', function () { var desc = [{ table: { diff --git a/tests/pageElementWriter.js b/tests/pageElementWriter.js index 6e52c97a8..ca51f7479 100644 --- a/tests/pageElementWriter.js +++ b/tests/pageElementWriter.js @@ -418,8 +418,10 @@ describe('PageElementWriter', function () { assert.equal(ctx.y, MARGINS.top); assert.equal(ctx.availableHeight, AVAILABLE_HEIGHT); assert.equal(ctx.availableWidth, AVAILABLE_WIDTH); - assert.equal(tracker.emit.callCount, 2); // move to first page to write a line, and then move to next page - assert.deepEqual(tracker.emit.getCall(1).args, ['pageChanged', {prevPage: 0, prevY: MARGINS.top + AVAILABLE_HEIGHT / 10, y: MARGINS.top}]); + assert.equal(tracker.emit.callCount, 3); // move to first page to write a line, and then move to next page + assert.deepEqual(tracker.emit.getCall(0).args[0], 'lineAdded'); + assert.deepEqual(tracker.emit.getCall(1).args[0], 'afterAddRepeatedTable'); + assert.deepEqual(tracker.emit.getCall(2).args, ['pageChanged', {prevPage: 0, prevY: MARGINS.top + AVAILABLE_HEIGHT / 10, y: MARGINS.top}]); }); it('should use existing page', function () { diff --git a/tests/tableProcessor.js b/tests/tableProcessor.js index 3076001a7..c2ffbe895 100644 --- a/tests/tableProcessor.js +++ b/tests/tableProcessor.js @@ -174,7 +174,8 @@ describe('TableProcessor', function () { }, repeatables: [], tracker: { - stopTracking: function () {} + stopTracking: function () {}, + startTracking: function () {} }, addVector: function () {}, popFromRepeatables: sinon.spy(),