pdfmake 0.3.12 → 0.3.13
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.
- package/CHANGELOG.md +16 -0
- package/build/pdfmake.js +81 -31
- package/build/pdfmake.js.map +1 -1
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/js/DocumentContext.js +3 -1
- package/js/ElementWriter.js +5 -2
- package/js/LayoutBuilder.js +12 -4
- package/js/OutputDocument.js +16 -3
- package/js/OutputDocumentServer.js +5 -0
- package/js/PDFDocument.js +5 -5
- package/js/PageElementWriter.js +5 -1
- package/js/TableProcessor.js +13 -0
- package/js/TextDecorator.js +3 -3
- package/js/columnCalculator.js +10 -4
- package/package.json +1 -1
- package/src/DocumentContext.js +1 -1
- package/src/ElementWriter.js +6 -2
- package/src/LayoutBuilder.js +12 -4
- package/src/OutputDocument.js +18 -3
- package/src/OutputDocumentServer.js +6 -0
- package/src/PDFDocument.js +5 -6
- package/src/PageElementWriter.js +5 -1
- package/src/TableProcessor.js +15 -0
- package/src/TextDecorator.js +4 -3
- package/src/columnCalculator.js +4 -4
package/js/DocumentContext.js
CHANGED
package/js/ElementWriter.js
CHANGED
|
@@ -277,6 +277,7 @@ class ElementWriter extends _events.EventEmitter {
|
|
|
277
277
|
if (!useBlockXOffset && block.height > ctx.availableHeight) {
|
|
278
278
|
return false;
|
|
279
279
|
}
|
|
280
|
+
let unbreakableFillsInserted = 0;
|
|
280
281
|
block.items.forEach(item => {
|
|
281
282
|
switch (item.type) {
|
|
282
283
|
case 'line':
|
|
@@ -296,9 +297,11 @@ class ElementWriter extends _events.EventEmitter {
|
|
|
296
297
|
(0, _tools.offsetVector)(v, useBlockXOffset ? block.xOffset || 0 : ctx.x, useBlockYOffset ? block.yOffset || 0 : ctx.y);
|
|
297
298
|
if (v._isFillColorFromUnbreakable) {
|
|
298
299
|
// If the item is a fillColor from an unbreakable block
|
|
299
|
-
// We have to add it at the beginning of the items body array of the page
|
|
300
|
+
// We have to add it at the beginning of the items body array of the page,
|
|
301
|
+
// after the fills already inserted from this block so that they keep their draw order
|
|
300
302
|
delete v._isFillColorFromUnbreakable;
|
|
301
|
-
const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page];
|
|
303
|
+
const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page] + unbreakableFillsInserted;
|
|
304
|
+
unbreakableFillsInserted++;
|
|
302
305
|
page.items.splice(endOfBackgroundItemsIndex, 0, {
|
|
303
306
|
type: 'vector',
|
|
304
307
|
item: v
|
package/js/LayoutBuilder.js
CHANGED
|
@@ -490,8 +490,9 @@ class LayoutBuilder {
|
|
|
490
490
|
* When in snaking columns, first tries moving to next column.
|
|
491
491
|
* If no columns available, moves to next page and resets x to left margin.
|
|
492
492
|
* @param {string} pageOrientation - Optional page orientation for the new page
|
|
493
|
+
* @param {TableProcessor} [tableProcessor] - Reapply table alignment in the new column
|
|
493
494
|
*/
|
|
494
|
-
snakingAwarePageBreak(pageOrientation) {
|
|
495
|
+
snakingAwarePageBreak(pageOrientation, tableProcessor) {
|
|
495
496
|
let ctx = this.writer.context();
|
|
496
497
|
let snakingSnapshot = ctx.getSnakingSnapshot();
|
|
497
498
|
if (!snakingSnapshot) {
|
|
@@ -500,7 +501,7 @@ class LayoutBuilder {
|
|
|
500
501
|
|
|
501
502
|
// Try flowing to next column first
|
|
502
503
|
if (this.writer.canMoveToNextColumn()) {
|
|
503
|
-
this.writer.moveToNextColumn();
|
|
504
|
+
this.writer.moveToNextColumn(tableProcessor ? context => tableProcessor.alignTable(context) : undefined);
|
|
504
505
|
return;
|
|
505
506
|
}
|
|
506
507
|
|
|
@@ -514,6 +515,9 @@ class LayoutBuilder {
|
|
|
514
515
|
let savedLastColumnWidth = ctx.lastColumnWidth;
|
|
515
516
|
ctx.resetSnakingColumnsForNewPage();
|
|
516
517
|
ctx.lastColumnWidth = savedLastColumnWidth;
|
|
518
|
+
if (tableProcessor) {
|
|
519
|
+
tableProcessor.alignTable(ctx);
|
|
520
|
+
}
|
|
517
521
|
}
|
|
518
522
|
|
|
519
523
|
// vertical container
|
|
@@ -964,7 +968,7 @@ class LayoutBuilder {
|
|
|
964
968
|
let rowTopPageY = this.cell._leftEndingCell._startingRowSpanY + this.cell._leftEndingCell._rowTopPageYPadding;
|
|
965
969
|
return this.cell._leftEndingCell._rowTopPageY - rowTopPageY + this.cell._leftEndingCell._bottomY;
|
|
966
970
|
} else {
|
|
967
|
-
if (this.cell.
|
|
971
|
+
if (this.cell._lastPageNumber !== this.cell._leftEndingCell._lastPageNumber) {
|
|
968
972
|
return this.bottomY - this.cell._leftEndingCell._bottomY;
|
|
969
973
|
}
|
|
970
974
|
return this.viewHeight + this.cell._leftEndingCell._bottomY - this.bottomY;
|
|
@@ -1038,7 +1042,11 @@ class LayoutBuilder {
|
|
|
1038
1042
|
// or fall back to a minimum of padding + line height + border
|
|
1039
1043
|
let minRowHeight = lastRowHeight > 0 ? lastRowHeight : processor.rowPaddingTop + 14 + processor.rowPaddingBottom + processor.bottomLineWidth + processor.topLineWidth;
|
|
1040
1044
|
if (this.writer.context().availableHeight < minRowHeight) {
|
|
1041
|
-
|
|
1045
|
+
// Column selection needs the container origin, before table alignment.
|
|
1046
|
+
if (processor.alignmentOffset) {
|
|
1047
|
+
this.writer.context().addMargin(-processor.alignmentOffset, processor.alignmentOffset);
|
|
1048
|
+
}
|
|
1049
|
+
this.snakingAwarePageBreak(undefined, processor);
|
|
1042
1050
|
|
|
1043
1051
|
// Skip border when headerRows present (header repeat includes it)
|
|
1044
1052
|
if (processor.layout.hLineWhenBroken !== false && !processor.headerRows) {
|
package/js/OutputDocument.js
CHANGED
|
@@ -9,13 +9,25 @@ class OutputDocument {
|
|
|
9
9
|
constructor(pdfDocumentPromise) {
|
|
10
10
|
this.pdfDocumentPromise = pdfDocumentPromise;
|
|
11
11
|
this.bufferPromise = null;
|
|
12
|
+
this.streamEnded = false;
|
|
13
|
+
this.streamEndListenerAttached = false;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* @returns {Promise<object>}
|
|
16
18
|
*/
|
|
17
|
-
getStream() {
|
|
18
|
-
|
|
19
|
+
async getStream() {
|
|
20
|
+
const stream = await this.pdfDocumentPromise;
|
|
21
|
+
if (this.streamEnded) {
|
|
22
|
+
throw new Error('PDF stream has already been consumed.');
|
|
23
|
+
}
|
|
24
|
+
if (!this.streamEndListenerAttached) {
|
|
25
|
+
stream.once('end', () => {
|
|
26
|
+
this.streamEnded = true;
|
|
27
|
+
});
|
|
28
|
+
this.streamEndListenerAttached = true;
|
|
29
|
+
}
|
|
30
|
+
return stream;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
/**
|
|
@@ -24,7 +36,7 @@ class OutputDocument {
|
|
|
24
36
|
getBuffer() {
|
|
25
37
|
const getBufferInternal = async () => {
|
|
26
38
|
const stream = await this.getStream();
|
|
27
|
-
return new Promise(resolve => {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
28
40
|
let chunks = [];
|
|
29
41
|
stream.on('data', chunk => {
|
|
30
42
|
chunks.push(chunk);
|
|
@@ -32,6 +44,7 @@ class OutputDocument {
|
|
|
32
44
|
stream.on('end', () => {
|
|
33
45
|
resolve(Buffer.concat(chunks));
|
|
34
46
|
});
|
|
47
|
+
stream.on('error', reject);
|
|
35
48
|
stream.end();
|
|
36
49
|
});
|
|
37
50
|
};
|
|
@@ -11,6 +11,11 @@ class OutputDocumentServer extends _OutputDocument.default {
|
|
|
11
11
|
* @returns {Promise}
|
|
12
12
|
*/
|
|
13
13
|
async write(filename) {
|
|
14
|
+
await this.pdfDocumentPromise;
|
|
15
|
+
if (this.bufferPromise !== null) {
|
|
16
|
+
await _fs.default.promises.writeFile(filename, await this.getBuffer());
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
14
19
|
const stream = await this.getStream();
|
|
15
20
|
const writeStream = _fs.default.createWriteStream(filename);
|
|
16
21
|
const streamEnded = new Promise((resolve, reject) => {
|
package/js/PDFDocument.js
CHANGED
|
@@ -129,12 +129,12 @@ class PDFDocument extends _pdfkit.PDFDocument {
|
|
|
129
129
|
}
|
|
130
130
|
return obj;
|
|
131
131
|
};
|
|
132
|
-
|
|
133
|
-
return checkRequired(src);
|
|
134
|
-
}
|
|
135
|
-
let attachment = checkRequired(this.attachments[src]);
|
|
132
|
+
let attachment = checkRequired(typeof src === 'object' ? src : this.attachments[src]);
|
|
136
133
|
if (this.virtualfs && this.virtualfs.existsSync(attachment.src)) {
|
|
137
|
-
return
|
|
134
|
+
return {
|
|
135
|
+
...attachment,
|
|
136
|
+
src: this.virtualfs.readFileSync(attachment.src)
|
|
137
|
+
};
|
|
138
138
|
}
|
|
139
139
|
this.validateLocalFile(attachment.src);
|
|
140
140
|
return attachment;
|
package/js/PageElementWriter.js
CHANGED
|
@@ -153,9 +153,13 @@ class PageElementWriter extends _ElementWriter.default {
|
|
|
153
153
|
/**
|
|
154
154
|
* Move to the next column in a column group (snaking columns).
|
|
155
155
|
* Handles repeatables and emits columnChanged event.
|
|
156
|
+
* @param {Function} [updateContext] - Adjust the new column before inserting repeatables
|
|
156
157
|
*/
|
|
157
|
-
moveToNextColumn() {
|
|
158
|
+
moveToNextColumn(updateContext) {
|
|
158
159
|
let nextColumn = this.context().moveToNextColumn();
|
|
160
|
+
if (updateContext) {
|
|
161
|
+
updateContext(this.context());
|
|
162
|
+
}
|
|
159
163
|
|
|
160
164
|
// Handle repeatables (like table headers) for the new column
|
|
161
165
|
this.repeatables.forEach(function (rep) {
|
package/js/TableProcessor.js
CHANGED
|
@@ -102,6 +102,7 @@ class TableProcessor {
|
|
|
102
102
|
availableWidth = writer.context().availableWidth - this.offsets.total;
|
|
103
103
|
_columnCalculator.default.buildColumnWidths(tableNode.table.widths, availableWidth, this.offsets.total, tableNode);
|
|
104
104
|
this.tableWidth = tableNode._offsets.total + getTableInnerContentWidth();
|
|
105
|
+
this.alignTable(writer.context());
|
|
105
106
|
this.rowSpanData = prepareRowSpanData();
|
|
106
107
|
this.cleanUpRepeatables = false;
|
|
107
108
|
|
|
@@ -134,6 +135,15 @@ class TableProcessor {
|
|
|
134
135
|
// update the border properties of all cells before drawing any lines
|
|
135
136
|
prepareCellBorders(this.tableNode.table.body);
|
|
136
137
|
}
|
|
138
|
+
alignTable(context) {
|
|
139
|
+
const freeWidth = Math.max(0, context.availableWidth - this.tableWidth);
|
|
140
|
+
this.alignmentOffset = this.tableNode.tableAlignment === 'center' ? freeWidth / 2 : this.tableNode.tableAlignment === 'right' ? freeWidth : 0;
|
|
141
|
+
if (this.alignmentOffset) {
|
|
142
|
+
// Keep the container width intact: column widths have already been resolved.
|
|
143
|
+
// Row snapshots preserve this origin across page breaks.
|
|
144
|
+
context.addMargin(this.alignmentOffset, -this.alignmentOffset);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
137
147
|
onRowBreak(rowIndex, writer) {
|
|
138
148
|
return () => {
|
|
139
149
|
let offset = this.rowPaddingTop + (!this.headerRows ? this.topLineWidth : 0);
|
|
@@ -340,6 +350,9 @@ class TableProcessor {
|
|
|
340
350
|
if (this.cleanUpRepeatables) {
|
|
341
351
|
writer.popFromRepeatables();
|
|
342
352
|
}
|
|
353
|
+
if (this.alignmentOffset) {
|
|
354
|
+
writer.context().addMargin(-this.alignmentOffset, this.alignmentOffset);
|
|
355
|
+
}
|
|
343
356
|
}
|
|
344
357
|
endRow(rowIndex, writer, pageBreaks) {
|
|
345
358
|
const getLineXs = () => {
|
package/js/TextDecorator.js
CHANGED
|
@@ -21,7 +21,7 @@ const groupDecorations = (line, pdfDocument) => {
|
|
|
21
21
|
let thickness = (0, _variableType.isNumber)(inline.decorationThickness) ? inline.decorationThickness : null;
|
|
22
22
|
for (let ii = 0, ll = decoration.length; ii < ll; ii++) {
|
|
23
23
|
let decorationItem = decoration[ii];
|
|
24
|
-
if (!currentGroup || decorationItem !== currentGroup.decoration || style !== currentGroup.decorationStyle || color !== currentGroup.decorationColor) {
|
|
24
|
+
if (!currentGroup || decorationItem !== currentGroup.decoration || style !== currentGroup.decorationStyle || color !== currentGroup.decorationColor || thickness !== currentGroup.decorationThickness) {
|
|
25
25
|
currentGroup = {
|
|
26
26
|
line: line,
|
|
27
27
|
decoration: decorationItem,
|
|
@@ -69,7 +69,7 @@ class TextDecorator {
|
|
|
69
69
|
let max = 0;
|
|
70
70
|
for (let i = 0, l = group.inlines.length; i < l; i++) {
|
|
71
71
|
let inline = group.inlines[i];
|
|
72
|
-
max = inline.fontSize > max ? i : max;
|
|
72
|
+
max = inline.fontSize > group.inlines[max].fontSize ? i : max;
|
|
73
73
|
}
|
|
74
74
|
return group.inlines[max];
|
|
75
75
|
};
|
|
@@ -135,7 +135,7 @@ class TextDecorator {
|
|
|
135
135
|
sv = 1;
|
|
136
136
|
let nbWaves = Math.ceil(totalWidth / (sh * 2)) + 1;
|
|
137
137
|
let rwx = x + firstInline.x - 1;
|
|
138
|
-
this.pdfDocument.rect(x + firstInline.x, y - sv, totalWidth,
|
|
138
|
+
this.pdfDocument.rect(x + firstInline.x, y - sv, totalWidth, 2 * sv).clip();
|
|
139
139
|
this.pdfDocument.lineWidth(lw / 3);
|
|
140
140
|
this.pdfDocument.moveTo(rwx, y);
|
|
141
141
|
for (let i = 0; i < nbWaves; i++) {
|
package/js/columnCalculator.js
CHANGED
|
@@ -12,7 +12,7 @@ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode)
|
|
|
12
12
|
let starMaxMax = 0;
|
|
13
13
|
let fixedColumns = [];
|
|
14
14
|
let initial_availableWidth = availableWidth;
|
|
15
|
-
columns.forEach(column => {
|
|
15
|
+
columns.forEach((column, colIndex) => {
|
|
16
16
|
if (isAutoColumn(column)) {
|
|
17
17
|
autoColumns.push(column);
|
|
18
18
|
autoMin += column._minWidth;
|
|
@@ -22,10 +22,16 @@ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode)
|
|
|
22
22
|
starMaxMin = Math.max(starMaxMin, column._minWidth);
|
|
23
23
|
starMaxMax = Math.max(starMaxMax, column._maxWidth);
|
|
24
24
|
} else {
|
|
25
|
-
fixedColumns.push(
|
|
25
|
+
fixedColumns.push({
|
|
26
|
+
column,
|
|
27
|
+
colIndex
|
|
28
|
+
});
|
|
26
29
|
}
|
|
27
30
|
});
|
|
28
|
-
fixedColumns.forEach((
|
|
31
|
+
fixedColumns.forEach(({
|
|
32
|
+
column: col,
|
|
33
|
+
colIndex
|
|
34
|
+
}) => {
|
|
29
35
|
// width specified as %
|
|
30
36
|
if ((0, _variableType.isString)(col.width) && /\d+%/.test(col.width)) {
|
|
31
37
|
// In tables we have to take into consideration the reserved width for paddings and borders
|
|
@@ -38,7 +44,7 @@ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode)
|
|
|
38
44
|
if (colIndex === 0) {
|
|
39
45
|
// first column assumes whole borderLeft and half of border right
|
|
40
46
|
reservedWidth = paddingLeft + paddingRight + borderLeft + borderRight / 2;
|
|
41
|
-
} else if (colIndex ===
|
|
47
|
+
} else if (colIndex === columns.length - 1) {
|
|
42
48
|
// last column assumes whole borderRight and half of border left
|
|
43
49
|
reservedWidth = paddingLeft + paddingRight + borderLeft / 2 + borderRight;
|
|
44
50
|
} else {
|
package/package.json
CHANGED
package/src/DocumentContext.js
CHANGED
|
@@ -493,7 +493,7 @@ class DocumentContext extends EventEmitter {
|
|
|
493
493
|
this.availableWidth = pageSize.width - pageMargin.left - pageMargin.right;
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
-
let page = { items: [], pageSize: pageSize, pageMargins: this.pageMargins, customProperties: customProperties };
|
|
496
|
+
let page = { items: [], pageSize: { ...pageSize }, pageMargins: this.pageMargins, customProperties: customProperties };
|
|
497
497
|
this.pages.push(page);
|
|
498
498
|
this.backgroundLength.push(0);
|
|
499
499
|
this.page = this.pages.length - 1;
|
package/src/ElementWriter.js
CHANGED
|
@@ -331,6 +331,8 @@ class ElementWriter extends EventEmitter {
|
|
|
331
331
|
return false;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
let unbreakableFillsInserted = 0;
|
|
335
|
+
|
|
334
336
|
block.items.forEach(item => {
|
|
335
337
|
switch (item.type) {
|
|
336
338
|
case 'line':
|
|
@@ -354,9 +356,11 @@ class ElementWriter extends EventEmitter {
|
|
|
354
356
|
offsetVector(v, useBlockXOffset ? (block.xOffset || 0) : ctx.x, useBlockYOffset ? (block.yOffset || 0) : ctx.y);
|
|
355
357
|
if (v._isFillColorFromUnbreakable) {
|
|
356
358
|
// If the item is a fillColor from an unbreakable block
|
|
357
|
-
// We have to add it at the beginning of the items body array of the page
|
|
359
|
+
// We have to add it at the beginning of the items body array of the page,
|
|
360
|
+
// after the fills already inserted from this block so that they keep their draw order
|
|
358
361
|
delete v._isFillColorFromUnbreakable;
|
|
359
|
-
const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page];
|
|
362
|
+
const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page] + unbreakableFillsInserted;
|
|
363
|
+
unbreakableFillsInserted++;
|
|
360
364
|
page.items.splice(endOfBackgroundItemsIndex, 0, {
|
|
361
365
|
type: 'vector',
|
|
362
366
|
item: v
|
package/src/LayoutBuilder.js
CHANGED
|
@@ -566,8 +566,9 @@ class LayoutBuilder {
|
|
|
566
566
|
* When in snaking columns, first tries moving to next column.
|
|
567
567
|
* If no columns available, moves to next page and resets x to left margin.
|
|
568
568
|
* @param {string} pageOrientation - Optional page orientation for the new page
|
|
569
|
+
* @param {TableProcessor} [tableProcessor] - Reapply table alignment in the new column
|
|
569
570
|
*/
|
|
570
|
-
snakingAwarePageBreak(pageOrientation) {
|
|
571
|
+
snakingAwarePageBreak(pageOrientation, tableProcessor) {
|
|
571
572
|
let ctx = this.writer.context();
|
|
572
573
|
let snakingSnapshot = ctx.getSnakingSnapshot();
|
|
573
574
|
if (!snakingSnapshot) {
|
|
@@ -576,7 +577,7 @@ class LayoutBuilder {
|
|
|
576
577
|
|
|
577
578
|
// Try flowing to next column first
|
|
578
579
|
if (this.writer.canMoveToNextColumn()) {
|
|
579
|
-
this.writer.moveToNextColumn();
|
|
580
|
+
this.writer.moveToNextColumn(tableProcessor ? context => tableProcessor.alignTable(context) : undefined);
|
|
580
581
|
return;
|
|
581
582
|
}
|
|
582
583
|
|
|
@@ -590,6 +591,9 @@ class LayoutBuilder {
|
|
|
590
591
|
let savedLastColumnWidth = ctx.lastColumnWidth;
|
|
591
592
|
ctx.resetSnakingColumnsForNewPage();
|
|
592
593
|
ctx.lastColumnWidth = savedLastColumnWidth;
|
|
594
|
+
if (tableProcessor) {
|
|
595
|
+
tableProcessor.alignTable(ctx);
|
|
596
|
+
}
|
|
593
597
|
}
|
|
594
598
|
|
|
595
599
|
// vertical container
|
|
@@ -1072,7 +1076,7 @@ class LayoutBuilder {
|
|
|
1072
1076
|
let rowTopPageY = this.cell._leftEndingCell._startingRowSpanY + this.cell._leftEndingCell._rowTopPageYPadding;
|
|
1073
1077
|
return this.cell._leftEndingCell._rowTopPageY - rowTopPageY + this.cell._leftEndingCell._bottomY;
|
|
1074
1078
|
} else {
|
|
1075
|
-
if (this.cell.
|
|
1079
|
+
if (this.cell._lastPageNumber !== this.cell._leftEndingCell._lastPageNumber) {
|
|
1076
1080
|
return this.bottomY - this.cell._leftEndingCell._bottomY;
|
|
1077
1081
|
}
|
|
1078
1082
|
|
|
@@ -1165,7 +1169,11 @@ class LayoutBuilder {
|
|
|
1165
1169
|
processor.bottomLineWidth + processor.topLineWidth
|
|
1166
1170
|
);
|
|
1167
1171
|
if (this.writer.context().availableHeight < minRowHeight) {
|
|
1168
|
-
|
|
1172
|
+
// Column selection needs the container origin, before table alignment.
|
|
1173
|
+
if (processor.alignmentOffset) {
|
|
1174
|
+
this.writer.context().addMargin(-processor.alignmentOffset, processor.alignmentOffset);
|
|
1175
|
+
}
|
|
1176
|
+
this.snakingAwarePageBreak(undefined, processor);
|
|
1169
1177
|
|
|
1170
1178
|
// Skip border when headerRows present (header repeat includes it)
|
|
1171
1179
|
if (processor.layout.hLineWhenBroken !== false && !processor.headerRows) {
|
package/src/OutputDocument.js
CHANGED
|
@@ -6,13 +6,27 @@ class OutputDocument {
|
|
|
6
6
|
constructor(pdfDocumentPromise) {
|
|
7
7
|
this.pdfDocumentPromise = pdfDocumentPromise;
|
|
8
8
|
this.bufferPromise = null;
|
|
9
|
+
this.streamEnded = false;
|
|
10
|
+
this.streamEndListenerAttached = false;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* @returns {Promise<object>}
|
|
13
15
|
*/
|
|
14
|
-
getStream() {
|
|
15
|
-
|
|
16
|
+
async getStream() {
|
|
17
|
+
const stream = await this.pdfDocumentPromise;
|
|
18
|
+
if (this.streamEnded) {
|
|
19
|
+
throw new Error('PDF stream has already been consumed.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!this.streamEndListenerAttached) {
|
|
23
|
+
stream.once('end', () => {
|
|
24
|
+
this.streamEnded = true;
|
|
25
|
+
});
|
|
26
|
+
this.streamEndListenerAttached = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return stream;
|
|
16
30
|
}
|
|
17
31
|
|
|
18
32
|
/**
|
|
@@ -21,7 +35,7 @@ class OutputDocument {
|
|
|
21
35
|
getBuffer() {
|
|
22
36
|
const getBufferInternal = (async () => {
|
|
23
37
|
const stream = await this.getStream();
|
|
24
|
-
return new Promise((resolve) => {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
25
39
|
let chunks = [];
|
|
26
40
|
stream.on('data', chunk => {
|
|
27
41
|
chunks.push(chunk);
|
|
@@ -29,6 +43,7 @@ class OutputDocument {
|
|
|
29
43
|
stream.on('end', () => {
|
|
30
44
|
resolve(Buffer.concat(chunks));
|
|
31
45
|
});
|
|
46
|
+
stream.on('error', reject);
|
|
32
47
|
stream.end();
|
|
33
48
|
});
|
|
34
49
|
});
|
|
@@ -8,6 +8,12 @@ class OutputDocumentServer extends OutputDocument {
|
|
|
8
8
|
* @returns {Promise}
|
|
9
9
|
*/
|
|
10
10
|
async write(filename) {
|
|
11
|
+
await this.pdfDocumentPromise;
|
|
12
|
+
if (this.bufferPromise !== null) {
|
|
13
|
+
await fs.promises.writeFile(filename, await this.getBuffer());
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
const stream = await this.getStream();
|
|
12
18
|
const writeStream = fs.createWriteStream(filename);
|
|
13
19
|
|
package/src/PDFDocument.js
CHANGED
|
@@ -154,14 +154,13 @@ class PDFDocument extends PDFKitDocument {
|
|
|
154
154
|
return obj;
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
return checkRequired(src);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let attachment = checkRequired(this.attachments[src]);
|
|
157
|
+
let attachment = checkRequired(typeof src === 'object' ? src : this.attachments[src]);
|
|
162
158
|
|
|
163
159
|
if (this.virtualfs && this.virtualfs.existsSync(attachment.src)) {
|
|
164
|
-
return
|
|
160
|
+
return {
|
|
161
|
+
...attachment,
|
|
162
|
+
src: this.virtualfs.readFileSync(attachment.src)
|
|
163
|
+
};
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
this.validateLocalFile(attachment.src);
|
package/src/PageElementWriter.js
CHANGED
|
@@ -176,9 +176,13 @@ class PageElementWriter extends ElementWriter {
|
|
|
176
176
|
/**
|
|
177
177
|
* Move to the next column in a column group (snaking columns).
|
|
178
178
|
* Handles repeatables and emits columnChanged event.
|
|
179
|
+
* @param {Function} [updateContext] - Adjust the new column before inserting repeatables
|
|
179
180
|
*/
|
|
180
|
-
moveToNextColumn() {
|
|
181
|
+
moveToNextColumn(updateContext) {
|
|
181
182
|
let nextColumn = this.context().moveToNextColumn();
|
|
183
|
+
if (updateContext) {
|
|
184
|
+
updateContext(this.context());
|
|
185
|
+
}
|
|
182
186
|
|
|
183
187
|
// Handle repeatables (like table headers) for the new column
|
|
184
188
|
this.repeatables.forEach(function (rep) {
|
package/src/TableProcessor.js
CHANGED
|
@@ -109,6 +109,7 @@ class TableProcessor {
|
|
|
109
109
|
ColumnCalculator.buildColumnWidths(tableNode.table.widths, availableWidth, this.offsets.total, tableNode);
|
|
110
110
|
|
|
111
111
|
this.tableWidth = tableNode._offsets.total + getTableInnerContentWidth();
|
|
112
|
+
this.alignTable(writer.context());
|
|
112
113
|
this.rowSpanData = prepareRowSpanData();
|
|
113
114
|
this.cleanUpRepeatables = false;
|
|
114
115
|
|
|
@@ -150,6 +151,17 @@ class TableProcessor {
|
|
|
150
151
|
prepareCellBorders(this.tableNode.table.body);
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
alignTable(context) {
|
|
155
|
+
const freeWidth = Math.max(0, context.availableWidth - this.tableWidth);
|
|
156
|
+
this.alignmentOffset = this.tableNode.tableAlignment === 'center' ? freeWidth / 2 :
|
|
157
|
+
(this.tableNode.tableAlignment === 'right' ? freeWidth : 0);
|
|
158
|
+
if (this.alignmentOffset) {
|
|
159
|
+
// Keep the container width intact: column widths have already been resolved.
|
|
160
|
+
// Row snapshots preserve this origin across page breaks.
|
|
161
|
+
context.addMargin(this.alignmentOffset, -this.alignmentOffset);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
153
165
|
onRowBreak(rowIndex, writer) {
|
|
154
166
|
return () => {
|
|
155
167
|
let offset = this.rowPaddingTop + (!this.headerRows ? this.topLineWidth : 0);
|
|
@@ -376,6 +388,9 @@ class TableProcessor {
|
|
|
376
388
|
if (this.cleanUpRepeatables) {
|
|
377
389
|
writer.popFromRepeatables();
|
|
378
390
|
}
|
|
391
|
+
if (this.alignmentOffset) {
|
|
392
|
+
writer.context().addMargin(-this.alignmentOffset, this.alignmentOffset);
|
|
393
|
+
}
|
|
379
394
|
}
|
|
380
395
|
|
|
381
396
|
endRow(rowIndex, writer, pageBreaks) {
|
package/src/TextDecorator.js
CHANGED
|
@@ -19,7 +19,8 @@ const groupDecorations = (line, pdfDocument) => {
|
|
|
19
19
|
for (let ii = 0, ll = decoration.length; ii < ll; ii++) {
|
|
20
20
|
let decorationItem = decoration[ii];
|
|
21
21
|
if (!currentGroup || decorationItem !== currentGroup.decoration ||
|
|
22
|
-
style !== currentGroup.decorationStyle || color !== currentGroup.decorationColor
|
|
22
|
+
style !== currentGroup.decorationStyle || color !== currentGroup.decorationColor ||
|
|
23
|
+
thickness !== currentGroup.decorationThickness) {
|
|
23
24
|
|
|
24
25
|
currentGroup = {
|
|
25
26
|
line: line,
|
|
@@ -79,7 +80,7 @@ class TextDecorator {
|
|
|
79
80
|
let max = 0;
|
|
80
81
|
for (let i = 0, l = group.inlines.length; i < l; i++) {
|
|
81
82
|
let inline = group.inlines[i];
|
|
82
|
-
max = inline.fontSize > max ? i : max;
|
|
83
|
+
max = inline.fontSize > group.inlines[max].fontSize ? i : max;
|
|
83
84
|
}
|
|
84
85
|
return group.inlines[max];
|
|
85
86
|
};
|
|
@@ -155,7 +156,7 @@ class TextDecorator {
|
|
|
155
156
|
let sh = 0.7, sv = 1;
|
|
156
157
|
let nbWaves = Math.ceil(totalWidth / (sh * 2)) + 1;
|
|
157
158
|
let rwx = x + firstInline.x - 1;
|
|
158
|
-
this.pdfDocument.rect(x + firstInline.x, y - sv, totalWidth,
|
|
159
|
+
this.pdfDocument.rect(x + firstInline.x, y - sv, totalWidth, 2 * sv).clip();
|
|
159
160
|
this.pdfDocument.lineWidth(lw / 3);
|
|
160
161
|
this.pdfDocument.moveTo(rwx, y);
|
|
161
162
|
for (let i = 0; i < nbWaves; i++) {
|
package/src/columnCalculator.js
CHANGED
|
@@ -10,7 +10,7 @@ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode)
|
|
|
10
10
|
let fixedColumns = [];
|
|
11
11
|
let initial_availableWidth = availableWidth;
|
|
12
12
|
|
|
13
|
-
columns.forEach(column => {
|
|
13
|
+
columns.forEach((column, colIndex) => {
|
|
14
14
|
if (isAutoColumn(column)) {
|
|
15
15
|
autoColumns.push(column);
|
|
16
16
|
autoMin += column._minWidth;
|
|
@@ -20,11 +20,11 @@ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode)
|
|
|
20
20
|
starMaxMin = Math.max(starMaxMin, column._minWidth);
|
|
21
21
|
starMaxMax = Math.max(starMaxMax, column._maxWidth);
|
|
22
22
|
} else {
|
|
23
|
-
fixedColumns.push(column);
|
|
23
|
+
fixedColumns.push({ column, colIndex });
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
fixedColumns.forEach((col, colIndex) => {
|
|
27
|
+
fixedColumns.forEach(({ column: col, colIndex }) => {
|
|
28
28
|
// width specified as %
|
|
29
29
|
if (isString(col.width) && /\d+%/.test(col.width)) {
|
|
30
30
|
// In tables we have to take into consideration the reserved width for paddings and borders
|
|
@@ -38,7 +38,7 @@ function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode)
|
|
|
38
38
|
// first column assumes whole borderLeft and half of border right
|
|
39
39
|
reservedWidth = paddingLeft + paddingRight + borderLeft + (borderRight / 2);
|
|
40
40
|
|
|
41
|
-
} else if (colIndex ===
|
|
41
|
+
} else if (colIndex === columns.length - 1) {
|
|
42
42
|
// last column assumes whole borderRight and half of border left
|
|
43
43
|
reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + borderRight;
|
|
44
44
|
|