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/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 = null, urlResolver = null) {
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 (err) {
32
- throw new Error('Invalid svg document (' + err + ')');
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>)');
@@ -20,7 +20,7 @@ class TableProcessor {
20
20
  const prepareRowSpanData = () => {
21
21
  let rsd = [];
22
22
  let x = 0;
23
- let lastWidth = 0;
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.urlResolver = null;
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
- let printer = new _Printer.default(this.fonts, this.virtualfs, this.urlResolver());
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.4",
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": "^9.39.2",
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": "^9.39.2",
29
- "eslint-plugin-jsdoc": "^62.5.4",
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.0",
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 = node.width || (node.height ? (node.height * ratio) : dimensions.width);
101
- node._height = node.height || (node.width ? node.width / ratio : dimensions.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 = {
@@ -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[lastPage] = Math.max(previousBottom, this.y);
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
- if (height && (saved.bottomMost.y - saved.y < height)) {
129
- this.height = height;
130
- } else {
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