@flowaccount/pdfmake 0.2.20-staging.2

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.
@@ -0,0 +1,1537 @@
1
+ 'use strict';
2
+
3
+ var cloneDeep = require('lodash/cloneDeep');
4
+ var TraversalTracker = require('./traversalTracker');
5
+ var DocPreprocessor = require('./docPreprocessor');
6
+ var DocMeasure = require('./docMeasure');
7
+ var DocumentContext = require('./documentContext');
8
+ var PageElementWriter = require('./pageElementWriter');
9
+ var ColumnCalculator = require('./columnCalculator');
10
+ var TableProcessor = require('./tableProcessor');
11
+ var Line = require('./line');
12
+ var isString = require('./helpers').isString;
13
+ var isArray = require('./helpers').isArray;
14
+ var isUndefined = require('./helpers').isUndefined;
15
+ var isNull = require('./helpers').isNull;
16
+ var pack = require('./helpers').pack;
17
+ var offsetVector = require('./helpers').offsetVector;
18
+ var fontStringify = require('./helpers').fontStringify;
19
+ var getNodeId = require('./helpers').getNodeId;
20
+ var isFunction = require('./helpers').isFunction;
21
+ var TextTools = require('./textTools');
22
+ var StyleContextStack = require('./styleContextStack');
23
+ var isNumber = require('./helpers').isNumber;
24
+
25
+ var footerBreak = false;
26
+ var testTracker;
27
+ var testWriter;
28
+ var testVerticalAlignStack = [];
29
+ var testResult = false;
30
+ var currentLayoutBuilder;
31
+
32
+ function addAll(target, otherArray) {
33
+ if (!isArray(target) || !isArray(otherArray) || otherArray.length === 0) {
34
+ return;
35
+ }
36
+
37
+ otherArray.forEach(function (item) {
38
+ target.push(item);
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Creates an instance of LayoutBuilder - layout engine which turns document-definition-object
44
+ * into a set of pages, lines, inlines and vectors ready to be rendered into a PDF
45
+ *
46
+ * @param {Object} pageSize - an object defining page width and height
47
+ * @param {Object} pageMargins - an object defining top, left, right and bottom margins
48
+ */
49
+ function LayoutBuilder(pageSize, pageMargins, imageMeasure, svgMeasure) {
50
+ this.pageSize = pageSize;
51
+ this.pageMargins = pageMargins;
52
+ this.tracker = new TraversalTracker();
53
+ this.imageMeasure = imageMeasure;
54
+ this.svgMeasure = svgMeasure;
55
+ this.tableLayouts = {};
56
+ this.nestedLevel = 0;
57
+ this.verticalAlignItemStack = [];
58
+ this.heightHeaderAndFooter = {};
59
+
60
+ this._footerColumnGuides = null;
61
+ this._footerGapOption = null;
62
+ }
63
+
64
+ LayoutBuilder.prototype.registerTableLayouts = function (tableLayouts) {
65
+ this.tableLayouts = pack(this.tableLayouts, tableLayouts);
66
+ };
67
+
68
+ /**
69
+ * Executes layout engine on document-definition-object and creates an array of pages
70
+ * containing positioned Blocks, Lines and inlines
71
+ *
72
+ * @param {Object} docStructure document-definition-object
73
+ * @param {Object} fontProvider font provider
74
+ * @param {Object} styleDictionary dictionary with style definitions
75
+ * @param {Object} defaultStyle default style definition
76
+ * @return {Array} an array of pages
77
+ */
78
+ LayoutBuilder.prototype.layoutDocument = function (docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark, pageBreakBeforeFct) {
79
+
80
+ function addPageBreaksIfNecessary(linearNodeList, pages) {
81
+
82
+ if (!isFunction(pageBreakBeforeFct)) {
83
+ return false;
84
+ }
85
+
86
+ linearNodeList = linearNodeList.filter(function (node) {
87
+ return node.positions.length > 0;
88
+ });
89
+
90
+ linearNodeList.forEach(function (node) {
91
+ var nodeInfo = {};
92
+ [
93
+ 'id', 'text', 'ul', 'ol', 'table', 'image', 'qr', 'canvas', 'svg', 'columns', 'layers',
94
+ 'headlineLevel', 'style', 'pageBreak', 'pageOrientation',
95
+ 'width', 'height'
96
+ ].forEach(function (key) {
97
+ if (node[key] !== undefined) {
98
+ nodeInfo[key] = node[key];
99
+ }
100
+ });
101
+ nodeInfo.startPosition = node.positions[0];
102
+ nodeInfo.pageNumbers = Array.from(new Set(node.positions.map(function (node) { return node.pageNumber; })));
103
+ nodeInfo.pages = pages.length;
104
+ nodeInfo.stack = isArray(node.stack);
105
+ nodeInfo.layers = isArray(node.layers);
106
+
107
+ node.nodeInfo = nodeInfo;
108
+ });
109
+
110
+ for (var index = 0; index < linearNodeList.length; index++) {
111
+ var node = linearNodeList[index];
112
+ if (node.pageBreak !== 'before' && !node.pageBreakCalculated) {
113
+ node.pageBreakCalculated = true;
114
+ var pageNumber = node.nodeInfo.pageNumbers[0];
115
+ var followingNodesOnPage = [];
116
+ var nodesOnNextPage = [];
117
+ var previousNodesOnPage = [];
118
+ if (pageBreakBeforeFct.length > 1) {
119
+ for (var ii = index + 1, l = linearNodeList.length; ii < l; ii++) {
120
+ if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
121
+ followingNodesOnPage.push(linearNodeList[ii].nodeInfo);
122
+ }
123
+ if (pageBreakBeforeFct.length > 2 && linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber + 1) > -1) {
124
+ nodesOnNextPage.push(linearNodeList[ii].nodeInfo);
125
+ }
126
+ }
127
+ }
128
+ if (pageBreakBeforeFct.length > 3) {
129
+ for (var jj = 0; jj < index; jj++) {
130
+ if (linearNodeList[jj].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
131
+ previousNodesOnPage.push(linearNodeList[jj].nodeInfo);
132
+ }
133
+ }
134
+ }
135
+ if (pageBreakBeforeFct(node.nodeInfo, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage)) {
136
+ node.pageBreak = 'before';
137
+ return true;
138
+ }
139
+ }
140
+ }
141
+
142
+ return false;
143
+ }
144
+
145
+ this.docPreprocessor = new DocPreprocessor();
146
+ this.docMeasure = new DocMeasure(fontProvider, styleDictionary, defaultStyle, this.imageMeasure, this.svgMeasure, this.tableLayouts, images);
147
+
148
+
149
+ function resetXYs(result) {
150
+ result.linearNodeList.forEach(function (node) {
151
+ node.resetXY();
152
+ });
153
+ }
154
+
155
+ var result = this.tryLayoutDocument(docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark);
156
+ while (addPageBreaksIfNecessary(result.linearNodeList, result.pages)) {
157
+ resetXYs(result);
158
+ result = this.tryLayoutDocument(docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark);
159
+ }
160
+
161
+ return result.pages;
162
+ };
163
+
164
+ LayoutBuilder.prototype.tryLayoutDocument = function (docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark) {
165
+ footerBreak = false;
166
+
167
+ this.verticalAlignItemStack = this.verticalAlignItemStack || [];
168
+ this.linearNodeList = [];
169
+ this.writer = new PageElementWriter(
170
+ new DocumentContext(this.pageSize, this.pageMargins, this._footerGapOption), this.tracker);
171
+
172
+ this.heightHeaderAndFooter = this.addHeadersAndFooters(header, footer) || {};
173
+ if (!isUndefined(this.heightHeaderAndFooter.header)) {
174
+ this.pageMargins.top = this.heightHeaderAndFooter.header + 1;
175
+ }
176
+
177
+ if (isArray(docStructure) && docStructure[2] && isArray(docStructure[2]) && docStructure[2][0] && docStructure[2][0].remark) {
178
+ var tableRemark = docStructure[2][0].remark;
179
+ var remarkLabel = docStructure[2][0];
180
+ var remarkDetail = docStructure[2][1] && docStructure[2][1].text;
181
+
182
+ docStructure[2].splice(0, 1);
183
+ if (docStructure[2].length > 0) {
184
+ docStructure[2].splice(0, 1);
185
+ }
186
+
187
+ var labelRow = [];
188
+ var detailRow = [];
189
+
190
+ labelRow.push(remarkLabel);
191
+ detailRow.push({ remarktest: true, text: remarkDetail });
192
+
193
+ tableRemark.table.body.push(labelRow);
194
+ tableRemark.table.body.push(detailRow);
195
+
196
+ tableRemark.table.headerRows = 1;
197
+
198
+ docStructure[2].push(tableRemark);
199
+ }
200
+
201
+ this.linearNodeList = [];
202
+ docStructure = this.docPreprocessor.preprocessDocument(docStructure);
203
+ docStructure = this.docMeasure.measureDocument(docStructure);
204
+
205
+ this.verticalAlignItemStack = [];
206
+ this.writer = new PageElementWriter(
207
+ new DocumentContext(this.pageSize, this.pageMargins, this._footerGapOption), this.tracker);
208
+
209
+ var _this = this;
210
+ this.writer.context().tracker.startTracking('pageAdded', function () {
211
+ _this.addBackground(background);
212
+ });
213
+
214
+ this.addBackground(background);
215
+ this.processNode(docStructure);
216
+ this.addHeadersAndFooters(header, footer,
217
+ (this.heightHeaderAndFooter.header || 0) + 1,
218
+ (this.heightHeaderAndFooter.footer || 0) + 1);
219
+ if (watermark != null) {
220
+ this.addWatermark(watermark, fontProvider, defaultStyle);
221
+ }
222
+
223
+ return { pages: this.writer.context().pages, linearNodeList: this.linearNodeList };
224
+ };
225
+
226
+ LayoutBuilder.prototype.applyFooterGapOption = function(opt) {
227
+ if (opt === true) {
228
+ opt = { enabled: true };
229
+ }
230
+
231
+ if (!opt) return;
232
+
233
+ if (typeof opt !== 'object') {
234
+ this._footerGapOption = { enabled: true };
235
+ return;
236
+ }
237
+
238
+ this._footerGapOption = {
239
+ enabled: opt.enabled !== false,
240
+ columns: opt.columns ? {
241
+ widths: Array.isArray(opt.columns.widths) ? opt.columns.widths.slice() : undefined,
242
+ widthLength: opt.columns.widths.length || 0,
243
+ stops: Array.isArray(opt.columns.stops) ? opt.columns.stops.slice() : undefined,
244
+ style: opt.columns.style ? Object.assign({}, opt.columns.style) : {},
245
+ includeOuter: opt.columns.includeOuter !== false
246
+ } : null
247
+ };
248
+ };
249
+
250
+ LayoutBuilder.prototype.addBackground = function (background) {
251
+ var backgroundGetter = isFunction(background) ? background : function () {
252
+ return background;
253
+ };
254
+
255
+ var context = this.writer.context();
256
+ var pageSize = context.getCurrentPage().pageSize;
257
+
258
+ var pageBackground = backgroundGetter(context.page + 1, pageSize);
259
+
260
+ if (pageBackground) {
261
+ this.writer.beginUnbreakableBlock(pageSize.width, pageSize.height);
262
+ pageBackground = this.docPreprocessor.preprocessDocument(pageBackground);
263
+ this.processNode(this.docMeasure.measureDocument(pageBackground));
264
+ this.writer.commitUnbreakableBlock(0, 0);
265
+ context.backgroundLength[context.page] += pageBackground.positions.length;
266
+ }
267
+ };
268
+
269
+ LayoutBuilder.prototype.addStaticRepeatable = function (headerOrFooter, sizeFunction) {
270
+ return this.addDynamicRepeatable(function () {
271
+ return JSON.parse(JSON.stringify(headerOrFooter)); // copy to new object
272
+ }, sizeFunction);
273
+ };
274
+
275
+ LayoutBuilder.prototype.addDynamicRepeatable = function (nodeGetter, sizeFunction) {
276
+ var pages = this.writer.context().pages;
277
+ var measuredHeight;
278
+
279
+ for (var pageIndex = 0, l = pages.length; pageIndex < l; pageIndex++) {
280
+ this.writer.context().page = pageIndex;
281
+
282
+ var node = nodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
283
+
284
+ if (node) {
285
+ var sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
286
+ this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
287
+ node = this.docPreprocessor.preprocessDocument(node);
288
+ this.processNode(this.docMeasure.measureDocument(node));
289
+ this.writer.commitUnbreakableBlock(sizes.x, sizes.y);
290
+ if (!isUndefined(node._height)) {
291
+ measuredHeight = node._height;
292
+ }
293
+ }
294
+ }
295
+
296
+ return measuredHeight;
297
+ };
298
+
299
+ LayoutBuilder.prototype.addHeadersAndFooters = function (header, footer, headerHeight, footerHeight) {
300
+ var measured = { header: undefined, footer: undefined };
301
+
302
+ var headerSizeFct = function (pageSize) {
303
+ var effectiveHeight = headerHeight;
304
+ if (isUndefined(effectiveHeight)) {
305
+ effectiveHeight = pageSize.height;
306
+ }
307
+ return {
308
+ x: 0,
309
+ y: 0,
310
+ width: pageSize.width,
311
+ height: effectiveHeight
312
+ };
313
+ };
314
+
315
+ var footerSizeFct = function (pageSize) {
316
+ var effectiveHeight = footerHeight;
317
+ if (isUndefined(effectiveHeight)) {
318
+ effectiveHeight = pageSize.height;
319
+ }
320
+ return {
321
+ x: 0,
322
+ y: pageSize.height - effectiveHeight,
323
+ width: pageSize.width,
324
+ height: effectiveHeight
325
+ };
326
+ };
327
+
328
+ if (this._footerGapOption && !this.writer.context()._footerGapOption) {
329
+ this.writer.context()._footerGapOption = this._footerGapOption;
330
+ }
331
+
332
+ if (isFunction(header)) {
333
+ measured.header = this.addDynamicRepeatable(header, headerSizeFct);
334
+ } else if (header) {
335
+ measured.header = this.addStaticRepeatable(header, headerSizeFct);
336
+ }
337
+
338
+ if (isFunction(footer)) {
339
+ measured.footer = this.addDynamicRepeatable(footer, footerSizeFct);
340
+ } else if (footer) {
341
+ measured.footer = this.addStaticRepeatable(footer, footerSizeFct);
342
+ }
343
+
344
+ return measured;
345
+ };
346
+
347
+ LayoutBuilder.prototype.addWatermark = function (watermark, fontProvider, defaultStyle) {
348
+ if (isString(watermark)) {
349
+ watermark = { 'text': watermark };
350
+ }
351
+
352
+ if (!watermark.text) { // empty watermark text
353
+ return;
354
+ }
355
+
356
+ var pages = this.writer.context().pages;
357
+ for (var i = 0, l = pages.length; i < l; i++) {
358
+ pages[i].watermark = getWatermarkObject({ ...watermark }, pages[i].pageSize, fontProvider, defaultStyle);
359
+ }
360
+
361
+ function getWatermarkObject(watermark, pageSize, fontProvider, defaultStyle) {
362
+ watermark.font = watermark.font || defaultStyle.font || 'Roboto';
363
+ watermark.fontSize = watermark.fontSize || 'auto';
364
+ watermark.color = watermark.color || 'black';
365
+ watermark.opacity = isNumber(watermark.opacity) ? watermark.opacity : 0.6;
366
+ watermark.bold = watermark.bold || false;
367
+ watermark.italics = watermark.italics || false;
368
+ watermark.angle = !isUndefined(watermark.angle) && !isNull(watermark.angle) ? watermark.angle : null;
369
+
370
+ if (watermark.angle === null) {
371
+ watermark.angle = Math.atan2(pageSize.height, pageSize.width) * -180 / Math.PI;
372
+ }
373
+
374
+ if (watermark.fontSize === 'auto') {
375
+ watermark.fontSize = getWatermarkFontSize(pageSize, watermark, fontProvider);
376
+ }
377
+
378
+ var watermarkObject = {
379
+ text: watermark.text,
380
+ font: fontProvider.provideFont(watermark.font, watermark.bold, watermark.italics),
381
+ fontSize: watermark.fontSize,
382
+ color: watermark.color,
383
+ opacity: watermark.opacity,
384
+ angle: watermark.angle
385
+ };
386
+
387
+ watermarkObject._size = getWatermarkSize(watermark, fontProvider);
388
+
389
+ return watermarkObject;
390
+ }
391
+
392
+ function getWatermarkSize(watermark, fontProvider) {
393
+ var textTools = new TextTools(fontProvider);
394
+ var styleContextStack = new StyleContextStack(null, { font: watermark.font, bold: watermark.bold, italics: watermark.italics });
395
+
396
+ styleContextStack.push({
397
+ fontSize: watermark.fontSize
398
+ });
399
+
400
+ var size = textTools.sizeOfString(watermark.text, styleContextStack);
401
+ var rotatedSize = textTools.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
402
+
403
+ return { size: size, rotatedSize: rotatedSize };
404
+ }
405
+
406
+ function getWatermarkFontSize(pageSize, watermark, fontProvider) {
407
+ var textTools = new TextTools(fontProvider);
408
+ var styleContextStack = new StyleContextStack(null, { font: watermark.font, bold: watermark.bold, italics: watermark.italics });
409
+ var rotatedSize;
410
+
411
+ /**
412
+ * Binary search the best font size.
413
+ * Initial bounds [0, 1000]
414
+ * Break when range < 1
415
+ */
416
+ var a = 0;
417
+ var b = 1000;
418
+ var c = (a + b) / 2;
419
+ while (Math.abs(a - b) > 1) {
420
+ styleContextStack.push({
421
+ fontSize: c
422
+ });
423
+ rotatedSize = textTools.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
424
+ if (rotatedSize.width > pageSize.width) {
425
+ b = c;
426
+ c = (a + b) / 2;
427
+ } else if (rotatedSize.width < pageSize.width) {
428
+ if (rotatedSize.height > pageSize.height) {
429
+ b = c;
430
+ c = (a + b) / 2;
431
+ } else {
432
+ a = c;
433
+ c = (a + b) / 2;
434
+ }
435
+ }
436
+ styleContextStack.pop();
437
+ }
438
+ /*
439
+ End binary search
440
+ */
441
+ return c;
442
+ }
443
+ };
444
+
445
+ function decorateNode(node) {
446
+ var x = node.x, y = node.y;
447
+ node.positions = [];
448
+
449
+ if (isArray(node.canvas)) {
450
+ node.canvas.forEach(function (vector) {
451
+ var x = vector.x, y = vector.y, x1 = vector.x1, y1 = vector.y1, x2 = vector.x2, y2 = vector.y2;
452
+ vector.resetXY = function () {
453
+ vector.x = x;
454
+ vector.y = y;
455
+ vector.x1 = x1;
456
+ vector.y1 = y1;
457
+ vector.x2 = x2;
458
+ vector.y2 = y2;
459
+ };
460
+ });
461
+ }
462
+
463
+ node.resetXY = function () {
464
+ node.x = x;
465
+ node.y = y;
466
+ if (isArray(node.canvas)) {
467
+ node.canvas.forEach(function (vector) {
468
+ vector.resetXY();
469
+ });
470
+ }
471
+ };
472
+ }
473
+
474
+ LayoutBuilder.prototype.processNode = function (node) {
475
+ var self = this;
476
+
477
+ if (footerBreak && (node.footerBreak || node.footer)) {
478
+ return;
479
+ }
480
+
481
+ if (node && node.unbreakable && node.summary && node.table && node.table.body &&
482
+ node.table.body[0] && node.table.body[0][0] && node.table.body[0][0].summaryBreak) {
483
+ testTracker = new TraversalTracker();
484
+ testWriter = new PageElementWriter(self.writer.context(), testTracker);
485
+ testVerticalAlignStack = self.verticalAlignItemStack.slice();
486
+ currentLayoutBuilder = self;
487
+ testResult = false;
488
+ var nodeForTest = cloneDeep(node);
489
+ if (nodeForTest.table.body[0]) {
490
+ nodeForTest.table.body[0].splice(0, 1);
491
+ }
492
+ processNode_test(nodeForTest);
493
+ currentLayoutBuilder = null;
494
+ if (testResult && node.table.body[0]) {
495
+ node.table.body[0].splice(0, 1);
496
+ }
497
+ }
498
+
499
+ this.linearNodeList.push(node);
500
+ decorateNode(node);
501
+
502
+ var prevTop = self.writer.context().getCurrentPosition().top;
503
+
504
+ applyMargins(function () {
505
+ var unbreakable = node.unbreakable;
506
+ if (unbreakable) {
507
+ self.writer.beginUnbreakableBlock();
508
+ }
509
+
510
+ var absPosition = node.absolutePosition;
511
+ if (absPosition) {
512
+ self.writer.context().beginDetachedBlock();
513
+ self.writer.context().moveTo(absPosition.x || 0, absPosition.y || 0);
514
+ }
515
+
516
+ var relPosition = node.relativePosition;
517
+ if (relPosition) {
518
+ self.writer.context().beginDetachedBlock();
519
+ self.writer.context().moveToRelative(relPosition.x || 0, relPosition.y || 0);
520
+ }
521
+
522
+ var verticalAlignBegin;
523
+ if (node.verticalAlign) {
524
+ verticalAlignBegin = self.writer.beginVerticalAlign(node.verticalAlign);
525
+ }
526
+
527
+ if (node.stack) {
528
+ self.processVerticalContainer(node);
529
+ } else if (node.layers) {
530
+ self.processLayers(node);
531
+ } else if (node.columns) {
532
+ self.processColumns(node);
533
+ } else if (node.ul) {
534
+ self.processList(false, node);
535
+ } else if (node.ol) {
536
+ self.processList(true, node);
537
+ } else if (node.table) {
538
+ self.processTable(node);
539
+ } else if (node.text !== undefined) {
540
+ self.processLeaf(node);
541
+ } else if (node.toc) {
542
+ self.processToc(node);
543
+ } else if (node.image) {
544
+ self.processImage(node);
545
+ } else if (node.svg) {
546
+ self.processSVG(node);
547
+ } else if (node.canvas) {
548
+ self.processCanvas(node);
549
+ } else if (node.qr) {
550
+ self.processQr(node);
551
+ } else if (!node._span) {
552
+ throw new Error('Unrecognized document structure: ' + JSON.stringify(node, fontStringify));
553
+ }
554
+
555
+ if ((absPosition || relPosition) && !node.absoluteRepeatable) {
556
+ self.writer.context().endDetachedBlock();
557
+ }
558
+
559
+ if (unbreakable) {
560
+ if (node.footer) {
561
+ footerBreak = self.writer.commitUnbreakableBlock(undefined, undefined, node.footer);
562
+ } else {
563
+ self.writer.commitUnbreakableBlock();
564
+ }
565
+ }
566
+
567
+ if (node.verticalAlign) {
568
+ var stackEntry = {
569
+ begin: verticalAlignBegin,
570
+ end: self.writer.endVerticalAlign(node.verticalAlign)
571
+ };
572
+ self.verticalAlignItemStack.push(stackEntry);
573
+ node._verticalAlignIdx = self.verticalAlignItemStack.length - 1;
574
+ }
575
+ });
576
+
577
+ node._height = self.writer.context().getCurrentPosition().top - prevTop;
578
+
579
+ function applyMargins(callback) {
580
+ var margin = node._margin;
581
+
582
+ if (node.pageBreak === 'before') {
583
+ self.writer.moveToNextPage(node.pageOrientation);
584
+ }
585
+
586
+ if (margin) {
587
+ self.writer.context().moveDown(margin[1]);
588
+ self.writer.context().addMargin(margin[0], margin[2]);
589
+ }
590
+
591
+ callback();
592
+
593
+ if (margin) {
594
+ self.writer.context().addMargin(-margin[0], -margin[2]);
595
+ self.writer.context().moveDown(margin[3]);
596
+ }
597
+
598
+ if (node.pageBreak === 'after') {
599
+ self.writer.moveToNextPage(node.pageOrientation);
600
+ }
601
+ }
602
+ };
603
+
604
+ // vertical container
605
+ LayoutBuilder.prototype.processVerticalContainer = function (node) {
606
+ var self = this;
607
+ node.stack.forEach(function (item) {
608
+ self.processNode(item);
609
+ addAll(node.positions, item.positions);
610
+
611
+ //TODO: paragraph gap
612
+ });
613
+ };
614
+
615
+ // layers
616
+ LayoutBuilder.prototype.processLayers = function(node) {
617
+ var self = this;
618
+ var ctxX = self.writer.context().x;
619
+ var ctxY = self.writer.context().y;
620
+ var maxX = ctxX;
621
+ var maxY = ctxY;
622
+ node.layers.forEach(function(item, i) {
623
+ self.writer.context().x = ctxX;
624
+ self.writer.context().y = ctxY;
625
+ self.processNode(item);
626
+ item._verticalAlignIdx = self.verticalAlignItemStack.length - 1;
627
+ addAll(node.positions, item.positions);
628
+ maxX = self.writer.context().x > maxX ? self.writer.context().x : maxX;
629
+ maxY = self.writer.context().y > maxY ? self.writer.context().y : maxY;
630
+ });
631
+ self.writer.context().x = maxX;
632
+ self.writer.context().y = maxY;
633
+ };
634
+
635
+ // columns
636
+ LayoutBuilder.prototype.processColumns = function (columnNode) {
637
+ this.nestedLevel++;
638
+ var columns = columnNode.columns;
639
+ var availableWidth = this.writer.context().availableWidth;
640
+ var gaps = gapArray(columnNode._gap);
641
+
642
+ if (gaps) {
643
+ availableWidth -= (gaps.length - 1) * columnNode._gap;
644
+ }
645
+
646
+ ColumnCalculator.buildColumnWidths(columns, availableWidth);
647
+ var result = this.processRow({
648
+ marginX: columnNode._margin ? [columnNode._margin[0], columnNode._margin[2]] : [0, 0],
649
+ cells: columns,
650
+ widths: columns,
651
+ gaps
652
+ });
653
+ addAll(columnNode.positions, result.positions);
654
+
655
+ this.nestedLevel--;
656
+ if (this.nestedLevel === 0) {
657
+ this.writer.context().resetMarginXTopParent();
658
+ }
659
+
660
+ function gapArray(gap) {
661
+ if (!gap) {
662
+ return null;
663
+ }
664
+
665
+ var gaps = [];
666
+ gaps.push(0);
667
+
668
+ for (var i = columns.length - 1; i > 0; i--) {
669
+ gaps.push(gap);
670
+ }
671
+
672
+ return gaps;
673
+ }
674
+ };
675
+
676
+ /**
677
+ * Searches for a cell in the same row that starts a rowspan and is positioned immediately before the current cell.
678
+ * Alternatively, it finds a cell where the colspan initiating the rowspan extends to the cell just before the current one.
679
+ *
680
+ * @param {Array<object>} arr - An array representing cells in a row.
681
+ * @param {number} i - The index of the current cell to search backward from.
682
+ * @returns {object|null} The starting cell of the rowspan if found; otherwise, `null`.
683
+ */
684
+ LayoutBuilder.prototype._findStartingRowSpanCell = function (arr, i) {
685
+ var requiredColspan = 1;
686
+ for (var index = i - 1; index >= 0; index--) {
687
+ if (!arr[index]._span) {
688
+ if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
689
+ return arr[index];
690
+ } else {
691
+ return null;
692
+ }
693
+ }
694
+ requiredColspan++;
695
+ }
696
+ return null;
697
+ };
698
+
699
+ /**
700
+ * Retrieves a page break description for a specified page from a list of page breaks.
701
+ *
702
+ * @param {Array<object>} pageBreaks - An array of page break descriptions, each containing `prevPage` properties.
703
+ * @param {number} page - The page number to find the associated page break for.
704
+ * @returns {object|undefined} The page break description object for the specified page if found; otherwise, `undefined`.
705
+ */
706
+ LayoutBuilder.prototype._getPageBreak = function (pageBreaks, page) {
707
+ return pageBreaks.find(desc => desc.prevPage === page);
708
+ };
709
+
710
+ LayoutBuilder.prototype._getPageBreakListBySpan = function (tableNode, page, rowIndex) {
711
+ if (!tableNode || !tableNode._breaksBySpan) {
712
+ return null;
713
+ }
714
+ const breaksList = tableNode._breaksBySpan.filter(desc => desc.prevPage === page && rowIndex <= desc.rowIndexOfSpanEnd);
715
+
716
+ var y = Number.MAX_VALUE,
717
+ prevY = Number.MIN_VALUE;
718
+
719
+ breaksList.forEach(b => {
720
+ prevY = Math.max(b.prevY, prevY);
721
+ y = Math.min(b.y, y);
722
+ });
723
+
724
+ return {
725
+ prevPage: page,
726
+ prevY: prevY,
727
+ y: y
728
+ };
729
+ };
730
+
731
+ LayoutBuilder.prototype._findSameRowPageBreakByRowSpanData = function (breaksBySpan, page, rowIndex) {
732
+ if (!breaksBySpan) {
733
+ return null;
734
+ }
735
+ return breaksBySpan.find(desc => desc.prevPage === page && rowIndex === desc.rowIndexOfSpanEnd);
736
+ };
737
+
738
+ LayoutBuilder.prototype._updatePageBreaksData = function (pageBreaks, tableNode, rowIndex) {
739
+ Object.keys(tableNode._bottomByPage).forEach(p => {
740
+ const page = Number(p);
741
+ const pageBreak = this._getPageBreak(pageBreaks, page);
742
+ if (pageBreak) {
743
+ pageBreak.prevY = Math.max(pageBreak.prevY, tableNode._bottomByPage[page]);
744
+ }
745
+ if (tableNode._breaksBySpan && tableNode._breaksBySpan.length > 0) {
746
+ const breaksBySpanList = tableNode._breaksBySpan.filter(pb => pb.prevPage === page && rowIndex <= pb.rowIndexOfSpanEnd);
747
+ if (breaksBySpanList && breaksBySpanList.length > 0) {
748
+ breaksBySpanList.forEach(b => {
749
+ b.prevY = Math.max(b.prevY, tableNode._bottomByPage[page]);
750
+ });
751
+ }
752
+ }
753
+ });
754
+ };
755
+
756
+ /**
757
+ * Resolves the Y-coordinates for a target object by comparing two break points.
758
+ *
759
+ * @param {object} break1 - The first break point with `prevY` and `y` properties.
760
+ * @param {object} break2 - The second break point with `prevY` and `y` properties.
761
+ * @param {object} target - The target object to be updated with resolved Y-coordinates.
762
+ * @property {number} target.prevY - Updated to the maximum `prevY` value between `break1` and `break2`.
763
+ * @property {number} target.y - Updated to the minimum `y` value between `break1` and `break2`.
764
+ */
765
+ LayoutBuilder.prototype._resolveBreakY = function (break1, break2, target) {
766
+ target.prevY = Math.max(break1.prevY, break2.prevY);
767
+ target.y = Math.min(break1.y, break2.y);
768
+ };
769
+
770
+ LayoutBuilder.prototype._storePageBreakData = function (data, startsRowSpan, pageBreaks, tableNode) {
771
+ var pageDesc;
772
+ var pageDescBySpan;
773
+
774
+ if (!startsRowSpan) {
775
+ pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
776
+ pageDescBySpan = this._getPageBreakListBySpan(tableNode, data.prevPage, data.rowIndex);
777
+ if (!pageDesc) {
778
+ pageDesc = Object.assign({}, data);
779
+ pageBreaks.push(pageDesc);
780
+ }
781
+
782
+ if (pageDescBySpan) {
783
+ this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
784
+ }
785
+ this._resolveBreakY(pageDesc, data, pageDesc);
786
+ } else {
787
+ var breaksBySpan = tableNode && tableNode._breaksBySpan || null;
788
+ pageDescBySpan = this._findSameRowPageBreakByRowSpanData(breaksBySpan, data.prevPage, data.rowIndex);
789
+ if (!pageDescBySpan) {
790
+ pageDescBySpan = Object.assign({}, data, {
791
+ rowIndexOfSpanEnd: data.rowIndex + data.rowSpan - 1
792
+ });
793
+ if (!tableNode._breaksBySpan) {
794
+ tableNode._breaksBySpan = [];
795
+ }
796
+ tableNode._breaksBySpan.push(pageDescBySpan);
797
+ }
798
+ pageDescBySpan.prevY = Math.max(pageDescBySpan.prevY, data.prevY);
799
+ pageDescBySpan.y = Math.min(pageDescBySpan.y, data.y);
800
+ pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
801
+ if (pageDesc) {
802
+ this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
803
+ }
804
+ }
805
+ };
806
+
807
+ /**
808
+ * Calculates the left offset for a column based on the specified gap values.
809
+ *
810
+ * @param {number} i - The index of the column for which the offset is being calculated.
811
+ * @param {Array<number>} gaps - An array of gap values for each column.
812
+ * @returns {number} The left offset for the column. Returns `gaps[i]` if it exists, otherwise `0`.
813
+ */
814
+ LayoutBuilder.prototype._colLeftOffset = function (i, gaps) {
815
+ if (gaps && gaps.length > i) {
816
+ return gaps[i];
817
+ }
818
+ return 0;
819
+ };
820
+
821
+ /**
822
+ * Retrieves the ending cell for a row span in case it exists in a specified table column.
823
+ *
824
+ * @param {Array<Array<object>>} tableBody - The table body, represented as a 2D array of cell objects.
825
+ * @param {number} rowIndex - The index of the starting row for the row span.
826
+ * @param {object} column - The column object containing row span information.
827
+ * @param {number} columnIndex - The index of the column within the row.
828
+ * @returns {object|null} The cell at the end of the row span if it exists; otherwise, `null`.
829
+ * @throws {Error} If the row span extends beyond the total row count.
830
+ */
831
+ LayoutBuilder.prototype._getRowSpanEndingCell = function (tableBody, rowIndex, column, columnIndex) {
832
+ if (column.rowSpan && column.rowSpan > 1) {
833
+ var endingRow = rowIndex + column.rowSpan - 1;
834
+ if (endingRow >= tableBody.length) {
835
+ throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
836
+ }
837
+ return tableBody[endingRow][columnIndex];
838
+ }
839
+
840
+ return null;
841
+ };
842
+
843
+ LayoutBuilder.prototype.processRow = function ({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableNode, tableBody, rowIndex, height }) {
844
+ var self = this;
845
+ var isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
846
+ var pageBreaks = [];
847
+ var pageBreaksByRowSpan = [];
848
+ var positions = [];
849
+ var willBreakByHeight = false;
850
+ var columnAlignIndexes = {};
851
+ widths = widths || cells;
852
+
853
+ // Check if row should break by height
854
+ if (!isUnbreakableRow && height > self.writer.context().availableHeight) {
855
+ willBreakByHeight = true;
856
+ }
857
+
858
+ // Use the marginX if we are in a top level table/column (not nested)
859
+ const marginXParent = self.nestedLevel === 1 ? marginX : null;
860
+ const _bottomByPage = tableNode ? tableNode._bottomByPage : null;
861
+ this.writer.context().beginColumnGroup(marginXParent, _bottomByPage);
862
+
863
+ for (var i = 0, l = cells.length; i < l; i++) {
864
+ var cell = cells[i];
865
+
866
+ // Page change handler
867
+
868
+ this.tracker.auto('pageChanged', storePageBreakClosure, function () {
869
+ var width = widths[i]._calcWidth;
870
+ var leftOffset = self._colLeftOffset(i, gaps);
871
+ // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
872
+ var startingSpanCell = self._findStartingRowSpanCell(cells, i);
873
+
874
+ if (cell.colSpan && cell.colSpan > 1) {
875
+ for (var j = 1; j < cell.colSpan; j++) {
876
+ width += widths[++i]._calcWidth + gaps[i];
877
+ }
878
+ }
879
+
880
+ // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
881
+ const rowSpanEndingCell = self._getRowSpanEndingCell(tableBody, rowIndex, cell, i);
882
+ if (rowSpanEndingCell) {
883
+ // We store a reference of the ending cell in the first cell of the rowspan
884
+ cell._endingCell = rowSpanEndingCell;
885
+ cell._endingCell._startingRowSpanY = cell._startingRowSpanY;
886
+ }
887
+
888
+ // If we are after a cell that started a rowspan
889
+ var endOfRowSpanCell = null;
890
+ if (startingSpanCell && startingSpanCell._endingCell) {
891
+ // Reference to the last cell of the rowspan
892
+ endOfRowSpanCell = startingSpanCell._endingCell;
893
+ // Store if we are in an unbreakable block when we save the context and the originalX
894
+ if (self.writer.transactionLevel > 0) {
895
+ endOfRowSpanCell._isUnbreakableContext = true;
896
+ endOfRowSpanCell._originalXOffset = self.writer.originalX;
897
+ }
898
+ }
899
+
900
+ // We pass the endingSpanCell reference to store the context just after processing rowspan cell
901
+ self.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell);
902
+
903
+ if (!cell._span) {
904
+ self.processNode(cell);
905
+ self.writer.context().updateBottomByPage();
906
+ addAll(positions, cell.positions);
907
+ if (cell.verticalAlign && cell._verticalAlignIdx !== undefined) {
908
+ columnAlignIndexes[i] = cell._verticalAlignIdx;
909
+ }
910
+ } else if (cell._columnEndingContext) {
911
+ var discountY = 0;
912
+ if (dontBreakRows) {
913
+ // Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
914
+ const ctxBeforeRowSpanLastRow = self.writer.writer.contextStack[self.writer.writer.contextStack.length - 1];
915
+ discountY = ctxBeforeRowSpanLastRow.y - cell._startingRowSpanY;
916
+ }
917
+ var originalXOffset = 0;
918
+ // If context was saved from an unbreakable block and we are not in an unbreakable block anymore
919
+ // We have to sum the originalX (X before starting unbreakable block) to X
920
+ if (cell._isUnbreakableContext && !self.writer.transactionLevel) {
921
+ originalXOffset = cell._originalXOffset;
922
+ }
923
+ // row-span ending
924
+ // Recover the context after processing the rowspanned cell
925
+ self.writer.context().markEnding(cell, originalXOffset, discountY);
926
+ }
927
+ });
928
+ }
929
+
930
+ // Check if last cell is part of a span
931
+ var endingSpanCell = null;
932
+ var lastColumn = cells.length > 0 ? cells[cells.length - 1] : null;
933
+ if (lastColumn) {
934
+ // Previous column cell has a rowspan
935
+ if (lastColumn._endingCell) {
936
+ endingSpanCell = lastColumn._endingCell;
937
+ // Previous column cell is part of a span
938
+ } else if (lastColumn._span === true) {
939
+ // We get the cell that started the span where we set a reference to the ending cell
940
+ const startingSpanCell = this._findStartingRowSpanCell(cells, cells.length);
941
+ if (startingSpanCell) {
942
+ // Context will be stored here (ending cell)
943
+ endingSpanCell = startingSpanCell._endingCell;
944
+ // Store if we are in an unbreakable block when we save the context and the originalX
945
+ if (this.writer.transactionLevel > 0) {
946
+ endingSpanCell._isUnbreakableContext = true;
947
+ endingSpanCell._originalXOffset = this.writer.originalX;
948
+ }
949
+ }
950
+ }
951
+ }
952
+
953
+ // If content did not break page, check if we should break by height
954
+ if (willBreakByHeight && !isUnbreakableRow && pageBreaks.length === 0) {
955
+ this.writer.context().moveDown(this.writer.context().availableHeight);
956
+ this.writer.moveToNextPage();
957
+ }
958
+
959
+ var bottomByPage = this.writer.context().completeColumnGroup(height, endingSpanCell);
960
+ var rowHeight = this.writer.context().height;
961
+ for (var colIndex = 0, columnsLength = cells.length; colIndex < columnsLength; colIndex++) {
962
+ var columnNode = cells[colIndex];
963
+ if (columnNode._span) {
964
+ continue;
965
+ }
966
+ if (columnNode.verticalAlign && columnAlignIndexes[colIndex] !== undefined) {
967
+ var alignEntry = self.verticalAlignItemStack[columnAlignIndexes[colIndex]];
968
+ if (alignEntry && alignEntry.begin && alignEntry.begin.item) {
969
+ alignEntry.begin.item.viewHeight = rowHeight;
970
+ alignEntry.begin.item.nodeHeight = columnNode._height;
971
+ }
972
+ }
973
+ if (columnNode.layers) {
974
+ columnNode.layers.forEach(function (layer) {
975
+ if (layer.verticalAlign && layer._verticalAlignIdx !== undefined) {
976
+ var layerEntry = self.verticalAlignItemStack[layer._verticalAlignIdx];
977
+ if (layerEntry && layerEntry.begin && layerEntry.begin.item) {
978
+ layerEntry.begin.item.viewHeight = rowHeight;
979
+ layerEntry.begin.item.nodeHeight = layer._height;
980
+ }
981
+ }
982
+ });
983
+ }
984
+ }
985
+
986
+ if (tableNode) {
987
+ tableNode._bottomByPage = bottomByPage;
988
+ // If there are page breaks in this row, update data with prevY of last cell
989
+ this._updatePageBreaksData(pageBreaks, tableNode, rowIndex);
990
+ }
991
+
992
+ return {
993
+ pageBreaksBySpan: pageBreaksByRowSpan,
994
+ pageBreaks: pageBreaks,
995
+ positions: positions
996
+ };
997
+
998
+ function storePageBreakClosure(data) {
999
+ const startsRowSpan = cell.rowSpan && cell.rowSpan > 1;
1000
+ if (startsRowSpan) {
1001
+ data.rowSpan = cell.rowSpan;
1002
+ }
1003
+ data.rowIndex = rowIndex;
1004
+ self._storePageBreakData(data, startsRowSpan, pageBreaks, tableNode);
1005
+ }
1006
+
1007
+ };
1008
+
1009
+ // lists
1010
+ LayoutBuilder.prototype.processList = function (orderedList, node) {
1011
+ var self = this,
1012
+ items = orderedList ? node.ol : node.ul,
1013
+ gapSize = node._gapSize;
1014
+
1015
+ this.writer.context().addMargin(gapSize.width);
1016
+
1017
+ var nextMarker;
1018
+ this.tracker.auto('lineAdded', addMarkerToFirstLeaf, function () {
1019
+ items.forEach(function (item) {
1020
+ nextMarker = item.listMarker;
1021
+ self.processNode(item);
1022
+ addAll(node.positions, item.positions);
1023
+ });
1024
+ });
1025
+
1026
+ this.writer.context().addMargin(-gapSize.width);
1027
+
1028
+ function addMarkerToFirstLeaf(line) {
1029
+ // I'm not very happy with the way list processing is implemented
1030
+ // (both code and algorithm should be rethinked)
1031
+ if (nextMarker) {
1032
+ var marker = nextMarker;
1033
+ nextMarker = null;
1034
+
1035
+ if (marker.canvas) {
1036
+ var vector = marker.canvas[0];
1037
+
1038
+ offsetVector(vector, -marker._minWidth, 0);
1039
+ self.writer.addVector(vector);
1040
+ } else if (marker._inlines) {
1041
+ var markerLine = new Line(self.pageSize.width);
1042
+ markerLine.addInline(marker._inlines[0]);
1043
+ markerLine.x = -marker._minWidth;
1044
+ markerLine.y = line.getAscenderHeight() - markerLine.getAscenderHeight();
1045
+ self.writer.addLine(markerLine, true);
1046
+ }
1047
+ }
1048
+ }
1049
+ };
1050
+
1051
+ // tables
1052
+ LayoutBuilder.prototype.processTable = function (tableNode) {
1053
+ this.nestedLevel++;
1054
+ var processor = new TableProcessor(tableNode);
1055
+
1056
+ processor.beginTable(this.writer);
1057
+
1058
+ var rowHeights = tableNode.table.heights;
1059
+ for (var i = 0, l = tableNode.table.body.length; i < l; i++) {
1060
+ // if dontBreakRows and row starts a rowspan
1061
+ // we store the 'y' of the beginning of each rowSpan
1062
+ if (processor.dontBreakRows) {
1063
+ tableNode.table.body[i].forEach(cell => {
1064
+ if (cell.rowSpan && cell.rowSpan > 1) {
1065
+ cell._startingRowSpanY = this.writer.context().y;
1066
+ }
1067
+ });
1068
+ }
1069
+
1070
+ processor.beginRow(i, this.writer);
1071
+
1072
+ var height;
1073
+ if (isFunction(rowHeights)) {
1074
+ height = rowHeights(i);
1075
+ } else if (isArray(rowHeights)) {
1076
+ height = rowHeights[i];
1077
+ } else {
1078
+ height = rowHeights;
1079
+ }
1080
+
1081
+ if (height === 'auto') {
1082
+ height = undefined;
1083
+ }
1084
+
1085
+ var pageBeforeProcessing = this.writer.context().page;
1086
+
1087
+ var result = this.processRow({
1088
+ marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
1089
+ dontBreakRows: processor.dontBreakRows,
1090
+ rowsWithoutPageBreak: processor.rowsWithoutPageBreak,
1091
+ cells: tableNode.table.body[i],
1092
+ widths: tableNode.table.widths,
1093
+ gaps: tableNode._offsets.offsets,
1094
+ tableBody: tableNode.table.body,
1095
+ tableNode,
1096
+ rowIndex: i,
1097
+ height
1098
+ });
1099
+ addAll(tableNode.positions, result.positions);
1100
+
1101
+ if (!result.pageBreaks || result.pageBreaks.length === 0) {
1102
+ var breaksBySpan = tableNode && tableNode._breaksBySpan || null;
1103
+ var breakBySpanData = this._findSameRowPageBreakByRowSpanData(breaksBySpan, pageBeforeProcessing, i);
1104
+ if (breakBySpanData) {
1105
+ var finalBreakBySpanData = this._getPageBreakListBySpan(tableNode, breakBySpanData.prevPage, i);
1106
+ result.pageBreaks.push(finalBreakBySpanData);
1107
+ }
1108
+ }
1109
+
1110
+ processor.endRow(i, this.writer, result.pageBreaks);
1111
+ }
1112
+
1113
+ processor.endTable(this.writer);
1114
+ this.nestedLevel--;
1115
+ if (this.nestedLevel === 0) {
1116
+ this.writer.context().resetMarginXTopParent();
1117
+ }
1118
+ };
1119
+
1120
+ // leafs (texts)
1121
+ LayoutBuilder.prototype.processLeaf = function (node) {
1122
+ var line = this.buildNextLine(node);
1123
+ if (line && (node.tocItem || node.id)) {
1124
+ line._node = node;
1125
+ }
1126
+ var currentHeight = (line) ? line.getHeight() : 0;
1127
+ var maxHeight = node.maxHeight || -1;
1128
+
1129
+ if (line) {
1130
+ var nodeId = getNodeId(node);
1131
+ if (nodeId) {
1132
+ line.id = nodeId;
1133
+ }
1134
+ }
1135
+
1136
+ if (node._tocItemRef) {
1137
+ line._pageNodeRef = node._tocItemRef;
1138
+ }
1139
+
1140
+ if (node._pageRef) {
1141
+ line._pageNodeRef = node._pageRef._nodeRef;
1142
+ }
1143
+
1144
+ if (line && line.inlines && isArray(line.inlines)) {
1145
+ for (var i = 0, l = line.inlines.length; i < l; i++) {
1146
+ if (line.inlines[i]._tocItemRef) {
1147
+ line.inlines[i]._pageNodeRef = line.inlines[i]._tocItemRef;
1148
+ }
1149
+
1150
+ if (line.inlines[i]._pageRef) {
1151
+ line.inlines[i]._pageNodeRef = line.inlines[i]._pageRef._nodeRef;
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ while (line && (maxHeight === -1 || currentHeight < maxHeight)) {
1157
+ var positions = this.writer.addLine(line);
1158
+ node.positions.push(positions);
1159
+ line = this.buildNextLine(node);
1160
+ if (line) {
1161
+ currentHeight += line.getHeight();
1162
+ }
1163
+ }
1164
+ };
1165
+
1166
+ LayoutBuilder.prototype.processToc = function (node) {
1167
+ if (node.toc.title) {
1168
+ this.processNode(node.toc.title);
1169
+ }
1170
+ if (node.toc._table) {
1171
+ this.processNode(node.toc._table);
1172
+ }
1173
+ };
1174
+
1175
+ LayoutBuilder.prototype.buildNextLine = function (textNode) {
1176
+
1177
+ function cloneInline(inline) {
1178
+ var newInline = inline.constructor();
1179
+ for (var key in inline) {
1180
+ newInline[key] = inline[key];
1181
+ }
1182
+ return newInline;
1183
+ }
1184
+
1185
+ function findMaxFitLength(text, maxWidth, measureFn) {
1186
+ let low = 1;
1187
+ let high = text.length;
1188
+ let bestFit = 1;
1189
+
1190
+ while (low <= high) {
1191
+ const mid = Math.floor((low + high) / 2);
1192
+ const part = text.substring(0, mid);
1193
+ const width = measureFn(part);
1194
+
1195
+ if (width <= maxWidth) {
1196
+ bestFit = mid;
1197
+ low = mid + 1;
1198
+ } else {
1199
+ high = mid - 1;
1200
+ }
1201
+ }
1202
+
1203
+ return bestFit;
1204
+ }
1205
+
1206
+ if (!textNode._inlines || textNode._inlines.length === 0) {
1207
+ return null;
1208
+ }
1209
+
1210
+ var line = new Line(this.writer.context().availableWidth);
1211
+ var textTools = new TextTools(null);
1212
+
1213
+ var isForceContinue = false;
1214
+ while (textNode._inlines && textNode._inlines.length > 0 &&
1215
+ (line.hasEnoughSpaceForInline(textNode._inlines[0], textNode._inlines.slice(1)) || isForceContinue)) {
1216
+ var isHardWrap = false;
1217
+ var inline = textNode._inlines.shift();
1218
+ isForceContinue = false;
1219
+
1220
+ if (!inline.noWrap && inline.text.length > 1 && inline.width > line.getAvailableWidth()) {
1221
+ var maxChars = findMaxFitLength(inline.text, line.getAvailableWidth(), function (txt) {
1222
+ return textTools.widthOfString(txt, inline.font, inline.fontSize, inline.characterSpacing, inline.fontFeatures);
1223
+ });
1224
+ if (maxChars < inline.text.length) {
1225
+ var newInline = cloneInline(inline);
1226
+
1227
+ newInline.text = inline.text.substr(maxChars);
1228
+ inline.text = inline.text.substr(0, maxChars);
1229
+
1230
+ newInline.width = textTools.widthOfString(newInline.text, newInline.font, newInline.fontSize, newInline.characterSpacing, newInline.fontFeatures);
1231
+ inline.width = textTools.widthOfString(inline.text, inline.font, inline.fontSize, inline.characterSpacing, inline.fontFeatures);
1232
+
1233
+ textNode._inlines.unshift(newInline);
1234
+ isHardWrap = true;
1235
+ }
1236
+ }
1237
+
1238
+ line.addInline(inline);
1239
+
1240
+ isForceContinue = inline.noNewLine && !isHardWrap;
1241
+ }
1242
+
1243
+ line.lastLineInParagraph = textNode._inlines.length === 0;
1244
+
1245
+ return line;
1246
+ };
1247
+
1248
+ // images
1249
+ LayoutBuilder.prototype.processImage = function (node) {
1250
+ var position = this.writer.addImage(node);
1251
+ node.positions.push(position);
1252
+ };
1253
+
1254
+ LayoutBuilder.prototype.processSVG = function (node) {
1255
+ var position = this.writer.addSVG(node);
1256
+ node.positions.push(position);
1257
+ };
1258
+
1259
+ LayoutBuilder.prototype.processCanvas = function (node) {
1260
+ var height = node._minHeight;
1261
+
1262
+ if (node.absolutePosition === undefined && this.writer.context().availableHeight < height) {
1263
+ // TODO: support for canvas larger than a page
1264
+ // TODO: support for other overflow methods
1265
+
1266
+ this.writer.moveToNextPage();
1267
+ }
1268
+
1269
+ this.writer.alignCanvas(node);
1270
+
1271
+ node.canvas.forEach(function (vector) {
1272
+ var position = this.writer.addVector(vector);
1273
+ node.positions.push(position);
1274
+ }, this);
1275
+
1276
+ this.writer.context().moveDown(height);
1277
+ };
1278
+
1279
+ LayoutBuilder.prototype.processQr = function (node) {
1280
+ var position = this.writer.addQr(node);
1281
+ node.positions.push(position);
1282
+ };
1283
+
1284
+ function processNode_test(node) {
1285
+ decorateNode(node);
1286
+
1287
+ var prevTop = testWriter.context().getCurrentPosition().top;
1288
+
1289
+ applyMargins(function () {
1290
+ var unbreakable = node.unbreakable;
1291
+ if (unbreakable) {
1292
+ testWriter.beginUnbreakableBlock();
1293
+ }
1294
+
1295
+ var absPosition = node.absolutePosition;
1296
+ if (absPosition) {
1297
+ testWriter.context().beginDetachedBlock();
1298
+ testWriter.context().moveTo(absPosition.x || 0, absPosition.y || 0);
1299
+ }
1300
+
1301
+ var relPosition = node.relativePosition;
1302
+ if (relPosition) {
1303
+ testWriter.context().beginDetachedBlock();
1304
+ if (typeof testWriter.context().moveToRelative === 'function') {
1305
+ testWriter.context().moveToRelative(relPosition.x || 0, relPosition.y || 0);
1306
+ } else if (currentLayoutBuilder && currentLayoutBuilder.writer) {
1307
+ testWriter.context().moveTo(
1308
+ (relPosition.x || 0) + currentLayoutBuilder.writer.context().x,
1309
+ (relPosition.y || 0) + currentLayoutBuilder.writer.context().y
1310
+ );
1311
+ }
1312
+ }
1313
+
1314
+ var verticalAlignBegin;
1315
+ if (node.verticalAlign) {
1316
+ verticalAlignBegin = testWriter.beginVerticalAlign(node.verticalAlign);
1317
+ }
1318
+
1319
+ if (node.stack) {
1320
+ processVerticalContainer_test(node);
1321
+ } else if (node.table) {
1322
+ processTable_test(node);
1323
+ } else if (node.text !== undefined) {
1324
+ processLeaf_test(node);
1325
+ }
1326
+
1327
+ if (absPosition || relPosition) {
1328
+ testWriter.context().endDetachedBlock();
1329
+ }
1330
+
1331
+ if (unbreakable) {
1332
+ testResult = testWriter.commitUnbreakableBlock_test();
1333
+ }
1334
+
1335
+ if (node.verticalAlign) {
1336
+ testVerticalAlignStack.push({ begin: verticalAlignBegin, end: testWriter.endVerticalAlign(node.verticalAlign) });
1337
+ }
1338
+ });
1339
+
1340
+ node._height = testWriter.context().getCurrentPosition().top - prevTop;
1341
+
1342
+ function applyMargins(callback) {
1343
+ var margin = node._margin;
1344
+
1345
+ if (node.pageBreak === 'before') {
1346
+ testWriter.moveToNextPage(node.pageOrientation);
1347
+ }
1348
+
1349
+ if (margin) {
1350
+ testWriter.context().moveDown(margin[1]);
1351
+ testWriter.context().addMargin(margin[0], margin[2]);
1352
+ }
1353
+
1354
+ callback();
1355
+
1356
+ if (margin) {
1357
+ testWriter.context().addMargin(-margin[0], -margin[2]);
1358
+ testWriter.context().moveDown(margin[3]);
1359
+ }
1360
+
1361
+ if (node.pageBreak === 'after') {
1362
+ testWriter.moveToNextPage(node.pageOrientation);
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ function processVerticalContainer_test(node) {
1368
+ node.stack.forEach(function (item) {
1369
+ processNode_test(item);
1370
+ addAll(node.positions, item.positions);
1371
+ });
1372
+ }
1373
+
1374
+ function processTable_test(tableNode) {
1375
+ var processor = new TableProcessor(tableNode);
1376
+ processor.beginTable(testWriter);
1377
+
1378
+ for (var i = 0, l = tableNode.table.body.length; i < l; i++) {
1379
+ processor.beginRow(i, testWriter);
1380
+ var result = processRow_test(tableNode.table.body[i], tableNode.table.widths, tableNode._offsets ? tableNode._offsets.offsets : null, tableNode.table.body, i);
1381
+ addAll(tableNode.positions, result.positions);
1382
+ processor.endRow(i, testWriter, result.pageBreaks);
1383
+ }
1384
+
1385
+ processor.endTable(testWriter);
1386
+ }
1387
+
1388
+ function processRow_test(columns, widths, gaps, tableBody, tableRow) {
1389
+ var pageBreaks = [];
1390
+ var positions = [];
1391
+
1392
+ testTracker.auto('pageChanged', storePageBreakData, function () {
1393
+ widths = widths || columns;
1394
+
1395
+ testWriter.context().beginColumnGroup();
1396
+
1397
+ var verticalAlignCols = {};
1398
+
1399
+ for (var i = 0, l = columns.length; i < l; i++) {
1400
+ var column = columns[i];
1401
+ var width = widths[i]._calcWidth || widths[i];
1402
+ var leftOffset = colLeftOffset(i);
1403
+ var colIndex = i;
1404
+ if (column.colSpan && column.colSpan > 1) {
1405
+ for (var j = 1; j < column.colSpan; j++) {
1406
+ width += (widths[++i]._calcWidth || widths[i]) + (gaps ? gaps[i] : 0);
1407
+ }
1408
+ }
1409
+
1410
+ testWriter.context().beginColumn(width, leftOffset, getEndingCell(column, i));
1411
+
1412
+ if (!column._span) {
1413
+ processNode_test(column);
1414
+ verticalAlignCols[colIndex] = testVerticalAlignStack.length - 1;
1415
+ addAll(positions, column.positions);
1416
+ } else if (column._columnEndingContext) {
1417
+ testWriter.context().markEnding(column);
1418
+ }
1419
+ }
1420
+
1421
+ testWriter.context().completeColumnGroup();
1422
+
1423
+ var rowHeight = testWriter.context().height;
1424
+ for (var c = 0, clen = columns.length; c < clen; c++) {
1425
+ var col = columns[c];
1426
+ if (col._span) {
1427
+ continue;
1428
+ }
1429
+ if (col.verticalAlign && verticalAlignCols[c] !== undefined) {
1430
+ var alignItem = testVerticalAlignStack[verticalAlignCols[c]].begin.item;
1431
+ alignItem.viewHeight = rowHeight;
1432
+ alignItem.nodeHeight = col._height;
1433
+ }
1434
+ }
1435
+ });
1436
+
1437
+ return { pageBreaks: pageBreaks, positions: positions };
1438
+
1439
+ function storePageBreakData(data) {
1440
+ var pageDesc;
1441
+ for (var idx = 0, len = pageBreaks.length; idx < len; idx++) {
1442
+ var desc = pageBreaks[idx];
1443
+ if (desc.prevPage === data.prevPage) {
1444
+ pageDesc = desc;
1445
+ break;
1446
+ }
1447
+ }
1448
+
1449
+ if (!pageDesc) {
1450
+ pageDesc = data;
1451
+ pageBreaks.push(pageDesc);
1452
+ }
1453
+ pageDesc.prevY = Math.max(pageDesc.prevY, data.prevY);
1454
+ pageDesc.y = Math.min(pageDesc.y, data.y);
1455
+ }
1456
+
1457
+ function colLeftOffset(i) {
1458
+ if (gaps && gaps.length > i) {
1459
+ return gaps[i];
1460
+ }
1461
+ return 0;
1462
+ }
1463
+
1464
+ function getEndingCell(column, columnIndex) {
1465
+ if (column.rowSpan && column.rowSpan > 1) {
1466
+ var endingRow = tableRow + column.rowSpan - 1;
1467
+ if (endingRow >= tableBody.length) {
1468
+ throw new Error('Row span for column ' + columnIndex + ' (with indexes starting from 0) exceeded row count');
1469
+ }
1470
+ return tableBody[endingRow][columnIndex];
1471
+ }
1472
+ return null;
1473
+ }
1474
+ }
1475
+
1476
+ function processLeaf_test(node) {
1477
+ var line = buildNextLine_test(node);
1478
+ var currentHeight = line ? line.getHeight() : 0;
1479
+ var maxHeight = node.maxHeight || -1;
1480
+
1481
+ while (line && (maxHeight === -1 || currentHeight < maxHeight)) {
1482
+ var positions = testWriter.addLine(line);
1483
+ node.positions.push(positions);
1484
+ line = buildNextLine_test(node);
1485
+ if (line) {
1486
+ currentHeight += line.getHeight();
1487
+ }
1488
+ }
1489
+ }
1490
+
1491
+ function buildNextLine_test(textNode) {
1492
+ function cloneInline(inline) {
1493
+ var newInline = inline.constructor();
1494
+ for (var key in inline) {
1495
+ newInline[key] = inline[key];
1496
+ }
1497
+ return newInline;
1498
+ }
1499
+
1500
+ if (!textNode._inlines || textNode._inlines.length === 0) {
1501
+ return null;
1502
+ }
1503
+
1504
+ var line = new Line(testWriter.context().availableWidth);
1505
+ var textTools = new TextTools(null);
1506
+
1507
+ while (textNode._inlines && textNode._inlines.length > 0 && line.hasEnoughSpaceForInline(textNode._inlines[0])) {
1508
+ var inline = textNode._inlines.shift();
1509
+
1510
+ if (!inline.noWrap && inline.text.length > 1 && inline.width > line.maxWidth) {
1511
+ var widthPerChar = inline.width / inline.text.length;
1512
+ var maxChars = Math.floor(line.maxWidth / widthPerChar);
1513
+ if (maxChars < 1) {
1514
+ maxChars = 1;
1515
+ }
1516
+ if (maxChars < inline.text.length) {
1517
+ var newInline = cloneInline(inline);
1518
+
1519
+ newInline.text = inline.text.substr(maxChars);
1520
+ inline.text = inline.text.substr(0, maxChars);
1521
+
1522
+ newInline.width = textTools.widthOfString(newInline.text, newInline.font, newInline.fontSize, newInline.characterSpacing);
1523
+ inline.width = textTools.widthOfString(inline.text, inline.font, inline.fontSize, inline.characterSpacing);
1524
+
1525
+ textNode._inlines.unshift(newInline);
1526
+ }
1527
+ }
1528
+
1529
+ line.addInline(inline);
1530
+ }
1531
+
1532
+ line.lastLineInParagraph = textNode._inlines.length === 0;
1533
+
1534
+ return line;
1535
+ }
1536
+
1537
+ module.exports = LayoutBuilder;