@flowaccount/pdfmake 1.0.5 → 1.0.6-staging.3

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.
@@ -1,789 +1,791 @@
1
- 'use strict';
2
-
3
- var ColumnCalculator = require('./columnCalculator');
4
- var isFunction = require('./helpers').isFunction;
5
- var isNumber = require('./helpers').isNumber;
6
- var isPositiveInteger = require('./helpers').isPositiveInteger;
7
-
8
- function TableProcessor(tableNode) {
9
- this.tableNode = tableNode;
10
- this.collectFooterColumns = Boolean(tableNode.footerGapCollect === 'product-items');
11
- }
12
-
13
- TableProcessor.prototype.beginTable = function (writer) {
14
- var tableNode;
15
- var availableWidth;
16
- var self = this;
17
-
18
- tableNode = this.tableNode;
19
- this.offsets = tableNode._offsets;
20
- this.layout = tableNode._layout;
21
- this.beginNewPage = false;
22
-
23
- if (tableNode.remark && writer.context().availableHeight) {
24
- if (writer.context().availableHeight < 50) {
25
- writer.context().moveDown(writer.context().availableHeight);
26
- this.beginNewPage = true;
27
- }
28
- }
29
-
30
- availableWidth = writer.context().availableWidth - this.offsets.total;
31
- ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth, this.offsets.total, tableNode);
32
-
33
- const offsets = this.offsets || { left: 0 };
34
- const leftOffset = offsets.left || 0;
35
-
36
- this.tableWidth = tableNode._offsets.total + getTableInnerContentWidth();
37
- this.rowSpanData = prepareRowSpanData();
38
- this.cleanUpRepeatables = false;
39
-
40
- // headersRows and rowsWithoutPageBreak (headerRows + keepWithHeaderRows)
41
- this.headerRows = 0;
42
- this.rowsWithoutPageBreak = 0;
43
-
44
- var headerRows = tableNode.table.headerRows;
45
-
46
- if (isPositiveInteger(headerRows)) {
47
- this.headerRows = headerRows;
48
-
49
- if (this.headerRows > tableNode.table.body.length) {
50
- throw new Error(`Too few rows in the table. Property headerRows requires at least ${this.headerRows}, contains only ${tableNode.table.body.length}`);
51
- }
52
-
53
- this.rowsWithoutPageBreak = this.headerRows;
54
-
55
- const keepWithHeaderRows = tableNode.table.keepWithHeaderRows;
56
-
57
- if (isPositiveInteger(keepWithHeaderRows)) {
58
- this.rowsWithoutPageBreak += keepWithHeaderRows;
59
- }
60
- }
61
-
62
- this.dontBreakRows = tableNode.table.dontBreakRows || false;
63
-
64
- if (this.rowsWithoutPageBreak || this.dontBreakRows) {
65
- writer.beginUnbreakableBlock();
66
- // Draw the top border of the table
67
- this.drawHorizontalLine(0, writer);
68
- if (this.rowsWithoutPageBreak && this.dontBreakRows) {
69
- writer.beginUnbreakableBlock();
70
- }
71
- }
72
-
73
- // update the border properties of all cells before drawing any lines
74
- prepareCellBorders(this.tableNode.table.body);
75
-
76
- function getTableInnerContentWidth() {
77
- var width = 0;
78
-
79
- tableNode.table.widths.forEach(function (w) {
80
- width += w._calcWidth;
81
- });
82
-
83
- return width;
84
- }
85
-
86
- function prepareRowSpanData() {
87
- var rsd = [];
88
- var x = 0;
89
- var lastWidth = 0;
90
-
91
- rsd.push({ left: 0, rowSpan: 0 });
92
-
93
- if (!self.tableNode.table.body[0]) {
94
- return rsd;
95
- }
96
- for (var i = 0, l = self.tableNode.table.body[0].length; i < l; i++) {
97
- var paddings = self.layout.paddingLeft(i, self.tableNode) + self.layout.paddingRight(i, self.tableNode);
98
- var lBorder = self.layout.vLineWidth(i, self.tableNode);
99
- lastWidth = paddings + lBorder + self.tableNode.table.widths[i]._calcWidth;
100
- rsd[rsd.length - 1].width = lastWidth;
101
- x += lastWidth;
102
- rsd.push({ left: x, rowSpan: 0, width: 0 });
103
- }
104
-
105
- return rsd;
106
- }
107
-
108
- // Iterate through all cells. If the current cell is the start of a
109
- // rowSpan/colSpan, update the border property of the cells on its
110
- // bottom/right accordingly. This is needed since each iteration of the
111
- // line-drawing loops draws lines for a single cell, not for an entire
112
- // rowSpan/colSpan.
113
- function prepareCellBorders(body) {
114
- for (var rowIndex = 0; rowIndex < body.length; rowIndex++) {
115
- var row = body[rowIndex];
116
-
117
- for (var colIndex = 0; colIndex < row.length; colIndex++) {
118
- var cell = row[colIndex];
119
-
120
- if (cell.border) {
121
- var rowSpan = cell.rowSpan || 1;
122
- var colSpan = cell.colSpan || 1;
123
-
124
- for (var rowOffset = 0; rowOffset < rowSpan; rowOffset++) {
125
- // set left border
126
- if (cell.border[0] !== undefined && rowOffset > 0) {
127
- setBorder(rowIndex + rowOffset, colIndex, 0, cell.border[0]);
128
- }
129
-
130
- // set right border
131
- if (cell.border[2] !== undefined) {
132
- setBorder(rowIndex + rowOffset, colIndex + colSpan - 1, 2, cell.border[2]);
133
- }
134
- }
135
-
136
- for (var colOffset = 0; colOffset < colSpan; colOffset++) {
137
- // set top border
138
- if (cell.border[1] !== undefined && colOffset > 0) {
139
- setBorder(rowIndex, colIndex + colOffset, 1, cell.border[1]);
140
- }
141
-
142
- // set bottom border
143
- if (cell.border[3] !== undefined) {
144
- setBorder(rowIndex + rowSpan - 1, colIndex + colOffset, 3, cell.border[3]);
145
- }
146
- }
147
- }
148
- }
149
- }
150
-
151
- // helper function to set the border for a given cell
152
- function setBorder(rowIndex, colIndex, borderIndex, borderValue) {
153
- var cell = body[rowIndex][colIndex];
154
- cell.border = cell.border || {};
155
- cell.border[borderIndex] = borderValue;
156
- }
157
- }
158
- };
159
-
160
- TableProcessor.prototype.onRowBreak = function (rowIndex, writer) {
161
- var self = this;
162
- return function () {
163
- var offset = self.rowPaddingTop + (!self.headerRows ? self.topLineWidth : 0);
164
- var currentPage = writer.context().getCurrentPage && writer.context().getCurrentPage();
165
- if (currentPage && currentPage.items[0] && currentPage.items[0].item.remark) {
166
- currentPage.items[0].item.lineColor = '#d5d5d5';
167
- }
168
- writer.context().availableHeight -= self.reservedAtBottom;
169
- writer.context().moveDown(offset);
170
- };
171
- };
172
-
173
- TableProcessor.prototype.beginRow = function (rowIndex, writer) {
174
- this.topLineWidth = this.layout.hLineWidth(rowIndex, this.tableNode, writer);
175
- this.rowPaddingTop = this.layout.paddingTop(rowIndex, this.tableNode);
176
- this.bottomLineWidth = this.layout.hLineWidth(rowIndex + 1, this.tableNode, writer);
177
- this.rowPaddingBottom = this.layout.paddingBottom(rowIndex, this.tableNode);
178
-
179
- this.rowCallback = this.onRowBreak(rowIndex, writer);
180
-
181
- if(this.tableNode.eventHandle && this.tableNode.eventHandle.beforePageChanged)
182
- {
183
-
184
- this.beforePageChanged = this.tableNode.eventHandle.beforePageChanged(this, rowIndex, writer);
185
- writer.tracker.startTracking('beforePageChanged', this.beforePageChanged);
186
- }
187
-
188
- writer.tracker.startTracking('pageChanged', this.rowCallback);
189
- if (rowIndex == 0 && !this.dontBreakRows && !this.rowsWithoutPageBreak) {
190
- // We store the 'y' to draw later and if necessary the top border of the table
191
- this._tableTopBorderY = writer.context().y;
192
- writer.context().moveDown(this.topLineWidth);
193
- }
194
- if (this.dontBreakRows && rowIndex > 0) {
195
- writer.beginUnbreakableBlock();
196
- }
197
- this.rowTopY = writer.context().y;
198
- this.reservedAtBottom = this.bottomLineWidth + this.rowPaddingBottom;
199
-
200
- writer.context().availableHeight -= this.reservedAtBottom;
201
-
202
- writer.context().moveDown(this.rowPaddingTop);
203
- };
204
-
205
- TableProcessor.prototype.drawHorizontalLine = function (lineIndex, writer, overrideY, moveDown = true, forcePage, isPageBreak = false) {
206
- var lineWidth = this.layout.hLineWidth(lineIndex, this.tableNode, writer, isPageBreak);
207
- if (lineWidth) {
208
- var style = this.layout.hLineStyle(lineIndex, this.tableNode);
209
- var dash;
210
- if (style && style.dash) {
211
- dash = style.dash;
212
- }
213
-
214
- var offset = lineWidth / 2;
215
- var currentLine = null;
216
- var body = this.tableNode.table.body;
217
- var cellAbove;
218
- var currentCell;
219
- var rowCellAbove;
220
-
221
- for (var i = 0, l = this.rowSpanData.length; i < l; i++) {
222
- var data = this.rowSpanData[i];
223
- var shouldDrawLine = !data.rowSpan;
224
- var borderColor = null;
225
-
226
- // draw only if the current cell requires a top border or the cell in the
227
- // row above requires a bottom border
228
- if (shouldDrawLine && i < l - 1) {
229
- var topBorder = false, bottomBorder = false, rowBottomBorder = false;
230
-
231
- // the cell in the row above
232
- if (lineIndex > 0) {
233
- cellAbove = body[lineIndex - 1][i];
234
- bottomBorder = cellAbove.border ? cellAbove.border[3] : this.layout.defaultBorder;
235
- if (bottomBorder && cellAbove.borderColor) {
236
- borderColor = cellAbove.borderColor[3];
237
- }
238
- }
239
-
240
- // the current cell
241
- if (lineIndex < body.length) {
242
- currentCell = body[lineIndex][i];
243
- topBorder = currentCell.border ? currentCell.border[1] : this.layout.defaultBorder;
244
- if (topBorder && borderColor == null && currentCell.borderColor) {
245
- borderColor = currentCell.borderColor[1];
246
- }
247
- }
248
-
249
- shouldDrawLine = topBorder || bottomBorder;
250
- }
251
-
252
- if (cellAbove && cellAbove._rowSpanCurrentOffset) {
253
- rowCellAbove = body[lineIndex - 1 - cellAbove._rowSpanCurrentOffset][i];
254
- rowBottomBorder = rowCellAbove && rowCellAbove.border ? rowCellAbove.border[3] : this.layout.defaultBorder;
255
- if (rowBottomBorder && rowCellAbove && rowCellAbove.borderColor) {
256
- borderColor = rowCellAbove.borderColor[3];
257
- }
258
- }
259
-
260
- if (borderColor == null) {
261
- borderColor = isFunction(this.layout.hLineColor) ? this.layout.hLineColor(lineIndex, this.tableNode, i, this.beginNewPage) : this.layout.hLineColor;
262
- }
263
-
264
- if (!currentLine && shouldDrawLine) {
265
- currentLine = { left: data.left, width: 0 };
266
- }
267
-
268
- if (shouldDrawLine) {
269
- var colSpanIndex = 0;
270
- if (rowCellAbove && rowCellAbove.colSpan && rowBottomBorder) {
271
- while (rowCellAbove.colSpan > colSpanIndex) {
272
- currentLine.width += (this.rowSpanData[i + colSpanIndex++].width || 0);
273
- }
274
- i += colSpanIndex - 1;
275
- } else if (cellAbove && cellAbove.colSpan && bottomBorder) {
276
- while (cellAbove.colSpan > colSpanIndex) {
277
- currentLine.width += (this.rowSpanData[i + colSpanIndex++].width || 0);
278
- }
279
- i += colSpanIndex - 1;
280
- } else if (currentCell && currentCell.colSpan && topBorder) {
281
- while (currentCell.colSpan > colSpanIndex) {
282
- currentLine.width += (this.rowSpanData[i + colSpanIndex++].width || 0);
283
- }
284
- i += colSpanIndex - 1;
285
- } else {
286
- currentLine.width += (this.rowSpanData[i].width || 0);
287
- }
288
- }
289
-
290
- var y = (overrideY || 0) + offset;
291
-
292
-
293
- if (shouldDrawLine) {
294
- if (currentLine && currentLine.width) {
295
- writer.addVector({
296
- type: 'line',
297
- remark: this.tableNode.remark,
298
- x1: currentLine.left,
299
- x2: currentLine.left + currentLine.width,
300
- y1: y,
301
- y2: y,
302
- lineWidth: lineWidth,
303
- dash: dash,
304
- lineColor: borderColor
305
- }, false, isNumber(overrideY), null, forcePage);
306
- currentLine = null;
307
- borderColor = null;
308
- cellAbove = null;
309
- currentCell = null;
310
- rowCellAbove = null;
311
- }
312
- }
313
- }
314
-
315
- if (moveDown) {
316
- writer.context().moveDown(lineWidth);
317
- }
318
- }
319
- };
320
-
321
-
322
- /**
323
- * Draws vertical lines to fill the gap when a row is forced to the next page.
324
- * This prevents missing vertical lines at the bottom of the page.
325
- * @param {number} rowIndex - The index of the row being forced to next page
326
- * @param {object} writer - The document writer
327
- * @param {number} y0 - Starting Y position
328
- * @param {number} y1 - Ending Y position (page break point)
329
- */
330
- TableProcessor.prototype.drawVerticalLinesForForcedPageBreak = function (rowIndex, writer, y0, y1) {
331
- if (!this.rowSpanData || rowIndex <= 0) {
332
- return;
333
- }
334
-
335
- var body = this.tableNode.table.body;
336
- var prevRowIndex = rowIndex - 1; // Use previous row for cell border detection
337
-
338
- // Get X positions for vertical lines (similar logic to endRow's getLineXs)
339
- var xs = [];
340
- var cols = 0;
341
-
342
- for (var i = 0, l = body[prevRowIndex].length; i < l; i++) {
343
- if (!cols) {
344
- xs.push({ x: this.rowSpanData[i].left, index: i });
345
- var item = body[prevRowIndex][i];
346
- cols = (item._colSpan || item.colSpan || 0);
347
- }
348
- if (cols > 0) {
349
- cols--;
350
- }
351
- }
352
- xs.push({ x: this.rowSpanData[this.rowSpanData.length - 1].left, index: this.rowSpanData.length - 1 });
353
-
354
- // Draw vertical lines for each column position
355
- for (var xi = 0, xl = xs.length; xi < xl; xi++) {
356
- var leftCellBorder = false;
357
- var colIndex = xs[xi].index;
358
-
359
- // Check if we need to draw a vertical line at this position
360
- // based on cell borders from the previous row
361
- var cell;
362
- if (colIndex < body[prevRowIndex].length) {
363
- cell = body[prevRowIndex][colIndex];
364
- leftCellBorder = cell.border ? cell.border[0] : this.layout.defaultBorder;
365
- }
366
-
367
- // Check cell before
368
- if (colIndex > 0 && !leftCellBorder) {
369
- cell = body[prevRowIndex][colIndex - 1];
370
- leftCellBorder = cell.border ? cell.border[2] : this.layout.defaultBorder;
371
- }
372
-
373
- if (leftCellBorder) {
374
- this.drawVerticalLine(
375
- xs[xi].x,
376
- y0,
377
- y1,
378
- xs[xi].index,
379
- writer,
380
- prevRowIndex,
381
- xi > 0 ? xs[xi - 1].index : null
382
- );
383
- }
384
- }
385
- };
386
-
387
- TableProcessor.prototype.drawVerticalLine = function (x, y0, y1, vLineColIndex, writer, vLineRowIndex, beforeVLineColIndex) {
388
- var width = this.layout.vLineWidth(vLineColIndex, this.tableNode);
389
- if (width === 0) {
390
- return;
391
- }
392
-
393
- var ctx = writer && typeof writer.context === 'function' ? writer.context() : null;
394
- if (ctx && this.collectFooterColumns) {
395
- var footerOpt = ctx._footerGapOption;
396
- if (footerOpt && footerOpt.enabled) {
397
- var columns = footerOpt.columns || (footerOpt.columns = {});
398
- var content = columns.content || (columns.content = { vLines: [], vLineWidths: [] });
399
- var contentVLinesLength = content.vLines.length || 0;
400
- var widthLength = footerOpt.columns.widthLength || 0;
401
-
402
- // Only collect if we haven't exceeded the width length
403
- // Need widthLength + 1 lines to include both left and right borders
404
- if(widthLength === 0 || contentVLinesLength <= widthLength){
405
- // Store the base X position (without width offset)
406
- content.vLines.push((ctx.x || 0) + x);
407
- // Store the actual line width used by the table
408
- content.vLineWidths.push(width);
409
- }
410
- }
411
- }
412
-
413
- var style = this.layout.vLineStyle(vLineColIndex, this.tableNode);
414
- var dash;
415
- if (style && style.dash) {
416
- dash = style.dash;
417
- }
418
-
419
- var body = this.tableNode.table.body;
420
- var cellBefore;
421
- var currentCell;
422
- var borderColor;
423
-
424
- // the cell in the col before
425
- if (vLineColIndex > 0) {
426
- cellBefore = body[vLineRowIndex][beforeVLineColIndex];
427
- if (cellBefore && cellBefore.borderColor) {
428
- if (cellBefore.border ? cellBefore.border[2] : this.layout.defaultBorder) {
429
- borderColor = cellBefore.borderColor[2];
430
- }
431
- }
432
- }
433
-
434
- // the current cell
435
- if (borderColor == null && vLineColIndex < body.length) {
436
- currentCell = body[vLineRowIndex][vLineColIndex];
437
- if (currentCell && currentCell.borderColor) {
438
- if (currentCell.border ? currentCell.border[0] : this.layout.defaultBorder) {
439
- borderColor = currentCell.borderColor[0];
440
- }
441
- }
442
- }
443
-
444
- if (borderColor == null && cellBefore && cellBefore._rowSpanCurrentOffset) {
445
- var rowCellBeforeAbove = body[vLineRowIndex - cellBefore._rowSpanCurrentOffset][beforeVLineColIndex];
446
- if (rowCellBeforeAbove.borderColor) {
447
- if (rowCellBeforeAbove.border ? rowCellBeforeAbove.border[2] : this.layout.defaultBorder) {
448
- borderColor = rowCellBeforeAbove.borderColor[2];
449
- }
450
- }
451
- }
452
-
453
- if (borderColor == null && currentCell && currentCell._rowSpanCurrentOffset) {
454
- var rowCurrentCellAbove = body[vLineRowIndex - currentCell._rowSpanCurrentOffset][vLineColIndex];
455
- if (rowCurrentCellAbove.borderColor) {
456
- if (rowCurrentCellAbove.border ? rowCurrentCellAbove.border[2] : this.layout.defaultBorder) {
457
- borderColor = rowCurrentCellAbove.borderColor[2];
458
- }
459
- }
460
- }
461
-
462
- if (borderColor == null) {
463
- borderColor = isFunction(this.layout.vLineColor) ? this.layout.vLineColor(vLineColIndex, this.tableNode, vLineRowIndex) : this.layout.vLineColor;
464
- }
465
- writer.addVector({
466
- type: 'line',
467
- x1: x + width / 2,
468
- x2: x + width / 2,
469
- y1: y0,
470
- y2: y1,
471
- lineWidth: width,
472
- dash: dash,
473
- lineColor: borderColor
474
- }, false, true);
475
- cellBefore = null;
476
- currentCell = null;
477
- borderColor = null;
478
- };
479
-
480
- TableProcessor.prototype.endTable = function (writer) {
481
- if (this.cleanUpRepeatables) {
482
- writer.popFromRepeatables();
483
- }
484
- };
485
-
486
- TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks, nextRowCells, layoutBuilder) {
487
- var l, i;
488
- var self = this;
489
- var tableLayout = this.tableNode && this.tableNode._layout;
490
- var nearBottomThreshold = (tableLayout && tableLayout.nearBottomThreshold) || 20;
491
-
492
- if(this.tableNode.eventHandle && this.tableNode.eventHandle.beforePageChanged)
493
- {
494
- writer.tracker.stopTracking('beforePageChanged', this.beforePageChanged);
495
- }
496
-
497
- writer.tracker.stopTracking('pageChanged', this.rowCallback);
498
- writer.context().moveDown(this.layout.paddingBottom(rowIndex, this.tableNode));
499
- writer.context().availableHeight += this.reservedAtBottom;
500
-
501
- var endingPage = writer.context().page;
502
- var endingY = writer.context().y;
503
-
504
- var xs = getLineXs();
505
-
506
- var ys = [];
507
-
508
- var hasBreaks = pageBreaks && pageBreaks.length > 0;
509
- var body = this.tableNode.table.body;
510
-
511
- ys.push({
512
- y0: this.rowTopY,
513
- page: hasBreaks ? pageBreaks[0].prevPage : endingPage
514
- });
515
-
516
- if (hasBreaks) {
517
- for (i = 0, l = pageBreaks.length; i < l; i++) {
518
- var pageBreak = pageBreaks[i];
519
- ys[ys.length - 1].y1 = pageBreak.prevY;
520
-
521
- ys.push({ y0: pageBreak.y, page: pageBreak.prevPage + 1, forced: pageBreak.forced });
522
- }
523
- }
524
-
525
- ys[ys.length - 1].y1 = endingY;
526
-
527
- var skipOrphanePadding = this.rowPaddingTop > 0 && (ys[0].y1 - ys[0].y0 === this.rowPaddingTop);
528
- if (rowIndex === 0 && !skipOrphanePadding && !this.rowsWithoutPageBreak && !this.dontBreakRows) {
529
- // Draw the top border of the table
530
- var pageTableStartedAt = null;
531
- if (pageBreaks && pageBreaks.length > 0) {
532
- // Get the page where table started at
533
- pageTableStartedAt = pageBreaks[0].prevPage;
534
- }
535
- this.drawHorizontalLine(0, writer, this._tableTopBorderY, false, pageTableStartedAt);
536
- }
537
- for (var yi = (skipOrphanePadding ? 1 : 0), yl = ys.length; yi < yl; yi++) {
538
- var willBreak = yi < ys.length - 1;
539
- var rowBreakWithoutHeader = (yi > 0 && !this.headerRows);
540
- var hzLineOffset = rowBreakWithoutHeader ? 0 : this.topLineWidth;
541
- var y1 = ys[yi].y0;
542
- var y2 = ys[yi].y1;
543
-
544
- if (willBreak) {
545
- y2 = y2 + this.rowPaddingBottom;
546
- }
547
-
548
- if (writer.context().page != ys[yi].page) {
549
- writer.context().page = ys[yi].page;
550
-
551
- //TODO: buggy, availableHeight should be updated on every pageChanged event
552
- // TableProcessor should be pageChanged listener, instead of processRow
553
- this.reservedAtBottom = 0;
554
- }
555
-
556
- // Draw horizontal lines before the vertical lines so they are not overridden
557
- // Only set isPageBreak=true when at TRUE page bottom (not just forced breaks)
558
- var ctx = writer.context();
559
- var currentPage = ctx.getCurrentPage && ctx.getCurrentPage();
560
- var pageHeight = currentPage ? (currentPage.pageSize.height - ctx.pageMargins.bottom) : 0;
561
-
562
- if (willBreak && this.layout.hLineWhenBroken !== false) {
563
- // Check if we're at the true page bottom
564
- var isAtTruePageBottom = (pageHeight - y2) <= nearBottomThreshold;
565
- this.drawHorizontalLine(rowIndex + 1, writer, y2, true, null, isAtTruePageBottom);
566
- }
567
- if (rowBreakWithoutHeader && this.layout.hLineWhenBroken !== false) {
568
- // Check if previous segment ended at true page bottom
569
- var prevSegmentY = (yi > 0) ? ys[yi - 1].y1 : 0;
570
- var prevWasAtPageBottom = (pageHeight - prevSegmentY) <= nearBottomThreshold;
571
- this.drawHorizontalLine(rowIndex, writer, y1, true, null, prevWasAtPageBottom);
572
- }
573
-
574
- for (i = 0, l = xs.length; i < l; i++) {
575
- var leftCellBorder = false;
576
- var rightCellBorder = false;
577
- var colIndex = xs[i].index;
578
-
579
- // current cell
580
- var cell;
581
- if (colIndex < body[rowIndex].length) {
582
- cell = body[rowIndex][colIndex];
583
- leftCellBorder = cell.border ? cell.border[0] : this.layout.defaultBorder;
584
- rightCellBorder = cell.border ? cell.border[2] : this.layout.defaultBorder;
585
- }
586
-
587
- // before cell
588
- if (colIndex > 0 && !leftCellBorder) {
589
- cell = body[rowIndex][colIndex - 1];
590
- leftCellBorder = cell.border ? cell.border[2] : this.layout.defaultBorder;
591
- }
592
-
593
- // after cell
594
- if (colIndex + 1 < body[rowIndex].length && !rightCellBorder) {
595
- cell = body[rowIndex][colIndex + 1];
596
- rightCellBorder = cell.border ? cell.border[0] : this.layout.defaultBorder;
597
- }
598
-
599
- if (leftCellBorder) {
600
- this.drawVerticalLine(xs[i].x, y1 - hzLineOffset, y2 + this.bottomLineWidth, xs[i].index, writer, rowIndex, xs[i - 1] ? xs[i - 1].index : null);
601
- }
602
-
603
- if (i < l - 1) {
604
- var fillColor = body[rowIndex][colIndex].fillColor;
605
- var fillOpacity = body[rowIndex][colIndex].fillOpacity;
606
- if (!fillColor) {
607
- fillColor = isFunction(this.layout.fillColor) ? this.layout.fillColor(rowIndex, this.tableNode, colIndex) : this.layout.fillColor;
608
- }
609
- if (!isNumber(fillOpacity)) {
610
- fillOpacity = isFunction(this.layout.fillOpacity) ? this.layout.fillOpacity(rowIndex, this.tableNode, colIndex) : this.layout.fillOpacity;
611
- }
612
- var overlayPattern = body[rowIndex][colIndex].overlayPattern;
613
- var overlayOpacity = body[rowIndex][colIndex].overlayOpacity;
614
- if (fillColor || overlayPattern) {
615
- var widthLeftBorder = leftCellBorder ? this.layout.vLineWidth(colIndex, this.tableNode) : 0;
616
- var widthRightBorder;
617
- if ((colIndex === 0 || colIndex + 1 == body[rowIndex].length) && !rightCellBorder) {
618
- widthRightBorder = this.layout.vLineWidth(colIndex + 1, this.tableNode);
619
- } else if (rightCellBorder) {
620
- widthRightBorder = this.layout.vLineWidth(colIndex + 1, this.tableNode) / 2;
621
- } else {
622
- widthRightBorder = 0;
623
- }
624
-
625
- var x1f = this.dontBreakRows ? xs[i].x + widthLeftBorder : xs[i].x + (widthLeftBorder / 2);
626
- var y1f = this.dontBreakRows ? y1 : y1 - (hzLineOffset / 2);
627
- var x2f = xs[i + 1].x + widthRightBorder;
628
- var y2f = this.dontBreakRows ? y2 + this.bottomLineWidth : y2 + (this.bottomLineWidth / 2);
629
- var bgWidth = x2f - x1f;
630
- var bgHeight = y2f - y1f;
631
- if (fillColor) {
632
- writer.addVector({
633
- type: 'rect',
634
- x: x1f,
635
- y: y1f,
636
- w: bgWidth,
637
- h: bgHeight,
638
- lineWidth: 0,
639
- color: fillColor,
640
- fillOpacity: fillOpacity,
641
- // mark if we are in an unbreakable block
642
- _isFillColorFromUnbreakable: !!writer.transactionLevel
643
- }, false, true, writer.context().backgroundLength[writer.context().page]);
644
- }
645
-
646
- if (overlayPattern) {
647
- writer.addVector({
648
- type: 'rect',
649
- x: x1f,
650
- y: y1f,
651
- w: bgWidth,
652
- h: bgHeight,
653
- lineWidth: 0,
654
- color: overlayPattern,
655
- fillOpacity: overlayOpacity
656
- }, false, true);
657
- }
658
- }
659
- }
660
- }
661
- }
662
-
663
- writer.context().page = endingPage;
664
- writer.context().y = endingY;
665
-
666
- var row = this.tableNode.table.body[rowIndex];
667
- for (i = 0, l = row.length; i < l; i++) {
668
- if (row[i].rowSpan) {
669
- this.rowSpanData[i].rowSpan = row[i].rowSpan;
670
-
671
- // fix colSpans
672
- if (row[i].colSpan && row[i].colSpan > 1) {
673
- for (var rowSpanIndex = 1; rowSpanIndex < row[i].rowSpan; rowSpanIndex++) {
674
- this.tableNode.table.body[rowIndex + rowSpanIndex][i]._colSpan = row[i].colSpan;
675
- }
676
- }
677
- // fix rowSpans
678
- if (row[i].rowSpan && row[i].rowSpan > 1) {
679
- for (var spanOffset = 1; spanOffset < row[i].rowSpan; spanOffset++) {
680
- this.tableNode.table.body[rowIndex + spanOffset][i]._rowSpanCurrentOffset = spanOffset;
681
- }
682
- }
683
- }
684
-
685
- if (this.rowSpanData[i].rowSpan > 0) {
686
- this.rowSpanData[i].rowSpan--;
687
- }
688
- }
689
-
690
- // Look ahead: Check if next row will cause a page break
691
- // If yes, skip drawing the horizontal line at the end of current row
692
- // This feature is OPT-IN: only enabled when forcePageBreakForAllRows === true
693
- var shouldSkipHorizontalLine = false;
694
-
695
- if (nextRowCells && layoutBuilder) {
696
- // Only perform look-ahead if forcePageBreakForAllRows is explicitly enabled
697
- // This prevents affecting existing PDFs that don't use this feature
698
- if (tableLayout && tableLayout.forcePageBreakForAllRows === true) {
699
- var nextRowEstimatedHeight = 0;
700
-
701
- // Try to get maximum cell height from next row's measured content
702
- if (layoutBuilder._getMaxCellHeight) {
703
- var maxNextCellHeight = layoutBuilder._getMaxCellHeight(nextRowCells);
704
- if (maxNextCellHeight > 0) {
705
- nextRowEstimatedHeight = maxNextCellHeight + 10; // Add padding
706
- }
707
- }
708
-
709
- // Fallback: use minRowHeight from table layout
710
- if (nextRowEstimatedHeight === 0) {
711
- var footerGapOpt = writer.context()._footerGapOption;
712
- nextRowEstimatedHeight = (tableLayout && tableLayout.minRowHeight) || (footerGapOpt && footerGapOpt.minRowHeight) || 80;
713
- }
714
-
715
- // Check current available height (after current row has been processed)
716
- var currentAvailableHeight = writer.context().availableHeight;
717
-
718
- // Skip drawing the horizontal line if EITHER:
719
- // 1. Next row won't fit (will cause page break), OR
720
- // 2. We're very close to page bottom (remaining space <= threshold)
721
- // This prevents duplicate/orphaned lines at page boundaries
722
- if (nextRowEstimatedHeight > currentAvailableHeight || currentAvailableHeight <= nearBottomThreshold) {
723
- // Exception: Check if this line should always be drawn (critical boundary)
724
- // Allow layout to specify via alwaysDrawHLine callback
725
- var shouldAlwaysDrawLine = false;
726
- if (typeof tableLayout.alwaysDrawHLine === 'function') {
727
- shouldAlwaysDrawLine = tableLayout.alwaysDrawHLine(rowIndex + 1, this.tableNode, writer);
728
- }
729
-
730
- if (!shouldAlwaysDrawLine) {
731
- shouldSkipHorizontalLine = true;
732
- }
733
- }
734
- }
735
- }
736
-
737
- // Draw horizontal line at the end of the row, unless next row will cause a page break
738
- if (!shouldSkipHorizontalLine) {
739
- this.drawHorizontalLine(rowIndex + 1, writer);
740
- }
741
-
742
- if (this.headerRows && rowIndex === this.headerRows - 1) {
743
- this.headerRepeatable = writer.currentBlockToRepeatable();
744
- }
745
-
746
- if (this.dontBreakRows) {
747
- writer.tracker.auto('pageChanged',
748
- function () {
749
- if (rowIndex > 0 && !self.headerRows && self.layout.hLineWhenBroken !== false) {
750
- // Draw the top border of the row after a page break
751
- self.drawHorizontalLine(rowIndex, writer);
752
- }
753
- },
754
- function () {
755
- writer.commitUnbreakableBlock();
756
- }
757
- );
758
- }
759
-
760
- if (this.headerRepeatable && (rowIndex === (this.rowsWithoutPageBreak - 1) || rowIndex === this.tableNode.table.body.length - 1)) {
761
- writer.commitUnbreakableBlock();
762
- writer.pushToRepeatables(this.headerRepeatable);
763
- this.cleanUpRepeatables = true;
764
- this.headerRepeatable = null;
765
- }
766
-
767
- function getLineXs() {
768
- var result = [];
769
- var cols = 0;
770
-
771
- for (var i = 0, l = self.tableNode.table.body[rowIndex].length; i < l; i++) {
772
- if (!cols) {
773
- result.push({ x: self.rowSpanData[i].left, index: i });
774
-
775
- var item = self.tableNode.table.body[rowIndex][i];
776
- cols = (item._colSpan || item.colSpan || 0);
777
- }
778
- if (cols > 0) {
779
- cols--;
780
- }
781
- }
782
-
783
- result.push({ x: self.rowSpanData[self.rowSpanData.length - 1].left, index: self.rowSpanData.length - 1 });
784
-
785
- return result;
786
- }
787
- };
788
-
789
- module.exports = TableProcessor;
1
+ 'use strict';
2
+
3
+ var ColumnCalculator = require('./columnCalculator');
4
+ var isFunction = require('./helpers').isFunction;
5
+ var isNumber = require('./helpers').isNumber;
6
+ var isPositiveInteger = require('./helpers').isPositiveInteger;
7
+
8
+ function TableProcessor(tableNode) {
9
+ this.tableNode = tableNode;
10
+ this.collectFooterColumns = Boolean(tableNode.footerGapCollect === 'product-items');
11
+ }
12
+
13
+ TableProcessor.prototype.beginTable = function (writer) {
14
+ var tableNode;
15
+ var availableWidth;
16
+ var self = this;
17
+
18
+ tableNode = this.tableNode;
19
+ this.offsets = tableNode._offsets;
20
+ this.layout = tableNode._layout;
21
+ this.beginNewPage = false;
22
+
23
+ if (tableNode.remark && writer.context().availableHeight) {
24
+ if (writer.context().availableHeight < 50) {
25
+ writer.context().moveDown(writer.context().availableHeight);
26
+ this.beginNewPage = true;
27
+ }
28
+ }
29
+
30
+ availableWidth = writer.context().availableWidth - this.offsets.total;
31
+ ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth, this.offsets.total, tableNode);
32
+
33
+ const offsets = this.offsets || { left: 0 };
34
+ const leftOffset = offsets.left || 0;
35
+
36
+ this.tableWidth = tableNode._offsets.total + getTableInnerContentWidth();
37
+ this.rowSpanData = prepareRowSpanData();
38
+ this.cleanUpRepeatables = false;
39
+
40
+ // headersRows and rowsWithoutPageBreak (headerRows + keepWithHeaderRows)
41
+ this.headerRows = 0;
42
+ this.rowsWithoutPageBreak = 0;
43
+
44
+ var headerRows = tableNode.table.headerRows;
45
+
46
+ if (isPositiveInteger(headerRows)) {
47
+ // Fix: If headerRows exceeds available rows, adjust to match body length
48
+ // This prevents errors when tables have empty bodies
49
+ if (headerRows > tableNode.table.body.length) {
50
+ this.headerRows = tableNode.table.body.length;
51
+ } else {
52
+ this.headerRows = headerRows;
53
+ }
54
+
55
+ this.rowsWithoutPageBreak = this.headerRows;
56
+
57
+ const keepWithHeaderRows = tableNode.table.keepWithHeaderRows;
58
+
59
+ if (isPositiveInteger(keepWithHeaderRows)) {
60
+ this.rowsWithoutPageBreak += keepWithHeaderRows;
61
+ }
62
+ }
63
+
64
+ this.dontBreakRows = tableNode.table.dontBreakRows || false;
65
+
66
+ if (this.rowsWithoutPageBreak || this.dontBreakRows) {
67
+ writer.beginUnbreakableBlock();
68
+ // Draw the top border of the table
69
+ this.drawHorizontalLine(0, writer);
70
+ if (this.rowsWithoutPageBreak && this.dontBreakRows) {
71
+ writer.beginUnbreakableBlock();
72
+ }
73
+ }
74
+
75
+ // update the border properties of all cells before drawing any lines
76
+ prepareCellBorders(this.tableNode.table.body);
77
+
78
+ function getTableInnerContentWidth() {
79
+ var width = 0;
80
+
81
+ tableNode.table.widths.forEach(function (w) {
82
+ width += w._calcWidth;
83
+ });
84
+
85
+ return width;
86
+ }
87
+
88
+ function prepareRowSpanData() {
89
+ var rsd = [];
90
+ var x = 0;
91
+ var lastWidth = 0;
92
+
93
+ rsd.push({ left: 0, rowSpan: 0 });
94
+
95
+ if (!self.tableNode.table.body[0]) {
96
+ return rsd;
97
+ }
98
+ for (var i = 0, l = self.tableNode.table.body[0].length; i < l; i++) {
99
+ var paddings = self.layout.paddingLeft(i, self.tableNode) + self.layout.paddingRight(i, self.tableNode);
100
+ var lBorder = self.layout.vLineWidth(i, self.tableNode);
101
+ lastWidth = paddings + lBorder + self.tableNode.table.widths[i]._calcWidth;
102
+ rsd[rsd.length - 1].width = lastWidth;
103
+ x += lastWidth;
104
+ rsd.push({ left: x, rowSpan: 0, width: 0 });
105
+ }
106
+
107
+ return rsd;
108
+ }
109
+
110
+ // Iterate through all cells. If the current cell is the start of a
111
+ // rowSpan/colSpan, update the border property of the cells on its
112
+ // bottom/right accordingly. This is needed since each iteration of the
113
+ // line-drawing loops draws lines for a single cell, not for an entire
114
+ // rowSpan/colSpan.
115
+ function prepareCellBorders(body) {
116
+ for (var rowIndex = 0; rowIndex < body.length; rowIndex++) {
117
+ var row = body[rowIndex];
118
+
119
+ for (var colIndex = 0; colIndex < row.length; colIndex++) {
120
+ var cell = row[colIndex];
121
+
122
+ if (cell.border) {
123
+ var rowSpan = cell.rowSpan || 1;
124
+ var colSpan = cell.colSpan || 1;
125
+
126
+ for (var rowOffset = 0; rowOffset < rowSpan; rowOffset++) {
127
+ // set left border
128
+ if (cell.border[0] !== undefined && rowOffset > 0) {
129
+ setBorder(rowIndex + rowOffset, colIndex, 0, cell.border[0]);
130
+ }
131
+
132
+ // set right border
133
+ if (cell.border[2] !== undefined) {
134
+ setBorder(rowIndex + rowOffset, colIndex + colSpan - 1, 2, cell.border[2]);
135
+ }
136
+ }
137
+
138
+ for (var colOffset = 0; colOffset < colSpan; colOffset++) {
139
+ // set top border
140
+ if (cell.border[1] !== undefined && colOffset > 0) {
141
+ setBorder(rowIndex, colIndex + colOffset, 1, cell.border[1]);
142
+ }
143
+
144
+ // set bottom border
145
+ if (cell.border[3] !== undefined) {
146
+ setBorder(rowIndex + rowSpan - 1, colIndex + colOffset, 3, cell.border[3]);
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ // helper function to set the border for a given cell
154
+ function setBorder(rowIndex, colIndex, borderIndex, borderValue) {
155
+ var cell = body[rowIndex][colIndex];
156
+ cell.border = cell.border || {};
157
+ cell.border[borderIndex] = borderValue;
158
+ }
159
+ }
160
+ };
161
+
162
+ TableProcessor.prototype.onRowBreak = function (rowIndex, writer) {
163
+ var self = this;
164
+ return function () {
165
+ var offset = self.rowPaddingTop + (!self.headerRows ? self.topLineWidth : 0);
166
+ var currentPage = writer.context().getCurrentPage && writer.context().getCurrentPage();
167
+ if (currentPage && currentPage.items[0] && currentPage.items[0].item.remark) {
168
+ currentPage.items[0].item.lineColor = '#d5d5d5';
169
+ }
170
+ writer.context().availableHeight -= self.reservedAtBottom;
171
+ writer.context().moveDown(offset);
172
+ };
173
+ };
174
+
175
+ TableProcessor.prototype.beginRow = function (rowIndex, writer) {
176
+ this.topLineWidth = this.layout.hLineWidth(rowIndex, this.tableNode, writer);
177
+ this.rowPaddingTop = this.layout.paddingTop(rowIndex, this.tableNode);
178
+ this.bottomLineWidth = this.layout.hLineWidth(rowIndex + 1, this.tableNode, writer);
179
+ this.rowPaddingBottom = this.layout.paddingBottom(rowIndex, this.tableNode);
180
+
181
+ this.rowCallback = this.onRowBreak(rowIndex, writer);
182
+
183
+ if(this.tableNode.eventHandle && this.tableNode.eventHandle.beforePageChanged)
184
+ {
185
+
186
+ this.beforePageChanged = this.tableNode.eventHandle.beforePageChanged(this, rowIndex, writer);
187
+ writer.tracker.startTracking('beforePageChanged', this.beforePageChanged);
188
+ }
189
+
190
+ writer.tracker.startTracking('pageChanged', this.rowCallback);
191
+ if (rowIndex == 0 && !this.dontBreakRows && !this.rowsWithoutPageBreak) {
192
+ // We store the 'y' to draw later and if necessary the top border of the table
193
+ this._tableTopBorderY = writer.context().y;
194
+ writer.context().moveDown(this.topLineWidth);
195
+ }
196
+ if (this.dontBreakRows && rowIndex > 0) {
197
+ writer.beginUnbreakableBlock();
198
+ }
199
+ this.rowTopY = writer.context().y;
200
+ this.reservedAtBottom = this.bottomLineWidth + this.rowPaddingBottom;
201
+
202
+ writer.context().availableHeight -= this.reservedAtBottom;
203
+
204
+ writer.context().moveDown(this.rowPaddingTop);
205
+ };
206
+
207
+ TableProcessor.prototype.drawHorizontalLine = function (lineIndex, writer, overrideY, moveDown = true, forcePage, isPageBreak = false) {
208
+ var lineWidth = this.layout.hLineWidth(lineIndex, this.tableNode, writer, isPageBreak);
209
+ if (lineWidth) {
210
+ var style = this.layout.hLineStyle(lineIndex, this.tableNode);
211
+ var dash;
212
+ if (style && style.dash) {
213
+ dash = style.dash;
214
+ }
215
+
216
+ var offset = lineWidth / 2;
217
+ var currentLine = null;
218
+ var body = this.tableNode.table.body;
219
+ var cellAbove;
220
+ var currentCell;
221
+ var rowCellAbove;
222
+
223
+ for (var i = 0, l = this.rowSpanData.length; i < l; i++) {
224
+ var data = this.rowSpanData[i];
225
+ var shouldDrawLine = !data.rowSpan;
226
+ var borderColor = null;
227
+
228
+ // draw only if the current cell requires a top border or the cell in the
229
+ // row above requires a bottom border
230
+ if (shouldDrawLine && i < l - 1) {
231
+ var topBorder = false, bottomBorder = false, rowBottomBorder = false;
232
+
233
+ // the cell in the row above
234
+ if (lineIndex > 0) {
235
+ cellAbove = body[lineIndex - 1][i];
236
+ bottomBorder = cellAbove.border ? cellAbove.border[3] : this.layout.defaultBorder;
237
+ if (bottomBorder && cellAbove.borderColor) {
238
+ borderColor = cellAbove.borderColor[3];
239
+ }
240
+ }
241
+
242
+ // the current cell
243
+ if (lineIndex < body.length) {
244
+ currentCell = body[lineIndex][i];
245
+ topBorder = currentCell.border ? currentCell.border[1] : this.layout.defaultBorder;
246
+ if (topBorder && borderColor == null && currentCell.borderColor) {
247
+ borderColor = currentCell.borderColor[1];
248
+ }
249
+ }
250
+
251
+ shouldDrawLine = topBorder || bottomBorder;
252
+ }
253
+
254
+ if (cellAbove && cellAbove._rowSpanCurrentOffset) {
255
+ rowCellAbove = body[lineIndex - 1 - cellAbove._rowSpanCurrentOffset][i];
256
+ rowBottomBorder = rowCellAbove && rowCellAbove.border ? rowCellAbove.border[3] : this.layout.defaultBorder;
257
+ if (rowBottomBorder && rowCellAbove && rowCellAbove.borderColor) {
258
+ borderColor = rowCellAbove.borderColor[3];
259
+ }
260
+ }
261
+
262
+ if (borderColor == null) {
263
+ borderColor = isFunction(this.layout.hLineColor) ? this.layout.hLineColor(lineIndex, this.tableNode, i, this.beginNewPage) : this.layout.hLineColor;
264
+ }
265
+
266
+ if (!currentLine && shouldDrawLine) {
267
+ currentLine = { left: data.left, width: 0 };
268
+ }
269
+
270
+ if (shouldDrawLine) {
271
+ var colSpanIndex = 0;
272
+ if (rowCellAbove && rowCellAbove.colSpan && rowBottomBorder) {
273
+ while (rowCellAbove.colSpan > colSpanIndex) {
274
+ currentLine.width += (this.rowSpanData[i + colSpanIndex++].width || 0);
275
+ }
276
+ i += colSpanIndex - 1;
277
+ } else if (cellAbove && cellAbove.colSpan && bottomBorder) {
278
+ while (cellAbove.colSpan > colSpanIndex) {
279
+ currentLine.width += (this.rowSpanData[i + colSpanIndex++].width || 0);
280
+ }
281
+ i += colSpanIndex - 1;
282
+ } else if (currentCell && currentCell.colSpan && topBorder) {
283
+ while (currentCell.colSpan > colSpanIndex) {
284
+ currentLine.width += (this.rowSpanData[i + colSpanIndex++].width || 0);
285
+ }
286
+ i += colSpanIndex - 1;
287
+ } else {
288
+ currentLine.width += (this.rowSpanData[i].width || 0);
289
+ }
290
+ }
291
+
292
+ var y = (overrideY || 0) + offset;
293
+
294
+
295
+ if (shouldDrawLine) {
296
+ if (currentLine && currentLine.width) {
297
+ writer.addVector({
298
+ type: 'line',
299
+ remark: this.tableNode.remark,
300
+ x1: currentLine.left,
301
+ x2: currentLine.left + currentLine.width,
302
+ y1: y,
303
+ y2: y,
304
+ lineWidth: lineWidth,
305
+ dash: dash,
306
+ lineColor: borderColor
307
+ }, false, isNumber(overrideY), null, forcePage);
308
+ currentLine = null;
309
+ borderColor = null;
310
+ cellAbove = null;
311
+ currentCell = null;
312
+ rowCellAbove = null;
313
+ }
314
+ }
315
+ }
316
+
317
+ if (moveDown) {
318
+ writer.context().moveDown(lineWidth);
319
+ }
320
+ }
321
+ };
322
+
323
+
324
+ /**
325
+ * Draws vertical lines to fill the gap when a row is forced to the next page.
326
+ * This prevents missing vertical lines at the bottom of the page.
327
+ * @param {number} rowIndex - The index of the row being forced to next page
328
+ * @param {object} writer - The document writer
329
+ * @param {number} y0 - Starting Y position
330
+ * @param {number} y1 - Ending Y position (page break point)
331
+ */
332
+ TableProcessor.prototype.drawVerticalLinesForForcedPageBreak = function (rowIndex, writer, y0, y1) {
333
+ if (!this.rowSpanData || rowIndex <= 0) {
334
+ return;
335
+ }
336
+
337
+ var body = this.tableNode.table.body;
338
+ var prevRowIndex = rowIndex - 1; // Use previous row for cell border detection
339
+
340
+ // Get X positions for vertical lines (similar logic to endRow's getLineXs)
341
+ var xs = [];
342
+ var cols = 0;
343
+
344
+ for (var i = 0, l = body[prevRowIndex].length; i < l; i++) {
345
+ if (!cols) {
346
+ xs.push({ x: this.rowSpanData[i].left, index: i });
347
+ var item = body[prevRowIndex][i];
348
+ cols = (item._colSpan || item.colSpan || 0);
349
+ }
350
+ if (cols > 0) {
351
+ cols--;
352
+ }
353
+ }
354
+ xs.push({ x: this.rowSpanData[this.rowSpanData.length - 1].left, index: this.rowSpanData.length - 1 });
355
+
356
+ // Draw vertical lines for each column position
357
+ for (var xi = 0, xl = xs.length; xi < xl; xi++) {
358
+ var leftCellBorder = false;
359
+ var colIndex = xs[xi].index;
360
+
361
+ // Check if we need to draw a vertical line at this position
362
+ // based on cell borders from the previous row
363
+ var cell;
364
+ if (colIndex < body[prevRowIndex].length) {
365
+ cell = body[prevRowIndex][colIndex];
366
+ leftCellBorder = cell.border ? cell.border[0] : this.layout.defaultBorder;
367
+ }
368
+
369
+ // Check cell before
370
+ if (colIndex > 0 && !leftCellBorder) {
371
+ cell = body[prevRowIndex][colIndex - 1];
372
+ leftCellBorder = cell.border ? cell.border[2] : this.layout.defaultBorder;
373
+ }
374
+
375
+ if (leftCellBorder) {
376
+ this.drawVerticalLine(
377
+ xs[xi].x,
378
+ y0,
379
+ y1,
380
+ xs[xi].index,
381
+ writer,
382
+ prevRowIndex,
383
+ xi > 0 ? xs[xi - 1].index : null
384
+ );
385
+ }
386
+ }
387
+ };
388
+
389
+ TableProcessor.prototype.drawVerticalLine = function (x, y0, y1, vLineColIndex, writer, vLineRowIndex, beforeVLineColIndex) {
390
+ var width = this.layout.vLineWidth(vLineColIndex, this.tableNode);
391
+ if (width === 0) {
392
+ return;
393
+ }
394
+
395
+ var ctx = writer && typeof writer.context === 'function' ? writer.context() : null;
396
+ if (ctx && this.collectFooterColumns) {
397
+ var footerOpt = ctx._footerGapOption;
398
+ if (footerOpt && footerOpt.enabled) {
399
+ var columns = footerOpt.columns || (footerOpt.columns = {});
400
+ var content = columns.content || (columns.content = { vLines: [], vLineWidths: [] });
401
+ var contentVLinesLength = content.vLines.length || 0;
402
+ var widthLength = footerOpt.columns.widthLength || 0;
403
+
404
+ // Only collect if we haven't exceeded the width length
405
+ // Need widthLength + 1 lines to include both left and right borders
406
+ if(widthLength === 0 || contentVLinesLength <= widthLength){
407
+ // Store the base X position (without width offset)
408
+ content.vLines.push((ctx.x || 0) + x);
409
+ // Store the actual line width used by the table
410
+ content.vLineWidths.push(width);
411
+ }
412
+ }
413
+ }
414
+
415
+ var style = this.layout.vLineStyle(vLineColIndex, this.tableNode);
416
+ var dash;
417
+ if (style && style.dash) {
418
+ dash = style.dash;
419
+ }
420
+
421
+ var body = this.tableNode.table.body;
422
+ var cellBefore;
423
+ var currentCell;
424
+ var borderColor;
425
+
426
+ // the cell in the col before
427
+ if (vLineColIndex > 0) {
428
+ cellBefore = body[vLineRowIndex][beforeVLineColIndex];
429
+ if (cellBefore && cellBefore.borderColor) {
430
+ if (cellBefore.border ? cellBefore.border[2] : this.layout.defaultBorder) {
431
+ borderColor = cellBefore.borderColor[2];
432
+ }
433
+ }
434
+ }
435
+
436
+ // the current cell
437
+ if (borderColor == null && vLineColIndex < body.length) {
438
+ currentCell = body[vLineRowIndex][vLineColIndex];
439
+ if (currentCell && currentCell.borderColor) {
440
+ if (currentCell.border ? currentCell.border[0] : this.layout.defaultBorder) {
441
+ borderColor = currentCell.borderColor[0];
442
+ }
443
+ }
444
+ }
445
+
446
+ if (borderColor == null && cellBefore && cellBefore._rowSpanCurrentOffset) {
447
+ var rowCellBeforeAbove = body[vLineRowIndex - cellBefore._rowSpanCurrentOffset][beforeVLineColIndex];
448
+ if (rowCellBeforeAbove.borderColor) {
449
+ if (rowCellBeforeAbove.border ? rowCellBeforeAbove.border[2] : this.layout.defaultBorder) {
450
+ borderColor = rowCellBeforeAbove.borderColor[2];
451
+ }
452
+ }
453
+ }
454
+
455
+ if (borderColor == null && currentCell && currentCell._rowSpanCurrentOffset) {
456
+ var rowCurrentCellAbove = body[vLineRowIndex - currentCell._rowSpanCurrentOffset][vLineColIndex];
457
+ if (rowCurrentCellAbove.borderColor) {
458
+ if (rowCurrentCellAbove.border ? rowCurrentCellAbove.border[2] : this.layout.defaultBorder) {
459
+ borderColor = rowCurrentCellAbove.borderColor[2];
460
+ }
461
+ }
462
+ }
463
+
464
+ if (borderColor == null) {
465
+ borderColor = isFunction(this.layout.vLineColor) ? this.layout.vLineColor(vLineColIndex, this.tableNode, vLineRowIndex) : this.layout.vLineColor;
466
+ }
467
+ writer.addVector({
468
+ type: 'line',
469
+ x1: x + width / 2,
470
+ x2: x + width / 2,
471
+ y1: y0,
472
+ y2: y1,
473
+ lineWidth: width,
474
+ dash: dash,
475
+ lineColor: borderColor
476
+ }, false, true);
477
+ cellBefore = null;
478
+ currentCell = null;
479
+ borderColor = null;
480
+ };
481
+
482
+ TableProcessor.prototype.endTable = function (writer) {
483
+ if (this.cleanUpRepeatables) {
484
+ writer.popFromRepeatables();
485
+ }
486
+ };
487
+
488
+ TableProcessor.prototype.endRow = function (rowIndex, writer, pageBreaks, nextRowCells, layoutBuilder) {
489
+ var l, i;
490
+ var self = this;
491
+ var tableLayout = this.tableNode && this.tableNode._layout;
492
+ var nearBottomThreshold = (tableLayout && tableLayout.nearBottomThreshold) || 20;
493
+
494
+ if(this.tableNode.eventHandle && this.tableNode.eventHandle.beforePageChanged)
495
+ {
496
+ writer.tracker.stopTracking('beforePageChanged', this.beforePageChanged);
497
+ }
498
+
499
+ writer.tracker.stopTracking('pageChanged', this.rowCallback);
500
+ writer.context().moveDown(this.layout.paddingBottom(rowIndex, this.tableNode));
501
+ writer.context().availableHeight += this.reservedAtBottom;
502
+
503
+ var endingPage = writer.context().page;
504
+ var endingY = writer.context().y;
505
+
506
+ var xs = getLineXs();
507
+
508
+ var ys = [];
509
+
510
+ var hasBreaks = pageBreaks && pageBreaks.length > 0;
511
+ var body = this.tableNode.table.body;
512
+
513
+ ys.push({
514
+ y0: this.rowTopY,
515
+ page: hasBreaks ? pageBreaks[0].prevPage : endingPage
516
+ });
517
+
518
+ if (hasBreaks) {
519
+ for (i = 0, l = pageBreaks.length; i < l; i++) {
520
+ var pageBreak = pageBreaks[i];
521
+ ys[ys.length - 1].y1 = pageBreak.prevY;
522
+
523
+ ys.push({ y0: pageBreak.y, page: pageBreak.prevPage + 1, forced: pageBreak.forced });
524
+ }
525
+ }
526
+
527
+ ys[ys.length - 1].y1 = endingY;
528
+
529
+ var skipOrphanePadding = this.rowPaddingTop > 0 && (ys[0].y1 - ys[0].y0 === this.rowPaddingTop);
530
+ if (rowIndex === 0 && !skipOrphanePadding && !this.rowsWithoutPageBreak && !this.dontBreakRows) {
531
+ // Draw the top border of the table
532
+ var pageTableStartedAt = null;
533
+ if (pageBreaks && pageBreaks.length > 0) {
534
+ // Get the page where table started at
535
+ pageTableStartedAt = pageBreaks[0].prevPage;
536
+ }
537
+ this.drawHorizontalLine(0, writer, this._tableTopBorderY, false, pageTableStartedAt);
538
+ }
539
+ for (var yi = (skipOrphanePadding ? 1 : 0), yl = ys.length; yi < yl; yi++) {
540
+ var willBreak = yi < ys.length - 1;
541
+ var rowBreakWithoutHeader = (yi > 0 && !this.headerRows);
542
+ var hzLineOffset = rowBreakWithoutHeader ? 0 : this.topLineWidth;
543
+ var y1 = ys[yi].y0;
544
+ var y2 = ys[yi].y1;
545
+
546
+ if (willBreak) {
547
+ y2 = y2 + this.rowPaddingBottom;
548
+ }
549
+
550
+ if (writer.context().page != ys[yi].page) {
551
+ writer.context().page = ys[yi].page;
552
+
553
+ //TODO: buggy, availableHeight should be updated on every pageChanged event
554
+ // TableProcessor should be pageChanged listener, instead of processRow
555
+ this.reservedAtBottom = 0;
556
+ }
557
+
558
+ // Draw horizontal lines before the vertical lines so they are not overridden
559
+ // Only set isPageBreak=true when at TRUE page bottom (not just forced breaks)
560
+ var ctx = writer.context();
561
+ var currentPage = ctx.getCurrentPage && ctx.getCurrentPage();
562
+ var pageHeight = currentPage ? (currentPage.pageSize.height - ctx.pageMargins.bottom) : 0;
563
+
564
+ if (willBreak && this.layout.hLineWhenBroken !== false) {
565
+ // Check if we're at the true page bottom
566
+ var isAtTruePageBottom = (pageHeight - y2) <= nearBottomThreshold;
567
+ this.drawHorizontalLine(rowIndex + 1, writer, y2, true, null, isAtTruePageBottom);
568
+ }
569
+ if (rowBreakWithoutHeader && this.layout.hLineWhenBroken !== false) {
570
+ // Check if previous segment ended at true page bottom
571
+ var prevSegmentY = (yi > 0) ? ys[yi - 1].y1 : 0;
572
+ var prevWasAtPageBottom = (pageHeight - prevSegmentY) <= nearBottomThreshold;
573
+ this.drawHorizontalLine(rowIndex, writer, y1, true, null, prevWasAtPageBottom);
574
+ }
575
+
576
+ for (i = 0, l = xs.length; i < l; i++) {
577
+ var leftCellBorder = false;
578
+ var rightCellBorder = false;
579
+ var colIndex = xs[i].index;
580
+
581
+ // current cell
582
+ var cell;
583
+ if (colIndex < body[rowIndex].length) {
584
+ cell = body[rowIndex][colIndex];
585
+ leftCellBorder = cell.border ? cell.border[0] : this.layout.defaultBorder;
586
+ rightCellBorder = cell.border ? cell.border[2] : this.layout.defaultBorder;
587
+ }
588
+
589
+ // before cell
590
+ if (colIndex > 0 && !leftCellBorder) {
591
+ cell = body[rowIndex][colIndex - 1];
592
+ leftCellBorder = cell.border ? cell.border[2] : this.layout.defaultBorder;
593
+ }
594
+
595
+ // after cell
596
+ if (colIndex + 1 < body[rowIndex].length && !rightCellBorder) {
597
+ cell = body[rowIndex][colIndex + 1];
598
+ rightCellBorder = cell.border ? cell.border[0] : this.layout.defaultBorder;
599
+ }
600
+
601
+ if (leftCellBorder) {
602
+ this.drawVerticalLine(xs[i].x, y1 - hzLineOffset, y2 + this.bottomLineWidth, xs[i].index, writer, rowIndex, xs[i - 1] ? xs[i - 1].index : null);
603
+ }
604
+
605
+ if (i < l - 1) {
606
+ var fillColor = body[rowIndex][colIndex].fillColor;
607
+ var fillOpacity = body[rowIndex][colIndex].fillOpacity;
608
+ if (!fillColor) {
609
+ fillColor = isFunction(this.layout.fillColor) ? this.layout.fillColor(rowIndex, this.tableNode, colIndex) : this.layout.fillColor;
610
+ }
611
+ if (!isNumber(fillOpacity)) {
612
+ fillOpacity = isFunction(this.layout.fillOpacity) ? this.layout.fillOpacity(rowIndex, this.tableNode, colIndex) : this.layout.fillOpacity;
613
+ }
614
+ var overlayPattern = body[rowIndex][colIndex].overlayPattern;
615
+ var overlayOpacity = body[rowIndex][colIndex].overlayOpacity;
616
+ if (fillColor || overlayPattern) {
617
+ var widthLeftBorder = leftCellBorder ? this.layout.vLineWidth(colIndex, this.tableNode) : 0;
618
+ var widthRightBorder;
619
+ if ((colIndex === 0 || colIndex + 1 == body[rowIndex].length) && !rightCellBorder) {
620
+ widthRightBorder = this.layout.vLineWidth(colIndex + 1, this.tableNode);
621
+ } else if (rightCellBorder) {
622
+ widthRightBorder = this.layout.vLineWidth(colIndex + 1, this.tableNode) / 2;
623
+ } else {
624
+ widthRightBorder = 0;
625
+ }
626
+
627
+ var x1f = this.dontBreakRows ? xs[i].x + widthLeftBorder : xs[i].x + (widthLeftBorder / 2);
628
+ var y1f = this.dontBreakRows ? y1 : y1 - (hzLineOffset / 2);
629
+ var x2f = xs[i + 1].x + widthRightBorder;
630
+ var y2f = this.dontBreakRows ? y2 + this.bottomLineWidth : y2 + (this.bottomLineWidth / 2);
631
+ var bgWidth = x2f - x1f;
632
+ var bgHeight = y2f - y1f;
633
+ if (fillColor) {
634
+ writer.addVector({
635
+ type: 'rect',
636
+ x: x1f,
637
+ y: y1f,
638
+ w: bgWidth,
639
+ h: bgHeight,
640
+ lineWidth: 0,
641
+ color: fillColor,
642
+ fillOpacity: fillOpacity,
643
+ // mark if we are in an unbreakable block
644
+ _isFillColorFromUnbreakable: !!writer.transactionLevel
645
+ }, false, true, writer.context().backgroundLength[writer.context().page]);
646
+ }
647
+
648
+ if (overlayPattern) {
649
+ writer.addVector({
650
+ type: 'rect',
651
+ x: x1f,
652
+ y: y1f,
653
+ w: bgWidth,
654
+ h: bgHeight,
655
+ lineWidth: 0,
656
+ color: overlayPattern,
657
+ fillOpacity: overlayOpacity
658
+ }, false, true);
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ writer.context().page = endingPage;
666
+ writer.context().y = endingY;
667
+
668
+ var row = this.tableNode.table.body[rowIndex];
669
+ for (i = 0, l = row.length; i < l; i++) {
670
+ if (row[i].rowSpan) {
671
+ this.rowSpanData[i].rowSpan = row[i].rowSpan;
672
+
673
+ // fix colSpans
674
+ if (row[i].colSpan && row[i].colSpan > 1) {
675
+ for (var rowSpanIndex = 1; rowSpanIndex < row[i].rowSpan; rowSpanIndex++) {
676
+ this.tableNode.table.body[rowIndex + rowSpanIndex][i]._colSpan = row[i].colSpan;
677
+ }
678
+ }
679
+ // fix rowSpans
680
+ if (row[i].rowSpan && row[i].rowSpan > 1) {
681
+ for (var spanOffset = 1; spanOffset < row[i].rowSpan; spanOffset++) {
682
+ this.tableNode.table.body[rowIndex + spanOffset][i]._rowSpanCurrentOffset = spanOffset;
683
+ }
684
+ }
685
+ }
686
+
687
+ if (this.rowSpanData[i].rowSpan > 0) {
688
+ this.rowSpanData[i].rowSpan--;
689
+ }
690
+ }
691
+
692
+ // Look ahead: Check if next row will cause a page break
693
+ // If yes, skip drawing the horizontal line at the end of current row
694
+ // This feature is OPT-IN: only enabled when forcePageBreakForAllRows === true
695
+ var shouldSkipHorizontalLine = false;
696
+
697
+ if (nextRowCells && layoutBuilder) {
698
+ // Only perform look-ahead if forcePageBreakForAllRows is explicitly enabled
699
+ // This prevents affecting existing PDFs that don't use this feature
700
+ if (tableLayout && tableLayout.forcePageBreakForAllRows === true) {
701
+ var nextRowEstimatedHeight = 0;
702
+
703
+ // Try to get maximum cell height from next row's measured content
704
+ if (layoutBuilder._getMaxCellHeight) {
705
+ var maxNextCellHeight = layoutBuilder._getMaxCellHeight(nextRowCells);
706
+ if (maxNextCellHeight > 0) {
707
+ nextRowEstimatedHeight = maxNextCellHeight + 10; // Add padding
708
+ }
709
+ }
710
+
711
+ // Fallback: use minRowHeight from table layout
712
+ if (nextRowEstimatedHeight === 0) {
713
+ var footerGapOpt = writer.context()._footerGapOption;
714
+ nextRowEstimatedHeight = (tableLayout && tableLayout.minRowHeight) || (footerGapOpt && footerGapOpt.minRowHeight) || 80;
715
+ }
716
+
717
+ // Check current available height (after current row has been processed)
718
+ var currentAvailableHeight = writer.context().availableHeight;
719
+
720
+ // Skip drawing the horizontal line if EITHER:
721
+ // 1. Next row won't fit (will cause page break), OR
722
+ // 2. We're very close to page bottom (remaining space <= threshold)
723
+ // This prevents duplicate/orphaned lines at page boundaries
724
+ if (nextRowEstimatedHeight > currentAvailableHeight || currentAvailableHeight <= nearBottomThreshold) {
725
+ // Exception: Check if this line should always be drawn (critical boundary)
726
+ // Allow layout to specify via alwaysDrawHLine callback
727
+ var shouldAlwaysDrawLine = false;
728
+ if (typeof tableLayout.alwaysDrawHLine === 'function') {
729
+ shouldAlwaysDrawLine = tableLayout.alwaysDrawHLine(rowIndex + 1, this.tableNode, writer);
730
+ }
731
+
732
+ if (!shouldAlwaysDrawLine) {
733
+ shouldSkipHorizontalLine = true;
734
+ }
735
+ }
736
+ }
737
+ }
738
+
739
+ // Draw horizontal line at the end of the row, unless next row will cause a page break
740
+ if (!shouldSkipHorizontalLine) {
741
+ this.drawHorizontalLine(rowIndex + 1, writer);
742
+ }
743
+
744
+ if (this.headerRows && rowIndex === this.headerRows - 1) {
745
+ this.headerRepeatable = writer.currentBlockToRepeatable();
746
+ }
747
+
748
+ if (this.dontBreakRows) {
749
+ writer.tracker.auto('pageChanged',
750
+ function () {
751
+ if (rowIndex > 0 && !self.headerRows && self.layout.hLineWhenBroken !== false) {
752
+ // Draw the top border of the row after a page break
753
+ self.drawHorizontalLine(rowIndex, writer);
754
+ }
755
+ },
756
+ function () {
757
+ writer.commitUnbreakableBlock();
758
+ }
759
+ );
760
+ }
761
+
762
+ if (this.headerRepeatable && (rowIndex === (this.rowsWithoutPageBreak - 1) || rowIndex === this.tableNode.table.body.length - 1)) {
763
+ writer.commitUnbreakableBlock();
764
+ writer.pushToRepeatables(this.headerRepeatable);
765
+ this.cleanUpRepeatables = true;
766
+ this.headerRepeatable = null;
767
+ }
768
+
769
+ function getLineXs() {
770
+ var result = [];
771
+ var cols = 0;
772
+
773
+ for (var i = 0, l = self.tableNode.table.body[rowIndex].length; i < l; i++) {
774
+ if (!cols) {
775
+ result.push({ x: self.rowSpanData[i].left, index: i });
776
+
777
+ var item = self.tableNode.table.body[rowIndex][i];
778
+ cols = (item._colSpan || item.colSpan || 0);
779
+ }
780
+ if (cols > 0) {
781
+ cols--;
782
+ }
783
+ }
784
+
785
+ result.push({ x: self.rowSpanData[self.rowSpanData.length - 1].left, index: self.rowSpanData.length - 1 });
786
+
787
+ return result;
788
+ }
789
+ };
790
+
791
+ module.exports = TableProcessor;