pdfmake 0.2.12 → 0.2.13

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.
@@ -1,857 +1,989 @@
1
- 'use strict';
2
-
3
- var TraversalTracker = require('./traversalTracker');
4
- var DocPreprocessor = require('./docPreprocessor');
5
- var DocMeasure = require('./docMeasure');
6
- var DocumentContext = require('./documentContext');
7
- var PageElementWriter = require('./pageElementWriter');
8
- var ColumnCalculator = require('./columnCalculator');
9
- var TableProcessor = require('./tableProcessor');
10
- var Line = require('./line');
11
- var isString = require('./helpers').isString;
12
- var isArray = require('./helpers').isArray;
13
- var isUndefined = require('./helpers').isUndefined;
14
- var isNull = require('./helpers').isNull;
15
- var pack = require('./helpers').pack;
16
- var offsetVector = require('./helpers').offsetVector;
17
- var fontStringify = require('./helpers').fontStringify;
18
- var getNodeId = require('./helpers').getNodeId;
19
- var isFunction = require('./helpers').isFunction;
20
- var TextTools = require('./textTools');
21
- var StyleContextStack = require('./styleContextStack');
22
- var isNumber = require('./helpers').isNumber;
23
-
24
- function addAll(target, otherArray) {
25
- otherArray.forEach(function (item) {
26
- target.push(item);
27
- });
28
- }
29
-
30
- /**
31
- * Creates an instance of LayoutBuilder - layout engine which turns document-definition-object
32
- * into a set of pages, lines, inlines and vectors ready to be rendered into a PDF
33
- *
34
- * @param {Object} pageSize - an object defining page width and height
35
- * @param {Object} pageMargins - an object defining top, left, right and bottom margins
36
- */
37
- function LayoutBuilder(pageSize, pageMargins, imageMeasure, svgMeasure) {
38
- this.pageSize = pageSize;
39
- this.pageMargins = pageMargins;
40
- this.tracker = new TraversalTracker();
41
- this.imageMeasure = imageMeasure;
42
- this.svgMeasure = svgMeasure;
43
- this.tableLayouts = {};
44
- }
45
-
46
- LayoutBuilder.prototype.registerTableLayouts = function (tableLayouts) {
47
- this.tableLayouts = pack(this.tableLayouts, tableLayouts);
48
- };
49
-
50
- /**
51
- * Executes layout engine on document-definition-object and creates an array of pages
52
- * containing positioned Blocks, Lines and inlines
53
- *
54
- * @param {Object} docStructure document-definition-object
55
- * @param {Object} fontProvider font provider
56
- * @param {Object} styleDictionary dictionary with style definitions
57
- * @param {Object} defaultStyle default style definition
58
- * @return {Array} an array of pages
59
- */
60
- LayoutBuilder.prototype.layoutDocument = function (docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark, pageBreakBeforeFct) {
61
-
62
- function addPageBreaksIfNecessary(linearNodeList, pages) {
63
-
64
- if (!isFunction(pageBreakBeforeFct)) {
65
- return false;
66
- }
67
-
68
- linearNodeList = linearNodeList.filter(function (node) {
69
- return node.positions.length > 0;
70
- });
71
-
72
- linearNodeList.forEach(function (node) {
73
- var nodeInfo = {};
74
- [
75
- 'id', 'text', 'ul', 'ol', 'table', 'image', 'qr', 'canvas', 'svg', 'columns',
76
- 'headlineLevel', 'style', 'pageBreak', 'pageOrientation',
77
- 'width', 'height'
78
- ].forEach(function (key) {
79
- if (node[key] !== undefined) {
80
- nodeInfo[key] = node[key];
81
- }
82
- });
83
- nodeInfo.startPosition = node.positions[0];
84
- nodeInfo.pageNumbers = Array.from(new Set(node.positions.map(function (node) { return node.pageNumber; })));
85
- nodeInfo.pages = pages.length;
86
- nodeInfo.stack = isArray(node.stack);
87
-
88
- node.nodeInfo = nodeInfo;
89
- });
90
-
91
- for (var index = 0; index < linearNodeList.length; index++) {
92
- var node = linearNodeList[index];
93
- if (node.pageBreak !== 'before' && !node.pageBreakCalculated) {
94
- node.pageBreakCalculated = true;
95
- var pageNumber = node.nodeInfo.pageNumbers[0];
96
- var followingNodesOnPage = [];
97
- var nodesOnNextPage = [];
98
- var previousNodesOnPage = [];
99
- if (pageBreakBeforeFct.length > 1) {
100
- for (var ii = index + 1, l = linearNodeList.length; ii < l; ii++) {
101
- if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
102
- followingNodesOnPage.push(linearNodeList[ii].nodeInfo);
103
- }
104
- if (pageBreakBeforeFct.length > 2 && linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber + 1) > -1) {
105
- nodesOnNextPage.push(linearNodeList[ii].nodeInfo);
106
- }
107
- }
108
- }
109
- if (pageBreakBeforeFct.length > 3) {
110
- for (var ii = 0; ii < index; ii++) {
111
- if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
112
- previousNodesOnPage.push(linearNodeList[ii].nodeInfo);
113
- }
114
- }
115
- }
116
- if (pageBreakBeforeFct(node.nodeInfo, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage)) {
117
- node.pageBreak = 'before';
118
- return true;
119
- }
120
- }
121
- }
122
-
123
- return false;
124
- }
125
-
126
- this.docPreprocessor = new DocPreprocessor();
127
- this.docMeasure = new DocMeasure(fontProvider, styleDictionary, defaultStyle, this.imageMeasure, this.svgMeasure, this.tableLayouts, images);
128
-
129
-
130
- function resetXYs(result) {
131
- result.linearNodeList.forEach(function (node) {
132
- node.resetXY();
133
- });
134
- }
135
-
136
- var result = this.tryLayoutDocument(docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark);
137
- while (addPageBreaksIfNecessary(result.linearNodeList, result.pages)) {
138
- resetXYs(result);
139
- result = this.tryLayoutDocument(docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark);
140
- }
141
-
142
- return result.pages;
143
- };
144
-
145
- LayoutBuilder.prototype.tryLayoutDocument = function (docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark, pageBreakBeforeFct) {
146
-
147
- this.linearNodeList = [];
148
- docStructure = this.docPreprocessor.preprocessDocument(docStructure);
149
- docStructure = this.docMeasure.measureDocument(docStructure);
150
-
151
- this.writer = new PageElementWriter(
152
- new DocumentContext(this.pageSize, this.pageMargins), this.tracker);
153
-
154
- var _this = this;
155
- this.writer.context().tracker.startTracking('pageAdded', function () {
156
- _this.addBackground(background);
157
- });
158
-
159
- this.addBackground(background);
160
- this.processNode(docStructure);
161
- this.addHeadersAndFooters(header, footer);
162
- if (watermark != null) {
163
- this.addWatermark(watermark, fontProvider, defaultStyle);
164
- }
165
-
166
- return { pages: this.writer.context().pages, linearNodeList: this.linearNodeList };
167
- };
168
-
169
-
170
- LayoutBuilder.prototype.addBackground = function (background) {
171
- var backgroundGetter = isFunction(background) ? background : function () {
172
- return background;
173
- };
174
-
175
- var context = this.writer.context();
176
- var pageSize = context.getCurrentPage().pageSize;
177
-
178
- var pageBackground = backgroundGetter(context.page + 1, pageSize);
179
-
180
- if (pageBackground) {
181
- this.writer.beginUnbreakableBlock(pageSize.width, pageSize.height);
182
- pageBackground = this.docPreprocessor.preprocessDocument(pageBackground);
183
- this.processNode(this.docMeasure.measureDocument(pageBackground));
184
- this.writer.commitUnbreakableBlock(0, 0);
185
- context.backgroundLength[context.page] += pageBackground.positions.length;
186
- }
187
- };
188
-
189
- LayoutBuilder.prototype.addStaticRepeatable = function (headerOrFooter, sizeFunction) {
190
- this.addDynamicRepeatable(function () {
191
- return JSON.parse(JSON.stringify(headerOrFooter)); // copy to new object
192
- }, sizeFunction);
193
- };
194
-
195
- LayoutBuilder.prototype.addDynamicRepeatable = function (nodeGetter, sizeFunction) {
196
- var pages = this.writer.context().pages;
197
-
198
- for (var pageIndex = 0, l = pages.length; pageIndex < l; pageIndex++) {
199
- this.writer.context().page = pageIndex;
200
-
201
- var node = nodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
202
-
203
- if (node) {
204
- var sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
205
- this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
206
- node = this.docPreprocessor.preprocessDocument(node);
207
- this.processNode(this.docMeasure.measureDocument(node));
208
- this.writer.commitUnbreakableBlock(sizes.x, sizes.y);
209
- }
210
- }
211
- };
212
-
213
- LayoutBuilder.prototype.addHeadersAndFooters = function (header, footer) {
214
- var headerSizeFct = function (pageSize, pageMargins) {
215
- return {
216
- x: 0,
217
- y: 0,
218
- width: pageSize.width,
219
- height: pageMargins.top
220
- };
221
- };
222
-
223
- var footerSizeFct = function (pageSize, pageMargins) {
224
- return {
225
- x: 0,
226
- y: pageSize.height - pageMargins.bottom,
227
- width: pageSize.width,
228
- height: pageMargins.bottom
229
- };
230
- };
231
-
232
- if (isFunction(header)) {
233
- this.addDynamicRepeatable(header, headerSizeFct);
234
- } else if (header) {
235
- this.addStaticRepeatable(header, headerSizeFct);
236
- }
237
-
238
- if (isFunction(footer)) {
239
- this.addDynamicRepeatable(footer, footerSizeFct);
240
- } else if (footer) {
241
- this.addStaticRepeatable(footer, footerSizeFct);
242
- }
243
- };
244
-
245
- LayoutBuilder.prototype.addWatermark = function (watermark, fontProvider, defaultStyle) {
246
- if (isString(watermark)) {
247
- watermark = { 'text': watermark };
248
- }
249
-
250
- if (!watermark.text) { // empty watermark text
251
- return;
252
- }
253
-
254
- watermark.font = watermark.font || defaultStyle.font || 'Roboto';
255
- watermark.fontSize = watermark.fontSize || 'auto';
256
- watermark.color = watermark.color || 'black';
257
- watermark.opacity = isNumber(watermark.opacity) ? watermark.opacity : 0.6;
258
- watermark.bold = watermark.bold || false;
259
- watermark.italics = watermark.italics || false;
260
- watermark.angle = !isUndefined(watermark.angle) && !isNull(watermark.angle) ? watermark.angle : null;
261
-
262
- if (watermark.angle === null) {
263
- watermark.angle = Math.atan2(this.pageSize.height, this.pageSize.width) * -180 / Math.PI;
264
- }
265
-
266
- if (watermark.fontSize === 'auto') {
267
- watermark.fontSize = getWatermarkFontSize(this.pageSize, watermark, fontProvider);
268
- }
269
-
270
- var watermarkObject = {
271
- text: watermark.text,
272
- font: fontProvider.provideFont(watermark.font, watermark.bold, watermark.italics),
273
- fontSize: watermark.fontSize,
274
- color: watermark.color,
275
- opacity: watermark.opacity,
276
- angle: watermark.angle
277
- };
278
-
279
- watermarkObject._size = getWatermarkSize(watermark, fontProvider);
280
-
281
- var pages = this.writer.context().pages;
282
- for (var i = 0, l = pages.length; i < l; i++) {
283
- pages[i].watermark = watermarkObject;
284
- }
285
-
286
- function getWatermarkSize(watermark, fontProvider) {
287
- var textTools = new TextTools(fontProvider);
288
- var styleContextStack = new StyleContextStack(null, { font: watermark.font, bold: watermark.bold, italics: watermark.italics });
289
-
290
- styleContextStack.push({
291
- fontSize: watermark.fontSize
292
- });
293
-
294
- var size = textTools.sizeOfString(watermark.text, styleContextStack);
295
- var rotatedSize = textTools.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
296
-
297
- return { size: size, rotatedSize: rotatedSize };
298
- }
299
-
300
- function getWatermarkFontSize(pageSize, watermark, fontProvider) {
301
- var textTools = new TextTools(fontProvider);
302
- var styleContextStack = new StyleContextStack(null, { font: watermark.font, bold: watermark.bold, italics: watermark.italics });
303
- var rotatedSize;
304
-
305
- /**
306
- * Binary search the best font size.
307
- * Initial bounds [0, 1000]
308
- * Break when range < 1
309
- */
310
- var a = 0;
311
- var b = 1000;
312
- var c = (a + b) / 2;
313
- while (Math.abs(a - b) > 1) {
314
- styleContextStack.push({
315
- fontSize: c
316
- });
317
- rotatedSize = textTools.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
318
- if (rotatedSize.width > pageSize.width) {
319
- b = c;
320
- c = (a + b) / 2;
321
- } else if (rotatedSize.width < pageSize.width) {
322
- if (rotatedSize.height > pageSize.height) {
323
- b = c;
324
- c = (a + b) / 2;
325
- } else {
326
- a = c;
327
- c = (a + b) / 2;
328
- }
329
- }
330
- styleContextStack.pop();
331
- }
332
- /*
333
- End binary search
334
- */
335
- return c;
336
- }
337
- };
338
-
339
- function decorateNode(node) {
340
- var x = node.x, y = node.y;
341
- node.positions = [];
342
-
343
- if (isArray(node.canvas)) {
344
- node.canvas.forEach(function (vector) {
345
- var x = vector.x, y = vector.y, x1 = vector.x1, y1 = vector.y1, x2 = vector.x2, y2 = vector.y2;
346
- vector.resetXY = function () {
347
- vector.x = x;
348
- vector.y = y;
349
- vector.x1 = x1;
350
- vector.y1 = y1;
351
- vector.x2 = x2;
352
- vector.y2 = y2;
353
- };
354
- });
355
- }
356
-
357
- node.resetXY = function () {
358
- node.x = x;
359
- node.y = y;
360
- if (isArray(node.canvas)) {
361
- node.canvas.forEach(function (vector) {
362
- vector.resetXY();
363
- });
364
- }
365
- };
366
- }
367
-
368
- LayoutBuilder.prototype.processNode = function (node) {
369
- var self = this;
370
-
371
- this.linearNodeList.push(node);
372
- decorateNode(node);
373
-
374
- applyMargins(function () {
375
- var unbreakable = node.unbreakable;
376
- if (unbreakable) {
377
- self.writer.beginUnbreakableBlock();
378
- }
379
-
380
- var absPosition = node.absolutePosition;
381
- if (absPosition) {
382
- self.writer.context().beginDetachedBlock();
383
- self.writer.context().moveTo(absPosition.x || 0, absPosition.y || 0);
384
- }
385
-
386
- var relPosition = node.relativePosition;
387
- if (relPosition) {
388
- self.writer.context().beginDetachedBlock();
389
- self.writer.context().moveToRelative(relPosition.x || 0, relPosition.y || 0);
390
- }
391
-
392
- if (node.stack) {
393
- self.processVerticalContainer(node);
394
- } else if (node.columns) {
395
- self.processColumns(node);
396
- } else if (node.ul) {
397
- self.processList(false, node);
398
- } else if (node.ol) {
399
- self.processList(true, node);
400
- } else if (node.table) {
401
- self.processTable(node);
402
- } else if (node.text !== undefined) {
403
- self.processLeaf(node);
404
- } else if (node.toc) {
405
- self.processToc(node);
406
- } else if (node.image) {
407
- self.processImage(node);
408
- } else if (node.svg) {
409
- self.processSVG(node);
410
- } else if (node.canvas) {
411
- self.processCanvas(node);
412
- } else if (node.qr) {
413
- self.processQr(node);
414
- } else if (!node._span) {
415
- throw 'Unrecognized document structure: ' + JSON.stringify(node, fontStringify);
416
- }
417
-
418
- if (absPosition || relPosition) {
419
- self.writer.context().endDetachedBlock();
420
- }
421
-
422
- if (unbreakable) {
423
- self.writer.commitUnbreakableBlock();
424
- }
425
- });
426
-
427
- function applyMargins(callback) {
428
- var margin = node._margin;
429
-
430
- if (node.pageBreak === 'before') {
431
- self.writer.moveToNextPage(node.pageOrientation);
432
- } else if (node.pageBreak === 'beforeOdd') {
433
- self.writer.moveToNextPage(node.pageOrientation);
434
- if ((self.writer.context().page + 1) % 2 === 1) {
435
- self.writer.moveToNextPage(node.pageOrientation);
436
- }
437
- } else if (node.pageBreak === 'beforeEven') {
438
- self.writer.moveToNextPage(node.pageOrientation);
439
- if ((self.writer.context().page + 1) % 2 === 0) {
440
- self.writer.moveToNextPage(node.pageOrientation);
441
- }
442
- }
443
-
444
- if (margin) {
445
- self.writer.context().moveDown(margin[1]);
446
- self.writer.context().addMargin(margin[0], margin[2]);
447
- }
448
-
449
- callback();
450
-
451
- if (margin) {
452
- self.writer.context().addMargin(-margin[0], -margin[2]);
453
- self.writer.context().moveDown(margin[3]);
454
- }
455
-
456
- if (node.pageBreak === 'after') {
457
- self.writer.moveToNextPage(node.pageOrientation);
458
- } else if (node.pageBreak === 'afterOdd') {
459
- self.writer.moveToNextPage(node.pageOrientation);
460
- if ((self.writer.context().page + 1) % 2 === 1) {
461
- self.writer.moveToNextPage(node.pageOrientation);
462
- }
463
- } else if (node.pageBreak === 'afterEven') {
464
- self.writer.moveToNextPage(node.pageOrientation);
465
- if ((self.writer.context().page + 1) % 2 === 0) {
466
- self.writer.moveToNextPage(node.pageOrientation);
467
- }
468
- }
469
- }
470
- };
471
-
472
- // vertical container
473
- LayoutBuilder.prototype.processVerticalContainer = function (node) {
474
- var self = this;
475
- node.stack.forEach(function (item) {
476
- self.processNode(item);
477
- addAll(node.positions, item.positions);
478
-
479
- //TODO: paragraph gap
480
- });
481
- };
482
-
483
- // columns
484
- LayoutBuilder.prototype.processColumns = function (columnNode) {
485
- var columns = columnNode.columns;
486
- var availableWidth = this.writer.context().availableWidth;
487
- var gaps = gapArray(columnNode._gap);
488
-
489
- if (gaps) {
490
- availableWidth -= (gaps.length - 1) * columnNode._gap;
491
- }
492
-
493
- ColumnCalculator.buildColumnWidths(columns, availableWidth);
494
- var result = this.processRow(columns, columns, gaps);
495
- addAll(columnNode.positions, result.positions);
496
-
497
-
498
- function gapArray(gap) {
499
- if (!gap) {
500
- return null;
501
- }
502
-
503
- var gaps = [];
504
- gaps.push(0);
505
-
506
- for (var i = columns.length - 1; i > 0; i--) {
507
- gaps.push(gap);
508
- }
509
-
510
- return gaps;
511
- }
512
- };
513
-
514
- LayoutBuilder.prototype.findStartingSpanCell = function (arr, i) {
515
- let requiredColspan = 1;
516
- for (let index = i - 1; index >= 0; index--) {
517
- if (!arr[index]._span) {
518
- if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
519
- return arr[index];
520
- } else {
521
- return null;
522
- }
523
- }
524
- requiredColspan++;
525
- }
526
- return null;
527
- }
528
-
529
- LayoutBuilder.prototype.processRow = function (columns, widths, gaps, tableBody, tableRow, height) {
530
- var self = this;
531
- var pageBreaks = [], positions = [];
532
-
533
- this.tracker.auto('pageChanged', storePageBreakData, function () {
534
- widths = widths || columns;
535
-
536
- self.writer.context().beginColumnGroup();
537
-
538
- for (var i = 0, l = columns.length; i < l; i++) {
539
- var column = columns[i];
540
- var width = widths[i]._calcWidth;
541
- var leftOffset = colLeftOffset(i);
542
-
543
- if (column.colSpan && column.colSpan > 1) {
544
- for (var j = 1; j < column.colSpan; j++) {
545
- width += widths[++i]._calcWidth + gaps[i];
546
- }
547
- }
548
-
549
- // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
550
- var endingCell = getEndingCell(column, i);
551
- if (endingCell) {
552
- // We store a reference of the ending cell in the first cell of the rowspan
553
- column._endingCell = endingCell;
554
- }
555
-
556
- // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
557
- var startingSpanCell = self.findStartingSpanCell(columns, i);
558
- var endingSpanCell = null;
559
- if (startingSpanCell && startingSpanCell._endingCell) {
560
- // Reference to the last cell of the rowspan
561
- endingSpanCell = startingSpanCell._endingCell;
562
- }
563
-
564
- // We pass the endingSpanCell reference to store the context just after processing rowspan cell
565
- self.writer.context().beginColumn(width, leftOffset, endingSpanCell);
566
- if (!column._span) {
567
- self.processNode(column);
568
- addAll(positions, column.positions);
569
- } else if (column._columnEndingContext) {
570
- // row-span ending
571
- // Recover the context after processing the rowspanned cell
572
- self.writer.context().markEnding(column);
573
- }
574
- }
575
-
576
- // Check if last cell is part of a span
577
- var endingSpanCell = null;
578
- var lastColumn = columns.length > 0 ? columns[columns.length - 1] : null;
579
- if (lastColumn) {
580
- // Previous column cell has a rowspan
581
- if (lastColumn._endingCell) {
582
- endingSpanCell = lastColumn._endingCell;
583
- // Previous column cell is part of a span
584
- } else if (lastColumn._span === true) {
585
- // We get the cell that started the span where we set a reference to the ending cell
586
- var startingSpanCell = self.findStartingSpanCell(columns, columns.length);
587
- if (startingSpanCell) {
588
- // Context will be stored here (ending cell)
589
- endingSpanCell = startingSpanCell._endingCell;
590
- }
591
- }
592
- }
593
-
594
- self.writer.context().completeColumnGroup(height, endingSpanCell);
595
- });
596
-
597
- return { pageBreaks: pageBreaks, positions: positions };
598
-
599
- function storePageBreakData(data) {
600
- var pageDesc;
601
-
602
- for (var i = 0, l = pageBreaks.length; i < l; i++) {
603
- var desc = pageBreaks[i];
604
- if (desc.prevPage === data.prevPage) {
605
- pageDesc = desc;
606
- break;
607
- }
608
- }
609
-
610
- if (!pageDesc) {
611
- pageDesc = data;
612
- pageBreaks.push(pageDesc);
613
- }
614
- pageDesc.prevY = Math.max(pageDesc.prevY, data.prevY);
615
- pageDesc.y = Math.min(pageDesc.y, data.y);
616
- }
617
-
618
- function colLeftOffset(i) {
619
- if (gaps && gaps.length > i) {
620
- return gaps[i];
621
- }
622
- return 0;
623
- }
624
-
625
- function getEndingCell(column, columnIndex) {
626
- if (column.rowSpan && column.rowSpan > 1) {
627
- var endingRow = tableRow + column.rowSpan - 1;
628
- if (endingRow >= tableBody.length) {
629
- throw 'Row span for column ' + columnIndex + ' (with indexes starting from 0) exceeded row count';
630
- }
631
- return tableBody[endingRow][columnIndex];
632
- }
633
-
634
- return null;
635
- }
636
- };
637
-
638
- // lists
639
- LayoutBuilder.prototype.processList = function (orderedList, node) {
640
- var self = this,
641
- items = orderedList ? node.ol : node.ul,
642
- gapSize = node._gapSize;
643
-
644
- this.writer.context().addMargin(gapSize.width);
645
-
646
- var nextMarker;
647
- this.tracker.auto('lineAdded', addMarkerToFirstLeaf, function () {
648
- items.forEach(function (item) {
649
- nextMarker = item.listMarker;
650
- self.processNode(item);
651
- addAll(node.positions, item.positions);
652
- });
653
- });
654
-
655
- this.writer.context().addMargin(-gapSize.width);
656
-
657
- function addMarkerToFirstLeaf(line) {
658
- // I'm not very happy with the way list processing is implemented
659
- // (both code and algorithm should be rethinked)
660
- if (nextMarker) {
661
- var marker = nextMarker;
662
- nextMarker = null;
663
-
664
- if (marker.canvas) {
665
- var vector = marker.canvas[0];
666
-
667
- offsetVector(vector, -marker._minWidth, 0);
668
- self.writer.addVector(vector);
669
- } else if (marker._inlines) {
670
- var markerLine = new Line(self.pageSize.width);
671
- markerLine.addInline(marker._inlines[0]);
672
- markerLine.x = -marker._minWidth;
673
- markerLine.y = line.getAscenderHeight() - markerLine.getAscenderHeight();
674
- self.writer.addLine(markerLine, true);
675
- }
676
- }
677
- }
678
- };
679
-
680
- // tables
681
- LayoutBuilder.prototype.processTable = function (tableNode) {
682
- var processor = new TableProcessor(tableNode);
683
-
684
- processor.beginTable(this.writer);
685
-
686
- var rowHeights = tableNode.table.heights;
687
- for (var i = 0, l = tableNode.table.body.length; i < l; i++) {
688
- processor.beginRow(i, this.writer);
689
-
690
- var height;
691
- if (isFunction(rowHeights)) {
692
- height = rowHeights(i);
693
- } else if (isArray(rowHeights)) {
694
- height = rowHeights[i];
695
- } else {
696
- height = rowHeights;
697
- }
698
-
699
- if (height === 'auto') {
700
- height = undefined;
701
- }
702
-
703
- var result = this.processRow(tableNode.table.body[i], tableNode.table.widths, tableNode._offsets.offsets, tableNode.table.body, i, height);
704
- addAll(tableNode.positions, result.positions);
705
-
706
- processor.endRow(i, this.writer, result.pageBreaks);
707
- }
708
-
709
- processor.endTable(this.writer);
710
- };
711
-
712
- // leafs (texts)
713
- LayoutBuilder.prototype.processLeaf = function (node) {
714
- var line = this.buildNextLine(node);
715
- if (line && (node.tocItem || node.id)) {
716
- line._node = node;
717
- }
718
- var currentHeight = (line) ? line.getHeight() : 0;
719
- var maxHeight = node.maxHeight || -1;
720
-
721
- if (line) {
722
- var nodeId = getNodeId(node);
723
- if (nodeId) {
724
- line.id = nodeId;
725
- }
726
- }
727
-
728
- if (node._tocItemRef) {
729
- line._pageNodeRef = node._tocItemRef;
730
- }
731
-
732
- if (node._pageRef) {
733
- line._pageNodeRef = node._pageRef._nodeRef;
734
- }
735
-
736
- if (line && line.inlines && isArray(line.inlines)) {
737
- for (var i = 0, l = line.inlines.length; i < l; i++) {
738
- if (line.inlines[i]._tocItemRef) {
739
- line.inlines[i]._pageNodeRef = line.inlines[i]._tocItemRef;
740
- }
741
-
742
- if (line.inlines[i]._pageRef) {
743
- line.inlines[i]._pageNodeRef = line.inlines[i]._pageRef._nodeRef;
744
- }
745
- }
746
- }
747
-
748
- while (line && (maxHeight === -1 || currentHeight < maxHeight)) {
749
- var positions = this.writer.addLine(line);
750
- node.positions.push(positions);
751
- line = this.buildNextLine(node);
752
- if (line) {
753
- currentHeight += line.getHeight();
754
- }
755
- }
756
- };
757
-
758
- LayoutBuilder.prototype.processToc = function (node) {
759
- if (node.toc.title) {
760
- this.processNode(node.toc.title);
761
- }
762
- if (node.toc._table) {
763
- this.processNode(node.toc._table);
764
- }
765
- };
766
-
767
- LayoutBuilder.prototype.buildNextLine = function (textNode) {
768
-
769
- function cloneInline(inline) {
770
- var newInline = inline.constructor();
771
- for (var key in inline) {
772
- newInline[key] = inline[key];
773
- }
774
- return newInline;
775
- }
776
-
777
- if (!textNode._inlines || textNode._inlines.length === 0) {
778
- return null;
779
- }
780
-
781
- var line = new Line(this.writer.context().availableWidth);
782
- var textTools = new TextTools(null);
783
-
784
- var isForceContinue = false;
785
- while (textNode._inlines && textNode._inlines.length > 0 &&
786
- (line.hasEnoughSpaceForInline(textNode._inlines[0], textNode._inlines.slice(1)) || isForceContinue)) {
787
- var isHardWrap = false;
788
- var inline = textNode._inlines.shift();
789
- isForceContinue = false;
790
-
791
- if (!inline.noWrap && inline.text.length > 1 && inline.width > line.getAvailableWidth()) {
792
- var widthPerChar = inline.width / inline.text.length;
793
- var maxChars = Math.floor(line.getAvailableWidth() / widthPerChar);
794
- if (maxChars < 1) {
795
- maxChars = 1;
796
- }
797
- if (maxChars < inline.text.length) {
798
- var newInline = cloneInline(inline);
799
-
800
- newInline.text = inline.text.substr(maxChars);
801
- inline.text = inline.text.substr(0, maxChars);
802
-
803
- newInline.width = textTools.widthOfString(newInline.text, newInline.font, newInline.fontSize, newInline.characterSpacing, newInline.fontFeatures);
804
- inline.width = textTools.widthOfString(inline.text, inline.font, inline.fontSize, inline.characterSpacing, inline.fontFeatures);
805
-
806
- textNode._inlines.unshift(newInline);
807
- isHardWrap = true;
808
- }
809
- }
810
-
811
- line.addInline(inline);
812
-
813
- isForceContinue = inline.noNewLine && !isHardWrap;
814
- }
815
-
816
- line.lastLineInParagraph = textNode._inlines.length === 0;
817
-
818
- return line;
819
- };
820
-
821
- // images
822
- LayoutBuilder.prototype.processImage = function (node) {
823
- var position = this.writer.addImage(node);
824
- node.positions.push(position);
825
- };
826
-
827
- LayoutBuilder.prototype.processSVG = function (node) {
828
- var position = this.writer.addSVG(node);
829
- node.positions.push(position);
830
- };
831
-
832
- LayoutBuilder.prototype.processCanvas = function (node) {
833
- var height = node._minHeight;
834
-
835
- if (node.absolutePosition === undefined && this.writer.context().availableHeight < height) {
836
- // TODO: support for canvas larger than a page
837
- // TODO: support for other overflow methods
838
-
839
- this.writer.moveToNextPage();
840
- }
841
-
842
- this.writer.alignCanvas(node);
843
-
844
- node.canvas.forEach(function (vector) {
845
- var position = this.writer.addVector(vector);
846
- node.positions.push(position);
847
- }, this);
848
-
849
- this.writer.context().moveDown(height);
850
- };
851
-
852
- LayoutBuilder.prototype.processQr = function (node) {
853
- var position = this.writer.addQr(node);
854
- node.positions.push(position);
855
- };
856
-
857
- module.exports = LayoutBuilder;
1
+ 'use strict';
2
+
3
+ var TraversalTracker = require('./traversalTracker');
4
+ var DocPreprocessor = require('./docPreprocessor');
5
+ var DocMeasure = require('./docMeasure');
6
+ var DocumentContext = require('./documentContext');
7
+ var PageElementWriter = require('./pageElementWriter');
8
+ var ColumnCalculator = require('./columnCalculator');
9
+ var TableProcessor = require('./tableProcessor');
10
+ var Line = require('./line');
11
+ var isString = require('./helpers').isString;
12
+ var isArray = require('./helpers').isArray;
13
+ var isUndefined = require('./helpers').isUndefined;
14
+ var isNull = require('./helpers').isNull;
15
+ var pack = require('./helpers').pack;
16
+ var offsetVector = require('./helpers').offsetVector;
17
+ var fontStringify = require('./helpers').fontStringify;
18
+ var getNodeId = require('./helpers').getNodeId;
19
+ var isFunction = require('./helpers').isFunction;
20
+ var TextTools = require('./textTools');
21
+ var StyleContextStack = require('./styleContextStack');
22
+ var isNumber = require('./helpers').isNumber;
23
+
24
+ function addAll(target, otherArray) {
25
+ otherArray.forEach(function (item) {
26
+ target.push(item);
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Creates an instance of LayoutBuilder - layout engine which turns document-definition-object
32
+ * into a set of pages, lines, inlines and vectors ready to be rendered into a PDF
33
+ *
34
+ * @param {Object} pageSize - an object defining page width and height
35
+ * @param {Object} pageMargins - an object defining top, left, right and bottom margins
36
+ */
37
+ function LayoutBuilder(pageSize, pageMargins, imageMeasure, svgMeasure) {
38
+ this.pageSize = pageSize;
39
+ this.pageMargins = pageMargins;
40
+ this.tracker = new TraversalTracker();
41
+ this.imageMeasure = imageMeasure;
42
+ this.svgMeasure = svgMeasure;
43
+ this.tableLayouts = {};
44
+ this.nestedLevel = 0;
45
+ }
46
+
47
+ LayoutBuilder.prototype.registerTableLayouts = function (tableLayouts) {
48
+ this.tableLayouts = pack(this.tableLayouts, tableLayouts);
49
+ };
50
+
51
+ /**
52
+ * Executes layout engine on document-definition-object and creates an array of pages
53
+ * containing positioned Blocks, Lines and inlines
54
+ *
55
+ * @param {Object} docStructure document-definition-object
56
+ * @param {Object} fontProvider font provider
57
+ * @param {Object} styleDictionary dictionary with style definitions
58
+ * @param {Object} defaultStyle default style definition
59
+ * @return {Array} an array of pages
60
+ */
61
+ LayoutBuilder.prototype.layoutDocument = function (docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark, pageBreakBeforeFct) {
62
+
63
+ function addPageBreaksIfNecessary(linearNodeList, pages) {
64
+
65
+ if (!isFunction(pageBreakBeforeFct)) {
66
+ return false;
67
+ }
68
+
69
+ linearNodeList = linearNodeList.filter(function (node) {
70
+ return node.positions.length > 0;
71
+ });
72
+
73
+ linearNodeList.forEach(function (node) {
74
+ var nodeInfo = {};
75
+ [
76
+ 'id', 'text', 'ul', 'ol', 'table', 'image', 'qr', 'canvas', 'svg', 'columns',
77
+ 'headlineLevel', 'style', 'pageBreak', 'pageOrientation',
78
+ 'width', 'height'
79
+ ].forEach(function (key) {
80
+ if (node[key] !== undefined) {
81
+ nodeInfo[key] = node[key];
82
+ }
83
+ });
84
+ nodeInfo.startPosition = node.positions[0];
85
+ nodeInfo.pageNumbers = Array.from(new Set(node.positions.map(function (node) { return node.pageNumber; })));
86
+ nodeInfo.pages = pages.length;
87
+ nodeInfo.stack = isArray(node.stack);
88
+
89
+ node.nodeInfo = nodeInfo;
90
+ });
91
+
92
+ for (var index = 0; index < linearNodeList.length; index++) {
93
+ var node = linearNodeList[index];
94
+ if (node.pageBreak !== 'before' && !node.pageBreakCalculated) {
95
+ node.pageBreakCalculated = true;
96
+ var pageNumber = node.nodeInfo.pageNumbers[0];
97
+ var followingNodesOnPage = [];
98
+ var nodesOnNextPage = [];
99
+ var previousNodesOnPage = [];
100
+ if (pageBreakBeforeFct.length > 1) {
101
+ for (var ii = index + 1, l = linearNodeList.length; ii < l; ii++) {
102
+ if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
103
+ followingNodesOnPage.push(linearNodeList[ii].nodeInfo);
104
+ }
105
+ if (pageBreakBeforeFct.length > 2 && linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber + 1) > -1) {
106
+ nodesOnNextPage.push(linearNodeList[ii].nodeInfo);
107
+ }
108
+ }
109
+ }
110
+ if (pageBreakBeforeFct.length > 3) {
111
+ for (var ii = 0; ii < index; ii++) {
112
+ if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
113
+ previousNodesOnPage.push(linearNodeList[ii].nodeInfo);
114
+ }
115
+ }
116
+ }
117
+ if (pageBreakBeforeFct(node.nodeInfo, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage)) {
118
+ node.pageBreak = 'before';
119
+ return true;
120
+ }
121
+ }
122
+ }
123
+
124
+ return false;
125
+ }
126
+
127
+ this.docPreprocessor = new DocPreprocessor();
128
+ this.docMeasure = new DocMeasure(fontProvider, styleDictionary, defaultStyle, this.imageMeasure, this.svgMeasure, this.tableLayouts, images);
129
+
130
+
131
+ function resetXYs(result) {
132
+ result.linearNodeList.forEach(function (node) {
133
+ node.resetXY();
134
+ });
135
+ }
136
+
137
+ var result = this.tryLayoutDocument(docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark);
138
+ while (addPageBreaksIfNecessary(result.linearNodeList, result.pages)) {
139
+ resetXYs(result);
140
+ result = this.tryLayoutDocument(docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark);
141
+ }
142
+
143
+ return result.pages;
144
+ };
145
+
146
+ LayoutBuilder.prototype.tryLayoutDocument = function (docStructure, fontProvider, styleDictionary, defaultStyle, background, header, footer, images, watermark, pageBreakBeforeFct) {
147
+
148
+ this.linearNodeList = [];
149
+ docStructure = this.docPreprocessor.preprocessDocument(docStructure);
150
+ docStructure = this.docMeasure.measureDocument(docStructure);
151
+
152
+ this.writer = new PageElementWriter(
153
+ new DocumentContext(this.pageSize, this.pageMargins), this.tracker);
154
+
155
+ var _this = this;
156
+ this.writer.context().tracker.startTracking('pageAdded', function () {
157
+ _this.addBackground(background);
158
+ });
159
+
160
+ this.addBackground(background);
161
+ this.processNode(docStructure);
162
+ this.addHeadersAndFooters(header, footer);
163
+ if (watermark != null) {
164
+ this.addWatermark(watermark, fontProvider, defaultStyle);
165
+ }
166
+
167
+ return { pages: this.writer.context().pages, linearNodeList: this.linearNodeList };
168
+ };
169
+
170
+
171
+ LayoutBuilder.prototype.addBackground = function (background) {
172
+ var backgroundGetter = isFunction(background) ? background : function () {
173
+ return background;
174
+ };
175
+
176
+ var context = this.writer.context();
177
+ var pageSize = context.getCurrentPage().pageSize;
178
+
179
+ var pageBackground = backgroundGetter(context.page + 1, pageSize);
180
+
181
+ if (pageBackground) {
182
+ this.writer.beginUnbreakableBlock(pageSize.width, pageSize.height);
183
+ pageBackground = this.docPreprocessor.preprocessDocument(pageBackground);
184
+ this.processNode(this.docMeasure.measureDocument(pageBackground));
185
+ this.writer.commitUnbreakableBlock(0, 0);
186
+ context.backgroundLength[context.page] += pageBackground.positions.length;
187
+ }
188
+ };
189
+
190
+ LayoutBuilder.prototype.addStaticRepeatable = function (headerOrFooter, sizeFunction) {
191
+ this.addDynamicRepeatable(function () {
192
+ return JSON.parse(JSON.stringify(headerOrFooter)); // copy to new object
193
+ }, sizeFunction);
194
+ };
195
+
196
+ LayoutBuilder.prototype.addDynamicRepeatable = function (nodeGetter, sizeFunction) {
197
+ var pages = this.writer.context().pages;
198
+
199
+ for (var pageIndex = 0, l = pages.length; pageIndex < l; pageIndex++) {
200
+ this.writer.context().page = pageIndex;
201
+
202
+ var node = nodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
203
+
204
+ if (node) {
205
+ var sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.pageMargins);
206
+ this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
207
+ node = this.docPreprocessor.preprocessDocument(node);
208
+ this.processNode(this.docMeasure.measureDocument(node));
209
+ this.writer.commitUnbreakableBlock(sizes.x, sizes.y);
210
+ }
211
+ }
212
+ };
213
+
214
+ LayoutBuilder.prototype.addHeadersAndFooters = function (header, footer) {
215
+ var headerSizeFct = function (pageSize, pageMargins) {
216
+ return {
217
+ x: 0,
218
+ y: 0,
219
+ width: pageSize.width,
220
+ height: pageMargins.top
221
+ };
222
+ };
223
+
224
+ var footerSizeFct = function (pageSize, pageMargins) {
225
+ return {
226
+ x: 0,
227
+ y: pageSize.height - pageMargins.bottom,
228
+ width: pageSize.width,
229
+ height: pageMargins.bottom
230
+ };
231
+ };
232
+
233
+ if (isFunction(header)) {
234
+ this.addDynamicRepeatable(header, headerSizeFct);
235
+ } else if (header) {
236
+ this.addStaticRepeatable(header, headerSizeFct);
237
+ }
238
+
239
+ if (isFunction(footer)) {
240
+ this.addDynamicRepeatable(footer, footerSizeFct);
241
+ } else if (footer) {
242
+ this.addStaticRepeatable(footer, footerSizeFct);
243
+ }
244
+ };
245
+
246
+ LayoutBuilder.prototype.addWatermark = function (watermark, fontProvider, defaultStyle) {
247
+ if (isString(watermark)) {
248
+ watermark = { 'text': watermark };
249
+ }
250
+
251
+ if (!watermark.text) { // empty watermark text
252
+ return;
253
+ }
254
+
255
+ watermark.font = watermark.font || defaultStyle.font || 'Roboto';
256
+ watermark.fontSize = watermark.fontSize || 'auto';
257
+ watermark.color = watermark.color || 'black';
258
+ watermark.opacity = isNumber(watermark.opacity) ? watermark.opacity : 0.6;
259
+ watermark.bold = watermark.bold || false;
260
+ watermark.italics = watermark.italics || false;
261
+ watermark.angle = !isUndefined(watermark.angle) && !isNull(watermark.angle) ? watermark.angle : null;
262
+
263
+ if (watermark.angle === null) {
264
+ watermark.angle = Math.atan2(this.pageSize.height, this.pageSize.width) * -180 / Math.PI;
265
+ }
266
+
267
+ if (watermark.fontSize === 'auto') {
268
+ watermark.fontSize = getWatermarkFontSize(this.pageSize, watermark, fontProvider);
269
+ }
270
+
271
+ var watermarkObject = {
272
+ text: watermark.text,
273
+ font: fontProvider.provideFont(watermark.font, watermark.bold, watermark.italics),
274
+ fontSize: watermark.fontSize,
275
+ color: watermark.color,
276
+ opacity: watermark.opacity,
277
+ angle: watermark.angle
278
+ };
279
+
280
+ watermarkObject._size = getWatermarkSize(watermark, fontProvider);
281
+
282
+ var pages = this.writer.context().pages;
283
+ for (var i = 0, l = pages.length; i < l; i++) {
284
+ pages[i].watermark = watermarkObject;
285
+ }
286
+
287
+ function getWatermarkSize(watermark, fontProvider) {
288
+ var textTools = new TextTools(fontProvider);
289
+ var styleContextStack = new StyleContextStack(null, { font: watermark.font, bold: watermark.bold, italics: watermark.italics });
290
+
291
+ styleContextStack.push({
292
+ fontSize: watermark.fontSize
293
+ });
294
+
295
+ var size = textTools.sizeOfString(watermark.text, styleContextStack);
296
+ var rotatedSize = textTools.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
297
+
298
+ return { size: size, rotatedSize: rotatedSize };
299
+ }
300
+
301
+ function getWatermarkFontSize(pageSize, watermark, fontProvider) {
302
+ var textTools = new TextTools(fontProvider);
303
+ var styleContextStack = new StyleContextStack(null, { font: watermark.font, bold: watermark.bold, italics: watermark.italics });
304
+ var rotatedSize;
305
+
306
+ /**
307
+ * Binary search the best font size.
308
+ * Initial bounds [0, 1000]
309
+ * Break when range < 1
310
+ */
311
+ var a = 0;
312
+ var b = 1000;
313
+ var c = (a + b) / 2;
314
+ while (Math.abs(a - b) > 1) {
315
+ styleContextStack.push({
316
+ fontSize: c
317
+ });
318
+ rotatedSize = textTools.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
319
+ if (rotatedSize.width > pageSize.width) {
320
+ b = c;
321
+ c = (a + b) / 2;
322
+ } else if (rotatedSize.width < pageSize.width) {
323
+ if (rotatedSize.height > pageSize.height) {
324
+ b = c;
325
+ c = (a + b) / 2;
326
+ } else {
327
+ a = c;
328
+ c = (a + b) / 2;
329
+ }
330
+ }
331
+ styleContextStack.pop();
332
+ }
333
+ /*
334
+ End binary search
335
+ */
336
+ return c;
337
+ }
338
+ };
339
+
340
+ function decorateNode(node) {
341
+ var x = node.x, y = node.y;
342
+ node.positions = [];
343
+
344
+ if (isArray(node.canvas)) {
345
+ node.canvas.forEach(function (vector) {
346
+ var x = vector.x, y = vector.y, x1 = vector.x1, y1 = vector.y1, x2 = vector.x2, y2 = vector.y2;
347
+ vector.resetXY = function () {
348
+ vector.x = x;
349
+ vector.y = y;
350
+ vector.x1 = x1;
351
+ vector.y1 = y1;
352
+ vector.x2 = x2;
353
+ vector.y2 = y2;
354
+ };
355
+ });
356
+ }
357
+
358
+ node.resetXY = function () {
359
+ node.x = x;
360
+ node.y = y;
361
+ if (isArray(node.canvas)) {
362
+ node.canvas.forEach(function (vector) {
363
+ vector.resetXY();
364
+ });
365
+ }
366
+ };
367
+ }
368
+
369
+ LayoutBuilder.prototype.processNode = function (node) {
370
+ var self = this;
371
+
372
+ this.linearNodeList.push(node);
373
+ decorateNode(node);
374
+
375
+ applyMargins(function () {
376
+ var unbreakable = node.unbreakable;
377
+ if (unbreakable) {
378
+ self.writer.beginUnbreakableBlock();
379
+ }
380
+
381
+ var absPosition = node.absolutePosition;
382
+ if (absPosition) {
383
+ self.writer.context().beginDetachedBlock();
384
+ self.writer.context().moveTo(absPosition.x || 0, absPosition.y || 0);
385
+ }
386
+
387
+ var relPosition = node.relativePosition;
388
+ if (relPosition) {
389
+ self.writer.context().beginDetachedBlock();
390
+ self.writer.context().moveToRelative(relPosition.x || 0, relPosition.y || 0);
391
+ }
392
+
393
+ if (node.stack) {
394
+ self.processVerticalContainer(node);
395
+ } else if (node.columns) {
396
+ self.processColumns(node);
397
+ } else if (node.ul) {
398
+ self.processList(false, node);
399
+ } else if (node.ol) {
400
+ self.processList(true, node);
401
+ } else if (node.table) {
402
+ self.processTable(node);
403
+ } else if (node.text !== undefined) {
404
+ self.processLeaf(node);
405
+ } else if (node.toc) {
406
+ self.processToc(node);
407
+ } else if (node.image) {
408
+ self.processImage(node);
409
+ } else if (node.svg) {
410
+ self.processSVG(node);
411
+ } else if (node.canvas) {
412
+ self.processCanvas(node);
413
+ } else if (node.qr) {
414
+ self.processQr(node);
415
+ } else if (!node._span) {
416
+ throw 'Unrecognized document structure: ' + JSON.stringify(node, fontStringify);
417
+ }
418
+
419
+ if (absPosition || relPosition) {
420
+ self.writer.context().endDetachedBlock();
421
+ }
422
+
423
+ if (unbreakable) {
424
+ self.writer.commitUnbreakableBlock();
425
+ }
426
+ });
427
+
428
+ function applyMargins(callback) {
429
+ var margin = node._margin;
430
+
431
+ if (node.pageBreak === 'before') {
432
+ self.writer.moveToNextPage(node.pageOrientation);
433
+ } else if (node.pageBreak === 'beforeOdd') {
434
+ self.writer.moveToNextPage(node.pageOrientation);
435
+ if ((self.writer.context().page + 1) % 2 === 1) {
436
+ self.writer.moveToNextPage(node.pageOrientation);
437
+ }
438
+ } else if (node.pageBreak === 'beforeEven') {
439
+ self.writer.moveToNextPage(node.pageOrientation);
440
+ if ((self.writer.context().page + 1) % 2 === 0) {
441
+ self.writer.moveToNextPage(node.pageOrientation);
442
+ }
443
+ }
444
+
445
+ const isDetachedBlock = node.relativePosition || node.absolutePosition;
446
+
447
+ // Detached nodes have no margins, their position is only determined by 'x' and 'y'
448
+ if (margin && !isDetachedBlock) {
449
+ const availableHeight = self.writer.context().availableHeight;
450
+ // If top margin is bigger than available space, move to next page
451
+ // Necessary for nodes inside tables
452
+ if (availableHeight - margin[1] < 0) {
453
+ // Consume the whole available space
454
+ self.writer.context().moveDown(availableHeight);
455
+ self.writer.moveToNextPage(node.pageOrientation);
456
+ /**
457
+ * TODO - Something to consider:
458
+ * Right now the node starts at the top of next page (after header)
459
+ * Another option would be to apply just the top margin that has not been consumed in the page before
460
+ * It would something like: this.write.context().moveDown(margin[1] - availableHeight)
461
+ */
462
+ } else {
463
+ self.writer.context().moveDown(margin[1]);
464
+ }
465
+
466
+ // Apply lateral margins
467
+ self.writer.context().addMargin(margin[0], margin[2]);
468
+ }
469
+
470
+ callback();
471
+
472
+ // Detached nodes have no margins, their position is only determined by 'x' and 'y'
473
+ if (margin && !isDetachedBlock) {
474
+ const availableHeight = self.writer.context().availableHeight;
475
+ // If bottom margin is bigger than available space, move to next page
476
+ // Necessary for nodes inside tables
477
+ if (availableHeight - margin[3] < 0) {
478
+ self.writer.context().moveDown(availableHeight);
479
+ self.writer.moveToNextPage(node.pageOrientation);
480
+ /**
481
+ * TODO - Something to consider:
482
+ * Right now next node starts at the top of next page (after header)
483
+ * Another option would be to apply the bottom margin that has not been consumed in the next page?
484
+ * It would something like: this.write.context().moveDown(margin[3] - availableHeight)
485
+ */
486
+ } else {
487
+ self.writer.context().moveDown(margin[3]);
488
+ }
489
+
490
+ // Apply lateral margins
491
+ self.writer.context().addMargin(-margin[0], -margin[2]);
492
+ }
493
+
494
+ if (node.pageBreak === 'after') {
495
+ self.writer.moveToNextPage(node.pageOrientation);
496
+ } else if (node.pageBreak === 'afterOdd') {
497
+ self.writer.moveToNextPage(node.pageOrientation);
498
+ if ((self.writer.context().page + 1) % 2 === 1) {
499
+ self.writer.moveToNextPage(node.pageOrientation);
500
+ }
501
+ } else if (node.pageBreak === 'afterEven') {
502
+ self.writer.moveToNextPage(node.pageOrientation);
503
+ if ((self.writer.context().page + 1) % 2 === 0) {
504
+ self.writer.moveToNextPage(node.pageOrientation);
505
+ }
506
+ }
507
+ }
508
+ };
509
+
510
+ // vertical container
511
+ LayoutBuilder.prototype.processVerticalContainer = function (node) {
512
+ var self = this;
513
+ node.stack.forEach(function (item) {
514
+ self.processNode(item);
515
+ addAll(node.positions, item.positions);
516
+
517
+ //TODO: paragraph gap
518
+ });
519
+ };
520
+
521
+ // columns
522
+ LayoutBuilder.prototype.processColumns = function (columnNode) {
523
+ this.nestedLevel++;
524
+ var columns = columnNode.columns;
525
+ var availableWidth = this.writer.context().availableWidth;
526
+ var gaps = gapArray(columnNode._gap);
527
+
528
+ if (gaps) {
529
+ availableWidth -= (gaps.length - 1) * columnNode._gap;
530
+ }
531
+
532
+ ColumnCalculator.buildColumnWidths(columns, availableWidth);
533
+ var result = this.processRow({
534
+ marginX: columnNode._margin ? [columnNode._margin[0], columnNode._margin[2]] : [0, 0],
535
+ cells: columns,
536
+ widths: columns,
537
+ gaps
538
+ });
539
+ addAll(columnNode.positions, result.positions);
540
+
541
+ this.nestedLevel--;
542
+ if (this.nestedLevel === 0) {
543
+ this.writer.context().resetMarginXTopParent();
544
+ }
545
+
546
+ function gapArray(gap) {
547
+ if (!gap) {
548
+ return null;
549
+ }
550
+
551
+ var gaps = [];
552
+ gaps.push(0);
553
+
554
+ for (var i = columns.length - 1; i > 0; i--) {
555
+ gaps.push(gap);
556
+ }
557
+
558
+ return gaps;
559
+ }
560
+ };
561
+
562
+ LayoutBuilder.prototype.findStartingSpanCell = function (arr, i) {
563
+ var requiredColspan = 1;
564
+ for (var index = i - 1; index >= 0; index--) {
565
+ if (!arr[index]._span) {
566
+ if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
567
+ return arr[index];
568
+ } else {
569
+ return null;
570
+ }
571
+ }
572
+ requiredColspan++;
573
+ }
574
+ return null;
575
+ };
576
+
577
+ LayoutBuilder.prototype.processRow = function ({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableBody, rowIndex, height }) {
578
+ var self = this;
579
+ var isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
580
+ var pageBreaks = [];
581
+ var positions = [];
582
+ var willBreakByHeight = false;
583
+
584
+ this.tracker.auto('pageChanged', storePageBreakData, function () {
585
+ // Check if row should break by height
586
+ if (!isUnbreakableRow && height > self.writer.context().availableHeight) {
587
+ willBreakByHeight = true;
588
+ }
589
+
590
+ widths = widths || cells;
591
+ // Use the marginX if we are in a top level table/column (not nested)
592
+ const marginXParent = self.nestedLevel === 1 ? marginX : null;
593
+
594
+ self.writer.context().beginColumnGroup(marginXParent);
595
+
596
+ for (var i = 0, l = cells.length; i < l; i++) {
597
+ var column = cells[i];
598
+ var width = widths[i]._calcWidth;
599
+ var leftOffset = colLeftOffset(i);
600
+
601
+ if (column.colSpan && column.colSpan > 1) {
602
+ for (var j = 1; j < column.colSpan; j++) {
603
+ width += widths[++i]._calcWidth + gaps[i];
604
+ }
605
+ }
606
+
607
+ // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
608
+ var endingCell = getEndingCell(column, i);
609
+ if (endingCell) {
610
+ // We store a reference of the ending cell in the first cell of the rowspan
611
+ column._endingCell = endingCell;
612
+ column._endingCell._startingRowSpanY = column._startingRowSpanY;
613
+ }
614
+
615
+ // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
616
+ var startingSpanCell = self.findStartingSpanCell(cells, i);
617
+ var endingSpanCell = null;
618
+ if (startingSpanCell && startingSpanCell._endingCell) {
619
+ // Reference to the last cell of the rowspan
620
+ endingSpanCell = startingSpanCell._endingCell;
621
+ // Store if we are in an unbreakable block when we save the context and the originalX
622
+ if (self.writer.transactionLevel > 0) {
623
+ endingSpanCell._isUnbreakableContext = true;
624
+ endingSpanCell._originalXOffset = self.writer.originalX;
625
+ }
626
+ }
627
+
628
+ // We pass the endingSpanCell reference to store the context just after processing rowspan cell
629
+ self.writer.context().beginColumn(width, leftOffset, endingSpanCell);
630
+
631
+ if (!column._span) {
632
+ self.processNode(column);
633
+ addAll(positions, column.positions);
634
+ } else if (column._columnEndingContext) {
635
+ var discountY = 0;
636
+ if (dontBreakRows) {
637
+ // Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
638
+ const ctxBeforeRowSpanLastRow = self.writer.contextStack[self.writer.contextStack.length - 1];
639
+ discountY = ctxBeforeRowSpanLastRow.y - column._startingRowSpanY;
640
+ }
641
+ var originalXOffset = 0;
642
+ // If context was saved from an unbreakable block and we are not in an unbreakable block anymore
643
+ // We have to sum the originalX (X before starting unbreakable block) to X
644
+ if (column._isUnbreakableContext && !self.writer.transactionLevel) {
645
+ originalXOffset = column._originalXOffset;
646
+ }
647
+ // row-span ending
648
+ // Recover the context after processing the rowspanned cell
649
+ self.writer.context().markEnding(column, originalXOffset, discountY);
650
+ }
651
+ }
652
+
653
+ // Check if last cell is part of a span
654
+ var endingSpanCell = null;
655
+ var lastColumn = cells.length > 0 ? cells[cells.length - 1] : null;
656
+ if (lastColumn) {
657
+ // Previous column cell has a rowspan
658
+ if (lastColumn._endingCell) {
659
+ endingSpanCell = lastColumn._endingCell;
660
+ // Previous column cell is part of a span
661
+ } else if (lastColumn._span === true) {
662
+ // We get the cell that started the span where we set a reference to the ending cell
663
+ var startingSpanCell = self.findStartingSpanCell(cells, cells.length);
664
+ if (startingSpanCell) {
665
+ // Context will be stored here (ending cell)
666
+ endingSpanCell = startingSpanCell._endingCell;
667
+ // Store if we are in an unbreakable block when we save the context and the originalX
668
+ if (self.writer.transactionLevel > 0) {
669
+ endingSpanCell._isUnbreakableContext = true;
670
+ endingSpanCell._originalXOffset = self.writer.originalX;
671
+ }
672
+ }
673
+ }
674
+ }
675
+
676
+ // If there are page breaks in this row, update data with prevY of last cell
677
+ updatePageBreakData(self.writer.context().page, self.writer.context().y);
678
+
679
+ // If content did not break page, check if we should break by height
680
+ if (!isUnbreakableRow && pageBreaks.length === 0 && willBreakByHeight) {
681
+ self.writer.context().moveDown(self.writer.context().availableHeight);
682
+ self.writer.moveToNextPage();
683
+ }
684
+
685
+ self.writer.context().completeColumnGroup(height, endingSpanCell);
686
+ });
687
+
688
+ return { pageBreaks: pageBreaks, positions: positions };
689
+
690
+ function updatePageBreakData(page, prevY) {
691
+ var pageDesc;
692
+ // Find page break data for this row and page
693
+ for (var i = 0, l = pageBreaks.length; i < l; i++) {
694
+ var desc = pageBreaks[i];
695
+ if (desc.prevPage === page) {
696
+ pageDesc = desc;
697
+ break;
698
+ }
699
+ }
700
+ // If row has page break in this page, update prevY
701
+ if (pageDesc) {
702
+ pageDesc.prevY = Math.max(pageDesc.prevY, prevY);
703
+ }
704
+ }
705
+
706
+ function storePageBreakData(data) {
707
+ var pageDesc;
708
+
709
+ for (var i = 0, l = pageBreaks.length; i < l; i++) {
710
+ var desc = pageBreaks[i];
711
+ if (desc.prevPage === data.prevPage) {
712
+ pageDesc = desc;
713
+ break;
714
+ }
715
+ }
716
+
717
+ if (!pageDesc) {
718
+ pageDesc = data;
719
+ pageBreaks.push(pageDesc);
720
+ }
721
+ pageDesc.prevY = Math.max(pageDesc.prevY, data.prevY);
722
+ pageDesc.y = Math.min(pageDesc.y, data.y);
723
+ }
724
+
725
+ function colLeftOffset(i) {
726
+ if (gaps && gaps.length > i) {
727
+ return gaps[i];
728
+ }
729
+ return 0;
730
+ }
731
+
732
+ function getEndingCell(column, columnIndex) {
733
+ if (column.rowSpan && column.rowSpan > 1) {
734
+ var endingRow = rowIndex + column.rowSpan - 1;
735
+ if (endingRow >= tableBody.length) {
736
+ throw 'Row span for column ' + columnIndex + ' (with indexes starting from 0) exceeded row count';
737
+ }
738
+ return tableBody[endingRow][columnIndex];
739
+ }
740
+
741
+ return null;
742
+ }
743
+ };
744
+
745
+ // lists
746
+ LayoutBuilder.prototype.processList = function (orderedList, node) {
747
+ var self = this,
748
+ items = orderedList ? node.ol : node.ul,
749
+ gapSize = node._gapSize;
750
+
751
+ this.writer.context().addMargin(gapSize.width);
752
+
753
+ var nextMarker;
754
+ this.tracker.auto('lineAdded', addMarkerToFirstLeaf, function () {
755
+ items.forEach(function (item) {
756
+ nextMarker = item.listMarker;
757
+ self.processNode(item);
758
+ addAll(node.positions, item.positions);
759
+ });
760
+ });
761
+
762
+ this.writer.context().addMargin(-gapSize.width);
763
+
764
+ function addMarkerToFirstLeaf(line) {
765
+ // I'm not very happy with the way list processing is implemented
766
+ // (both code and algorithm should be rethinked)
767
+ if (nextMarker) {
768
+ var marker = nextMarker;
769
+ nextMarker = null;
770
+
771
+ if (marker.canvas) {
772
+ var vector = marker.canvas[0];
773
+
774
+ offsetVector(vector, -marker._minWidth, 0);
775
+ self.writer.addVector(vector);
776
+ } else if (marker._inlines) {
777
+ var markerLine = new Line(self.pageSize.width);
778
+ markerLine.addInline(marker._inlines[0]);
779
+ markerLine.x = -marker._minWidth;
780
+ markerLine.y = line.getAscenderHeight() - markerLine.getAscenderHeight();
781
+ self.writer.addLine(markerLine, true);
782
+ }
783
+ }
784
+ }
785
+ };
786
+
787
+ // tables
788
+ LayoutBuilder.prototype.processTable = function (tableNode) {
789
+ this.nestedLevel++;
790
+ var processor = new TableProcessor(tableNode);
791
+
792
+ processor.beginTable(this.writer);
793
+
794
+ var rowHeights = tableNode.table.heights;
795
+ for (var i = 0, l = tableNode.table.body.length; i < l; i++) {
796
+ // if dontBreakRows and row starts a rowspan
797
+ // we store the 'y' of the beginning of each rowSpan
798
+ if (processor.dontBreakRows) {
799
+ tableNode.table.body[i].forEach(cell => {
800
+ if (cell.rowSpan && cell.rowSpan > 1) {
801
+ cell._startingRowSpanY = this.writer.context().y;
802
+ }
803
+ });
804
+ }
805
+
806
+ processor.beginRow(i, this.writer);
807
+
808
+ var height;
809
+ if (isFunction(rowHeights)) {
810
+ height = rowHeights(i);
811
+ } else if (isArray(rowHeights)) {
812
+ height = rowHeights[i];
813
+ } else {
814
+ height = rowHeights;
815
+ }
816
+
817
+ if (height === 'auto') {
818
+ height = undefined;
819
+ }
820
+
821
+ var result = this.processRow({
822
+ marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
823
+ dontBreakRows: processor.dontBreakRows,
824
+ rowsWithoutPageBreak: processor.rowsWithoutPageBreak,
825
+ cells: tableNode.table.body[i],
826
+ widths: tableNode.table.widths,
827
+ gaps: tableNode._offsets.offsets,
828
+ tableBody: tableNode.table.body,
829
+ rowIndex: i,
830
+ height
831
+ });
832
+ addAll(tableNode.positions, result.positions);
833
+
834
+ processor.endRow(i, this.writer, result.pageBreaks);
835
+ }
836
+
837
+ processor.endTable(this.writer);
838
+ this.nestedLevel--;
839
+ if (this.nestedLevel === 0) {
840
+ this.writer.context().resetMarginXTopParent();
841
+ }
842
+ };
843
+
844
+ // leafs (texts)
845
+ LayoutBuilder.prototype.processLeaf = function (node) {
846
+ var line = this.buildNextLine(node);
847
+ if (line && (node.tocItem || node.id)) {
848
+ line._node = node;
849
+ }
850
+ var currentHeight = (line) ? line.getHeight() : 0;
851
+ var maxHeight = node.maxHeight || -1;
852
+
853
+ if (line) {
854
+ var nodeId = getNodeId(node);
855
+ if (nodeId) {
856
+ line.id = nodeId;
857
+ }
858
+ }
859
+
860
+ if (node._tocItemRef) {
861
+ line._pageNodeRef = node._tocItemRef;
862
+ }
863
+
864
+ if (node._pageRef) {
865
+ line._pageNodeRef = node._pageRef._nodeRef;
866
+ }
867
+
868
+ if (line && line.inlines && isArray(line.inlines)) {
869
+ for (var i = 0, l = line.inlines.length; i < l; i++) {
870
+ if (line.inlines[i]._tocItemRef) {
871
+ line.inlines[i]._pageNodeRef = line.inlines[i]._tocItemRef;
872
+ }
873
+
874
+ if (line.inlines[i]._pageRef) {
875
+ line.inlines[i]._pageNodeRef = line.inlines[i]._pageRef._nodeRef;
876
+ }
877
+ }
878
+ }
879
+
880
+ while (line && (maxHeight === -1 || currentHeight < maxHeight)) {
881
+ var positions = this.writer.addLine(line);
882
+ node.positions.push(positions);
883
+ line = this.buildNextLine(node);
884
+ if (line) {
885
+ currentHeight += line.getHeight();
886
+ }
887
+ }
888
+ };
889
+
890
+ LayoutBuilder.prototype.processToc = function (node) {
891
+ if (node.toc.title) {
892
+ this.processNode(node.toc.title);
893
+ }
894
+ if (node.toc._table) {
895
+ this.processNode(node.toc._table);
896
+ }
897
+ };
898
+
899
+ LayoutBuilder.prototype.buildNextLine = function (textNode) {
900
+
901
+ function cloneInline(inline) {
902
+ var newInline = inline.constructor();
903
+ for (var key in inline) {
904
+ newInline[key] = inline[key];
905
+ }
906
+ return newInline;
907
+ }
908
+
909
+ if (!textNode._inlines || textNode._inlines.length === 0) {
910
+ return null;
911
+ }
912
+
913
+ var line = new Line(this.writer.context().availableWidth);
914
+ var textTools = new TextTools(null);
915
+
916
+ var isForceContinue = false;
917
+ while (textNode._inlines && textNode._inlines.length > 0 &&
918
+ (line.hasEnoughSpaceForInline(textNode._inlines[0], textNode._inlines.slice(1)) || isForceContinue)) {
919
+ var isHardWrap = false;
920
+ var inline = textNode._inlines.shift();
921
+ isForceContinue = false;
922
+
923
+ if (!inline.noWrap && inline.text.length > 1 && inline.width > line.getAvailableWidth()) {
924
+ var widthPerChar = inline.width / inline.text.length;
925
+ var maxChars = Math.floor(line.getAvailableWidth() / widthPerChar);
926
+ if (maxChars < 1) {
927
+ maxChars = 1;
928
+ }
929
+ if (maxChars < inline.text.length) {
930
+ var newInline = cloneInline(inline);
931
+
932
+ newInline.text = inline.text.substr(maxChars);
933
+ inline.text = inline.text.substr(0, maxChars);
934
+
935
+ newInline.width = textTools.widthOfString(newInline.text, newInline.font, newInline.fontSize, newInline.characterSpacing, newInline.fontFeatures);
936
+ inline.width = textTools.widthOfString(inline.text, inline.font, inline.fontSize, inline.characterSpacing, inline.fontFeatures);
937
+
938
+ textNode._inlines.unshift(newInline);
939
+ isHardWrap = true;
940
+ }
941
+ }
942
+
943
+ line.addInline(inline);
944
+
945
+ isForceContinue = inline.noNewLine && !isHardWrap;
946
+ }
947
+
948
+ line.lastLineInParagraph = textNode._inlines.length === 0;
949
+
950
+ return line;
951
+ };
952
+
953
+ // images
954
+ LayoutBuilder.prototype.processImage = function (node) {
955
+ var position = this.writer.addImage(node);
956
+ node.positions.push(position);
957
+ };
958
+
959
+ LayoutBuilder.prototype.processSVG = function (node) {
960
+ var position = this.writer.addSVG(node);
961
+ node.positions.push(position);
962
+ };
963
+
964
+ LayoutBuilder.prototype.processCanvas = function (node) {
965
+ var height = node._minHeight;
966
+
967
+ if (node.absolutePosition === undefined && this.writer.context().availableHeight < height) {
968
+ // TODO: support for canvas larger than a page
969
+ // TODO: support for other overflow methods
970
+
971
+ this.writer.moveToNextPage();
972
+ }
973
+
974
+ this.writer.alignCanvas(node);
975
+
976
+ node.canvas.forEach(function (vector) {
977
+ var position = this.writer.addVector(vector);
978
+ node.positions.push(position);
979
+ }, this);
980
+
981
+ this.writer.context().moveDown(height);
982
+ };
983
+
984
+ LayoutBuilder.prototype.processQr = function (node) {
985
+ var position = this.writer.addQr(node);
986
+ node.positions.push(position);
987
+ };
988
+
989
+ module.exports = LayoutBuilder;