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/js/LayoutBuilder.js
CHANGED
|
@@ -36,6 +36,7 @@ class LayoutBuilder {
|
|
|
36
36
|
this.pageMargins = pageMargins;
|
|
37
37
|
this.svgMeasure = svgMeasure;
|
|
38
38
|
this.tableLayouts = {};
|
|
39
|
+
this.nestedLevel = 0;
|
|
39
40
|
}
|
|
40
41
|
registerTableLayouts(tableLayouts) {
|
|
41
42
|
this.tableLayouts = (0, _tools.pack)(this.tableLayouts, tableLayouts);
|
|
@@ -61,7 +62,16 @@ class LayoutBuilder {
|
|
|
61
62
|
if (typeof pageBreakBeforeFct !== 'function') {
|
|
62
63
|
return false;
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
const hasRenderableContent = node => {
|
|
66
|
+
if (!node || node.positions.length === 0) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (node.text === '' && !node.listMarker) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
linearNodeList = linearNodeList.filter(hasRenderableContent);
|
|
65
75
|
linearNodeList.forEach(node => {
|
|
66
76
|
let nodeInfo = {};
|
|
67
77
|
['id', 'text', 'ul', 'ol', 'table', 'image', 'qr', 'canvas', 'svg', 'columns', 'headlineLevel', 'style', 'pageBreak', 'pageOrientation', 'width', 'height'].forEach(key => {
|
|
@@ -131,19 +141,31 @@ class LayoutBuilder {
|
|
|
131
141
|
return result.pages;
|
|
132
142
|
}
|
|
133
143
|
tryLayoutDocument(docStructure, pdfDocument, styleDictionary, defaultStyle, background, header, footer, watermark) {
|
|
144
|
+
const isNecessaryAddFirstPage = docStructure => {
|
|
145
|
+
if (docStructure.stack && docStructure.stack.length > 0 && docStructure.stack[0].section) {
|
|
146
|
+
return false;
|
|
147
|
+
} else if (docStructure.section) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
};
|
|
134
152
|
this.linearNodeList = [];
|
|
135
153
|
docStructure = this.docPreprocessor.preprocessDocument(docStructure);
|
|
136
154
|
docStructure = this.docMeasure.measureDocument(docStructure);
|
|
137
|
-
this.writer = new _PageElementWriter.default(new _DocumentContext.default(
|
|
138
|
-
this.writer.context().addListener('pageAdded',
|
|
139
|
-
|
|
155
|
+
this.writer = new _PageElementWriter.default(new _DocumentContext.default());
|
|
156
|
+
this.writer.context().addListener('pageAdded', page => {
|
|
157
|
+
let backgroundGetter = background;
|
|
158
|
+
if (page.customProperties['background'] || page.customProperties['background'] === null) {
|
|
159
|
+
backgroundGetter = page.customProperties['background'];
|
|
160
|
+
}
|
|
161
|
+
this.addBackground(backgroundGetter);
|
|
140
162
|
});
|
|
141
|
-
|
|
163
|
+
if (isNecessaryAddFirstPage(docStructure)) {
|
|
164
|
+
this.writer.addPage(this.pageSize, null, this.pageMargins);
|
|
165
|
+
}
|
|
142
166
|
this.processNode(docStructure);
|
|
143
167
|
this.addHeadersAndFooters(header, footer);
|
|
144
|
-
|
|
145
|
-
this.addWatermark(watermark, pdfDocument, defaultStyle);
|
|
146
|
-
}
|
|
168
|
+
this.addWatermark(watermark, pdfDocument, defaultStyle);
|
|
147
169
|
return {
|
|
148
170
|
pages: this.writer.context().pages,
|
|
149
171
|
linearNodeList: this.linearNodeList
|
|
@@ -156,27 +178,30 @@ class LayoutBuilder {
|
|
|
156
178
|
let pageBackground = backgroundGetter(context.page + 1, pageSize);
|
|
157
179
|
if (pageBackground) {
|
|
158
180
|
this.writer.beginUnbreakableBlock(pageSize.width, pageSize.height);
|
|
159
|
-
pageBackground = this.docPreprocessor.
|
|
160
|
-
this.processNode(this.docMeasure.
|
|
181
|
+
pageBackground = this.docPreprocessor.preprocessBlock(pageBackground);
|
|
182
|
+
this.processNode(this.docMeasure.measureBlock(pageBackground));
|
|
161
183
|
this.writer.commitUnbreakableBlock(0, 0);
|
|
162
184
|
context.backgroundLength[context.page] += pageBackground.positions.length;
|
|
163
185
|
}
|
|
164
186
|
}
|
|
165
|
-
|
|
166
|
-
this.addDynamicRepeatable(() =>
|
|
167
|
-
// copy to new object
|
|
168
|
-
JSON.parse(JSON.stringify(headerOrFooter)), sizeFunction);
|
|
169
|
-
}
|
|
170
|
-
addDynamicRepeatable(nodeGetter, sizeFunction) {
|
|
187
|
+
addDynamicRepeatable(nodeGetter, sizeFunction, customPropertyName) {
|
|
171
188
|
let pages = this.writer.context().pages;
|
|
172
189
|
for (let pageIndex = 0, l = pages.length; pageIndex < l; pageIndex++) {
|
|
173
190
|
this.writer.context().page = pageIndex;
|
|
174
|
-
let
|
|
191
|
+
let customProperties = this.writer.context().getCurrentPage().customProperties;
|
|
192
|
+
let pageNodeGetter = nodeGetter;
|
|
193
|
+
if (customProperties[customPropertyName] || customProperties[customPropertyName] === null) {
|
|
194
|
+
pageNodeGetter = customProperties[customPropertyName];
|
|
195
|
+
}
|
|
196
|
+
if (typeof pageNodeGetter === 'undefined' || pageNodeGetter === null) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
let node = pageNodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
|
|
175
200
|
if (node) {
|
|
176
|
-
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
|
|
201
|
+
let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.writer.context().getCurrentPage().pageMargins);
|
|
177
202
|
this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
|
|
178
|
-
node = this.docPreprocessor.
|
|
179
|
-
this.processNode(this.docMeasure.
|
|
203
|
+
node = this.docPreprocessor.preprocessBlock(node);
|
|
204
|
+
this.processNode(this.docMeasure.measureBlock(node));
|
|
180
205
|
this.writer.commitUnbreakableBlock(sizes.x, sizes.y);
|
|
181
206
|
}
|
|
182
207
|
}
|
|
@@ -194,52 +219,56 @@ class LayoutBuilder {
|
|
|
194
219
|
width: pageSize.width,
|
|
195
220
|
height: pageMargins.bottom
|
|
196
221
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
} else if (header) {
|
|
200
|
-
this.addStaticRepeatable(header, headerSizeFct);
|
|
201
|
-
}
|
|
202
|
-
if (typeof footer === 'function') {
|
|
203
|
-
this.addDynamicRepeatable(footer, footerSizeFct);
|
|
204
|
-
} else if (footer) {
|
|
205
|
-
this.addStaticRepeatable(footer, footerSizeFct);
|
|
206
|
-
}
|
|
222
|
+
this.addDynamicRepeatable(header, headerSizeFct, 'header');
|
|
223
|
+
this.addDynamicRepeatable(footer, footerSizeFct, 'footer');
|
|
207
224
|
}
|
|
208
225
|
addWatermark(watermark, pdfDocument, defaultStyle) {
|
|
209
|
-
if ((0, _variableType.isString)(watermark)) {
|
|
210
|
-
watermark = {
|
|
211
|
-
'text': watermark
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
if (!watermark.text) {
|
|
215
|
-
// empty watermark text
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
watermark.font = watermark.font || defaultStyle.font || 'Roboto';
|
|
219
|
-
watermark.fontSize = watermark.fontSize || 'auto';
|
|
220
|
-
watermark.color = watermark.color || 'black';
|
|
221
|
-
watermark.opacity = (0, _variableType.isNumber)(watermark.opacity) ? watermark.opacity : 0.6;
|
|
222
|
-
watermark.bold = watermark.bold || false;
|
|
223
|
-
watermark.italics = watermark.italics || false;
|
|
224
|
-
watermark.angle = (0, _variableType.isValue)(watermark.angle) ? watermark.angle : null;
|
|
225
|
-
if (watermark.angle === null) {
|
|
226
|
-
watermark.angle = Math.atan2(this.pageSize.height, this.pageSize.width) * -180 / Math.PI;
|
|
227
|
-
}
|
|
228
|
-
if (watermark.fontSize === 'auto') {
|
|
229
|
-
watermark.fontSize = getWatermarkFontSize(this.pageSize, watermark, pdfDocument);
|
|
230
|
-
}
|
|
231
|
-
let watermarkObject = {
|
|
232
|
-
text: watermark.text,
|
|
233
|
-
font: pdfDocument.provideFont(watermark.font, watermark.bold, watermark.italics),
|
|
234
|
-
fontSize: watermark.fontSize,
|
|
235
|
-
color: watermark.color,
|
|
236
|
-
opacity: watermark.opacity,
|
|
237
|
-
angle: watermark.angle
|
|
238
|
-
};
|
|
239
|
-
watermarkObject._size = getWatermarkSize(watermark, pdfDocument);
|
|
240
226
|
let pages = this.writer.context().pages;
|
|
241
227
|
for (let i = 0, l = pages.length; i < l; i++) {
|
|
242
|
-
|
|
228
|
+
let pageWatermark = watermark;
|
|
229
|
+
if (pages[i].customProperties['watermark'] || pages[i].customProperties['watermark'] === null) {
|
|
230
|
+
pageWatermark = pages[i].customProperties['watermark'];
|
|
231
|
+
}
|
|
232
|
+
if (pageWatermark === undefined || pageWatermark === null) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if ((0, _variableType.isString)(pageWatermark)) {
|
|
236
|
+
pageWatermark = {
|
|
237
|
+
'text': pageWatermark
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
if (!pageWatermark.text) {
|
|
241
|
+
// empty watermark text
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
pages[i].watermark = getWatermarkObject({
|
|
245
|
+
...pageWatermark
|
|
246
|
+
}, pages[i].pageSize, pdfDocument, defaultStyle);
|
|
247
|
+
}
|
|
248
|
+
function getWatermarkObject(watermark, pageSize, pdfDocument, defaultStyle) {
|
|
249
|
+
watermark.font = watermark.font || defaultStyle.font || 'Roboto';
|
|
250
|
+
watermark.fontSize = watermark.fontSize || 'auto';
|
|
251
|
+
watermark.color = watermark.color || 'black';
|
|
252
|
+
watermark.opacity = (0, _variableType.isNumber)(watermark.opacity) ? watermark.opacity : 0.6;
|
|
253
|
+
watermark.bold = watermark.bold || false;
|
|
254
|
+
watermark.italics = watermark.italics || false;
|
|
255
|
+
watermark.angle = (0, _variableType.isValue)(watermark.angle) ? watermark.angle : null;
|
|
256
|
+
if (watermark.angle === null) {
|
|
257
|
+
watermark.angle = Math.atan2(pageSize.height, pageSize.width) * -180 / Math.PI;
|
|
258
|
+
}
|
|
259
|
+
if (watermark.fontSize === 'auto') {
|
|
260
|
+
watermark.fontSize = getWatermarkFontSize(pageSize, watermark, pdfDocument);
|
|
261
|
+
}
|
|
262
|
+
let watermarkObject = {
|
|
263
|
+
text: watermark.text,
|
|
264
|
+
font: pdfDocument.provideFont(watermark.font, watermark.bold, watermark.italics),
|
|
265
|
+
fontSize: watermark.fontSize,
|
|
266
|
+
color: watermark.color,
|
|
267
|
+
opacity: watermark.opacity,
|
|
268
|
+
angle: watermark.angle
|
|
269
|
+
};
|
|
270
|
+
watermarkObject._size = getWatermarkSize(watermark, pdfDocument);
|
|
271
|
+
return watermarkObject;
|
|
243
272
|
}
|
|
244
273
|
function getWatermarkSize(watermark, pdfDocument) {
|
|
245
274
|
let textInlines = new _TextInlines.default(pdfDocument);
|
|
@@ -316,14 +345,50 @@ class LayoutBuilder {
|
|
|
316
345
|
this.writer.moveToNextPage(node.pageOrientation);
|
|
317
346
|
}
|
|
318
347
|
}
|
|
319
|
-
|
|
320
|
-
|
|
348
|
+
const isDetachedBlock = node.relativePosition || node.absolutePosition;
|
|
349
|
+
|
|
350
|
+
// Detached nodes have no margins, their position is only determined by 'x' and 'y'
|
|
351
|
+
if (margin && !isDetachedBlock) {
|
|
352
|
+
const availableHeight = this.writer.context().availableHeight;
|
|
353
|
+
// If top margin is bigger than available space, move to next page
|
|
354
|
+
// Necessary for nodes inside tables
|
|
355
|
+
if (availableHeight - margin[1] < 0) {
|
|
356
|
+
// Consume the whole available space
|
|
357
|
+
this.writer.context().moveDown(availableHeight);
|
|
358
|
+
this.writer.moveToNextPage(node.pageOrientation);
|
|
359
|
+
/**
|
|
360
|
+
* TODO - Something to consider:
|
|
361
|
+
* Right now the node starts at the top of next page (after header)
|
|
362
|
+
* Another option would be to apply just the top margin that has not been consumed in the page before
|
|
363
|
+
* It would something like: this.write.context().moveDown(margin[1] - availableHeight)
|
|
364
|
+
*/
|
|
365
|
+
} else {
|
|
366
|
+
this.writer.context().moveDown(margin[1]);
|
|
367
|
+
}
|
|
368
|
+
// Apply lateral margins
|
|
321
369
|
this.writer.context().addMargin(margin[0], margin[2]);
|
|
322
370
|
}
|
|
323
371
|
callback();
|
|
324
|
-
|
|
372
|
+
|
|
373
|
+
// Detached nodes have no margins, their position is only determined by 'x' and 'y'
|
|
374
|
+
if (margin && !isDetachedBlock) {
|
|
375
|
+
const availableHeight = this.writer.context().availableHeight;
|
|
376
|
+
// If bottom margin is bigger than available space, move to next page
|
|
377
|
+
// Necessary for nodes inside tables
|
|
378
|
+
if (availableHeight - margin[3] < 0) {
|
|
379
|
+
this.writer.context().moveDown(availableHeight);
|
|
380
|
+
this.writer.moveToNextPage(node.pageOrientation);
|
|
381
|
+
/**
|
|
382
|
+
* TODO - Something to consider:
|
|
383
|
+
* Right now next node starts at the top of next page (after header)
|
|
384
|
+
* Another option would be to apply the bottom margin that has not been consumed in the next page?
|
|
385
|
+
* It would something like: this.write.context().moveDown(margin[3] - availableHeight)
|
|
386
|
+
*/
|
|
387
|
+
} else {
|
|
388
|
+
this.writer.context().moveDown(margin[3]);
|
|
389
|
+
}
|
|
390
|
+
// Apply lateral margins
|
|
325
391
|
this.writer.context().addMargin(-margin[0], -margin[2]);
|
|
326
|
-
this.writer.context().moveDown(margin[3]);
|
|
327
392
|
}
|
|
328
393
|
if (node.pageBreak === 'after') {
|
|
329
394
|
this.writer.moveToNextPage(node.pageOrientation);
|
|
@@ -358,6 +423,8 @@ class LayoutBuilder {
|
|
|
358
423
|
}
|
|
359
424
|
if (node.stack) {
|
|
360
425
|
this.processVerticalContainer(node);
|
|
426
|
+
} else if (node.section) {
|
|
427
|
+
this.processSection(node);
|
|
361
428
|
} else if (node.columns) {
|
|
362
429
|
this.processColumns(node);
|
|
363
430
|
} else if (node.ul) {
|
|
@@ -402,8 +469,65 @@ class LayoutBuilder {
|
|
|
402
469
|
}, this);
|
|
403
470
|
}
|
|
404
471
|
|
|
472
|
+
// section
|
|
473
|
+
processSection(sectionNode) {
|
|
474
|
+
// TODO: properties
|
|
475
|
+
|
|
476
|
+
let page = this.writer.context().getCurrentPage();
|
|
477
|
+
if (!page || page && page.items.length) {
|
|
478
|
+
// move to new empty page
|
|
479
|
+
// page definition inherit from current page
|
|
480
|
+
if (sectionNode.pageSize === 'inherit') {
|
|
481
|
+
sectionNode.pageSize = page ? {
|
|
482
|
+
width: page.pageSize.width,
|
|
483
|
+
height: page.pageSize.height
|
|
484
|
+
} : undefined;
|
|
485
|
+
}
|
|
486
|
+
if (sectionNode.pageOrientation === 'inherit') {
|
|
487
|
+
sectionNode.pageOrientation = page ? page.pageSize.orientation : undefined;
|
|
488
|
+
}
|
|
489
|
+
if (sectionNode.pageMargins === 'inherit') {
|
|
490
|
+
sectionNode.pageMargins = page ? page.pageMargins : undefined;
|
|
491
|
+
}
|
|
492
|
+
if (sectionNode.header === 'inherit') {
|
|
493
|
+
sectionNode.header = page ? page.customProperties.header : undefined;
|
|
494
|
+
}
|
|
495
|
+
if (sectionNode.footer === 'inherit') {
|
|
496
|
+
sectionNode.footer = page ? page.customProperties.footer : undefined;
|
|
497
|
+
}
|
|
498
|
+
if (sectionNode.background === 'inherit') {
|
|
499
|
+
sectionNode.background = page ? page.customProperties.background : undefined;
|
|
500
|
+
}
|
|
501
|
+
if (sectionNode.watermark === 'inherit') {
|
|
502
|
+
sectionNode.watermark = page ? page.customProperties.watermark : undefined;
|
|
503
|
+
}
|
|
504
|
+
if (sectionNode.header && typeof sectionNode.header !== 'function' && sectionNode.header !== null) {
|
|
505
|
+
sectionNode.header = (0, _tools.convertToDynamicContent)(sectionNode.header);
|
|
506
|
+
}
|
|
507
|
+
if (sectionNode.footer && typeof sectionNode.footer !== 'function' && sectionNode.footer !== null) {
|
|
508
|
+
sectionNode.footer = (0, _tools.convertToDynamicContent)(sectionNode.footer);
|
|
509
|
+
}
|
|
510
|
+
let customProperties = {};
|
|
511
|
+
if (typeof sectionNode.header !== 'undefined') {
|
|
512
|
+
customProperties.header = sectionNode.header;
|
|
513
|
+
}
|
|
514
|
+
if (typeof sectionNode.footer !== 'undefined') {
|
|
515
|
+
customProperties.footer = sectionNode.footer;
|
|
516
|
+
}
|
|
517
|
+
if (typeof sectionNode.background !== 'undefined') {
|
|
518
|
+
customProperties.background = sectionNode.background;
|
|
519
|
+
}
|
|
520
|
+
if (typeof sectionNode.watermark !== 'undefined') {
|
|
521
|
+
customProperties.watermark = sectionNode.watermark;
|
|
522
|
+
}
|
|
523
|
+
this.writer.addPage(sectionNode.pageSize || this.pageSize, sectionNode.pageOrientation, sectionNode.pageMargins || this.pageMargins, customProperties);
|
|
524
|
+
}
|
|
525
|
+
this.processNode(sectionNode.section);
|
|
526
|
+
}
|
|
527
|
+
|
|
405
528
|
// columns
|
|
406
529
|
processColumns(columnNode) {
|
|
530
|
+
this.nestedLevel++;
|
|
407
531
|
let columns = columnNode.columns;
|
|
408
532
|
let availableWidth = this.writer.context().availableWidth;
|
|
409
533
|
let gaps = gapArray(columnNode._gap);
|
|
@@ -411,8 +535,17 @@ class LayoutBuilder {
|
|
|
411
535
|
availableWidth -= (gaps.length - 1) * columnNode._gap;
|
|
412
536
|
}
|
|
413
537
|
_columnCalculator.default.buildColumnWidths(columns, availableWidth);
|
|
414
|
-
let result = this.processRow(
|
|
538
|
+
let result = this.processRow({
|
|
539
|
+
marginX: columnNode._margin ? [columnNode._margin[0], columnNode._margin[2]] : [0, 0],
|
|
540
|
+
cells: columns,
|
|
541
|
+
widths: columns,
|
|
542
|
+
gaps
|
|
543
|
+
});
|
|
415
544
|
addAll(columnNode.positions, result.positions);
|
|
545
|
+
this.nestedLevel--;
|
|
546
|
+
if (this.nestedLevel === 0) {
|
|
547
|
+
this.writer.context().resetMarginXTopParent();
|
|
548
|
+
}
|
|
416
549
|
function gapArray(gap) {
|
|
417
550
|
if (!gap) {
|
|
418
551
|
return null;
|
|
@@ -425,7 +558,16 @@ class LayoutBuilder {
|
|
|
425
558
|
return gaps;
|
|
426
559
|
}
|
|
427
560
|
}
|
|
428
|
-
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Searches for a cell in the same row that starts a rowspan and is positioned immediately before the current cell.
|
|
564
|
+
* Alternatively, it finds a cell where the colspan initiating the rowspan extends to the cell just before the current one.
|
|
565
|
+
*
|
|
566
|
+
* @param {Array<object>} arr - An array representing cells in a row.
|
|
567
|
+
* @param {number} i - The index of the current cell to search backward from.
|
|
568
|
+
* @returns {object|null} The starting cell of the rowspan if found; otherwise, `null`.
|
|
569
|
+
*/
|
|
570
|
+
_findStartingRowSpanCell(arr, i) {
|
|
429
571
|
let requiredColspan = 1;
|
|
430
572
|
for (let index = i - 1; index >= 0; index--) {
|
|
431
573
|
if (!arr[index]._span) {
|
|
@@ -439,68 +581,240 @@ class LayoutBuilder {
|
|
|
439
581
|
}
|
|
440
582
|
return null;
|
|
441
583
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Retrieves a page break description for a specified page from a list of page breaks.
|
|
587
|
+
*
|
|
588
|
+
* @param {Array<object>} pageBreaks - An array of page break descriptions, each containing `prevPage` properties.
|
|
589
|
+
* @param {number} page - The page number to find the associated page break for.
|
|
590
|
+
* @returns {object|undefined} The page break description object for the specified page if found; otherwise, `undefined`.
|
|
591
|
+
*/
|
|
592
|
+
_getPageBreak(pageBreaks, page) {
|
|
593
|
+
return pageBreaks.find(desc => desc.prevPage === page);
|
|
594
|
+
}
|
|
595
|
+
_getPageBreakListBySpan(tableNode, page, rowIndex) {
|
|
596
|
+
if (!tableNode || !tableNode._breaksBySpan) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
const breaksList = tableNode._breaksBySpan.filter(desc => desc.prevPage === page && rowIndex <= desc.rowIndexOfSpanEnd);
|
|
600
|
+
let y = Number.MAX_VALUE,
|
|
601
|
+
prevY = Number.MIN_VALUE;
|
|
602
|
+
breaksList.forEach(b => {
|
|
603
|
+
prevY = Math.max(b.prevY, prevY);
|
|
604
|
+
y = Math.min(b.y, y);
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
prevPage: page,
|
|
608
|
+
prevY: prevY,
|
|
609
|
+
y: y
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
_findSameRowPageBreakByRowSpanData(breaksBySpan, page, rowIndex) {
|
|
613
|
+
if (!breaksBySpan) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
return breaksBySpan.find(desc => desc.prevPage === page && rowIndex === desc.rowIndexOfSpanEnd);
|
|
617
|
+
}
|
|
618
|
+
_updatePageBreaksData(pageBreaks, tableNode, rowIndex) {
|
|
619
|
+
Object.keys(tableNode._bottomByPage).forEach(p => {
|
|
620
|
+
const page = Number(p);
|
|
621
|
+
const pageBreak = this._getPageBreak(pageBreaks, page);
|
|
622
|
+
if (pageBreak) {
|
|
623
|
+
pageBreak.prevY = Math.max(pageBreak.prevY, tableNode._bottomByPage[page]);
|
|
624
|
+
}
|
|
625
|
+
if (tableNode._breaksBySpan && tableNode._breaksBySpan.length > 0) {
|
|
626
|
+
const breaksBySpanList = tableNode._breaksBySpan.filter(pb => pb.prevPage === page && rowIndex <= pb.rowIndexOfSpanEnd);
|
|
627
|
+
if (breaksBySpanList && breaksBySpanList.length > 0) {
|
|
628
|
+
breaksBySpanList.forEach(b => {
|
|
629
|
+
b.prevY = Math.max(b.prevY, tableNode._bottomByPage[page]);
|
|
630
|
+
});
|
|
450
631
|
}
|
|
451
632
|
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Resolves the Y-coordinates for a target object by comparing two break points.
|
|
638
|
+
*
|
|
639
|
+
* @param {object} break1 - The first break point with `prevY` and `y` properties.
|
|
640
|
+
* @param {object} break2 - The second break point with `prevY` and `y` properties.
|
|
641
|
+
* @param {object} target - The target object to be updated with resolved Y-coordinates.
|
|
642
|
+
* @property {number} target.prevY - Updated to the maximum `prevY` value between `break1` and `break2`.
|
|
643
|
+
* @property {number} target.y - Updated to the minimum `y` value between `break1` and `break2`.
|
|
644
|
+
*/
|
|
645
|
+
_resolveBreakY(break1, break2, target) {
|
|
646
|
+
target.prevY = Math.max(break1.prevY, break2.prevY);
|
|
647
|
+
target.y = Math.min(break1.y, break2.y);
|
|
648
|
+
}
|
|
649
|
+
_storePageBreakData(data, startsRowSpan, pageBreaks, tableNode) {
|
|
650
|
+
if (!startsRowSpan) {
|
|
651
|
+
let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
|
|
652
|
+
let pageDescBySpan = this._getPageBreakListBySpan(tableNode, data.prevPage, data.rowIndex);
|
|
452
653
|
if (!pageDesc) {
|
|
453
|
-
pageDesc =
|
|
654
|
+
pageDesc = {
|
|
655
|
+
...data
|
|
656
|
+
};
|
|
454
657
|
pageBreaks.push(pageDesc);
|
|
455
658
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
659
|
+
if (pageDescBySpan) {
|
|
660
|
+
this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
|
|
661
|
+
}
|
|
662
|
+
this._resolveBreakY(pageDesc, data, pageDesc);
|
|
663
|
+
} else {
|
|
664
|
+
const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
|
|
665
|
+
let pageDescBySpan = this._findSameRowPageBreakByRowSpanData(breaksBySpan, data.prevPage, data.rowIndex);
|
|
666
|
+
if (!pageDescBySpan) {
|
|
667
|
+
pageDescBySpan = {
|
|
668
|
+
...data,
|
|
669
|
+
rowIndexOfSpanEnd: data.rowIndex + data.rowSpan - 1
|
|
670
|
+
};
|
|
671
|
+
if (!tableNode._breaksBySpan) {
|
|
672
|
+
tableNode._breaksBySpan = [];
|
|
673
|
+
}
|
|
674
|
+
tableNode._breaksBySpan.push(pageDescBySpan);
|
|
675
|
+
}
|
|
676
|
+
pageDescBySpan.prevY = Math.max(pageDescBySpan.prevY, data.prevY);
|
|
677
|
+
pageDescBySpan.y = Math.min(pageDescBySpan.y, data.y);
|
|
678
|
+
let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
|
|
679
|
+
if (pageDesc) {
|
|
680
|
+
this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Calculates the left offset for a column based on the specified gap values.
|
|
686
|
+
*
|
|
687
|
+
* @param {number} i - The index of the column for which the offset is being calculated.
|
|
688
|
+
* @param {Array<number>} gaps - An array of gap values for each column.
|
|
689
|
+
* @returns {number} The left offset for the column. Returns `gaps[i]` if it exists, otherwise `0`.
|
|
690
|
+
*/
|
|
691
|
+
_colLeftOffset(i, gaps) {
|
|
692
|
+
if (gaps && gaps.length > i) {
|
|
693
|
+
return gaps[i];
|
|
694
|
+
}
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Retrieves the ending cell for a row span in case it exists in a specified table column.
|
|
700
|
+
*
|
|
701
|
+
* @param {Array<Array<object>>} tableBody - The table body, represented as a 2D array of cell objects.
|
|
702
|
+
* @param {number} rowIndex - The index of the starting row for the row span.
|
|
703
|
+
* @param {object} column - The column object containing row span information.
|
|
704
|
+
* @param {number} columnIndex - The index of the column within the row.
|
|
705
|
+
* @returns {object|null} The cell at the end of the row span if it exists; otherwise, `null`.
|
|
706
|
+
* @throws {Error} If the row span extends beyond the total row count.
|
|
707
|
+
*/
|
|
708
|
+
_getRowSpanEndingCell(tableBody, rowIndex, column, columnIndex) {
|
|
709
|
+
if (column.rowSpan && column.rowSpan > 1) {
|
|
710
|
+
let endingRow = rowIndex + column.rowSpan - 1;
|
|
711
|
+
if (endingRow >= tableBody.length) {
|
|
712
|
+
throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
|
|
713
|
+
}
|
|
714
|
+
return tableBody[endingRow][columnIndex];
|
|
715
|
+
}
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
processRow({
|
|
719
|
+
marginX = [0, 0],
|
|
720
|
+
dontBreakRows = false,
|
|
721
|
+
rowsWithoutPageBreak = 0,
|
|
722
|
+
cells,
|
|
723
|
+
widths,
|
|
724
|
+
gaps,
|
|
725
|
+
tableNode,
|
|
726
|
+
tableBody,
|
|
727
|
+
rowIndex,
|
|
728
|
+
height
|
|
729
|
+
}) {
|
|
730
|
+
const isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
|
|
459
731
|
let pageBreaks = [];
|
|
732
|
+
let pageBreaksByRowSpan = [];
|
|
460
733
|
let positions = [];
|
|
461
|
-
|
|
462
|
-
widths = widths ||
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
734
|
+
let willBreakByHeight = false;
|
|
735
|
+
widths = widths || cells;
|
|
736
|
+
|
|
737
|
+
// Check if row should break by height
|
|
738
|
+
if (!isUnbreakableRow && height > this.writer.context().availableHeight) {
|
|
739
|
+
willBreakByHeight = true;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Use the marginX if we are in a top level table/column (not nested)
|
|
743
|
+
const marginXParent = this.nestedLevel === 1 ? marginX : null;
|
|
744
|
+
const _bottomByPage = tableNode ? tableNode._bottomByPage : null;
|
|
745
|
+
this.writer.context().beginColumnGroup(marginXParent, _bottomByPage);
|
|
746
|
+
for (let i = 0, l = cells.length; i < l; i++) {
|
|
747
|
+
let cell = cells[i];
|
|
748
|
+
|
|
749
|
+
// Page change handler
|
|
750
|
+
const storePageBreakClosure = data => {
|
|
751
|
+
const startsRowSpan = cell.rowSpan && cell.rowSpan > 1;
|
|
752
|
+
if (startsRowSpan) {
|
|
753
|
+
data.rowSpan = cell.rowSpan;
|
|
754
|
+
}
|
|
755
|
+
data.rowIndex = rowIndex;
|
|
756
|
+
this._storePageBreakData(data, startsRowSpan, pageBreaks, tableNode);
|
|
757
|
+
};
|
|
758
|
+
this.writer.addListener('pageChanged', storePageBreakClosure);
|
|
466
759
|
let width = widths[i]._calcWidth;
|
|
467
|
-
let leftOffset =
|
|
468
|
-
if
|
|
469
|
-
|
|
760
|
+
let leftOffset = this._colLeftOffset(i, gaps);
|
|
761
|
+
// Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
|
|
762
|
+
let startingSpanCell = this._findStartingRowSpanCell(cells, i);
|
|
763
|
+
if (cell.colSpan && cell.colSpan > 1) {
|
|
764
|
+
for (let j = 1; j < cell.colSpan; j++) {
|
|
470
765
|
width += widths[++i]._calcWidth + gaps[i];
|
|
471
766
|
}
|
|
472
767
|
}
|
|
473
768
|
|
|
474
769
|
// if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
770
|
+
const rowSpanEndingCell = this._getRowSpanEndingCell(tableBody, rowIndex, cell, i);
|
|
771
|
+
if (rowSpanEndingCell) {
|
|
477
772
|
// We store a reference of the ending cell in the first cell of the rowspan
|
|
478
|
-
|
|
773
|
+
cell._endingCell = rowSpanEndingCell;
|
|
774
|
+
cell._endingCell._startingRowSpanY = cell._startingRowSpanY;
|
|
479
775
|
}
|
|
480
776
|
|
|
481
|
-
//
|
|
482
|
-
let
|
|
483
|
-
let endingSpanCell = null;
|
|
777
|
+
// If we are after a cell that started a rowspan
|
|
778
|
+
let endOfRowSpanCell = null;
|
|
484
779
|
if (startingSpanCell && startingSpanCell._endingCell) {
|
|
485
780
|
// Reference to the last cell of the rowspan
|
|
486
|
-
|
|
781
|
+
endOfRowSpanCell = startingSpanCell._endingCell;
|
|
782
|
+
// Store if we are in an unbreakable block when we save the context and the originalX
|
|
783
|
+
if (this.writer.transactionLevel > 0) {
|
|
784
|
+
endOfRowSpanCell._isUnbreakableContext = true;
|
|
785
|
+
endOfRowSpanCell._originalXOffset = this.writer.originalX;
|
|
786
|
+
}
|
|
487
787
|
}
|
|
488
788
|
|
|
489
789
|
// We pass the endingSpanCell reference to store the context just after processing rowspan cell
|
|
490
|
-
this.writer.context().beginColumn(width, leftOffset,
|
|
491
|
-
if (!
|
|
492
|
-
this.processNode(
|
|
493
|
-
|
|
494
|
-
|
|
790
|
+
this.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell);
|
|
791
|
+
if (!cell._span) {
|
|
792
|
+
this.processNode(cell);
|
|
793
|
+
this.writer.context().updateBottomByPage();
|
|
794
|
+
addAll(positions, cell.positions);
|
|
795
|
+
} else if (cell._columnEndingContext) {
|
|
796
|
+
let discountY = 0;
|
|
797
|
+
if (dontBreakRows) {
|
|
798
|
+
// Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
|
|
799
|
+
const ctxBeforeRowSpanLastRow = this.writer.contextStack[this.writer.contextStack.length - 1];
|
|
800
|
+
discountY = ctxBeforeRowSpanLastRow.y - cell._startingRowSpanY;
|
|
801
|
+
}
|
|
802
|
+
let originalXOffset = 0;
|
|
803
|
+
// If context was saved from an unbreakable block and we are not in an unbreakable block anymore
|
|
804
|
+
// We have to sum the originalX (X before starting unbreakable block) to X
|
|
805
|
+
if (cell._isUnbreakableContext && !this.writer.transactionLevel) {
|
|
806
|
+
originalXOffset = cell._originalXOffset;
|
|
807
|
+
}
|
|
495
808
|
// row-span ending
|
|
496
809
|
// Recover the context after processing the rowspanned cell
|
|
497
|
-
this.writer.context().markEnding(
|
|
810
|
+
this.writer.context().markEnding(cell, originalXOffset, discountY);
|
|
498
811
|
}
|
|
812
|
+
this.writer.removeListener('pageChanged', storePageBreakClosure);
|
|
499
813
|
}
|
|
500
814
|
|
|
501
815
|
// Check if last cell is part of a span
|
|
502
816
|
let endingSpanCell = null;
|
|
503
|
-
const lastColumn =
|
|
817
|
+
const lastColumn = cells.length > 0 ? cells[cells.length - 1] : null;
|
|
504
818
|
if (lastColumn) {
|
|
505
819
|
// Previous column cell has a rowspan
|
|
506
820
|
if (lastColumn._endingCell) {
|
|
@@ -508,35 +822,35 @@ class LayoutBuilder {
|
|
|
508
822
|
// Previous column cell is part of a span
|
|
509
823
|
} else if (lastColumn._span === true) {
|
|
510
824
|
// We get the cell that started the span where we set a reference to the ending cell
|
|
511
|
-
const startingSpanCell = this.
|
|
825
|
+
const startingSpanCell = this._findStartingRowSpanCell(cells, cells.length);
|
|
512
826
|
if (startingSpanCell) {
|
|
513
827
|
// Context will be stored here (ending cell)
|
|
514
828
|
endingSpanCell = startingSpanCell._endingCell;
|
|
829
|
+
// Store if we are in an unbreakable block when we save the context and the originalX
|
|
830
|
+
if (this.writer.transactionLevel > 0) {
|
|
831
|
+
endingSpanCell._isUnbreakableContext = true;
|
|
832
|
+
endingSpanCell._originalXOffset = this.writer.originalX;
|
|
833
|
+
}
|
|
515
834
|
}
|
|
516
835
|
}
|
|
517
836
|
}
|
|
518
|
-
|
|
519
|
-
|
|
837
|
+
|
|
838
|
+
// If content did not break page, check if we should break by height
|
|
839
|
+
if (willBreakByHeight && !isUnbreakableRow && pageBreaks.length === 0) {
|
|
840
|
+
this.writer.context().moveDown(this.writer.context().availableHeight);
|
|
841
|
+
this.writer.moveToNextPage();
|
|
842
|
+
}
|
|
843
|
+
const bottomByPage = this.writer.context().completeColumnGroup(height, endingSpanCell);
|
|
844
|
+
if (tableNode) {
|
|
845
|
+
tableNode._bottomByPage = bottomByPage;
|
|
846
|
+
// If there are page breaks in this row, update data with prevY of last cell
|
|
847
|
+
this._updatePageBreaksData(pageBreaks, tableNode, rowIndex);
|
|
848
|
+
}
|
|
520
849
|
return {
|
|
850
|
+
pageBreaksBySpan: pageBreaksByRowSpan,
|
|
521
851
|
pageBreaks: pageBreaks,
|
|
522
852
|
positions: positions
|
|
523
853
|
};
|
|
524
|
-
function colLeftOffset(i) {
|
|
525
|
-
if (gaps && gaps.length > i) {
|
|
526
|
-
return gaps[i];
|
|
527
|
-
}
|
|
528
|
-
return 0;
|
|
529
|
-
}
|
|
530
|
-
function getEndingCell(column, columnIndex) {
|
|
531
|
-
if (column.rowSpan && column.rowSpan > 1) {
|
|
532
|
-
let endingRow = tableRow + column.rowSpan - 1;
|
|
533
|
-
if (endingRow >= tableBody.length) {
|
|
534
|
-
throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
|
|
535
|
-
}
|
|
536
|
-
return tableBody[endingRow][columnIndex];
|
|
537
|
-
}
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
854
|
}
|
|
541
855
|
|
|
542
856
|
// lists
|
|
@@ -576,10 +890,20 @@ class LayoutBuilder {
|
|
|
576
890
|
|
|
577
891
|
// tables
|
|
578
892
|
processTable(tableNode) {
|
|
893
|
+
this.nestedLevel++;
|
|
579
894
|
let processor = new _TableProcessor.default(tableNode);
|
|
580
895
|
processor.beginTable(this.writer);
|
|
581
896
|
let rowHeights = tableNode.table.heights;
|
|
582
897
|
for (let i = 0, l = tableNode.table.body.length; i < l; i++) {
|
|
898
|
+
// if dontBreakRows and row starts a rowspan
|
|
899
|
+
// we store the 'y' of the beginning of each rowSpan
|
|
900
|
+
if (processor.dontBreakRows) {
|
|
901
|
+
tableNode.table.body[i].forEach(cell => {
|
|
902
|
+
if (cell.rowSpan && cell.rowSpan > 1) {
|
|
903
|
+
cell._startingRowSpanY = this.writer.context().y;
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
583
907
|
processor.beginRow(i, this.writer);
|
|
584
908
|
let height;
|
|
585
909
|
if (typeof rowHeights === 'function') {
|
|
@@ -592,11 +916,35 @@ class LayoutBuilder {
|
|
|
592
916
|
if (height === 'auto') {
|
|
593
917
|
height = undefined;
|
|
594
918
|
}
|
|
595
|
-
|
|
919
|
+
const pageBeforeProcessing = this.writer.context().page;
|
|
920
|
+
let result = this.processRow({
|
|
921
|
+
marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
|
|
922
|
+
dontBreakRows: processor.dontBreakRows,
|
|
923
|
+
rowsWithoutPageBreak: processor.rowsWithoutPageBreak,
|
|
924
|
+
cells: tableNode.table.body[i],
|
|
925
|
+
widths: tableNode.table.widths,
|
|
926
|
+
gaps: tableNode._offsets.offsets,
|
|
927
|
+
tableBody: tableNode.table.body,
|
|
928
|
+
tableNode,
|
|
929
|
+
rowIndex: i,
|
|
930
|
+
height
|
|
931
|
+
});
|
|
596
932
|
addAll(tableNode.positions, result.positions);
|
|
933
|
+
if (!result.pageBreaks || result.pageBreaks.length === 0) {
|
|
934
|
+
const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
|
|
935
|
+
const breakBySpanData = this._findSameRowPageBreakByRowSpanData(breaksBySpan, pageBeforeProcessing, i);
|
|
936
|
+
if (breakBySpanData) {
|
|
937
|
+
const finalBreakBySpanData = this._getPageBreakListBySpan(tableNode, breakBySpanData.prevPage, i);
|
|
938
|
+
result.pageBreaks.push(finalBreakBySpanData);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
597
941
|
processor.endRow(i, this.writer, result.pageBreaks);
|
|
598
942
|
}
|
|
599
943
|
processor.endTable(this.writer);
|
|
944
|
+
this.nestedLevel--;
|
|
945
|
+
if (this.nestedLevel === 0) {
|
|
946
|
+
this.writer.context().resetMarginXTopParent();
|
|
947
|
+
}
|
|
600
948
|
}
|
|
601
949
|
|
|
602
950
|
// leafs (texts)
|
|
@@ -654,6 +1002,23 @@ class LayoutBuilder {
|
|
|
654
1002
|
}
|
|
655
1003
|
return newInline;
|
|
656
1004
|
}
|
|
1005
|
+
function findMaxFitLength(text, maxWidth, measureFn) {
|
|
1006
|
+
let low = 1;
|
|
1007
|
+
let high = text.length;
|
|
1008
|
+
let bestFit = 1;
|
|
1009
|
+
while (low <= high) {
|
|
1010
|
+
const mid = Math.floor((low + high) / 2);
|
|
1011
|
+
const part = text.substring(0, mid);
|
|
1012
|
+
const width = measureFn(part);
|
|
1013
|
+
if (width <= maxWidth) {
|
|
1014
|
+
bestFit = mid;
|
|
1015
|
+
low = mid + 1;
|
|
1016
|
+
} else {
|
|
1017
|
+
high = mid - 1;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return bestFit;
|
|
1021
|
+
}
|
|
657
1022
|
if (!textNode._inlines || textNode._inlines.length === 0) {
|
|
658
1023
|
return null;
|
|
659
1024
|
}
|
|
@@ -665,11 +1030,7 @@ class LayoutBuilder {
|
|
|
665
1030
|
let inline = textNode._inlines.shift();
|
|
666
1031
|
isForceContinue = false;
|
|
667
1032
|
if (!inline.noWrap && inline.text.length > 1 && inline.width > line.getAvailableWidth()) {
|
|
668
|
-
let
|
|
669
|
-
let maxChars = Math.floor(line.getAvailableWidth() / widthPerChar);
|
|
670
|
-
if (maxChars < 1) {
|
|
671
|
-
maxChars = 1;
|
|
672
|
-
}
|
|
1033
|
+
let maxChars = findMaxFitLength(inline.text, line.getAvailableWidth(), txt => textInlines.widthOfText(txt, inline));
|
|
673
1034
|
if (maxChars < inline.text.length) {
|
|
674
1035
|
let newInline = cloneInline(inline);
|
|
675
1036
|
newInline.text = inline.text.substr(maxChars);
|