pdfmake 0.3.4 → 0.3.5

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.
@@ -173,11 +173,130 @@ class PageElementWriter extends ElementWriter {
173
173
  this.repeatables.pop();
174
174
  }
175
175
 
176
+ /**
177
+ * Move to the next column in a column group (snaking columns).
178
+ * Handles repeatables and emits columnChanged event.
179
+ */
180
+ moveToNextColumn() {
181
+ let nextColumn = this.context().moveToNextColumn();
182
+
183
+ // Handle repeatables (like table headers) for the new column
184
+ this.repeatables.forEach(function (rep) {
185
+ // In snaking columns, we WANT headers to repeat.
186
+ // However, in Standard Page Breaks, headers are drawn using useBlockXOffset=true (original absolute X).
187
+ // This works for page breaks because margins are consistent.
188
+ // In Snaking Columns, the X position changes for each column.
189
+ // If we use true, the header is drawn at the *original* X position (Col 1), overlapping/invisible.
190
+ // We MUST use false to force drawing relative to the CURRENT context X (new column start).
191
+ this.addFragment(rep, false);
192
+ }, this);
193
+
194
+ this.emit('columnChanged', {
195
+ prevY: nextColumn.prevY,
196
+ y: this.context().y
197
+ });
198
+ }
199
+
200
+ /**
201
+ * Check if currently in a column group that can move to next column.
202
+ * Only returns true if snakingColumns is enabled for the column group.
203
+ * @returns {boolean}
204
+ */
205
+ canMoveToNextColumn() {
206
+ let ctx = this.context();
207
+ let snakingSnapshot = ctx.getSnakingSnapshot();
208
+
209
+ if (snakingSnapshot) {
210
+ // Check if we're inside a nested (non-snaking) column group.
211
+ // If so, don't allow a snaking column move — it would corrupt
212
+ // the inner row's layout (e.g. product name in col 1, price in col 2).
213
+ // The inner row should complete via normal page break instead.
214
+ for (let i = ctx.snapshots.length - 1; i >= 0; i--) {
215
+ let snap = ctx.snapshots[i];
216
+ if (snap.snakingColumns) {
217
+ break; // Reached the snaking snapshot, no inner groups found
218
+ }
219
+ if (!snap.overflowed) {
220
+ return false; // Found a non-snaking, non-overflowed inner group
221
+ }
222
+ }
223
+
224
+ let overflowCount = 0;
225
+ for (let i = ctx.snapshots.length - 1; i >= 0; i--) {
226
+ if (ctx.snapshots[i].overflowed) {
227
+ overflowCount++;
228
+ } else {
229
+ break;
230
+ }
231
+ }
232
+
233
+ if (snakingSnapshot.columnWidths &&
234
+ overflowCount >= snakingSnapshot.columnWidths.length - 1) {
235
+ return false;
236
+ }
237
+
238
+ let currentColumnWidth = ctx.availableWidth || ctx.lastColumnWidth || 0;
239
+ let nextColumnWidth = snakingSnapshot.columnWidths ?
240
+ snakingSnapshot.columnWidths[overflowCount + 1] : currentColumnWidth;
241
+ let nextX = ctx.x + currentColumnWidth + (snakingSnapshot.gap || 0);
242
+ let page = ctx.getCurrentPage();
243
+ let pageWidth = page.pageSize.width;
244
+ let rightMargin = page.pageMargins ? page.pageMargins.right : 0;
245
+ let parentRightMargin = ctx.marginXTopParent ? ctx.marginXTopParent[1] : 0;
246
+ let rightBoundary = pageWidth - rightMargin - parentRightMargin;
247
+
248
+ return (nextX + nextColumnWidth) <= (rightBoundary + 1);
249
+ }
250
+ return false;
251
+ }
252
+
176
253
  _fitOnPage(addFct) {
177
254
  let position = addFct();
178
255
  if (!position) {
179
- this.moveToNextPage();
180
- position = addFct();
256
+ if (this.canMoveToNextColumn()) {
257
+ this.moveToNextColumn();
258
+ position = addFct();
259
+ }
260
+
261
+ if (!position) {
262
+ let ctx = this.context();
263
+ let snakingSnapshot = ctx.getSnakingSnapshot();
264
+
265
+ if (snakingSnapshot) {
266
+ if (ctx.isInNestedNonSnakingGroup()) {
267
+ // Inside a table cell within snaking columns — use standard page break.
268
+ // Don't reset snaking state; the table handles its own breaks.
269
+ // Column breaks happen between rows in processTable instead.
270
+ this.moveToNextPage();
271
+ } else {
272
+ this.moveToNextPage();
273
+
274
+ // Save lastColumnWidth before reset — if we're inside a nested
275
+ // column group (e.g. product/price row), the reset would overwrite
276
+ // it with the snaking column width, corrupting inner column layout.
277
+ let savedLastColumnWidth = ctx.lastColumnWidth;
278
+ ctx.resetSnakingColumnsForNewPage();
279
+ ctx.lastColumnWidth = savedLastColumnWidth;
280
+ }
281
+
282
+ position = addFct();
283
+ } else {
284
+ while (ctx.snapshots.length > 0 && ctx.snapshots[ctx.snapshots.length - 1].overflowed) {
285
+ let popped = ctx.snapshots.pop();
286
+ let prevSnapshot = ctx.snapshots[ctx.snapshots.length - 1];
287
+ if (prevSnapshot) {
288
+ ctx.x = prevSnapshot.x;
289
+ ctx.y = prevSnapshot.y;
290
+ ctx.availableHeight = prevSnapshot.availableHeight;
291
+ ctx.availableWidth = popped.availableWidth;
292
+ ctx.lastColumnWidth = prevSnapshot.lastColumnWidth;
293
+ }
294
+ }
295
+
296
+ this.moveToNextPage();
297
+ position = addFct();
298
+ }
299
+ }
181
300
  }
182
301
  return position;
183
302
  }
package/src/Renderer.js CHANGED
@@ -41,6 +41,7 @@ class Renderer {
41
41
  constructor(pdfDocument, progressCallback) {
42
42
  this.pdfDocument = pdfDocument;
43
43
  this.progressCallback = progressCallback;
44
+ this.outlineMap = [];
44
45
  }
45
46
 
46
47
  renderPages(pages) {
@@ -128,6 +129,18 @@ class Renderer {
128
129
  }
129
130
  }
130
131
 
132
+ if (line._outline) {
133
+ let parentOutline = this.pdfDocument.outline;
134
+ if (line._outline.parentId && this.outlineMap[line._outline.parentId]) {
135
+ parentOutline = this.outlineMap[line._outline.parentId];
136
+ }
137
+
138
+ let outline = parentOutline.addItem(line._outline.text, { expanded: line._outline.expanded });
139
+ if (line._outline.id) {
140
+ this.outlineMap[line._outline.id] = outline;
141
+ }
142
+ }
143
+
131
144
  if (line._pageNodeRef) {
132
145
  preparePageNodeRefLine(line._pageNodeRef, line.inlines[0]);
133
146
  }
package/src/SVGMeasure.js CHANGED
@@ -26,8 +26,8 @@ const parseSVG = (svgString) => {
26
26
 
27
27
  try {
28
28
  doc = new XmlDocument(svgString);
29
- } catch (err) {
30
- throw new Error('Invalid svg document (' + err + ')');
29
+ } catch (error) {
30
+ throw new Error('Invalid svg document (' + error + ')', { cause: error });
31
31
  }
32
32
 
33
33
  if (doc.name !== "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
 
25
25
  rsd.push({ left: 0, rowSpan: 0 });
26
26
 
@@ -274,7 +274,6 @@ class TableProcessor {
274
274
  lineColor: borderColor
275
275
  }, false, isNumber(overrideY), null, forcePage);
276
276
  currentLine = null;
277
- borderColor = null;
278
277
  cellAbove = null;
279
278
  currentCell = null;
280
279
  rowCellAbove = null;
@@ -356,9 +355,6 @@ class TableProcessor {
356
355
  dash: dash,
357
356
  lineColor: borderColor
358
357
  }, false, true);
359
- cellBefore = null;
360
- currentCell = null;
361
- borderColor = null;
362
358
  }
363
359
 
364
360
  endTable(writer) {
@@ -6,7 +6,7 @@ async function fetchUrl(url, headers = {}) {
6
6
  }
7
7
  return await response.arrayBuffer();
8
8
  } catch (error) {
9
- throw new Error(`Network request failed (url: "${url}", error: ${error.message})`);
9
+ throw new Error(`Network request failed (url: "${url}", error: ${error.message})`, { cause: error });
10
10
  }
11
11
  }
12
12