pdfmake 0.3.0-beta.10 → 0.3.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -113,13 +113,18 @@ class TableProcessor {
113
113
  }
114
114
  }
115
115
  this.dontBreakRows = tableNode.table.dontBreakRows || false;
116
- if (this.rowsWithoutPageBreak) {
116
+ if (this.rowsWithoutPageBreak || this.dontBreakRows) {
117
117
  writer.beginUnbreakableBlock();
118
+ // Draw the top border of the table
119
+ this.drawHorizontalLine(0, writer);
120
+ if (this.rowsWithoutPageBreak && this.dontBreakRows) {
121
+ // We just increase the value of transactionLevel
122
+ writer.beginUnbreakableBlock();
123
+ }
118
124
  }
119
125
 
120
126
  // update the border properties of all cells before drawing any lines
121
127
  prepareCellBorders(this.tableNode.table.body);
122
- this.drawHorizontalLine(0, writer);
123
128
  }
124
129
  onRowBreak(rowIndex, writer) {
125
130
  return () => {
@@ -135,7 +140,12 @@ class TableProcessor {
135
140
  this.rowPaddingBottom = this.layout.paddingBottom(rowIndex, this.tableNode);
136
141
  this.rowCallback = this.onRowBreak(rowIndex, writer);
137
142
  writer.addListener('pageChanged', this.rowCallback);
138
- if (this.dontBreakRows) {
143
+ if (rowIndex == 0 && !this.dontBreakRows && !this.rowsWithoutPageBreak) {
144
+ // We store the 'y' to draw later and if necessary the top border of the table
145
+ this._tableTopBorderY = writer.context().y;
146
+ writer.context().moveDown(this.topLineWidth);
147
+ }
148
+ if (this.dontBreakRows && rowIndex > 0) {
139
149
  writer.beginUnbreakableBlock();
140
150
  }
141
151
  this.rowTopY = writer.context().y;
@@ -143,7 +153,7 @@ class TableProcessor {
143
153
  writer.context().availableHeight -= this.reservedAtBottom;
144
154
  writer.context().moveDown(this.rowPaddingTop);
145
155
  }
146
- drawHorizontalLine(lineIndex, writer, overrideY) {
156
+ drawHorizontalLine(lineIndex, writer, overrideY, moveDown = true, forcePage) {
147
157
  let lineWidth = this.layout.hLineWidth(lineIndex, this.tableNode);
148
158
  if (lineWidth) {
149
159
  let style = this.layout.hLineStyle(lineIndex, this.tableNode);
@@ -237,7 +247,7 @@ class TableProcessor {
237
247
  lineWidth: lineWidth,
238
248
  dash: dash,
239
249
  lineColor: borderColor
240
- }, false, overrideY);
250
+ }, false, (0, _variableType.isNumber)(overrideY), null, forcePage);
241
251
  currentLine = null;
242
252
  borderColor = null;
243
253
  cellAbove = null;
@@ -246,7 +256,9 @@ class TableProcessor {
246
256
  }
247
257
  }
248
258
  }
249
- writer.context().moveDown(lineWidth);
259
+ if (moveDown) {
260
+ writer.context().moveDown(lineWidth);
261
+ }
250
262
  }
251
263
  }
252
264
  drawVerticalLine(x, y0, y1, vLineColIndex, writer, vLineRowIndex, beforeVLineColIndex) {
@@ -369,6 +381,15 @@ class TableProcessor {
369
381
  }
370
382
  ys[ys.length - 1].y1 = endingY;
371
383
  let skipOrphanePadding = ys[0].y1 - ys[0].y0 === this.rowPaddingTop;
384
+ if (rowIndex === 0 && !skipOrphanePadding && !this.rowsWithoutPageBreak && !this.dontBreakRows) {
385
+ // Draw the top border of the table
386
+ let pageTableStartedAt = null;
387
+ if (pageBreaks && pageBreaks.length > 0) {
388
+ // Get the page where table started at
389
+ pageTableStartedAt = pageBreaks[0].prevPage;
390
+ }
391
+ this.drawHorizontalLine(0, writer, this._tableTopBorderY, false, pageTableStartedAt);
392
+ }
372
393
  for (let yi = skipOrphanePadding ? 1 : 0, yl = ys.length; yi < yl; yi++) {
373
394
  let willBreak = yi < ys.length - 1;
374
395
  let rowBreakWithoutHeader = yi > 0 && !this.headerRows;
@@ -385,6 +406,14 @@ class TableProcessor {
385
406
  // TableProcessor should be pageChanged listener, instead of processRow
386
407
  this.reservedAtBottom = 0;
387
408
  }
409
+
410
+ // Draw horizontal lines before the vertical lines so they are not overridden
411
+ if (willBreak && this.layout.hLineWhenBroken !== false) {
412
+ this.drawHorizontalLine(rowIndex + 1, writer, y2);
413
+ }
414
+ if (rowBreakWithoutHeader && this.layout.hLineWhenBroken !== false) {
415
+ this.drawHorizontalLine(rowIndex, writer, y1);
416
+ }
388
417
  for (let i = 0, l = xs.length; i < l; i++) {
389
418
  let leftCellBorder = false;
390
419
  let rightCellBorder = false;
@@ -467,12 +496,6 @@ class TableProcessor {
467
496
  }
468
497
  }
469
498
  }
470
- if (willBreak && this.layout.hLineWhenBroken !== false) {
471
- this.drawHorizontalLine(rowIndex + 1, writer, y2);
472
- }
473
- if (rowBreakWithoutHeader && this.layout.hLineWhenBroken !== false) {
474
- this.drawHorizontalLine(rowIndex, writer, y1);
475
- }
476
499
  }
477
500
  writer.context().page = endingPage;
478
501
  writer.context().y = endingY;
@@ -505,7 +528,8 @@ class TableProcessor {
505
528
  }
506
529
  if (this.dontBreakRows) {
507
530
  const pageChangedCallback = () => {
508
- if (!this.headerRows && this.layout.hLineWhenBroken !== false) {
531
+ if (rowIndex > 0 && !this.headerRows && this.layout.hLineWhenBroken !== false) {
532
+ // Draw the top border of the row after a page break
509
533
  this.drawHorizontalLine(rowIndex, writer);
510
534
  }
511
535
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdfmake",
3
- "version": "0.3.0-beta.10",
3
+ "version": "0.3.0-beta.12",
4
4
  "description": "Client/server side PDF printing in pure JavaScript",
5
5
  "main": "js/index.js",
6
6
  "esnext": "src/index.js",
@@ -10,33 +10,33 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@foliojs-fork/linebreak": "^1.1.2",
13
- "@foliojs-fork/pdfkit": "^0.14.0",
13
+ "@foliojs-fork/pdfkit": "^0.15.1",
14
14
  "iconv-lite": "^0.6.3",
15
15
  "xmldoc": "^1.3.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@babel/cli": "^7.25.6",
19
- "@babel/core": "^7.25.2",
20
- "@babel/plugin-transform-modules-commonjs": "^7.24.8",
21
- "@babel/preset-env": "^7.25.4",
22
- "@eslint/js": "^9.9.1",
18
+ "@babel/cli": "^7.25.9",
19
+ "@babel/core": "^7.26.0",
20
+ "@babel/plugin-transform-modules-commonjs": "^7.25.9",
21
+ "@babel/preset-env": "^7.26.0",
22
+ "@eslint/js": "^9.14.0",
23
23
  "assert": "^2.1.0",
24
- "babel-loader": "^9.1.3",
24
+ "babel-loader": "^9.2.1",
25
25
  "brfs": "^2.0.2",
26
26
  "browserify-zlib": "^0.2.0",
27
27
  "buffer": "6.0.3",
28
28
  "core-js": "3.19.0",
29
- "eslint": "^9.9.1",
30
- "eslint-plugin-jsdoc": "^50.2.2",
29
+ "eslint": "^9.14.0",
30
+ "eslint-plugin-jsdoc": "^50.4.3",
31
31
  "expose-loader": "^5.0.0",
32
32
  "file-saver": "^2.0.5",
33
- "globals": "^15.9.0",
34
- "mocha": "^10.7.3",
33
+ "globals": "^15.11.0",
34
+ "mocha": "^10.8.2",
35
35
  "npm-run-all": "^4.1.5",
36
36
  "process": "^0.11.10",
37
37
  "rewire": "^7.0.0",
38
38
  "shx": "^0.3.4",
39
- "sinon": "^18.0.0",
39
+ "sinon": "^19.0.2",
40
40
  "source-map-loader": "^5.0.0",
41
41
  "stream-browserify": "^3.0.0",
42
42
  "string-replace-webpack-plugin": "^0.1.3",
@@ -44,7 +44,7 @@
44
44
  "terser-webpack-plugin": "^5.3.10",
45
45
  "transform-loader": "^0.2.4",
46
46
  "util": "^0.12.5",
47
- "webpack": "^5.94.0",
47
+ "webpack": "^5.96.1",
48
48
  "webpack-cli": "^5.1.4"
49
49
  },
50
50
  "engines": {
@@ -1,9 +1,9 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2019 SVG-to-PDFKit contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
-
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 SVG-to-PDFKit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -23,13 +23,14 @@ class DocumentContext extends EventEmitter {
23
23
  this.addPage(pageSize);
24
24
  }
25
25
 
26
- beginColumnGroup(marginXTopParent) {
26
+ beginColumnGroup(marginXTopParent, bottomByPage = {}) {
27
27
  this.snapshots.push({
28
28
  x: this.x,
29
29
  y: this.y,
30
30
  availableHeight: this.availableHeight,
31
31
  availableWidth: this.availableWidth,
32
32
  page: this.page,
33
+ bottomByPage: bottomByPage ? bottomByPage : {},
33
34
  bottomMost: {
34
35
  x: this.x,
35
36
  y: this.y,
@@ -46,6 +47,16 @@ class DocumentContext extends EventEmitter {
46
47
  }
47
48
  }
48
49
 
50
+ updateBottomByPage () {
51
+ const lastSnapshot = this.snapshots[this.snapshots.length - 1];
52
+ const lastPage = this.page;
53
+ let previousBottom = -Number.MIN_VALUE;
54
+ if (lastSnapshot.bottomByPage[lastPage]) {
55
+ previousBottom = lastSnapshot.bottomByPage[lastPage];
56
+ }
57
+ lastSnapshot.bottomByPage[lastPage] = Math.max(previousBottom, this.y);
58
+ }
59
+
49
60
  resetMarginXTopParent() {
50
61
  this.marginXTopParent = null;
51
62
  }
@@ -118,6 +129,7 @@ class DocumentContext extends EventEmitter {
118
129
  this.availableHeight -= (y - saved.bottomMost.y);
119
130
  }
120
131
  this.lastColumnWidth = saved.lastColumnWidth;
132
+ return saved.bottomByPage;
121
133
  }
122
134
 
123
135
  addMargin(left, right) {
@@ -259,9 +259,12 @@ class ElementWriter extends EventEmitter {
259
259
  }
260
260
  }
261
261
 
262
- addVector(vector, ignoreContextX, ignoreContextY, index) {
262
+ addVector(vector, ignoreContextX, ignoreContextY, index, forcePage) {
263
263
  let context = this.context();
264
264
  let page = context.getCurrentPage();
265
+ if (isNumber(forcePage)) {
266
+ page = context.pages[forcePage];
267
+ }
265
268
  let position = this.getCurrentPositionOnPage();
266
269
 
267
270
  if (page) {
@@ -539,7 +539,15 @@ class LayoutBuilder {
539
539
  }
540
540
  }
541
541
 
542
- findStartingSpanCell(arr, i) {
542
+ /**
543
+ * Searches for a cell in the same row that starts a rowspan and is positioned immediately before the current cell.
544
+ * Alternatively, it finds a cell where the colspan initiating the rowspan extends to the cell just before the current one.
545
+ *
546
+ * @param {Array<object>} arr - An array representing cells in a row.
547
+ * @param {number} i - The index of the current cell to search backward from.
548
+ * @returns {object|null} The starting cell of the rowspan if found; otherwise, `null`.
549
+ */
550
+ _findStartingRowSpanCell(arr, i) {
543
551
  let requiredColspan = 1;
544
552
  for (let index = i - 1; index >= 0; index--) {
545
553
  if (!arr[index]._span) {
@@ -554,114 +562,239 @@ class LayoutBuilder {
554
562
  return null;
555
563
  }
556
564
 
557
- processRow({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableBody, rowIndex, height }) {
558
- const updatePageBreakData = (page, prevY) => {
559
- let pageDesc;
560
- // Find page break data for this row and page
561
- for (let i = 0, l = pageBreaks.length; i < l; i++) {
562
- let desc = pageBreaks[i];
563
- if (desc.prevPage === page) {
564
- pageDesc = desc;
565
- break;
566
- }
567
- }
568
- // If row has page break in this page, update prevY
569
- if (pageDesc) {
570
- pageDesc.prevY = Math.max(pageDesc.prevY, prevY);
571
- }
565
+ /**
566
+ * Retrieves a page break description for a specified page from a list of page breaks.
567
+ *
568
+ * @param {Array<object>} pageBreaks - An array of page break descriptions, each containing `prevPage` properties.
569
+ * @param {number} page - The page number to find the associated page break for.
570
+ * @returns {object|undefined} The page break description object for the specified page if found; otherwise, `undefined`.
571
+ */
572
+ _getPageBreak(pageBreaks, page) {
573
+ return pageBreaks.find(desc => desc.prevPage === page);
574
+ }
575
+
576
+ _getPageBreakListBySpan(tableNode, page, rowIndex) {
577
+ if (!tableNode || !tableNode._breaksBySpan) {
578
+ return null;
579
+ }
580
+ const breaksList = tableNode._breaksBySpan.filter(desc => desc.prevPage === page && rowIndex <= desc.rowIndexOfSpanEnd);
581
+
582
+ let y = Number.MAX_VALUE,
583
+ prevY = Number.MIN_VALUE;
584
+
585
+ breaksList.forEach(b => {
586
+ prevY = Math.max(b.prevY, prevY);
587
+ y = Math.min(b.y, y);
588
+ });
589
+
590
+ return {
591
+ prevPage: page,
592
+ prevY: prevY,
593
+ y: y
572
594
  };
595
+ }
596
+
597
+ _findSameRowPageBreakByRowSpanData(breaksBySpan, page, rowIndex) {
598
+ if (!breaksBySpan) {
599
+ return null;
600
+ }
601
+ return breaksBySpan.find(desc => desc.prevPage === page && rowIndex === desc.rowIndexOfSpanEnd);
602
+ }
573
603
 
574
- const storePageBreakData = data => {
575
- let pageDesc;
604
+ _updatePageBreaksData(pageBreaks, tableNode, rowIndex) {
605
+ Object.keys(tableNode._bottomByPage).forEach(p => {
606
+ const page = Number(p);
607
+ const pageBreak = this._getPageBreak(pageBreaks, page);
608
+ if (pageBreak) {
609
+ pageBreak.prevY = Math.max(pageBreak.prevY, tableNode._bottomByPage[page]);
610
+ }
611
+ if (tableNode._breaksBySpan && tableNode._breaksBySpan.length > 0) {
612
+ const breaksBySpanList = tableNode._breaksBySpan.filter(pb => pb.prevPage === page && rowIndex <= pb.rowIndexOfSpanEnd);
576
613
 
577
- for (let i = 0, l = pageBreaks.length; i < l; i++) {
578
- let desc = pageBreaks[i];
579
- if (desc.prevPage === data.prevPage) {
580
- pageDesc = desc;
581
- break;
614
+ if (breaksBySpanList && breaksBySpanList.length > 0) {
615
+ breaksBySpanList.forEach(b => {
616
+ b.prevY = Math.max(b.prevY, tableNode._bottomByPage[page]);
617
+ });
582
618
  }
583
619
  }
620
+ });
621
+ }
584
622
 
623
+ /**
624
+ * Resolves the Y-coordinates for a target object by comparing two break points.
625
+ *
626
+ * @param {object} break1 - The first break point with `prevY` and `y` properties.
627
+ * @param {object} break2 - The second break point with `prevY` and `y` properties.
628
+ * @param {object} target - The target object to be updated with resolved Y-coordinates.
629
+ * @property {number} target.prevY - Updated to the maximum `prevY` value between `break1` and `break2`.
630
+ * @property {number} target.y - Updated to the minimum `y` value between `break1` and `break2`.
631
+ */
632
+ _resolveBreakY (break1, break2, target) {
633
+ target.prevY = Math.max(break1.prevY, break2.prevY);
634
+ target.y = Math.min(break1.y, break2.y);
635
+ };
636
+
637
+ _storePageBreakData (data, startsRowSpan, pageBreaks, tableNode) {
638
+ if (!startsRowSpan) {
639
+ let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
640
+ let pageDescBySpan = this._getPageBreakListBySpan(tableNode, data.prevPage, data.rowIndex);
585
641
  if (!pageDesc) {
586
- pageDesc = data;
642
+ pageDesc = {
643
+ ...data
644
+ };
587
645
  pageBreaks.push(pageDesc);
588
646
  }
589
- pageDesc.prevY = Math.max(pageDesc.prevY, data.prevY);
590
- pageDesc.y = Math.min(pageDesc.y, data.y);
591
- };
647
+ if (pageDescBySpan) {
648
+ this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
649
+ }
650
+ this._resolveBreakY(pageDesc, data, pageDesc);
651
+ } else {
652
+ const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
653
+ let pageDescBySpan = this._findSameRowPageBreakByRowSpanData(breaksBySpan, data.prevPage, data.rowIndex);
654
+ if (!pageDescBySpan) {
655
+ pageDescBySpan = {
656
+ ...data,
657
+ rowIndexOfSpanEnd: data.rowIndex + data.rowSpan - 1
658
+ };
659
+ if (!tableNode._breaksBySpan) {
660
+ tableNode._breaksBySpan = [];
661
+ }
662
+ tableNode._breaksBySpan.push(pageDescBySpan);
663
+ }
664
+ pageDescBySpan.prevY = Math.max(pageDescBySpan.prevY, data.prevY);
665
+ pageDescBySpan.y = Math.min(pageDescBySpan.y, data.y);
666
+ let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
667
+ if (pageDesc) {
668
+ this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
669
+ }
670
+ }
671
+ };
592
672
 
673
+ /**
674
+ * Calculates the left offset for a column based on the specified gap values.
675
+ *
676
+ * @param {number} i - The index of the column for which the offset is being calculated.
677
+ * @param {Array<number>} gaps - An array of gap values for each column.
678
+ * @returns {number} The left offset for the column. Returns `gaps[i]` if it exists, otherwise `0`.
679
+ */
680
+ _colLeftOffset(i, gaps) {
681
+ if (gaps && gaps.length > i) {
682
+ return gaps[i];
683
+ }
684
+ return 0;
685
+ }
686
+
687
+ /**
688
+ * Retrieves the ending cell for a row span in case it exists in a specified table column.
689
+ *
690
+ * @param {Array<Array<object>>} tableBody - The table body, represented as a 2D array of cell objects.
691
+ * @param {number} rowIndex - The index of the starting row for the row span.
692
+ * @param {object} column - The column object containing row span information.
693
+ * @param {number} columnIndex - The index of the column within the row.
694
+ * @returns {object|null} The cell at the end of the row span if it exists; otherwise, `null`.
695
+ * @throws {Error} If the row span extends beyond the total row count.
696
+ */
697
+ _getRowSpanEndingCell(tableBody, rowIndex, column, columnIndex) {
698
+ if (column.rowSpan && column.rowSpan > 1) {
699
+ let endingRow = rowIndex + column.rowSpan - 1;
700
+ if (endingRow >= tableBody.length) {
701
+ throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
702
+ }
703
+ return tableBody[endingRow][columnIndex];
704
+ }
705
+
706
+ return null;
707
+ }
708
+
709
+ processRow({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableNode, tableBody, rowIndex, height }) {
593
710
  const isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
594
711
  let pageBreaks = [];
712
+ let pageBreaksByRowSpan = [];
595
713
  let positions = [];
596
714
  let willBreakByHeight = false;
597
- this.writer.addListener('pageChanged', storePageBreakData);
715
+ widths = widths || cells;
598
716
 
599
717
  // Check if row should break by height
600
718
  if (!isUnbreakableRow && height > this.writer.context().availableHeight) {
601
719
  willBreakByHeight = true;
602
720
  }
603
721
 
604
- widths = widths || cells;
605
722
  // Use the marginX if we are in a top level table/column (not nested)
606
723
  const marginXParent = this.nestedLevel === 1 ? marginX : null;
607
-
608
- this.writer.context().beginColumnGroup(marginXParent);
724
+ const _bottomByPage = tableNode ? tableNode._bottomByPage : null;
725
+ this.writer.context().beginColumnGroup(marginXParent, _bottomByPage);
609
726
 
610
727
  for (let i = 0, l = cells.length; i < l; i++) {
611
- let column = cells[i];
728
+ let cell = cells[i];
729
+
730
+ // Page change handler
731
+ const storePageBreakClosure = data => {
732
+ const startsRowSpan = cell.rowSpan && cell.rowSpan > 1;
733
+ if (startsRowSpan) {
734
+ data.rowSpan = cell.rowSpan;
735
+ }
736
+ data.rowIndex = rowIndex;
737
+ this._storePageBreakData(data, startsRowSpan, pageBreaks, tableNode);
738
+ };
739
+
740
+ this.writer.addListener('pageChanged', storePageBreakClosure);
741
+
612
742
  let width = widths[i]._calcWidth;
613
- let leftOffset = colLeftOffset(i);
743
+ let leftOffset = this._colLeftOffset(i, gaps);
744
+ // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
745
+ let startingSpanCell = this._findStartingRowSpanCell(cells, i);
614
746
 
615
- if (column.colSpan && column.colSpan > 1) {
616
- for (let j = 1; j < column.colSpan; j++) {
747
+ if (cell.colSpan && cell.colSpan > 1) {
748
+ for (let j = 1; j < cell.colSpan; j++) {
617
749
  width += widths[++i]._calcWidth + gaps[i];
618
750
  }
619
751
  }
620
752
 
621
753
  // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
622
- const endingCell = getEndingCell(column, i);
623
- if (endingCell) {
754
+ const rowSpanEndingCell = this._getRowSpanEndingCell(tableBody, rowIndex, cell, i);
755
+ if (rowSpanEndingCell) {
624
756
  // We store a reference of the ending cell in the first cell of the rowspan
625
- column._endingCell = endingCell;
626
- column._endingCell._startingRowSpanY = column._startingRowSpanY;
757
+ cell._endingCell = rowSpanEndingCell;
758
+ cell._endingCell._startingRowSpanY = cell._startingRowSpanY;
627
759
  }
628
760
 
629
- // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
630
- let startingSpanCell = this.findStartingSpanCell(cells, i);
631
- let endingSpanCell = null;
761
+ // If we are after a cell that started a rowspan
762
+ let endOfRowSpanCell = null;
632
763
  if (startingSpanCell && startingSpanCell._endingCell) {
633
764
  // Reference to the last cell of the rowspan
634
- endingSpanCell = startingSpanCell._endingCell;
765
+ endOfRowSpanCell = startingSpanCell._endingCell;
635
766
  // Store if we are in an unbreakable block when we save the context and the originalX
636
767
  if (this.writer.transactionLevel > 0) {
637
- endingSpanCell._isUnbreakableContext = true;
638
- endingSpanCell._originalXOffset = this.writer.originalX;
768
+ endOfRowSpanCell._isUnbreakableContext = true;
769
+ endOfRowSpanCell._originalXOffset = this.writer.originalX;
639
770
  }
640
771
  }
641
772
 
642
773
  // We pass the endingSpanCell reference to store the context just after processing rowspan cell
643
- this.writer.context().beginColumn(width, leftOffset, endingSpanCell);
774
+ this.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell);
644
775
 
645
- if (!column._span) {
646
- this.processNode(column);
647
- addAll(positions, column.positions);
648
- } else if (column._columnEndingContext) {
776
+ if (!cell._span) {
777
+ this.processNode(cell);
778
+ this.writer.context().updateBottomByPage();
779
+ addAll(positions, cell.positions);
780
+ } else if (cell._columnEndingContext) {
649
781
  let discountY = 0;
650
782
  if (dontBreakRows) {
651
783
  // Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
652
784
  const ctxBeforeRowSpanLastRow = this.writer.contextStack[this.writer.contextStack.length - 1];
653
- discountY = ctxBeforeRowSpanLastRow.y - column._startingRowSpanY;
785
+ discountY = ctxBeforeRowSpanLastRow.y - cell._startingRowSpanY;
654
786
  }
655
787
  let originalXOffset = 0;
656
788
  // If context was saved from an unbreakable block and we are not in an unbreakable block anymore
657
789
  // We have to sum the originalX (X before starting unbreakable block) to X
658
- if (column._isUnbreakableContext && !this.writer.transactionLevel) {
659
- originalXOffset = column._originalXOffset;
790
+ if (cell._isUnbreakableContext && !this.writer.transactionLevel) {
791
+ originalXOffset = cell._originalXOffset;
660
792
  }
661
793
  // row-span ending
662
794
  // Recover the context after processing the rowspanned cell
663
- this.writer.context().markEnding(column, originalXOffset, discountY);
795
+ this.writer.context().markEnding(cell, originalXOffset, discountY);
664
796
  }
797
+ this.writer.removeListener('pageChanged', storePageBreakClosure);
665
798
  }
666
799
 
667
800
  // Check if last cell is part of a span
@@ -674,7 +807,7 @@ class LayoutBuilder {
674
807
  // Previous column cell is part of a span
675
808
  } else if (lastColumn._span === true) {
676
809
  // We get the cell that started the span where we set a reference to the ending cell
677
- const startingSpanCell = this.findStartingSpanCell(cells, cells.length);
810
+ const startingSpanCell = this._findStartingRowSpanCell(cells, cells.length);
678
811
  if (startingSpanCell) {
679
812
  // Context will be stored here (ending cell)
680
813
  endingSpanCell = startingSpanCell._endingCell;
@@ -687,38 +820,25 @@ class LayoutBuilder {
687
820
  }
688
821
  }
689
822
 
690
- // If there are page breaks in this row, update data with prevY of last cell
691
- updatePageBreakData(this.writer.context().page, this.writer.context().y);
692
-
693
823
  // If content did not break page, check if we should break by height
694
- if (!isUnbreakableRow && pageBreaks.length === 0 && willBreakByHeight) {
824
+ if (willBreakByHeight && !isUnbreakableRow && pageBreaks.length === 0) {
695
825
  this.writer.context().moveDown(this.writer.context().availableHeight);
696
826
  this.writer.moveToNextPage();
697
827
  }
698
828
 
699
- this.writer.context().completeColumnGroup(height, endingSpanCell);
700
- this.writer.removeListener('pageChanged', storePageBreakData);
829
+ const bottomByPage = this.writer.context().completeColumnGroup(height, endingSpanCell);
701
830
 
702
- return { pageBreaks: pageBreaks, positions: positions };
703
-
704
- function colLeftOffset(i) {
705
- if (gaps && gaps.length > i) {
706
- return gaps[i];
707
- }
708
- return 0;
831
+ if (tableNode) {
832
+ tableNode._bottomByPage = bottomByPage;
833
+ // If there are page breaks in this row, update data with prevY of last cell
834
+ this._updatePageBreaksData(pageBreaks, tableNode, rowIndex);
709
835
  }
710
836
 
711
- function getEndingCell(column, columnIndex) {
712
- if (column.rowSpan && column.rowSpan > 1) {
713
- let endingRow = rowIndex + column.rowSpan - 1;
714
- if (endingRow >= tableBody.length) {
715
- throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
716
- }
717
- return tableBody[endingRow][columnIndex];
718
- }
719
-
720
- return null;
721
- }
837
+ return {
838
+ pageBreaksBySpan : pageBreaksByRowSpan,
839
+ pageBreaks: pageBreaks,
840
+ positions: positions
841
+ };
722
842
  }
723
843
 
724
844
  // lists
@@ -799,6 +919,8 @@ class LayoutBuilder {
799
919
  height = undefined;
800
920
  }
801
921
 
922
+ const pageBeforeProcessing = this.writer.context().page;
923
+
802
924
  let result = this.processRow({
803
925
  marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
804
926
  dontBreakRows: processor.dontBreakRows,
@@ -807,12 +929,22 @@ class LayoutBuilder {
807
929
  widths: tableNode.table.widths,
808
930
  gaps: tableNode._offsets.offsets,
809
931
  tableBody: tableNode.table.body,
932
+ tableNode,
810
933
  rowIndex: i,
811
934
  height
812
935
  });
813
936
 
814
937
  addAll(tableNode.positions, result.positions);
815
938
 
939
+ if (!result.pageBreaks || result.pageBreaks.length === 0) {
940
+ const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
941
+ const breakBySpanData = this._findSameRowPageBreakByRowSpanData(breaksBySpan, pageBeforeProcessing, i);
942
+ if (breakBySpanData) {
943
+ const finalBreakBySpanData = this._getPageBreakListBySpan(tableNode, breakBySpanData.prevPage, i);
944
+ result.pageBreaks.push(finalBreakBySpanData);
945
+ }
946
+ }
947
+
816
948
  processor.endRow(i, this.writer, result.pageBreaks);
817
949
  }
818
950
 
@@ -39,8 +39,8 @@ class PageElementWriter extends ElementWriter {
39
39
  return this._fitOnPage(() => super.addAttachment(attachment, index));
40
40
  }
41
41
 
42
- addVector(vector, ignoreContextX, ignoreContextY, index) {
43
- return super.addVector(vector, ignoreContextX, ignoreContextY, index);
42
+ addVector(vector, ignoreContextX, ignoreContextY, index, forcePage) {
43
+ return super.addVector(vector, ignoreContextX, ignoreContextY, index, forcePage);
44
44
  }
45
45
 
46
46
  beginClip(width, height) {