pdfmake 0.3.0-beta.9 → 0.3.0
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 +7 -48
- package/LICENSE +1 -1
- package/README.md +78 -85
- package/build/pdfmake.js +66833 -75014
- package/build/pdfmake.js.map +1 -1
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/build/vfs_fonts.js +4 -4
- package/fonts/Roboto/Roboto-Italic.ttf +0 -0
- package/fonts/Roboto/Roboto-Medium.ttf +0 -0
- package/fonts/Roboto/Roboto-MediumItalic.ttf +0 -0
- package/fonts/Roboto/Roboto-Regular.ttf +0 -0
- package/js/DocMeasure.js +20 -1
- package/js/DocPreprocessor.js +21 -6
- package/js/DocumentContext.js +64 -17
- package/js/ElementWriter.js +31 -8
- package/js/LayoutBuilder.js +488 -127
- package/js/OutputDocument.js +22 -34
- package/js/OutputDocumentServer.js +6 -11
- package/js/PDFDocument.js +1 -1
- package/js/PageElementWriter.js +17 -2
- package/js/Printer.js +133 -133
- package/js/Renderer.js +22 -14
- package/js/SVGMeasure.js +2 -2
- package/js/StyleContextStack.js +4 -0
- package/js/TableProcessor.js +40 -14
- package/js/TextBreaker.js +31 -4
- package/js/TextInlines.js +1 -1
- package/js/URLResolver.js +25 -55
- package/js/base.js +1 -1
- package/js/browser-extensions/OutputDocumentBrowser.js +35 -72
- package/js/browser-extensions/index.js +2 -2
- package/js/browser-extensions/pdfMake.js +0 -12
- package/js/browser-extensions/standard-fonts/Courier.js +4 -4
- package/js/browser-extensions/standard-fonts/Helvetica.js +4 -4
- package/js/browser-extensions/standard-fonts/Symbol.js +1 -1
- package/js/browser-extensions/standard-fonts/Times.js +4 -4
- package/js/browser-extensions/standard-fonts/ZapfDingbats.js +1 -1
- package/js/helpers/tools.js +6 -0
- package/js/index.js +1 -1
- package/package.json +25 -24
- package/src/DocMeasure.js +21 -1
- package/src/DocPreprocessor.js +25 -6
- package/src/DocumentContext.js +56 -20
- package/src/ElementWriter.js +30 -7
- package/src/LayoutBuilder.js +531 -144
- package/src/OutputDocument.js +23 -37
- package/src/OutputDocumentServer.js +6 -11
- package/src/PDFDocument.js +1 -1
- package/src/PageElementWriter.js +21 -2
- package/src/Printer.js +134 -131
- package/src/Renderer.js +13 -15
- package/src/SVGMeasure.js +2 -2
- package/src/StyleContextStack.js +4 -0
- package/src/TableProcessor.js +42 -18
- package/src/TextBreaker.js +24 -5
- package/src/TextInlines.js +1 -1
- package/src/URLResolver.js +24 -58
- package/src/base.js +1 -1
- package/src/browser-extensions/OutputDocumentBrowser.js +33 -71
- package/src/browser-extensions/index.js +3 -3
- package/src/browser-extensions/pdfMake.js +0 -14
- package/src/browser-extensions/standard-fonts/Courier.js +4 -4
- package/src/browser-extensions/standard-fonts/Helvetica.js +4 -4
- package/src/browser-extensions/standard-fonts/Symbol.js +1 -1
- package/src/browser-extensions/standard-fonts/Times.js +4 -4
- package/src/browser-extensions/standard-fonts/ZapfDingbats.js +1 -1
- package/src/columnCalculator.js +12 -12
- package/src/helpers/tools.js +5 -0
- package/src/helpers/variableType.js +1 -1
- package/src/index.js +1 -1
- package/standard-fonts/Helvetica.js +0 -1
- package/js/browser-extensions/URLBrowserResolver.js +0 -76
- package/src/browser-extensions/URLBrowserResolver.js +0 -84
package/src/LayoutBuilder.js
CHANGED
|
@@ -7,7 +7,7 @@ import TableProcessor from './TableProcessor';
|
|
|
7
7
|
import Line from './Line';
|
|
8
8
|
import { isString, isValue, isNumber } from './helpers/variableType';
|
|
9
9
|
import { stringifyNode, getNodeId } from './helpers/node';
|
|
10
|
-
import { pack, offsetVector } from './helpers/tools';
|
|
10
|
+
import { pack, offsetVector, convertToDynamicContent } from './helpers/tools';
|
|
11
11
|
import TextInlines from './TextInlines';
|
|
12
12
|
import StyleContextStack from './StyleContextStack';
|
|
13
13
|
|
|
@@ -32,6 +32,7 @@ class LayoutBuilder {
|
|
|
32
32
|
this.pageMargins = pageMargins;
|
|
33
33
|
this.svgMeasure = svgMeasure;
|
|
34
34
|
this.tableLayouts = {};
|
|
35
|
+
this.nestedLevel = 0;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
registerTableLayouts(tableLayouts) {
|
|
@@ -71,7 +72,17 @@ class LayoutBuilder {
|
|
|
71
72
|
return false;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
const hasRenderableContent = node => {
|
|
76
|
+
if (!node || node.positions.length === 0) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (node.text === '' && !node.listMarker) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
linearNodeList = linearNodeList.filter(hasRenderableContent);
|
|
75
86
|
|
|
76
87
|
linearNodeList.forEach(node => {
|
|
77
88
|
let nodeInfo = {};
|
|
@@ -167,23 +178,42 @@ class LayoutBuilder {
|
|
|
167
178
|
watermark
|
|
168
179
|
) {
|
|
169
180
|
|
|
181
|
+
const isNecessaryAddFirstPage = (docStructure) => {
|
|
182
|
+
if (docStructure.stack && docStructure.stack.length > 0 && docStructure.stack[0].section) {
|
|
183
|
+
return false;
|
|
184
|
+
} else if (docStructure.section) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return true;
|
|
189
|
+
};
|
|
190
|
+
|
|
170
191
|
this.linearNodeList = [];
|
|
171
192
|
docStructure = this.docPreprocessor.preprocessDocument(docStructure);
|
|
172
193
|
docStructure = this.docMeasure.measureDocument(docStructure);
|
|
173
194
|
|
|
174
|
-
this.writer = new PageElementWriter(
|
|
175
|
-
new DocumentContext(this.pageSize, this.pageMargins));
|
|
195
|
+
this.writer = new PageElementWriter(new DocumentContext());
|
|
176
196
|
|
|
177
|
-
this.writer.context().addListener('pageAdded', () => {
|
|
178
|
-
|
|
197
|
+
this.writer.context().addListener('pageAdded', (page) => {
|
|
198
|
+
let backgroundGetter = background;
|
|
199
|
+
if (page.customProperties['background'] || page.customProperties['background'] === null) {
|
|
200
|
+
backgroundGetter = page.customProperties['background'];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.addBackground(backgroundGetter);
|
|
179
204
|
});
|
|
180
205
|
|
|
181
|
-
|
|
206
|
+
if (isNecessaryAddFirstPage(docStructure)) {
|
|
207
|
+
this.writer.addPage(
|
|
208
|
+
this.pageSize,
|
|
209
|
+
null,
|
|
210
|
+
this.pageMargins
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
182
214
|
this.processNode(docStructure);
|
|
183
215
|
this.addHeadersAndFooters(header, footer);
|
|
184
|
-
|
|
185
|
-
this.addWatermark(watermark, pdfDocument, defaultStyle);
|
|
186
|
-
}
|
|
216
|
+
this.addWatermark(watermark, pdfDocument, defaultStyle);
|
|
187
217
|
|
|
188
218
|
return { pages: this.writer.context().pages, linearNodeList: this.linearNodeList };
|
|
189
219
|
}
|
|
@@ -198,31 +228,37 @@ class LayoutBuilder {
|
|
|
198
228
|
|
|
199
229
|
if (pageBackground) {
|
|
200
230
|
this.writer.beginUnbreakableBlock(pageSize.width, pageSize.height);
|
|
201
|
-
pageBackground = this.docPreprocessor.
|
|
202
|
-
this.processNode(this.docMeasure.
|
|
231
|
+
pageBackground = this.docPreprocessor.preprocessBlock(pageBackground);
|
|
232
|
+
this.processNode(this.docMeasure.measureBlock(pageBackground));
|
|
203
233
|
this.writer.commitUnbreakableBlock(0, 0);
|
|
204
234
|
context.backgroundLength[context.page] += pageBackground.positions.length;
|
|
205
235
|
}
|
|
206
236
|
}
|
|
207
237
|
|
|
208
|
-
|
|
209
|
-
this.addDynamicRepeatable(() => // copy to new object
|
|
210
|
-
JSON.parse(JSON.stringify(headerOrFooter)), sizeFunction);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
addDynamicRepeatable(nodeGetter, sizeFunction) {
|
|
238
|
+
addDynamicRepeatable(nodeGetter, sizeFunction, customPropertyName) {
|
|
214
239
|
let pages = this.writer.context().pages;
|
|
215
240
|
|
|
216
241
|
for (let pageIndex = 0, l = pages.length; pageIndex < l; pageIndex++) {
|
|
217
242
|
this.writer.context().page = pageIndex;
|
|
218
243
|
|
|
219
|
-
let
|
|
244
|
+
let customProperties = this.writer.context().getCurrentPage().customProperties;
|
|
245
|
+
|
|
246
|
+
let pageNodeGetter = nodeGetter;
|
|
247
|
+
if (customProperties[customPropertyName] || customProperties[customPropertyName] === null) {
|
|
248
|
+
pageNodeGetter = customProperties[customPropertyName];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if ((typeof pageNodeGetter === 'undefined') || (pageNodeGetter === null)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let node = pageNodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
|
|
220
256
|
|
|
221
257
|
if (node) {
|
|
222
|
-
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
|
|
258
|
+
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.writer.context().getCurrentPage().pageMargins);
|
|
223
259
|
this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
|
|
224
|
-
node = this.docPreprocessor.
|
|
225
|
-
this.processNode(this.docMeasure.
|
|
260
|
+
node = this.docPreprocessor.preprocessBlock(node);
|
|
261
|
+
this.processNode(this.docMeasure.measureBlock(node));
|
|
226
262
|
this.writer.commitUnbreakableBlock(sizes.x, sizes.y);
|
|
227
263
|
}
|
|
228
264
|
}
|
|
@@ -243,58 +279,62 @@ class LayoutBuilder {
|
|
|
243
279
|
height: pageMargins.bottom
|
|
244
280
|
});
|
|
245
281
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} else if (header) {
|
|
249
|
-
this.addStaticRepeatable(header, headerSizeFct);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (typeof footer === 'function') {
|
|
253
|
-
this.addDynamicRepeatable(footer, footerSizeFct);
|
|
254
|
-
} else if (footer) {
|
|
255
|
-
this.addStaticRepeatable(footer, footerSizeFct);
|
|
256
|
-
}
|
|
282
|
+
this.addDynamicRepeatable(header, headerSizeFct, 'header');
|
|
283
|
+
this.addDynamicRepeatable(footer, footerSizeFct, 'footer');
|
|
257
284
|
}
|
|
258
285
|
|
|
259
286
|
addWatermark(watermark, pdfDocument, defaultStyle) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
287
|
+
let pages = this.writer.context().pages;
|
|
288
|
+
for (let i = 0, l = pages.length; i < l; i++) {
|
|
289
|
+
let pageWatermark = watermark;
|
|
290
|
+
if (pages[i].customProperties['watermark'] || pages[i].customProperties['watermark'] === null) {
|
|
291
|
+
pageWatermark = pages[i].customProperties['watermark'];
|
|
292
|
+
}
|
|
263
293
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
294
|
+
if (pageWatermark === undefined || pageWatermark === null) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
267
297
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
watermark.opacity = isNumber(watermark.opacity) ? watermark.opacity : 0.6;
|
|
272
|
-
watermark.bold = watermark.bold || false;
|
|
273
|
-
watermark.italics = watermark.italics || false;
|
|
274
|
-
watermark.angle = isValue(watermark.angle) ? watermark.angle : null;
|
|
298
|
+
if (isString(pageWatermark)) {
|
|
299
|
+
pageWatermark = { 'text': pageWatermark };
|
|
300
|
+
}
|
|
275
301
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
302
|
+
if (!pageWatermark.text) { // empty watermark text
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
279
305
|
|
|
280
|
-
|
|
281
|
-
watermark.fontSize = getWatermarkFontSize(this.pageSize, watermark, pdfDocument);
|
|
306
|
+
pages[i].watermark = getWatermarkObject({ ...pageWatermark }, pages[i].pageSize, pdfDocument, defaultStyle);
|
|
282
307
|
}
|
|
283
308
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
309
|
+
function getWatermarkObject(watermark, pageSize, pdfDocument, defaultStyle) {
|
|
310
|
+
watermark.font = watermark.font || defaultStyle.font || 'Roboto';
|
|
311
|
+
watermark.fontSize = watermark.fontSize || 'auto';
|
|
312
|
+
watermark.color = watermark.color || 'black';
|
|
313
|
+
watermark.opacity = isNumber(watermark.opacity) ? watermark.opacity : 0.6;
|
|
314
|
+
watermark.bold = watermark.bold || false;
|
|
315
|
+
watermark.italics = watermark.italics || false;
|
|
316
|
+
watermark.angle = isValue(watermark.angle) ? watermark.angle : null;
|
|
317
|
+
|
|
318
|
+
if (watermark.angle === null) {
|
|
319
|
+
watermark.angle = Math.atan2(pageSize.height, pageSize.width) * -180 / Math.PI;
|
|
320
|
+
}
|
|
292
321
|
|
|
293
|
-
|
|
322
|
+
if (watermark.fontSize === 'auto') {
|
|
323
|
+
watermark.fontSize = getWatermarkFontSize(pageSize, watermark, pdfDocument);
|
|
324
|
+
}
|
|
294
325
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
326
|
+
let watermarkObject = {
|
|
327
|
+
text: watermark.text,
|
|
328
|
+
font: pdfDocument.provideFont(watermark.font, watermark.bold, watermark.italics),
|
|
329
|
+
fontSize: watermark.fontSize,
|
|
330
|
+
color: watermark.color,
|
|
331
|
+
opacity: watermark.opacity,
|
|
332
|
+
angle: watermark.angle
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
watermarkObject._size = getWatermarkSize(watermark, pdfDocument);
|
|
336
|
+
|
|
337
|
+
return watermarkObject;
|
|
298
338
|
}
|
|
299
339
|
|
|
300
340
|
function getWatermarkSize(watermark, pdfDocument) {
|
|
@@ -369,16 +409,50 @@ class LayoutBuilder {
|
|
|
369
409
|
}
|
|
370
410
|
}
|
|
371
411
|
|
|
372
|
-
|
|
373
|
-
|
|
412
|
+
const isDetachedBlock = node.relativePosition || node.absolutePosition;
|
|
413
|
+
|
|
414
|
+
// Detached nodes have no margins, their position is only determined by 'x' and 'y'
|
|
415
|
+
if (margin && !isDetachedBlock) {
|
|
416
|
+
const availableHeight = this.writer.context().availableHeight;
|
|
417
|
+
// If top margin is bigger than available space, move to next page
|
|
418
|
+
// Necessary for nodes inside tables
|
|
419
|
+
if (availableHeight - margin[1] < 0) {
|
|
420
|
+
// Consume the whole available space
|
|
421
|
+
this.writer.context().moveDown(availableHeight);
|
|
422
|
+
this.writer.moveToNextPage(node.pageOrientation);
|
|
423
|
+
/**
|
|
424
|
+
* TODO - Something to consider:
|
|
425
|
+
* Right now the node starts at the top of next page (after header)
|
|
426
|
+
* Another option would be to apply just the top margin that has not been consumed in the page before
|
|
427
|
+
* It would something like: this.write.context().moveDown(margin[1] - availableHeight)
|
|
428
|
+
*/
|
|
429
|
+
} else {
|
|
430
|
+
this.writer.context().moveDown(margin[1]);
|
|
431
|
+
}
|
|
432
|
+
// Apply lateral margins
|
|
374
433
|
this.writer.context().addMargin(margin[0], margin[2]);
|
|
375
434
|
}
|
|
376
|
-
|
|
377
435
|
callback();
|
|
378
436
|
|
|
379
|
-
|
|
437
|
+
// Detached nodes have no margins, their position is only determined by 'x' and 'y'
|
|
438
|
+
if (margin && !isDetachedBlock) {
|
|
439
|
+
const availableHeight = this.writer.context().availableHeight;
|
|
440
|
+
// If bottom margin is bigger than available space, move to next page
|
|
441
|
+
// Necessary for nodes inside tables
|
|
442
|
+
if (availableHeight - margin[3] < 0) {
|
|
443
|
+
this.writer.context().moveDown(availableHeight);
|
|
444
|
+
this.writer.moveToNextPage(node.pageOrientation);
|
|
445
|
+
/**
|
|
446
|
+
* TODO - Something to consider:
|
|
447
|
+
* Right now next node starts at the top of next page (after header)
|
|
448
|
+
* Another option would be to apply the bottom margin that has not been consumed in the next page?
|
|
449
|
+
* It would something like: this.write.context().moveDown(margin[3] - availableHeight)
|
|
450
|
+
*/
|
|
451
|
+
} else {
|
|
452
|
+
this.writer.context().moveDown(margin[3]);
|
|
453
|
+
}
|
|
454
|
+
// Apply lateral margins
|
|
380
455
|
this.writer.context().addMargin(-margin[0], -margin[2]);
|
|
381
|
-
this.writer.context().moveDown(margin[3]);
|
|
382
456
|
}
|
|
383
457
|
|
|
384
458
|
if (node.pageBreak === 'after') {
|
|
@@ -419,6 +493,8 @@ class LayoutBuilder {
|
|
|
419
493
|
|
|
420
494
|
if (node.stack) {
|
|
421
495
|
this.processVerticalContainer(node);
|
|
496
|
+
} else if (node.section) {
|
|
497
|
+
this.processSection(node);
|
|
422
498
|
} else if (node.columns) {
|
|
423
499
|
this.processColumns(node);
|
|
424
500
|
} else if (node.ul) {
|
|
@@ -465,8 +541,78 @@ class LayoutBuilder {
|
|
|
465
541
|
}, this);
|
|
466
542
|
}
|
|
467
543
|
|
|
544
|
+
// section
|
|
545
|
+
processSection(sectionNode) {
|
|
546
|
+
// TODO: properties
|
|
547
|
+
|
|
548
|
+
let page = this.writer.context().getCurrentPage();
|
|
549
|
+
if (!page || (page && page.items.length)) { // move to new empty page
|
|
550
|
+
// page definition inherit from current page
|
|
551
|
+
if (sectionNode.pageSize === 'inherit') {
|
|
552
|
+
sectionNode.pageSize = page ? { width: page.pageSize.width, height: page.pageSize.height } : undefined;
|
|
553
|
+
}
|
|
554
|
+
if (sectionNode.pageOrientation === 'inherit') {
|
|
555
|
+
sectionNode.pageOrientation = page ? page.pageSize.orientation : undefined;
|
|
556
|
+
}
|
|
557
|
+
if (sectionNode.pageMargins === 'inherit') {
|
|
558
|
+
sectionNode.pageMargins = page ? page.pageMargins : undefined;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (sectionNode.header === 'inherit') {
|
|
562
|
+
sectionNode.header = page ? page.customProperties.header : undefined;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (sectionNode.footer === 'inherit') {
|
|
566
|
+
sectionNode.footer = page ? page.customProperties.footer : undefined;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (sectionNode.background === 'inherit') {
|
|
570
|
+
sectionNode.background = page ? page.customProperties.background : undefined;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (sectionNode.watermark === 'inherit') {
|
|
574
|
+
sectionNode.watermark = page ? page.customProperties.watermark : undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (sectionNode.header && typeof sectionNode.header !== 'function' && sectionNode.header !== null) {
|
|
578
|
+
sectionNode.header = convertToDynamicContent(sectionNode.header);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (sectionNode.footer && typeof sectionNode.footer !== 'function' && sectionNode.footer !== null) {
|
|
582
|
+
sectionNode.footer = convertToDynamicContent(sectionNode.footer);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let customProperties = {};
|
|
586
|
+
if (typeof sectionNode.header !== 'undefined') {
|
|
587
|
+
customProperties.header = sectionNode.header;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (typeof sectionNode.footer !== 'undefined') {
|
|
591
|
+
customProperties.footer = sectionNode.footer;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (typeof sectionNode.background !== 'undefined') {
|
|
595
|
+
customProperties.background = sectionNode.background;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (typeof sectionNode.watermark !== 'undefined') {
|
|
599
|
+
customProperties.watermark = sectionNode.watermark;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
this.writer.addPage(
|
|
603
|
+
sectionNode.pageSize || this.pageSize,
|
|
604
|
+
sectionNode.pageOrientation,
|
|
605
|
+
sectionNode.pageMargins || this.pageMargins,
|
|
606
|
+
customProperties
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
this.processNode(sectionNode.section);
|
|
611
|
+
}
|
|
612
|
+
|
|
468
613
|
// columns
|
|
469
614
|
processColumns(columnNode) {
|
|
615
|
+
this.nestedLevel++;
|
|
470
616
|
let columns = columnNode.columns;
|
|
471
617
|
let availableWidth = this.writer.context().availableWidth;
|
|
472
618
|
let gaps = gapArray(columnNode._gap);
|
|
@@ -476,9 +622,17 @@ class LayoutBuilder {
|
|
|
476
622
|
}
|
|
477
623
|
|
|
478
624
|
ColumnCalculator.buildColumnWidths(columns, availableWidth);
|
|
479
|
-
let result = this.processRow(
|
|
625
|
+
let result = this.processRow({
|
|
626
|
+
marginX: columnNode._margin ? [columnNode._margin[0], columnNode._margin[2]] : [0, 0],
|
|
627
|
+
cells: columns,
|
|
628
|
+
widths: columns,
|
|
629
|
+
gaps
|
|
630
|
+
});
|
|
480
631
|
addAll(columnNode.positions, result.positions);
|
|
481
|
-
|
|
632
|
+
this.nestedLevel--;
|
|
633
|
+
if (this.nestedLevel === 0) {
|
|
634
|
+
this.writer.context().resetMarginXTopParent();
|
|
635
|
+
}
|
|
482
636
|
function gapArray(gap) {
|
|
483
637
|
if (!gap) {
|
|
484
638
|
return null;
|
|
@@ -495,130 +649,306 @@ class LayoutBuilder {
|
|
|
495
649
|
}
|
|
496
650
|
}
|
|
497
651
|
|
|
498
|
-
|
|
652
|
+
/**
|
|
653
|
+
* Searches for a cell in the same row that starts a rowspan and is positioned immediately before the current cell.
|
|
654
|
+
* Alternatively, it finds a cell where the colspan initiating the rowspan extends to the cell just before the current one.
|
|
655
|
+
*
|
|
656
|
+
* @param {Array<object>} arr - An array representing cells in a row.
|
|
657
|
+
* @param {number} i - The index of the current cell to search backward from.
|
|
658
|
+
* @returns {object|null} The starting cell of the rowspan if found; otherwise, `null`.
|
|
659
|
+
*/
|
|
660
|
+
_findStartingRowSpanCell(arr, i) {
|
|
499
661
|
let requiredColspan = 1;
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
662
|
+
for (let index = i - 1; index >= 0; index--) {
|
|
663
|
+
if (!arr[index]._span) {
|
|
664
|
+
if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
|
|
665
|
+
return arr[index];
|
|
666
|
+
} else {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
requiredColspan++;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Retrieves a page break description for a specified page from a list of page breaks.
|
|
677
|
+
*
|
|
678
|
+
* @param {Array<object>} pageBreaks - An array of page break descriptions, each containing `prevPage` properties.
|
|
679
|
+
* @param {number} page - The page number to find the associated page break for.
|
|
680
|
+
* @returns {object|undefined} The page break description object for the specified page if found; otherwise, `undefined`.
|
|
681
|
+
*/
|
|
682
|
+
_getPageBreak(pageBreaks, page) {
|
|
683
|
+
return pageBreaks.find(desc => desc.prevPage === page);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
_getPageBreakListBySpan(tableNode, page, rowIndex) {
|
|
687
|
+
if (!tableNode || !tableNode._breaksBySpan) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const breaksList = tableNode._breaksBySpan.filter(desc => desc.prevPage === page && rowIndex <= desc.rowIndexOfSpanEnd);
|
|
691
|
+
|
|
692
|
+
let y = Number.MAX_VALUE,
|
|
693
|
+
prevY = Number.MIN_VALUE;
|
|
694
|
+
|
|
695
|
+
breaksList.forEach(b => {
|
|
696
|
+
prevY = Math.max(b.prevY, prevY);
|
|
697
|
+
y = Math.min(b.y, y);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
return {
|
|
701
|
+
prevPage: page,
|
|
702
|
+
prevY: prevY,
|
|
703
|
+
y: y
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
_findSameRowPageBreakByRowSpanData(breaksBySpan, page, rowIndex) {
|
|
708
|
+
if (!breaksBySpan) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
return breaksBySpan.find(desc => desc.prevPage === page && rowIndex === desc.rowIndexOfSpanEnd);
|
|
511
712
|
}
|
|
512
713
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
714
|
+
_updatePageBreaksData(pageBreaks, tableNode, rowIndex) {
|
|
715
|
+
Object.keys(tableNode._bottomByPage).forEach(p => {
|
|
716
|
+
const page = Number(p);
|
|
717
|
+
const pageBreak = this._getPageBreak(pageBreaks, page);
|
|
718
|
+
if (pageBreak) {
|
|
719
|
+
pageBreak.prevY = Math.max(pageBreak.prevY, tableNode._bottomByPage[page]);
|
|
720
|
+
}
|
|
721
|
+
if (tableNode._breaksBySpan && tableNode._breaksBySpan.length > 0) {
|
|
722
|
+
const breaksBySpanList = tableNode._breaksBySpan.filter(pb => pb.prevPage === page && rowIndex <= pb.rowIndexOfSpanEnd);
|
|
516
723
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
break;
|
|
724
|
+
if (breaksBySpanList && breaksBySpanList.length > 0) {
|
|
725
|
+
breaksBySpanList.forEach(b => {
|
|
726
|
+
b.prevY = Math.max(b.prevY, tableNode._bottomByPage[page]);
|
|
727
|
+
});
|
|
522
728
|
}
|
|
523
729
|
}
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Resolves the Y-coordinates for a target object by comparing two break points.
|
|
735
|
+
*
|
|
736
|
+
* @param {object} break1 - The first break point with `prevY` and `y` properties.
|
|
737
|
+
* @param {object} break2 - The second break point with `prevY` and `y` properties.
|
|
738
|
+
* @param {object} target - The target object to be updated with resolved Y-coordinates.
|
|
739
|
+
* @property {number} target.prevY - Updated to the maximum `prevY` value between `break1` and `break2`.
|
|
740
|
+
* @property {number} target.y - Updated to the minimum `y` value between `break1` and `break2`.
|
|
741
|
+
*/
|
|
742
|
+
_resolveBreakY(break1, break2, target) {
|
|
743
|
+
target.prevY = Math.max(break1.prevY, break2.prevY);
|
|
744
|
+
target.y = Math.min(break1.y, break2.y);
|
|
745
|
+
};
|
|
524
746
|
|
|
747
|
+
_storePageBreakData(data, startsRowSpan, pageBreaks, tableNode) {
|
|
748
|
+
if (!startsRowSpan) {
|
|
749
|
+
let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
|
|
750
|
+
let pageDescBySpan = this._getPageBreakListBySpan(tableNode, data.prevPage, data.rowIndex);
|
|
525
751
|
if (!pageDesc) {
|
|
526
|
-
pageDesc =
|
|
752
|
+
pageDesc = {
|
|
753
|
+
...data
|
|
754
|
+
};
|
|
527
755
|
pageBreaks.push(pageDesc);
|
|
528
756
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
757
|
+
if (pageDescBySpan) {
|
|
758
|
+
this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
|
|
759
|
+
}
|
|
760
|
+
this._resolveBreakY(pageDesc, data, pageDesc);
|
|
761
|
+
} else {
|
|
762
|
+
const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
|
|
763
|
+
let pageDescBySpan = this._findSameRowPageBreakByRowSpanData(breaksBySpan, data.prevPage, data.rowIndex);
|
|
764
|
+
if (!pageDescBySpan) {
|
|
765
|
+
pageDescBySpan = {
|
|
766
|
+
...data,
|
|
767
|
+
rowIndexOfSpanEnd: data.rowIndex + data.rowSpan - 1
|
|
768
|
+
};
|
|
769
|
+
if (!tableNode._breaksBySpan) {
|
|
770
|
+
tableNode._breaksBySpan = [];
|
|
771
|
+
}
|
|
772
|
+
tableNode._breaksBySpan.push(pageDescBySpan);
|
|
773
|
+
}
|
|
774
|
+
pageDescBySpan.prevY = Math.max(pageDescBySpan.prevY, data.prevY);
|
|
775
|
+
pageDescBySpan.y = Math.min(pageDescBySpan.y, data.y);
|
|
776
|
+
let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
|
|
777
|
+
if (pageDesc) {
|
|
778
|
+
this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Calculates the left offset for a column based on the specified gap values.
|
|
785
|
+
*
|
|
786
|
+
* @param {number} i - The index of the column for which the offset is being calculated.
|
|
787
|
+
* @param {Array<number>} gaps - An array of gap values for each column.
|
|
788
|
+
* @returns {number} The left offset for the column. Returns `gaps[i]` if it exists, otherwise `0`.
|
|
789
|
+
*/
|
|
790
|
+
_colLeftOffset(i, gaps) {
|
|
791
|
+
if (gaps && gaps.length > i) {
|
|
792
|
+
return gaps[i];
|
|
793
|
+
}
|
|
794
|
+
return 0;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Retrieves the ending cell for a row span in case it exists in a specified table column.
|
|
799
|
+
*
|
|
800
|
+
* @param {Array<Array<object>>} tableBody - The table body, represented as a 2D array of cell objects.
|
|
801
|
+
* @param {number} rowIndex - The index of the starting row for the row span.
|
|
802
|
+
* @param {object} column - The column object containing row span information.
|
|
803
|
+
* @param {number} columnIndex - The index of the column within the row.
|
|
804
|
+
* @returns {object|null} The cell at the end of the row span if it exists; otherwise, `null`.
|
|
805
|
+
* @throws {Error} If the row span extends beyond the total row count.
|
|
806
|
+
*/
|
|
807
|
+
_getRowSpanEndingCell(tableBody, rowIndex, column, columnIndex) {
|
|
808
|
+
if (column.rowSpan && column.rowSpan > 1) {
|
|
809
|
+
let endingRow = rowIndex + column.rowSpan - 1;
|
|
810
|
+
if (endingRow >= tableBody.length) {
|
|
811
|
+
throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
|
|
812
|
+
}
|
|
813
|
+
return tableBody[endingRow][columnIndex];
|
|
814
|
+
}
|
|
532
815
|
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
processRow({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableNode, tableBody, rowIndex, height }) {
|
|
820
|
+
const isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
|
|
533
821
|
let pageBreaks = [];
|
|
822
|
+
let pageBreaksByRowSpan = [];
|
|
534
823
|
let positions = [];
|
|
824
|
+
let willBreakByHeight = false;
|
|
825
|
+
widths = widths || cells;
|
|
826
|
+
|
|
827
|
+
// Check if row should break by height
|
|
828
|
+
if (!isUnbreakableRow && height > this.writer.context().availableHeight) {
|
|
829
|
+
willBreakByHeight = true;
|
|
830
|
+
}
|
|
535
831
|
|
|
536
|
-
|
|
832
|
+
// Use the marginX if we are in a top level table/column (not nested)
|
|
833
|
+
const marginXParent = this.nestedLevel === 1 ? marginX : null;
|
|
834
|
+
const _bottomByPage = tableNode ? tableNode._bottomByPage : null;
|
|
835
|
+
this.writer.context().beginColumnGroup(marginXParent, _bottomByPage);
|
|
537
836
|
|
|
538
|
-
|
|
837
|
+
for (let i = 0, l = cells.length; i < l; i++) {
|
|
838
|
+
let cell = cells[i];
|
|
539
839
|
|
|
540
|
-
|
|
840
|
+
// Page change handler
|
|
841
|
+
const storePageBreakClosure = data => {
|
|
842
|
+
const startsRowSpan = cell.rowSpan && cell.rowSpan > 1;
|
|
843
|
+
if (startsRowSpan) {
|
|
844
|
+
data.rowSpan = cell.rowSpan;
|
|
845
|
+
}
|
|
846
|
+
data.rowIndex = rowIndex;
|
|
847
|
+
this._storePageBreakData(data, startsRowSpan, pageBreaks, tableNode);
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
this.writer.addListener('pageChanged', storePageBreakClosure);
|
|
541
851
|
|
|
542
|
-
for (let i = 0, l = columns.length; i < l; i++) {
|
|
543
|
-
let column = columns[i];
|
|
544
852
|
let width = widths[i]._calcWidth;
|
|
545
|
-
let leftOffset =
|
|
853
|
+
let leftOffset = this._colLeftOffset(i, gaps);
|
|
854
|
+
// Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
|
|
855
|
+
let startingSpanCell = this._findStartingRowSpanCell(cells, i);
|
|
546
856
|
|
|
547
|
-
if (
|
|
548
|
-
for (let j = 1; j <
|
|
857
|
+
if (cell.colSpan && cell.colSpan > 1) {
|
|
858
|
+
for (let j = 1; j < cell.colSpan; j++) {
|
|
549
859
|
width += widths[++i]._calcWidth + gaps[i];
|
|
550
860
|
}
|
|
551
861
|
}
|
|
552
862
|
|
|
553
863
|
// if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
864
|
+
const rowSpanEndingCell = this._getRowSpanEndingCell(tableBody, rowIndex, cell, i);
|
|
865
|
+
if (rowSpanEndingCell) {
|
|
556
866
|
// We store a reference of the ending cell in the first cell of the rowspan
|
|
557
|
-
|
|
867
|
+
cell._endingCell = rowSpanEndingCell;
|
|
868
|
+
cell._endingCell._startingRowSpanY = cell._startingRowSpanY;
|
|
558
869
|
}
|
|
559
870
|
|
|
560
|
-
//
|
|
561
|
-
let
|
|
562
|
-
let endingSpanCell = null;
|
|
871
|
+
// If we are after a cell that started a rowspan
|
|
872
|
+
let endOfRowSpanCell = null;
|
|
563
873
|
if (startingSpanCell && startingSpanCell._endingCell) {
|
|
564
874
|
// Reference to the last cell of the rowspan
|
|
565
|
-
|
|
875
|
+
endOfRowSpanCell = startingSpanCell._endingCell;
|
|
876
|
+
// Store if we are in an unbreakable block when we save the context and the originalX
|
|
877
|
+
if (this.writer.transactionLevel > 0) {
|
|
878
|
+
endOfRowSpanCell._isUnbreakableContext = true;
|
|
879
|
+
endOfRowSpanCell._originalXOffset = this.writer.originalX;
|
|
880
|
+
}
|
|
566
881
|
}
|
|
567
882
|
|
|
568
883
|
// We pass the endingSpanCell reference to store the context just after processing rowspan cell
|
|
569
|
-
this.writer.context().beginColumn(width, leftOffset,
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
884
|
+
this.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell);
|
|
885
|
+
|
|
886
|
+
if (!cell._span) {
|
|
887
|
+
this.processNode(cell);
|
|
888
|
+
this.writer.context().updateBottomByPage();
|
|
889
|
+
addAll(positions, cell.positions);
|
|
890
|
+
} else if (cell._columnEndingContext) {
|
|
891
|
+
let discountY = 0;
|
|
892
|
+
if (dontBreakRows) {
|
|
893
|
+
// Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
|
|
894
|
+
const ctxBeforeRowSpanLastRow = this.writer.contextStack[this.writer.contextStack.length - 1];
|
|
895
|
+
discountY = ctxBeforeRowSpanLastRow.y - cell._startingRowSpanY;
|
|
896
|
+
}
|
|
897
|
+
let originalXOffset = 0;
|
|
898
|
+
// If context was saved from an unbreakable block and we are not in an unbreakable block anymore
|
|
899
|
+
// We have to sum the originalX (X before starting unbreakable block) to X
|
|
900
|
+
if (cell._isUnbreakableContext && !this.writer.transactionLevel) {
|
|
901
|
+
originalXOffset = cell._originalXOffset;
|
|
902
|
+
}
|
|
574
903
|
// row-span ending
|
|
575
904
|
// Recover the context after processing the rowspanned cell
|
|
576
|
-
this.writer.context().markEnding(
|
|
905
|
+
this.writer.context().markEnding(cell, originalXOffset, discountY);
|
|
577
906
|
}
|
|
907
|
+
this.writer.removeListener('pageChanged', storePageBreakClosure);
|
|
578
908
|
}
|
|
579
909
|
|
|
580
910
|
// Check if last cell is part of a span
|
|
581
911
|
let endingSpanCell = null;
|
|
582
|
-
const lastColumn =
|
|
912
|
+
const lastColumn = cells.length > 0 ? cells[cells.length - 1] : null;
|
|
583
913
|
if (lastColumn) {
|
|
584
914
|
// Previous column cell has a rowspan
|
|
585
915
|
if (lastColumn._endingCell) {
|
|
586
916
|
endingSpanCell = lastColumn._endingCell;
|
|
587
|
-
|
|
917
|
+
// Previous column cell is part of a span
|
|
588
918
|
} else if (lastColumn._span === true) {
|
|
589
919
|
// We get the cell that started the span where we set a reference to the ending cell
|
|
590
|
-
const startingSpanCell = this.
|
|
920
|
+
const startingSpanCell = this._findStartingRowSpanCell(cells, cells.length);
|
|
591
921
|
if (startingSpanCell) {
|
|
592
922
|
// Context will be stored here (ending cell)
|
|
593
923
|
endingSpanCell = startingSpanCell._endingCell;
|
|
924
|
+
// Store if we are in an unbreakable block when we save the context and the originalX
|
|
925
|
+
if (this.writer.transactionLevel > 0) {
|
|
926
|
+
endingSpanCell._isUnbreakableContext = true;
|
|
927
|
+
endingSpanCell._originalXOffset = this.writer.originalX;
|
|
928
|
+
}
|
|
594
929
|
}
|
|
595
930
|
}
|
|
596
931
|
}
|
|
597
932
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return { pageBreaks: pageBreaks, positions: positions };
|
|
603
|
-
|
|
604
|
-
function colLeftOffset(i) {
|
|
605
|
-
if (gaps && gaps.length > i) {
|
|
606
|
-
return gaps[i];
|
|
607
|
-
}
|
|
608
|
-
return 0;
|
|
933
|
+
// If content did not break page, check if we should break by height
|
|
934
|
+
if (willBreakByHeight && !isUnbreakableRow && pageBreaks.length === 0) {
|
|
935
|
+
this.writer.context().moveDown(this.writer.context().availableHeight);
|
|
936
|
+
this.writer.moveToNextPage();
|
|
609
937
|
}
|
|
610
938
|
|
|
611
|
-
|
|
612
|
-
if (column.rowSpan && column.rowSpan > 1) {
|
|
613
|
-
let endingRow = tableRow + column.rowSpan - 1;
|
|
614
|
-
if (endingRow >= tableBody.length) {
|
|
615
|
-
throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
|
|
616
|
-
}
|
|
617
|
-
return tableBody[endingRow][columnIndex];
|
|
618
|
-
}
|
|
939
|
+
const bottomByPage = this.writer.context().completeColumnGroup(height, endingSpanCell);
|
|
619
940
|
|
|
620
|
-
|
|
941
|
+
if (tableNode) {
|
|
942
|
+
tableNode._bottomByPage = bottomByPage;
|
|
943
|
+
// If there are page breaks in this row, update data with prevY of last cell
|
|
944
|
+
this._updatePageBreaksData(pageBreaks, tableNode, rowIndex);
|
|
621
945
|
}
|
|
946
|
+
|
|
947
|
+
return {
|
|
948
|
+
pageBreaksBySpan: pageBreaksByRowSpan,
|
|
949
|
+
pageBreaks: pageBreaks,
|
|
950
|
+
positions: positions
|
|
951
|
+
};
|
|
622
952
|
}
|
|
623
953
|
|
|
624
954
|
// lists
|
|
@@ -667,12 +997,23 @@ class LayoutBuilder {
|
|
|
667
997
|
|
|
668
998
|
// tables
|
|
669
999
|
processTable(tableNode) {
|
|
1000
|
+
this.nestedLevel++;
|
|
670
1001
|
let processor = new TableProcessor(tableNode);
|
|
671
1002
|
|
|
672
1003
|
processor.beginTable(this.writer);
|
|
673
1004
|
|
|
674
1005
|
let rowHeights = tableNode.table.heights;
|
|
675
1006
|
for (let i = 0, l = tableNode.table.body.length; i < l; i++) {
|
|
1007
|
+
// if dontBreakRows and row starts a rowspan
|
|
1008
|
+
// we store the 'y' of the beginning of each rowSpan
|
|
1009
|
+
if (processor.dontBreakRows) {
|
|
1010
|
+
tableNode.table.body[i].forEach(cell => {
|
|
1011
|
+
if (cell.rowSpan && cell.rowSpan > 1) {
|
|
1012
|
+
cell._startingRowSpanY = this.writer.context().y;
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
676
1017
|
processor.beginRow(i, this.writer);
|
|
677
1018
|
|
|
678
1019
|
let height;
|
|
@@ -688,13 +1029,40 @@ class LayoutBuilder {
|
|
|
688
1029
|
height = undefined;
|
|
689
1030
|
}
|
|
690
1031
|
|
|
691
|
-
|
|
1032
|
+
const pageBeforeProcessing = this.writer.context().page;
|
|
1033
|
+
|
|
1034
|
+
let result = this.processRow({
|
|
1035
|
+
marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
|
|
1036
|
+
dontBreakRows: processor.dontBreakRows,
|
|
1037
|
+
rowsWithoutPageBreak: processor.rowsWithoutPageBreak,
|
|
1038
|
+
cells: tableNode.table.body[i],
|
|
1039
|
+
widths: tableNode.table.widths,
|
|
1040
|
+
gaps: tableNode._offsets.offsets,
|
|
1041
|
+
tableBody: tableNode.table.body,
|
|
1042
|
+
tableNode,
|
|
1043
|
+
rowIndex: i,
|
|
1044
|
+
height
|
|
1045
|
+
});
|
|
1046
|
+
|
|
692
1047
|
addAll(tableNode.positions, result.positions);
|
|
693
1048
|
|
|
1049
|
+
if (!result.pageBreaks || result.pageBreaks.length === 0) {
|
|
1050
|
+
const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
|
|
1051
|
+
const breakBySpanData = this._findSameRowPageBreakByRowSpanData(breaksBySpan, pageBeforeProcessing, i);
|
|
1052
|
+
if (breakBySpanData) {
|
|
1053
|
+
const finalBreakBySpanData = this._getPageBreakListBySpan(tableNode, breakBySpanData.prevPage, i);
|
|
1054
|
+
result.pageBreaks.push(finalBreakBySpanData);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
694
1058
|
processor.endRow(i, this.writer, result.pageBreaks);
|
|
695
1059
|
}
|
|
696
1060
|
|
|
697
1061
|
processor.endTable(this.writer);
|
|
1062
|
+
this.nestedLevel--;
|
|
1063
|
+
if (this.nestedLevel === 0) {
|
|
1064
|
+
this.writer.context().resetMarginXTopParent();
|
|
1065
|
+
}
|
|
698
1066
|
}
|
|
699
1067
|
|
|
700
1068
|
// leafs (texts)
|
|
@@ -762,6 +1130,27 @@ class LayoutBuilder {
|
|
|
762
1130
|
return newInline;
|
|
763
1131
|
}
|
|
764
1132
|
|
|
1133
|
+
function findMaxFitLength(text, maxWidth, measureFn) {
|
|
1134
|
+
let low = 1;
|
|
1135
|
+
let high = text.length;
|
|
1136
|
+
let bestFit = 1;
|
|
1137
|
+
|
|
1138
|
+
while (low <= high) {
|
|
1139
|
+
const mid = Math.floor((low + high) / 2);
|
|
1140
|
+
const part = text.substring(0, mid);
|
|
1141
|
+
const width = measureFn(part);
|
|
1142
|
+
|
|
1143
|
+
if (width <= maxWidth) {
|
|
1144
|
+
bestFit = mid;
|
|
1145
|
+
low = mid + 1;
|
|
1146
|
+
} else {
|
|
1147
|
+
high = mid - 1;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return bestFit;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
765
1154
|
if (!textNode._inlines || textNode._inlines.length === 0) {
|
|
766
1155
|
return null;
|
|
767
1156
|
}
|
|
@@ -777,11 +1166,9 @@ class LayoutBuilder {
|
|
|
777
1166
|
isForceContinue = false;
|
|
778
1167
|
|
|
779
1168
|
if (!inline.noWrap && inline.text.length > 1 && inline.width > line.getAvailableWidth()) {
|
|
780
|
-
let
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
maxChars = 1;
|
|
784
|
-
}
|
|
1169
|
+
let maxChars = findMaxFitLength(inline.text, line.getAvailableWidth(), (txt) =>
|
|
1170
|
+
textInlines.widthOfText(txt, inline)
|
|
1171
|
+
);
|
|
785
1172
|
if (maxChars < inline.text.length) {
|
|
786
1173
|
let newInline = cloneInline(inline);
|
|
787
1174
|
|