pdfmake 0.3.4 → 0.3.6
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 +26 -1
- package/README.md +1 -0
- package/build/pdfmake.js +612 -104
- package/build/pdfmake.js.map +1 -1
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/js/DocMeasure.js +7 -2
- package/js/DocumentContext.js +228 -4
- package/js/LayoutBuilder.js +136 -8
- package/js/PDFDocument.js +3 -1
- package/js/PageElementWriter.js +109 -2
- package/js/Printer.js +1 -19
- package/js/Renderer.js +13 -0
- package/js/SVGMeasure.js +4 -2
- package/js/TableProcessor.js +1 -5
- package/js/URLResolver.js +14 -1
- package/js/base.js +19 -2
- package/js/browser-extensions/index.js +0 -2
- package/js/index.js +0 -2
- package/package.json +5 -5
- package/src/DocMeasure.js +8 -2
- package/src/DocumentContext.js +243 -9
- package/src/LayoutBuilder.js +145 -11
- package/src/PDFDocument.js +1 -1
- package/src/PageElementWriter.js +121 -2
- package/src/Printer.js +1 -20
- package/src/Renderer.js +13 -0
- package/src/SVGMeasure.js +2 -2
- package/src/TableProcessor.js +1 -5
- package/src/URLResolver.js +14 -1
- package/src/base.js +24 -2
- package/src/browser-extensions/index.js +0 -2
- package/src/index.js +0 -2
package/js/Printer.js
CHANGED
|
@@ -11,28 +11,13 @@ var _Renderer = _interopRequireDefault(require("./Renderer"));
|
|
|
11
11
|
var _variableType = require("./helpers/variableType");
|
|
12
12
|
var _tools = require("./helpers/tools");
|
|
13
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
-
/**
|
|
15
|
-
* Printer which turns document definition into a pdf
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* var fontDescriptors = {
|
|
19
|
-
* Roboto: {
|
|
20
|
-
* normal: 'fonts/Roboto-Regular.ttf',
|
|
21
|
-
* bold: 'fonts/Roboto-Medium.ttf',
|
|
22
|
-
* italics: 'fonts/Roboto-Italic.ttf',
|
|
23
|
-
* bolditalics: 'fonts/Roboto-MediumItalic.ttf'
|
|
24
|
-
* }
|
|
25
|
-
* };
|
|
26
|
-
*
|
|
27
|
-
* var printer = new PdfPrinter(fontDescriptors);
|
|
28
|
-
*/
|
|
29
14
|
class PdfPrinter {
|
|
30
15
|
/**
|
|
31
16
|
* @param {object} fontDescriptors font definition dictionary
|
|
32
17
|
* @param {object} virtualfs
|
|
33
18
|
* @param {object} urlResolver
|
|
34
19
|
*/
|
|
35
|
-
constructor(fontDescriptors, virtualfs
|
|
20
|
+
constructor(fontDescriptors, virtualfs, urlResolver) {
|
|
36
21
|
this.fontDescriptors = fontDescriptors;
|
|
37
22
|
this.virtualfs = virtualfs;
|
|
38
23
|
this.urlResolver = urlResolver;
|
|
@@ -126,9 +111,6 @@ class PdfPrinter {
|
|
|
126
111
|
headers: {}
|
|
127
112
|
};
|
|
128
113
|
};
|
|
129
|
-
if (this.urlResolver === null) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
114
|
for (let font in this.fontDescriptors) {
|
|
133
115
|
if (this.fontDescriptors.hasOwnProperty(font)) {
|
|
134
116
|
if (this.fontDescriptors[font].normal) {
|
package/js/Renderer.js
CHANGED
|
@@ -42,6 +42,7 @@ class Renderer {
|
|
|
42
42
|
constructor(pdfDocument, progressCallback) {
|
|
43
43
|
this.pdfDocument = pdfDocument;
|
|
44
44
|
this.progressCallback = progressCallback;
|
|
45
|
+
this.outlineMap = [];
|
|
45
46
|
}
|
|
46
47
|
renderPages(pages) {
|
|
47
48
|
this.pdfDocument._pdfMakePages = pages; // TODO: Why?
|
|
@@ -121,6 +122,18 @@ class Renderer {
|
|
|
121
122
|
break;
|
|
122
123
|
}
|
|
123
124
|
}
|
|
125
|
+
if (line._outline) {
|
|
126
|
+
let parentOutline = this.pdfDocument.outline;
|
|
127
|
+
if (line._outline.parentId && this.outlineMap[line._outline.parentId]) {
|
|
128
|
+
parentOutline = this.outlineMap[line._outline.parentId];
|
|
129
|
+
}
|
|
130
|
+
let outline = parentOutline.addItem(line._outline.text, {
|
|
131
|
+
expanded: line._outline.expanded
|
|
132
|
+
});
|
|
133
|
+
if (line._outline.id) {
|
|
134
|
+
this.outlineMap[line._outline.id] = outline;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
124
137
|
if (line._pageNodeRef) {
|
|
125
138
|
preparePageNodeRefLine(line._pageNodeRef, line.inlines[0]);
|
|
126
139
|
}
|
package/js/SVGMeasure.js
CHANGED
|
@@ -28,8 +28,10 @@ const parseSVG = svgString => {
|
|
|
28
28
|
let doc;
|
|
29
29
|
try {
|
|
30
30
|
doc = new _xmldoc.XmlDocument(svgString);
|
|
31
|
-
} catch (
|
|
32
|
-
throw new Error('Invalid svg document (' +
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error('Invalid svg document (' + error + ')', {
|
|
33
|
+
cause: error
|
|
34
|
+
});
|
|
33
35
|
}
|
|
34
36
|
if (doc.name !== "svg") {
|
|
35
37
|
throw new Error('Invalid svg document (expected <svg>)');
|
package/js/TableProcessor.js
CHANGED
|
@@ -20,7 +20,7 @@ class TableProcessor {
|
|
|
20
20
|
const prepareRowSpanData = () => {
|
|
21
21
|
let rsd = [];
|
|
22
22
|
let x = 0;
|
|
23
|
-
let lastWidth
|
|
23
|
+
let lastWidth;
|
|
24
24
|
rsd.push({
|
|
25
25
|
left: 0,
|
|
26
26
|
rowSpan: 0
|
|
@@ -250,7 +250,6 @@ class TableProcessor {
|
|
|
250
250
|
lineColor: borderColor
|
|
251
251
|
}, false, (0, _variableType.isNumber)(overrideY), null, forcePage);
|
|
252
252
|
currentLine = null;
|
|
253
|
-
borderColor = null;
|
|
254
253
|
cellAbove = null;
|
|
255
254
|
currentCell = null;
|
|
256
255
|
rowCellAbove = null;
|
|
@@ -325,9 +324,6 @@ class TableProcessor {
|
|
|
325
324
|
dash: dash,
|
|
326
325
|
lineColor: borderColor
|
|
327
326
|
}, false, true);
|
|
328
|
-
cellBefore = null;
|
|
329
|
-
currentCell = null;
|
|
330
|
-
borderColor = null;
|
|
331
327
|
}
|
|
332
328
|
endTable(writer) {
|
|
333
329
|
if (this.cleanUpRepeatables) {
|
package/js/URLResolver.js
CHANGED
|
@@ -12,13 +12,23 @@ async function fetchUrl(url, headers = {}) {
|
|
|
12
12
|
}
|
|
13
13
|
return await response.arrayBuffer();
|
|
14
14
|
} catch (error) {
|
|
15
|
-
throw new Error(`Network request failed (url: "${url}", error: ${error.message})
|
|
15
|
+
throw new Error(`Network request failed (url: "${url}", error: ${error.message})`, {
|
|
16
|
+
cause: error
|
|
17
|
+
});
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
class URLResolver {
|
|
19
21
|
constructor(fs) {
|
|
20
22
|
this.fs = fs;
|
|
21
23
|
this.resolving = {};
|
|
24
|
+
this.urlAccessPolicy = undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {(url: string) => boolean} callback
|
|
29
|
+
*/
|
|
30
|
+
setUrlAccessPolicy(callback) {
|
|
31
|
+
this.urlAccessPolicy = callback;
|
|
22
32
|
}
|
|
23
33
|
resolve(url, headers = {}) {
|
|
24
34
|
const resolveUrlInternal = async () => {
|
|
@@ -26,6 +36,9 @@ class URLResolver {
|
|
|
26
36
|
if (this.fs.existsSync(url)) {
|
|
27
37
|
return; // url was downloaded earlier
|
|
28
38
|
}
|
|
39
|
+
if (typeof this.urlAccessPolicy !== 'undefined' && this.urlAccessPolicy(url) !== true) {
|
|
40
|
+
throw new Error(`Access to URL denied by resource access policy: ${url}`);
|
|
41
|
+
}
|
|
29
42
|
const buffer = await fetchUrl(url, headers);
|
|
30
43
|
this.fs.writeFileSync(url, buffer);
|
|
31
44
|
}
|
package/js/base.js
CHANGED
|
@@ -6,11 +6,12 @@ var _Printer = _interopRequireDefault(require("./Printer"));
|
|
|
6
6
|
var _virtualFs = _interopRequireDefault(require("./virtual-fs"));
|
|
7
7
|
var _tools = require("./helpers/tools");
|
|
8
8
|
var _variableType = require("./helpers/variableType");
|
|
9
|
+
var _URLResolver = _interopRequireDefault(require("./URLResolver"));
|
|
9
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
11
|
class pdfmake {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.virtualfs = _virtualFs.default;
|
|
13
|
-
this.
|
|
14
|
+
this.urlAccessPolicy = undefined;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -27,10 +28,26 @@ class pdfmake {
|
|
|
27
28
|
}
|
|
28
29
|
options.progressCallback = this.progressCallback;
|
|
29
30
|
options.tableLayouts = this.tableLayouts;
|
|
30
|
-
|
|
31
|
+
const isServer = typeof process !== 'undefined' && process?.versions?.node;
|
|
32
|
+
if (typeof this.urlAccessPolicy === 'undefined' && isServer) {
|
|
33
|
+
console.warn('No URL access policy defined. Consider using setUrlAccessPolicy() to restrict external resource downloads.');
|
|
34
|
+
}
|
|
35
|
+
let urlResolver = new _URLResolver.default(this.virtualfs);
|
|
36
|
+
urlResolver.setUrlAccessPolicy(this.urlAccessPolicy);
|
|
37
|
+
let printer = new _Printer.default(this.fonts, this.virtualfs, urlResolver);
|
|
31
38
|
const pdfDocumentPromise = printer.createPdfKitDocument(docDefinition, options);
|
|
32
39
|
return this._transformToDocument(pdfDocumentPromise);
|
|
33
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {(url: string) => boolean} callback
|
|
44
|
+
*/
|
|
45
|
+
setUrlAccessPolicy(callback) {
|
|
46
|
+
if (callback !== undefined && typeof callback !== 'function') {
|
|
47
|
+
throw new Error("Parameter 'callback' has an invalid type. Function or undefined expected.");
|
|
48
|
+
}
|
|
49
|
+
this.urlAccessPolicy = callback;
|
|
50
|
+
}
|
|
34
51
|
setProgressCallback(callback) {
|
|
35
52
|
this.progressCallback = callback;
|
|
36
53
|
}
|
|
@@ -4,7 +4,6 @@ exports.__esModule = true;
|
|
|
4
4
|
exports.default = void 0;
|
|
5
5
|
var _base = _interopRequireDefault(require("../base"));
|
|
6
6
|
var _OutputDocumentBrowser = _interopRequireDefault(require("./OutputDocumentBrowser"));
|
|
7
|
-
var _URLResolver = _interopRequireDefault(require("../URLResolver"));
|
|
8
7
|
var _fs = _interopRequireDefault(require("fs"));
|
|
9
8
|
var _configurator = _interopRequireDefault(require("core-js/configurator"));
|
|
10
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -23,7 +22,6 @@ let defaultClientFonts = {
|
|
|
23
22
|
class pdfmake extends _base.default {
|
|
24
23
|
constructor() {
|
|
25
24
|
super();
|
|
26
|
-
this.urlResolver = () => new _URLResolver.default(this.virtualfs);
|
|
27
25
|
this.fonts = defaultClientFonts;
|
|
28
26
|
}
|
|
29
27
|
addFontContainer(fontContainer) {
|
package/js/index.js
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const pdfmakeBase = require('./base').default;
|
|
4
4
|
const OutputDocumentServer = require('./OutputDocumentServer').default;
|
|
5
|
-
const URLResolver = require('./URLResolver').default;
|
|
6
5
|
class pdfmake extends pdfmakeBase {
|
|
7
6
|
constructor() {
|
|
8
7
|
super();
|
|
9
|
-
this.urlResolver = () => new URLResolver(this.virtualfs);
|
|
10
8
|
}
|
|
11
9
|
_transformToDocument(doc) {
|
|
12
10
|
return new OutputDocumentServer(doc);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdfmake",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Client/server side PDF printing in pure JavaScript",
|
|
5
5
|
"main": "js/index.js",
|
|
6
6
|
"esnext": "src/index.js",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"@babel/core": "^7.29.0",
|
|
19
19
|
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
|
20
20
|
"@babel/preset-env": "^7.29.0",
|
|
21
|
-
"@eslint/js": "^
|
|
21
|
+
"@eslint/js": "^10.0.1",
|
|
22
22
|
"assert": "^2.1.0",
|
|
23
23
|
"babel-loader": "^10.0.0",
|
|
24
24
|
"brfs": "^2.0.2",
|
|
25
25
|
"browserify-zlib": "^0.2.0",
|
|
26
26
|
"buffer": "^6.0.3",
|
|
27
27
|
"core-js": "^3.48.0",
|
|
28
|
-
"eslint": "^
|
|
29
|
-
"eslint-plugin-jsdoc": "^62.
|
|
28
|
+
"eslint": "^10.0.1",
|
|
29
|
+
"eslint-plugin-jsdoc": "^62.7.0",
|
|
30
30
|
"expose-loader": "^5.0.1",
|
|
31
31
|
"file-saver": "^2.0.5",
|
|
32
32
|
"globals": "^17.3.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"terser-webpack-plugin": "^5.3.16",
|
|
44
44
|
"transform-loader": "^0.2.4",
|
|
45
45
|
"util": "^0.12.5",
|
|
46
|
-
"webpack": "^5.105.
|
|
46
|
+
"webpack": "^5.105.2",
|
|
47
47
|
"webpack-cli": "^6.0.1"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
package/src/DocMeasure.js
CHANGED
|
@@ -95,10 +95,12 @@ class DocMeasure {
|
|
|
95
95
|
node._width = node._minWidth = node._maxWidth = node.cover.width;
|
|
96
96
|
node._height = node._minHeight = node._maxHeight = node.cover.height;
|
|
97
97
|
} else {
|
|
98
|
+
let nodeWidth = isNumber(node.width) ? node.width : undefined;
|
|
99
|
+
let nodeHeight = isNumber(node.height) ? node.height : undefined;
|
|
98
100
|
let ratio = dimensions.width / dimensions.height;
|
|
99
101
|
|
|
100
|
-
node._width = node._minWidth = node._maxWidth =
|
|
101
|
-
node._height =
|
|
102
|
+
node._width = node._minWidth = node._maxWidth = nodeWidth || (nodeHeight ? (nodeHeight * ratio) : dimensions.width);
|
|
103
|
+
node._height = nodeHeight || (nodeWidth ? nodeWidth / ratio : dimensions.height);
|
|
102
104
|
|
|
103
105
|
if (isNumber(node.maxWidth) && node.maxWidth < node._width) {
|
|
104
106
|
node._width = node._minWidth = node._maxWidth = node.maxWidth;
|
|
@@ -218,6 +220,10 @@ class DocMeasure {
|
|
|
218
220
|
{ text: item._textNodeRef.text, linkToDestination: destination, alignment: 'left', style: lineStyle, margin: lineMargin },
|
|
219
221
|
{ text: '00000', linkToDestination: destination, alignment: 'right', _tocItemRef: item._nodeRef, style: lineNumberStyle, margin: [0, lineMargin[1], 0, lineMargin[3]] }
|
|
220
222
|
]);
|
|
223
|
+
|
|
224
|
+
if (node.toc.outlines) {
|
|
225
|
+
item._textNodeRef.outline = item._textNodeRef.outline || true;
|
|
226
|
+
}
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
node.toc._table = {
|
package/src/DocumentContext.js
CHANGED
|
@@ -19,7 +19,7 @@ class DocumentContext extends EventEmitter {
|
|
|
19
19
|
this.backgroundLength = [];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
beginColumnGroup(marginXTopParent, bottomByPage = {}) {
|
|
22
|
+
beginColumnGroup(marginXTopParent, bottomByPage = {}, snakingColumns = false, columnGap = 0, columnWidths = null) {
|
|
23
23
|
this.snapshots.push({
|
|
24
24
|
x: this.x,
|
|
25
25
|
y: this.y,
|
|
@@ -34,7 +34,10 @@ class DocumentContext extends EventEmitter {
|
|
|
34
34
|
availableWidth: this.availableWidth,
|
|
35
35
|
page: this.page
|
|
36
36
|
},
|
|
37
|
-
lastColumnWidth: this.lastColumnWidth
|
|
37
|
+
lastColumnWidth: this.lastColumnWidth,
|
|
38
|
+
snakingColumns: snakingColumns,
|
|
39
|
+
gap: columnGap,
|
|
40
|
+
columnWidths: columnWidths
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
this.lastColumnWidth = 0;
|
|
@@ -45,20 +48,74 @@ class DocumentContext extends EventEmitter {
|
|
|
45
48
|
|
|
46
49
|
updateBottomByPage() {
|
|
47
50
|
const lastSnapshot = this.snapshots[this.snapshots.length - 1];
|
|
51
|
+
if (!lastSnapshot) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
48
54
|
const lastPage = this.page;
|
|
49
55
|
let previousBottom = -Number.MIN_VALUE;
|
|
50
|
-
if (lastSnapshot.bottomByPage[lastPage]) {
|
|
56
|
+
if (lastSnapshot.bottomByPage && lastSnapshot.bottomByPage[lastPage]) {
|
|
51
57
|
previousBottom = lastSnapshot.bottomByPage[lastPage];
|
|
52
58
|
}
|
|
53
|
-
lastSnapshot.bottomByPage
|
|
59
|
+
if (lastSnapshot.bottomByPage) {
|
|
60
|
+
lastSnapshot.bottomByPage[lastPage] = Math.max(previousBottom, this.y);
|
|
61
|
+
}
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
resetMarginXTopParent() {
|
|
57
65
|
this.marginXTopParent = null;
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Find the most recent (deepest) snaking column group snapshot.
|
|
70
|
+
* @returns {object|null}
|
|
71
|
+
*/
|
|
72
|
+
getSnakingSnapshot() {
|
|
73
|
+
for (let i = this.snapshots.length - 1; i >= 0; i--) {
|
|
74
|
+
if (this.snapshots[i].snakingColumns) {
|
|
75
|
+
return this.snapshots[i];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
inSnakingColumns() {
|
|
82
|
+
return !!this.getSnakingSnapshot();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if we're inside a nested non-snaking column group (e.g., a table row)
|
|
87
|
+
* within an outer snaking column group. This is used to prevent snaking-specific
|
|
88
|
+
* breaks inside table cells — the table's own page break mechanism should handle
|
|
89
|
+
* row breaks, and column breaks should happen between rows.
|
|
90
|
+
* @returns {boolean}
|
|
91
|
+
*/
|
|
92
|
+
isInNestedNonSnakingGroup() {
|
|
93
|
+
for (let i = this.snapshots.length - 1; i >= 0; i--) {
|
|
94
|
+
let snap = this.snapshots[i];
|
|
95
|
+
if (snap.snakingColumns) {
|
|
96
|
+
return false; // Reached snaking snapshot without finding inner group
|
|
97
|
+
}
|
|
98
|
+
if (!snap.overflowed) {
|
|
99
|
+
return true; // Found non-snaking, non-overflowed inner group
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
60
105
|
beginColumn(width, offset, endingCell) {
|
|
106
|
+
// Find the correct snapshot for this column group.
|
|
107
|
+
// When a snaking column break (moveToNextColumn) occurs during inner column
|
|
108
|
+
// processing, overflowed snapshots may sit above this column group's snapshot.
|
|
109
|
+
// We need to skip past those to find the one from our beginColumnGroup call.
|
|
61
110
|
let saved = this.snapshots[this.snapshots.length - 1];
|
|
111
|
+
if (saved && saved.overflowed) {
|
|
112
|
+
for (let i = this.snapshots.length - 1; i >= 0; i--) {
|
|
113
|
+
if (!this.snapshots[i].overflowed) {
|
|
114
|
+
saved = this.snapshots[i];
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
62
119
|
|
|
63
120
|
this.calculateBottomMost(saved, endingCell);
|
|
64
121
|
|
|
@@ -102,6 +159,48 @@ class DocumentContext extends EventEmitter {
|
|
|
102
159
|
completeColumnGroup(height, endingCell) {
|
|
103
160
|
let saved = this.snapshots.pop();
|
|
104
161
|
|
|
162
|
+
// Track the maximum bottom position across all columns (including overflowed).
|
|
163
|
+
// Critical for snaking: content after columns must appear below the tallest column.
|
|
164
|
+
let maxBottomY = this.y;
|
|
165
|
+
let maxBottomPage = this.page;
|
|
166
|
+
let maxBottomAvailableHeight = this.availableHeight;
|
|
167
|
+
|
|
168
|
+
// Pop overflowed snapshots created by moveToNextColumn (snaking columns).
|
|
169
|
+
// Merge their bottomMost values to find the true maximum.
|
|
170
|
+
while (saved && saved.overflowed) {
|
|
171
|
+
let bm = bottomMostContext(
|
|
172
|
+
{
|
|
173
|
+
page: maxBottomPage,
|
|
174
|
+
y: maxBottomY,
|
|
175
|
+
availableHeight: maxBottomAvailableHeight
|
|
176
|
+
},
|
|
177
|
+
saved.bottomMost || {}
|
|
178
|
+
);
|
|
179
|
+
maxBottomPage = bm.page;
|
|
180
|
+
maxBottomY = bm.y;
|
|
181
|
+
maxBottomAvailableHeight = bm.availableHeight;
|
|
182
|
+
saved = this.snapshots.pop();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!saved) {
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Apply the max bottom from all overflowed columns to this base snapshot
|
|
190
|
+
if (
|
|
191
|
+
maxBottomPage > saved.bottomMost.page ||
|
|
192
|
+
(maxBottomPage === saved.bottomMost.page &&
|
|
193
|
+
maxBottomY > saved.bottomMost.y)
|
|
194
|
+
) {
|
|
195
|
+
saved.bottomMost = {
|
|
196
|
+
x: saved.x,
|
|
197
|
+
y: maxBottomY,
|
|
198
|
+
page: maxBottomPage,
|
|
199
|
+
availableHeight: maxBottomAvailableHeight,
|
|
200
|
+
availableWidth: saved.availableWidth
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
105
204
|
this.calculateBottomMost(saved, endingCell);
|
|
106
205
|
|
|
107
206
|
this.x = saved.x;
|
|
@@ -125,16 +224,152 @@ class DocumentContext extends EventEmitter {
|
|
|
125
224
|
this.availableHeight -= (y - saved.bottomMost.y);
|
|
126
225
|
}
|
|
127
226
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
227
|
+
if (height && (saved.bottomMost.y - saved.y < height)) {
|
|
228
|
+
this.height = height;
|
|
229
|
+
} else {
|
|
131
230
|
this.height = saved.bottomMost.y - saved.y;
|
|
132
|
-
|
|
231
|
+
}
|
|
133
232
|
|
|
134
233
|
this.lastColumnWidth = saved.lastColumnWidth;
|
|
135
234
|
return saved.bottomByPage;
|
|
136
235
|
}
|
|
137
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Move to the next column in a column group (snaking columns).
|
|
239
|
+
* Creates an overflowed snapshot to track that we've moved to the next column.
|
|
240
|
+
* @returns {object} Position info for the new column
|
|
241
|
+
*/
|
|
242
|
+
moveToNextColumn() {
|
|
243
|
+
let prevY = this.y;
|
|
244
|
+
let snakingSnapshot = this.getSnakingSnapshot();
|
|
245
|
+
|
|
246
|
+
if (!snakingSnapshot) {
|
|
247
|
+
return { prevY: prevY, y: this.y };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Update snaking snapshot's bottomMost with current position BEFORE resetting.
|
|
251
|
+
// This captures where content reached in the current column (overflow point).
|
|
252
|
+
this.calculateBottomMost(snakingSnapshot, null);
|
|
253
|
+
|
|
254
|
+
// Calculate new X position: move right by current column width + gap
|
|
255
|
+
let overflowCount = 0;
|
|
256
|
+
for (let i = this.snapshots.length - 1; i >= 0; i--) {
|
|
257
|
+
if (this.snapshots[i].overflowed) {
|
|
258
|
+
overflowCount++;
|
|
259
|
+
} else {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let currentColumnWidth = (snakingSnapshot.columnWidths && snakingSnapshot.columnWidths[overflowCount]) || this.lastColumnWidth || this.availableWidth;
|
|
265
|
+
let nextColumnWidth = (snakingSnapshot.columnWidths && snakingSnapshot.columnWidths[overflowCount + 1]) || currentColumnWidth;
|
|
266
|
+
|
|
267
|
+
this.lastColumnWidth = nextColumnWidth;
|
|
268
|
+
|
|
269
|
+
let newX = this.x + (currentColumnWidth || 0) + (snakingSnapshot.gap || 0);
|
|
270
|
+
let newY = snakingSnapshot.y;
|
|
271
|
+
|
|
272
|
+
this.snapshots.push({
|
|
273
|
+
x: newX,
|
|
274
|
+
y: newY,
|
|
275
|
+
availableHeight: snakingSnapshot.availableHeight,
|
|
276
|
+
availableWidth: nextColumnWidth,
|
|
277
|
+
page: this.page,
|
|
278
|
+
overflowed: true,
|
|
279
|
+
bottomMost: {
|
|
280
|
+
x: newX,
|
|
281
|
+
y: newY,
|
|
282
|
+
availableHeight: snakingSnapshot.availableHeight,
|
|
283
|
+
availableWidth: nextColumnWidth,
|
|
284
|
+
page: this.page
|
|
285
|
+
},
|
|
286
|
+
lastColumnWidth: nextColumnWidth,
|
|
287
|
+
snakingColumns: true,
|
|
288
|
+
gap: snakingSnapshot.gap,
|
|
289
|
+
columnWidths: snakingSnapshot.columnWidths
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
this.x = newX;
|
|
293
|
+
this.y = newY;
|
|
294
|
+
this.availableHeight = snakingSnapshot.availableHeight;
|
|
295
|
+
this.availableWidth = nextColumnWidth;
|
|
296
|
+
|
|
297
|
+
// Sync non-overflowed inner snapshots (e.g. inner column groups for
|
|
298
|
+
// product/price rows) with the new snaking column position.
|
|
299
|
+
// Without this, inner beginColumn would read stale y/page/x values.
|
|
300
|
+
for (let i = this.snapshots.length - 2; i >= 0; i--) {
|
|
301
|
+
let snapshot = this.snapshots[i];
|
|
302
|
+
if (snapshot.overflowed || snapshot.snakingColumns) {
|
|
303
|
+
break; // Stop at first overflowed or snaking snapshot
|
|
304
|
+
}
|
|
305
|
+
snapshot.x = newX;
|
|
306
|
+
snapshot.y = newY;
|
|
307
|
+
snapshot.page = this.page;
|
|
308
|
+
snapshot.availableHeight = snakingSnapshot.availableHeight;
|
|
309
|
+
if (snapshot.bottomMost) {
|
|
310
|
+
snapshot.bottomMost.x = newX;
|
|
311
|
+
snapshot.bottomMost.y = newY;
|
|
312
|
+
snapshot.bottomMost.page = this.page;
|
|
313
|
+
snapshot.bottomMost.availableHeight = snakingSnapshot.availableHeight;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { prevY: prevY, y: this.y };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Reset snaking column state when moving to a new page.
|
|
322
|
+
* Clears overflowed snapshots, resets X to left margin, sets width to first column,
|
|
323
|
+
* and syncs all snapshots to new page coordinates.
|
|
324
|
+
*/
|
|
325
|
+
resetSnakingColumnsForNewPage() {
|
|
326
|
+
let snakingSnapshot = this.getSnakingSnapshot();
|
|
327
|
+
if (!snakingSnapshot) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let pageTopY = this.pageMargins.top;
|
|
332
|
+
let pageInnerHeight = this.getCurrentPage().pageSize.height - this.pageMargins.top - this.pageMargins.bottom;
|
|
333
|
+
|
|
334
|
+
// When moving to new page, start at first column.
|
|
335
|
+
// Reset width to FIRST column width, not last column from previous page.
|
|
336
|
+
let firstColumnWidth = snakingSnapshot.columnWidths ? snakingSnapshot.columnWidths[0] : (this.lastColumnWidth || this.availableWidth);
|
|
337
|
+
|
|
338
|
+
// Clean up overflowed snapshots
|
|
339
|
+
while (this.snapshots.length > 1 && this.snapshots[this.snapshots.length - 1].overflowed) {
|
|
340
|
+
this.snapshots.pop();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Reset X to start of first column (left margin)
|
|
344
|
+
if (this.marginXTopParent) {
|
|
345
|
+
this.x = this.pageMargins.left + this.marginXTopParent[0];
|
|
346
|
+
} else {
|
|
347
|
+
this.x = this.pageMargins.left;
|
|
348
|
+
}
|
|
349
|
+
this.availableWidth = firstColumnWidth;
|
|
350
|
+
this.lastColumnWidth = firstColumnWidth;
|
|
351
|
+
|
|
352
|
+
// Sync all snapshots to new page state.
|
|
353
|
+
// When page break occurs within snaking columns, update ALL snapshots
|
|
354
|
+
// (not just snaking column snapshots) to reflect new page coordinates.
|
|
355
|
+
// This ensures nested structures (like inner product/price columns)
|
|
356
|
+
// don't retain stale values that would cause layout corruption.
|
|
357
|
+
for (let i = 0; i < this.snapshots.length; i++) {
|
|
358
|
+
let snapshot = this.snapshots[i];
|
|
359
|
+
let isSnakingSnapshot = !!snapshot.snakingColumns;
|
|
360
|
+
snapshot.x = this.x;
|
|
361
|
+
snapshot.y = isSnakingSnapshot ? pageTopY : this.y;
|
|
362
|
+
snapshot.availableHeight = isSnakingSnapshot ? pageInnerHeight : this.availableHeight;
|
|
363
|
+
snapshot.page = this.page;
|
|
364
|
+
if (snapshot.bottomMost) {
|
|
365
|
+
snapshot.bottomMost.x = this.x;
|
|
366
|
+
snapshot.bottomMost.y = isSnakingSnapshot ? pageTopY : this.y;
|
|
367
|
+
snapshot.bottomMost.availableHeight = isSnakingSnapshot ? pageInnerHeight : this.availableHeight;
|
|
368
|
+
snapshot.bottomMost.page = this.page;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
138
373
|
addMargin(left, right) {
|
|
139
374
|
this.x += left;
|
|
140
375
|
this.availableWidth -= left + (right || 0);
|
|
@@ -318,7 +553,6 @@ const getPageSize = (currentPage, newPageOrientation) => {
|
|
|
318
553
|
height: currentPage.pageSize.height
|
|
319
554
|
};
|
|
320
555
|
}
|
|
321
|
-
|
|
322
556
|
};
|
|
323
557
|
|
|
324
558
|
|