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.
- package/CHANGELOG.md +11 -1
- package/README.md +1 -0
- package/build/pdfmake.js +514 -28
- 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 +3 -0
- 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/Renderer.js +13 -0
- package/js/SVGMeasure.js +4 -2
- package/js/TableProcessor.js +1 -5
- package/js/URLResolver.js +3 -1
- package/package.json +5 -5
- package/src/DocMeasure.js +4 -0
- 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/Renderer.js +13 -0
- package/src/SVGMeasure.js +2 -2
- package/src/TableProcessor.js +1 -5
- package/src/URLResolver.js +1 -1
package/src/PageElementWriter.js
CHANGED
|
@@ -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.
|
|
180
|
-
|
|
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 (
|
|
30
|
-
throw new Error('Invalid svg document (' +
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error('Invalid svg document (' + error + ')', { cause: error });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
if (doc.name !== "svg") {
|
package/src/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
|
|
|
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) {
|
package/src/URLResolver.js
CHANGED
|
@@ -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
|
|