@digicole/pdfmake-rtl 2.1.0 → 2.1.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +118 -83
  2. package/README.md +11 -10
  3. package/build/pdfmake.js +71 -42
  4. package/build/pdfmake.js.map +1 -1
  5. package/build/pdfmake.min.js +2 -2
  6. package/build/pdfmake.min.js.map +1 -1
  7. package/build/vfs_fonts.js +11 -11
  8. package/js/3rd-party/svg-to-pdfkit/source.js +3823 -0
  9. package/js/3rd-party/svg-to-pdfkit.js +7 -0
  10. package/js/DocMeasure.js +713 -0
  11. package/js/DocPreprocessor.js +275 -0
  12. package/js/DocumentContext.js +310 -0
  13. package/js/ElementWriter.js +687 -0
  14. package/js/LayoutBuilder.js +1240 -0
  15. package/js/Line.js +113 -0
  16. package/js/OutputDocument.js +64 -0
  17. package/js/OutputDocumentServer.js +29 -0
  18. package/js/PDFDocument.js +144 -0
  19. package/js/PageElementWriter.js +161 -0
  20. package/js/PageSize.js +74 -0
  21. package/js/Printer.js +351 -0
  22. package/js/Renderer.js +417 -0
  23. package/js/SVGMeasure.js +92 -0
  24. package/js/StyleContextStack.js +191 -0
  25. package/js/TableProcessor.js +575 -0
  26. package/js/TextBreaker.js +166 -0
  27. package/js/TextDecorator.js +152 -0
  28. package/js/TextInlines.js +244 -0
  29. package/js/URLResolver.js +43 -0
  30. package/js/base.js +59 -0
  31. package/js/browser-extensions/OutputDocumentBrowser.js +82 -0
  32. package/js/browser-extensions/fonts/Cairo.js +38 -0
  33. package/js/browser-extensions/fonts/Roboto.js +38 -0
  34. package/js/browser-extensions/index.js +59 -0
  35. package/js/browser-extensions/pdfMake.js +3 -0
  36. package/js/browser-extensions/standard-fonts/Courier.js +38 -0
  37. package/js/browser-extensions/standard-fonts/Helvetica.js +38 -0
  38. package/js/browser-extensions/standard-fonts/Symbol.js +23 -0
  39. package/js/browser-extensions/standard-fonts/Times.js +38 -0
  40. package/js/browser-extensions/standard-fonts/ZapfDingbats.js +23 -0
  41. package/js/browser-extensions/virtual-fs-cjs.js +3 -0
  42. package/js/columnCalculator.js +148 -0
  43. package/js/helpers/node.js +123 -0
  44. package/js/helpers/tools.js +46 -0
  45. package/js/helpers/variableType.js +59 -0
  46. package/js/index.js +15 -0
  47. package/js/qrEnc.js +721 -0
  48. package/js/rtlUtils.js +519 -0
  49. package/js/standardPageSizes.js +56 -0
  50. package/js/tableLayouts.js +98 -0
  51. package/js/virtual-fs.js +60 -0
  52. package/package.json +1 -1
  53. package/src/{docMeasure.js → DocMeasure.js} +8 -8
  54. package/src/{elementWriter.js → ElementWriter.js} +3 -3
  55. package/src/{layoutBuilder.js → LayoutBuilder.js} +1406 -1393
  56. package/src/{tableProcessor.js → TableProcessor.js} +633 -620
  57. package/src/rtlUtils.js +503 -500
  58. /package/src/{docPreprocessor.js → DocPreprocessor.js} +0 -0
  59. /package/src/{documentContext.js → DocumentContext.js} +0 -0
  60. /package/src/{line.js → Line.js} +0 -0
  61. /package/src/{pageElementWriter.js → PageElementWriter.js} +0 -0
  62. /package/src/{printer.js → Printer.js} +0 -0
  63. /package/src/{svgMeasure.js → SVGMeasure.js} +0 -0
  64. /package/src/{styleContextStack.js → StyleContextStack.js} +0 -0
  65. /package/src/{textDecorator.js → TextDecorator.js} +0 -0
@@ -0,0 +1,1240 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _DocPreprocessor = _interopRequireDefault(require("./DocPreprocessor"));
6
+ var _DocMeasure = _interopRequireDefault(require("./DocMeasure"));
7
+ var _DocumentContext = _interopRequireDefault(require("./DocumentContext"));
8
+ var _PageElementWriter = _interopRequireDefault(require("./PageElementWriter"));
9
+ var _columnCalculator = _interopRequireDefault(require("./columnCalculator"));
10
+ var _TableProcessor = _interopRequireDefault(require("./TableProcessor"));
11
+ var _Line = _interopRequireDefault(require("./Line"));
12
+ var _variableType = require("./helpers/variableType");
13
+ var _node = require("./helpers/node");
14
+ var _tools = require("./helpers/tools");
15
+ var _TextInlines = _interopRequireDefault(require("./TextInlines"));
16
+ var _StyleContextStack = _interopRequireDefault(require("./StyleContextStack"));
17
+ var _rtlUtils = require("./rtlUtils");
18
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
+ function addAll(target, otherArray) {
20
+ otherArray.forEach(item => {
21
+ target.push(item);
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Layout engine which turns document-definition-object into a set of pages, lines, inlines
27
+ * and vectors ready to be rendered into a PDF
28
+ */
29
+ class LayoutBuilder {
30
+ /**
31
+ * @param {object} pageSize - an object defining page width and height
32
+ * @param {object} pageMargins - an object defining top, left, right and bottom margins
33
+ * @param {object} svgMeasure
34
+ */
35
+ constructor(pageSize, pageMargins, svgMeasure) {
36
+ this.pageSize = pageSize;
37
+ this.pageMargins = pageMargins;
38
+ this.svgMeasure = svgMeasure;
39
+ this.tableLayouts = {};
40
+ this.nestedLevel = 0;
41
+ this.verticalAlignmentItemStack = [];
42
+ }
43
+ registerTableLayouts(tableLayouts) {
44
+ this.tableLayouts = (0, _tools.pack)(this.tableLayouts, tableLayouts);
45
+ }
46
+
47
+ /**
48
+ * Executes layout engine on document-definition-object and creates an array of pages
49
+ * containing positioned Blocks, Lines and inlines
50
+ *
51
+ * @param {object} docStructure document-definition-object
52
+ * @param {object} pdfDocument pdfkit document
53
+ * @param {object} styleDictionary dictionary with style definitions
54
+ * @param {object} defaultStyle default style definition
55
+ * @param {object} background
56
+ * @param {object} header
57
+ * @param {object} footer
58
+ * @param {object} watermark
59
+ * @param {object} pageBreakBeforeFct
60
+ * @returns {Array} an array of pages
61
+ */
62
+ layoutDocument(docStructure, pdfDocument, styleDictionary, defaultStyle, background, header, footer, watermark, pageBreakBeforeFct) {
63
+ function addPageBreaksIfNecessary(linearNodeList, pages) {
64
+ if (typeof pageBreakBeforeFct !== 'function') {
65
+ return false;
66
+ }
67
+ const hasRenderableContent = node => {
68
+ if (!node || node.positions.length === 0) {
69
+ return false;
70
+ }
71
+ if (node.text === '' && !node.listMarker) {
72
+ return false;
73
+ }
74
+ return true;
75
+ };
76
+ linearNodeList = linearNodeList.filter(hasRenderableContent);
77
+ linearNodeList.forEach(node => {
78
+ let nodeInfo = {};
79
+ ['id', 'text', 'ul', 'ol', 'table', 'image', 'qr', 'canvas', 'svg', 'columns', 'headlineLevel', 'style', 'pageBreak', 'pageOrientation', 'width', 'height'].forEach(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(node => node.pageNumber)));
86
+ nodeInfo.pages = pages.length;
87
+ nodeInfo.stack = Array.isArray(node.stack);
88
+ node.nodeInfo = nodeInfo;
89
+ });
90
+ for (let index = 0; index < linearNodeList.length; index++) {
91
+ let node = linearNodeList[index];
92
+ if (node.pageBreak !== 'before' && !node.pageBreakCalculated) {
93
+ node.pageBreakCalculated = true;
94
+ let pageNumber = node.nodeInfo.pageNumbers[0];
95
+ if (pageBreakBeforeFct(node.nodeInfo, {
96
+ getFollowingNodesOnPage: () => {
97
+ let followingNodesOnPage = [];
98
+ for (let ii = index + 1, l = linearNodeList.length; ii < l; ii++) {
99
+ if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
100
+ followingNodesOnPage.push(linearNodeList[ii].nodeInfo);
101
+ }
102
+ }
103
+ return followingNodesOnPage;
104
+ },
105
+ getNodesOnNextPage: () => {
106
+ let nodesOnNextPage = [];
107
+ for (let ii = index + 1, l = linearNodeList.length; ii < l; ii++) {
108
+ if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber + 1) > -1) {
109
+ nodesOnNextPage.push(linearNodeList[ii].nodeInfo);
110
+ }
111
+ }
112
+ return nodesOnNextPage;
113
+ },
114
+ getPreviousNodesOnPage: () => {
115
+ let previousNodesOnPage = [];
116
+ for (let ii = 0; ii < index; ii++) {
117
+ if (linearNodeList[ii].nodeInfo.pageNumbers.indexOf(pageNumber) > -1) {
118
+ previousNodesOnPage.push(linearNodeList[ii].nodeInfo);
119
+ }
120
+ }
121
+ return previousNodesOnPage;
122
+ }
123
+ })) {
124
+ node.pageBreak = 'before';
125
+ return true;
126
+ }
127
+ }
128
+ }
129
+ return false;
130
+ }
131
+ this.docPreprocessor = new _DocPreprocessor.default();
132
+ this.docMeasure = new _DocMeasure.default(pdfDocument, styleDictionary, defaultStyle, this.svgMeasure, this.tableLayouts);
133
+ function resetXYs(result) {
134
+ result.linearNodeList.forEach(node => {
135
+ node.resetXY();
136
+ });
137
+ }
138
+ let result = this.tryLayoutDocument(docStructure, pdfDocument, styleDictionary, defaultStyle, background, header, footer, watermark);
139
+ while (addPageBreaksIfNecessary(result.linearNodeList, result.pages)) {
140
+ resetXYs(result);
141
+ result = this.tryLayoutDocument(docStructure, pdfDocument, styleDictionary, defaultStyle, background, header, footer, watermark);
142
+ }
143
+ return result.pages;
144
+ }
145
+ tryLayoutDocument(docStructure, pdfDocument, styleDictionary, defaultStyle, background, header, footer, watermark) {
146
+ const isNecessaryAddFirstPage = docStructure => {
147
+ if (docStructure.stack && docStructure.stack.length > 0 && docStructure.stack[0].section) {
148
+ return false;
149
+ } else if (docStructure.section) {
150
+ return false;
151
+ }
152
+ return true;
153
+ };
154
+ this.linearNodeList = [];
155
+ docStructure = this.docPreprocessor.preprocessDocument(docStructure);
156
+ docStructure = this.docMeasure.measureDocument(docStructure);
157
+ this.writer = new _PageElementWriter.default(new _DocumentContext.default());
158
+ this.writer.context().addListener('pageAdded', page => {
159
+ let backgroundGetter = background;
160
+ if (page.customProperties['background'] || page.customProperties['background'] === null) {
161
+ backgroundGetter = page.customProperties['background'];
162
+ }
163
+ this.addBackground(backgroundGetter);
164
+ });
165
+ if (isNecessaryAddFirstPage(docStructure)) {
166
+ this.writer.addPage(this.pageSize, null, this.pageMargins);
167
+ }
168
+ this.processNode(docStructure);
169
+ this.addHeadersAndFooters(header, footer);
170
+ this.addWatermark(watermark, pdfDocument, defaultStyle);
171
+ return {
172
+ pages: this.writer.context().pages,
173
+ linearNodeList: this.linearNodeList
174
+ };
175
+ }
176
+ addBackground(background) {
177
+ let backgroundGetter = typeof background === 'function' ? background : () => background;
178
+ let context = this.writer.context();
179
+ let pageSize = context.getCurrentPage().pageSize;
180
+ let pageBackground = backgroundGetter(context.page + 1, pageSize);
181
+ if (pageBackground) {
182
+ this.writer.beginUnbreakableBlock(pageSize.width, pageSize.height);
183
+ pageBackground = this.docPreprocessor.preprocessBlock(pageBackground);
184
+ this.processNode(this.docMeasure.measureBlock(pageBackground));
185
+ this.writer.commitUnbreakableBlock(0, 0);
186
+ context.backgroundLength[context.page] += pageBackground.positions.length;
187
+ }
188
+ }
189
+ addDynamicRepeatable(nodeGetter, sizeFunction, customPropertyName) {
190
+ let pages = this.writer.context().pages;
191
+ for (let pageIndex = 0, l = pages.length; pageIndex < l; pageIndex++) {
192
+ this.writer.context().page = pageIndex;
193
+ let customProperties = this.writer.context().getCurrentPage().customProperties;
194
+ let pageNodeGetter = nodeGetter;
195
+ if (customProperties[customPropertyName] || customProperties[customPropertyName] === null) {
196
+ pageNodeGetter = customProperties[customPropertyName];
197
+ }
198
+ if (typeof pageNodeGetter === 'undefined' || pageNodeGetter === null) {
199
+ continue;
200
+ }
201
+ let node = pageNodeGetter(pageIndex + 1, l, this.writer.context().pages[pageIndex].pageSize);
202
+ if (node) {
203
+ let sizes = sizeFunction(this.writer.context().getCurrentPage().pageSize, this.writer.context().getCurrentPage().pageMargins);
204
+ this.writer.beginUnbreakableBlock(sizes.width, sizes.height);
205
+ node = this.docPreprocessor.preprocessBlock(node);
206
+ this.processNode(this.docMeasure.measureBlock(node));
207
+ this.writer.commitUnbreakableBlock(sizes.x, sizes.y);
208
+ }
209
+ }
210
+ }
211
+ addHeadersAndFooters(header, footer) {
212
+ const headerSizeFct = (pageSize, pageMargins) => ({
213
+ x: 0,
214
+ y: 0,
215
+ width: pageSize.width,
216
+ height: pageMargins.top
217
+ });
218
+ const footerSizeFct = (pageSize, pageMargins) => ({
219
+ x: 0,
220
+ y: pageSize.height - pageMargins.bottom,
221
+ width: pageSize.width,
222
+ height: pageMargins.bottom
223
+ });
224
+ this.addDynamicRepeatable(header, headerSizeFct, 'header');
225
+ this.addDynamicRepeatable(footer, footerSizeFct, 'footer');
226
+ }
227
+ addWatermark(watermark, pdfDocument, defaultStyle) {
228
+ let pages = this.writer.context().pages;
229
+ for (let i = 0, l = pages.length; i < l; i++) {
230
+ let pageWatermark = watermark;
231
+ if (pages[i].customProperties['watermark'] || pages[i].customProperties['watermark'] === null) {
232
+ pageWatermark = pages[i].customProperties['watermark'];
233
+ }
234
+ if (pageWatermark === undefined || pageWatermark === null) {
235
+ continue;
236
+ }
237
+ if ((0, _variableType.isString)(pageWatermark)) {
238
+ pageWatermark = {
239
+ 'text': pageWatermark
240
+ };
241
+ }
242
+ if (!pageWatermark.text) {
243
+ // empty watermark text
244
+ continue;
245
+ }
246
+ pages[i].watermark = getWatermarkObject({
247
+ ...pageWatermark
248
+ }, pages[i].pageSize, pdfDocument, defaultStyle);
249
+ }
250
+ function getWatermarkObject(watermark, pageSize, pdfDocument, defaultStyle) {
251
+ watermark.font = watermark.font || defaultStyle.font || 'Roboto';
252
+ watermark.fontSize = watermark.fontSize || 'auto';
253
+ watermark.color = watermark.color || 'black';
254
+ watermark.opacity = (0, _variableType.isNumber)(watermark.opacity) ? watermark.opacity : 0.6;
255
+ watermark.bold = watermark.bold || false;
256
+ watermark.italics = watermark.italics || false;
257
+ watermark.angle = (0, _variableType.isValue)(watermark.angle) ? watermark.angle : null;
258
+ if (watermark.angle === null) {
259
+ watermark.angle = Math.atan2(pageSize.height, pageSize.width) * -180 / Math.PI;
260
+ }
261
+ if (watermark.fontSize === 'auto') {
262
+ watermark.fontSize = getWatermarkFontSize(pageSize, watermark, pdfDocument);
263
+ }
264
+ let watermarkObject = {
265
+ text: watermark.text,
266
+ font: pdfDocument.provideFont(watermark.font, watermark.bold, watermark.italics),
267
+ fontSize: watermark.fontSize,
268
+ color: watermark.color,
269
+ opacity: watermark.opacity,
270
+ angle: watermark.angle
271
+ };
272
+ watermarkObject._size = getWatermarkSize(watermark, pdfDocument);
273
+ return watermarkObject;
274
+ }
275
+ function getWatermarkSize(watermark, pdfDocument) {
276
+ let textInlines = new _TextInlines.default(pdfDocument);
277
+ let styleContextStack = new _StyleContextStack.default(null, {
278
+ font: watermark.font,
279
+ bold: watermark.bold,
280
+ italics: watermark.italics
281
+ });
282
+ styleContextStack.push({
283
+ fontSize: watermark.fontSize
284
+ });
285
+ let size = textInlines.sizeOfText(watermark.text, styleContextStack);
286
+ let rotatedSize = textInlines.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
287
+ return {
288
+ size: size,
289
+ rotatedSize: rotatedSize
290
+ };
291
+ }
292
+ function getWatermarkFontSize(pageSize, watermark, pdfDocument) {
293
+ let textInlines = new _TextInlines.default(pdfDocument);
294
+ let styleContextStack = new _StyleContextStack.default(null, {
295
+ font: watermark.font,
296
+ bold: watermark.bold,
297
+ italics: watermark.italics
298
+ });
299
+ let rotatedSize;
300
+
301
+ /**
302
+ * Binary search the best font size.
303
+ * Initial bounds [0, 1000]
304
+ * Break when range < 1
305
+ */
306
+ let a = 0;
307
+ let b = 1000;
308
+ let c = (a + b) / 2;
309
+ while (Math.abs(a - b) > 1) {
310
+ styleContextStack.push({
311
+ fontSize: c
312
+ });
313
+ rotatedSize = textInlines.sizeOfRotatedText(watermark.text, watermark.angle, styleContextStack);
314
+ if (rotatedSize.width > pageSize.width) {
315
+ b = c;
316
+ c = (a + b) / 2;
317
+ } else if (rotatedSize.width < pageSize.width) {
318
+ if (rotatedSize.height > pageSize.height) {
319
+ b = c;
320
+ c = (a + b) / 2;
321
+ } else {
322
+ a = c;
323
+ c = (a + b) / 2;
324
+ }
325
+ }
326
+ styleContextStack.pop();
327
+ }
328
+ /*
329
+ End binary search
330
+ */
331
+ return c;
332
+ }
333
+ }
334
+ processNode(node, isVerticalAlignmentAllowed = false) {
335
+ const applyMargins = callback => {
336
+ let margin = node._margin;
337
+ if (node.pageBreak === 'before') {
338
+ this.writer.moveToNextPage(node.pageOrientation);
339
+ } else if (node.pageBreak === 'beforeOdd') {
340
+ this.writer.moveToNextPage(node.pageOrientation);
341
+ if ((this.writer.context().page + 1) % 2 === 1) {
342
+ this.writer.moveToNextPage(node.pageOrientation);
343
+ }
344
+ } else if (node.pageBreak === 'beforeEven') {
345
+ this.writer.moveToNextPage(node.pageOrientation);
346
+ if ((this.writer.context().page + 1) % 2 === 0) {
347
+ this.writer.moveToNextPage(node.pageOrientation);
348
+ }
349
+ }
350
+ const isDetachedBlock = node.relativePosition || node.absolutePosition;
351
+
352
+ // Detached nodes have no margins, their position is only determined by 'x' and 'y'
353
+ if (margin && !isDetachedBlock) {
354
+ const availableHeight = this.writer.context().availableHeight;
355
+ // If top margin is bigger than available space, move to next page
356
+ // Necessary for nodes inside tables
357
+ if (availableHeight - margin[1] < 0) {
358
+ // Consume the whole available space
359
+ this.writer.context().moveDown(availableHeight);
360
+ this.writer.moveToNextPage(node.pageOrientation);
361
+ /**
362
+ * TODO - Something to consider:
363
+ * Right now the node starts at the top of next page (after header)
364
+ * Another option would be to apply just the top margin that has not been consumed in the page before
365
+ * It would something like: this.write.context().moveDown(margin[1] - availableHeight)
366
+ */
367
+ } else {
368
+ this.writer.context().moveDown(margin[1]);
369
+ }
370
+ // Apply lateral margins
371
+ this.writer.context().addMargin(margin[0], margin[2]);
372
+ }
373
+ callback();
374
+
375
+ // Detached nodes have no margins, their position is only determined by 'x' and 'y'
376
+ if (margin && !isDetachedBlock) {
377
+ const availableHeight = this.writer.context().availableHeight;
378
+ // If bottom margin is bigger than available space, move to next page
379
+ // Necessary for nodes inside tables
380
+ if (availableHeight - margin[3] < 0) {
381
+ this.writer.context().moveDown(availableHeight);
382
+ this.writer.moveToNextPage(node.pageOrientation);
383
+ /**
384
+ * TODO - Something to consider:
385
+ * Right now next node starts at the top of next page (after header)
386
+ * Another option would be to apply the bottom margin that has not been consumed in the next page?
387
+ * It would something like: this.write.context().moveDown(margin[3] - availableHeight)
388
+ */
389
+ } else {
390
+ this.writer.context().moveDown(margin[3]);
391
+ }
392
+ // Apply lateral margins
393
+ this.writer.context().addMargin(-margin[0], -margin[2]);
394
+ }
395
+ if (node.pageBreak === 'after') {
396
+ this.writer.moveToNextPage(node.pageOrientation);
397
+ } else if (node.pageBreak === 'afterOdd') {
398
+ this.writer.moveToNextPage(node.pageOrientation);
399
+ if ((this.writer.context().page + 1) % 2 === 1) {
400
+ this.writer.moveToNextPage(node.pageOrientation);
401
+ }
402
+ } else if (node.pageBreak === 'afterEven') {
403
+ this.writer.moveToNextPage(node.pageOrientation);
404
+ if ((this.writer.context().page + 1) % 2 === 0) {
405
+ this.writer.moveToNextPage(node.pageOrientation);
406
+ }
407
+ }
408
+ };
409
+ this.linearNodeList.push(node);
410
+ decorateNode(node);
411
+ var prevTop = this.writer.context().getCurrentPosition().top;
412
+ applyMargins(() => {
413
+ let verticalAlignment = node.verticalAlignment;
414
+ if (isVerticalAlignmentAllowed && verticalAlignment) {
415
+ var verticalAlignmentBegin = this.writer.beginVerticalAlignment(verticalAlignment);
416
+ }
417
+ let unbreakable = node.unbreakable;
418
+ if (unbreakable) {
419
+ this.writer.beginUnbreakableBlock();
420
+ }
421
+ let absPosition = node.absolutePosition;
422
+ if (absPosition) {
423
+ this.writer.context().beginDetachedBlock();
424
+ this.writer.context().moveTo(absPosition.x || 0, absPosition.y || 0);
425
+ }
426
+ let relPosition = node.relativePosition;
427
+ if (relPosition) {
428
+ this.writer.context().beginDetachedBlock();
429
+ this.writer.context().moveToRelative(relPosition.x || 0, relPosition.y || 0);
430
+ }
431
+ if (node.stack) {
432
+ this.processVerticalContainer(node);
433
+ } else if (node.section) {
434
+ this.processSection(node);
435
+ } else if (node.columns) {
436
+ this.processColumns(node);
437
+ } else if (node.ul) {
438
+ this.processList(false, node);
439
+ } else if (node.ol) {
440
+ this.processList(true, node);
441
+ } else if (node.table) {
442
+ this.processTable(node);
443
+ } else if (node.text !== undefined) {
444
+ this.processLeaf(node);
445
+ } else if (node.toc) {
446
+ this.processToc(node);
447
+ } else if (node.image) {
448
+ this.processImage(node);
449
+ } else if (node.svg) {
450
+ this.processSVG(node);
451
+ } else if (node.canvas) {
452
+ this.processCanvas(node);
453
+ } else if (node.qr) {
454
+ this.processQr(node);
455
+ } else if (node.attachment) {
456
+ this.processAttachment(node);
457
+ } else if (!node._span) {
458
+ throw new Error(`Unrecognized document structure: ${(0, _node.stringifyNode)(node)}`);
459
+ }
460
+ if (absPosition || relPosition) {
461
+ this.writer.context().endDetachedBlock();
462
+ }
463
+ if (unbreakable) {
464
+ this.writer.commitUnbreakableBlock();
465
+ }
466
+ if (isVerticalAlignmentAllowed && verticalAlignment) {
467
+ this.verticalAlignmentItemStack.push({
468
+ begin: verticalAlignmentBegin,
469
+ end: this.writer.endVerticalAlignment(verticalAlignment)
470
+ });
471
+ }
472
+ });
473
+
474
+ // TODO: for vertical alignment and does not work (at least) when page break in node
475
+ node.__height = this.writer.context().getCurrentPosition().top - prevTop;
476
+ }
477
+
478
+ // vertical container
479
+ processVerticalContainer(node) {
480
+ node.stack.forEach(item => {
481
+ this.processNode(item);
482
+ addAll(node.positions, item.positions);
483
+
484
+ //TODO: paragraph gap
485
+ }, this);
486
+ }
487
+
488
+ // section
489
+ processSection(sectionNode) {
490
+ // TODO: properties
491
+
492
+ let page = this.writer.context().getCurrentPage();
493
+ if (!page || page && page.items.length) {
494
+ // move to new empty page
495
+ // page definition inherit from current page
496
+ if (sectionNode.pageSize === 'inherit') {
497
+ sectionNode.pageSize = page ? {
498
+ width: page.pageSize.width,
499
+ height: page.pageSize.height
500
+ } : undefined;
501
+ }
502
+ if (sectionNode.pageOrientation === 'inherit') {
503
+ sectionNode.pageOrientation = page ? page.pageSize.orientation : undefined;
504
+ }
505
+ if (sectionNode.pageMargins === 'inherit') {
506
+ sectionNode.pageMargins = page ? page.pageMargins : undefined;
507
+ }
508
+ if (sectionNode.header === 'inherit') {
509
+ sectionNode.header = page ? page.customProperties.header : undefined;
510
+ }
511
+ if (sectionNode.footer === 'inherit') {
512
+ sectionNode.footer = page ? page.customProperties.footer : undefined;
513
+ }
514
+ if (sectionNode.background === 'inherit') {
515
+ sectionNode.background = page ? page.customProperties.background : undefined;
516
+ }
517
+ if (sectionNode.watermark === 'inherit') {
518
+ sectionNode.watermark = page ? page.customProperties.watermark : undefined;
519
+ }
520
+ if (sectionNode.header && typeof sectionNode.header !== 'function' && sectionNode.header !== null) {
521
+ sectionNode.header = (0, _tools.convertToDynamicContent)(sectionNode.header);
522
+ }
523
+ if (sectionNode.footer && typeof sectionNode.footer !== 'function' && sectionNode.footer !== null) {
524
+ sectionNode.footer = (0, _tools.convertToDynamicContent)(sectionNode.footer);
525
+ }
526
+ let customProperties = {};
527
+ if (typeof sectionNode.header !== 'undefined') {
528
+ customProperties.header = sectionNode.header;
529
+ }
530
+ if (typeof sectionNode.footer !== 'undefined') {
531
+ customProperties.footer = sectionNode.footer;
532
+ }
533
+ if (typeof sectionNode.background !== 'undefined') {
534
+ customProperties.background = sectionNode.background;
535
+ }
536
+ if (typeof sectionNode.watermark !== 'undefined') {
537
+ customProperties.watermark = sectionNode.watermark;
538
+ }
539
+ this.writer.addPage(sectionNode.pageSize || this.pageSize, sectionNode.pageOrientation, sectionNode.pageMargins || this.pageMargins, customProperties);
540
+ }
541
+ this.processNode(sectionNode.section);
542
+ }
543
+
544
+ // columns
545
+ processColumns(columnNode) {
546
+ this.nestedLevel++;
547
+ let columns = columnNode.columns;
548
+ let availableWidth = this.writer.context().availableWidth;
549
+ let gaps = gapArray(columnNode._gap);
550
+ if (gaps) {
551
+ availableWidth -= (gaps.length - 1) * columnNode._gap;
552
+ }
553
+ _columnCalculator.default.buildColumnWidths(columns, availableWidth);
554
+ let result = this.processRow({
555
+ marginX: columnNode._margin ? [columnNode._margin[0], columnNode._margin[2]] : [0, 0],
556
+ cells: columns,
557
+ widths: columns,
558
+ gaps
559
+ });
560
+ addAll(columnNode.positions, result.positions);
561
+ this.nestedLevel--;
562
+ if (this.nestedLevel === 0) {
563
+ this.writer.context().resetMarginXTopParent();
564
+ }
565
+ function gapArray(gap) {
566
+ if (!gap) {
567
+ return null;
568
+ }
569
+ let gaps = [];
570
+ gaps.push(0);
571
+ for (let i = columns.length - 1; i > 0; i--) {
572
+ gaps.push(gap);
573
+ }
574
+ return gaps;
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Searches for a cell in the same row that starts a rowspan and is positioned immediately before the current cell.
580
+ * Alternatively, it finds a cell where the colspan initiating the rowspan extends to the cell just before the current one.
581
+ *
582
+ * @param {Array<object>} arr - An array representing cells in a row.
583
+ * @param {number} i - The index of the current cell to search backward from.
584
+ * @returns {object|null} The starting cell of the rowspan if found; otherwise, `null`.
585
+ */
586
+ _findStartingRowSpanCell(arr, i) {
587
+ let requiredColspan = 1;
588
+ for (let index = i - 1; index >= 0; index--) {
589
+ if (!arr[index]._span) {
590
+ if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
591
+ return arr[index];
592
+ } else {
593
+ return null;
594
+ }
595
+ }
596
+ requiredColspan++;
597
+ }
598
+ return null;
599
+ }
600
+
601
+ /**
602
+ * Retrieves a page break description for a specified page from a list of page breaks.
603
+ *
604
+ * @param {Array<object>} pageBreaks - An array of page break descriptions, each containing `prevPage` properties.
605
+ * @param {number} page - The page number to find the associated page break for.
606
+ * @returns {object|undefined} The page break description object for the specified page if found; otherwise, `undefined`.
607
+ */
608
+ _getPageBreak(pageBreaks, page) {
609
+ return pageBreaks.find(desc => desc.prevPage === page);
610
+ }
611
+ _getPageBreakListBySpan(tableNode, page, rowIndex) {
612
+ if (!tableNode || !tableNode._breaksBySpan) {
613
+ return null;
614
+ }
615
+ const breaksList = tableNode._breaksBySpan.filter(desc => desc.prevPage === page && rowIndex <= desc.rowIndexOfSpanEnd);
616
+ let y = Number.MAX_VALUE,
617
+ prevY = Number.MIN_VALUE;
618
+ breaksList.forEach(b => {
619
+ prevY = Math.max(b.prevY, prevY);
620
+ y = Math.min(b.y, y);
621
+ });
622
+ return {
623
+ prevPage: page,
624
+ prevY: prevY,
625
+ y: y
626
+ };
627
+ }
628
+ _findSameRowPageBreakByRowSpanData(breaksBySpan, page, rowIndex) {
629
+ if (!breaksBySpan) {
630
+ return null;
631
+ }
632
+ return breaksBySpan.find(desc => desc.prevPage === page && rowIndex === desc.rowIndexOfSpanEnd);
633
+ }
634
+ _updatePageBreaksData(pageBreaks, tableNode, rowIndex) {
635
+ Object.keys(tableNode._bottomByPage).forEach(p => {
636
+ const page = Number(p);
637
+ const pageBreak = this._getPageBreak(pageBreaks, page);
638
+ if (pageBreak) {
639
+ pageBreak.prevY = Math.max(pageBreak.prevY, tableNode._bottomByPage[page]);
640
+ }
641
+ if (tableNode._breaksBySpan && tableNode._breaksBySpan.length > 0) {
642
+ const breaksBySpanList = tableNode._breaksBySpan.filter(pb => pb.prevPage === page && rowIndex <= pb.rowIndexOfSpanEnd);
643
+ if (breaksBySpanList && breaksBySpanList.length > 0) {
644
+ breaksBySpanList.forEach(b => {
645
+ b.prevY = Math.max(b.prevY, tableNode._bottomByPage[page]);
646
+ });
647
+ }
648
+ }
649
+ });
650
+ }
651
+
652
+ /**
653
+ * Resolves the Y-coordinates for a target object by comparing two break points.
654
+ *
655
+ * @param {object} break1 - The first break point with `prevY` and `y` properties.
656
+ * @param {object} break2 - The second break point with `prevY` and `y` properties.
657
+ * @param {object} target - The target object to be updated with resolved Y-coordinates.
658
+ * @property {number} target.prevY - Updated to the maximum `prevY` value between `break1` and `break2`.
659
+ * @property {number} target.y - Updated to the minimum `y` value between `break1` and `break2`.
660
+ */
661
+ _resolveBreakY(break1, break2, target) {
662
+ target.prevY = Math.max(break1.prevY, break2.prevY);
663
+ target.y = Math.min(break1.y, break2.y);
664
+ }
665
+ _storePageBreakData(data, startsRowSpan, pageBreaks, tableNode) {
666
+ if (!startsRowSpan) {
667
+ let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
668
+ let pageDescBySpan = this._getPageBreakListBySpan(tableNode, data.prevPage, data.rowIndex);
669
+ if (!pageDesc) {
670
+ pageDesc = {
671
+ ...data
672
+ };
673
+ pageBreaks.push(pageDesc);
674
+ }
675
+ if (pageDescBySpan) {
676
+ this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
677
+ }
678
+ this._resolveBreakY(pageDesc, data, pageDesc);
679
+ } else {
680
+ const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
681
+ let pageDescBySpan = this._findSameRowPageBreakByRowSpanData(breaksBySpan, data.prevPage, data.rowIndex);
682
+ if (!pageDescBySpan) {
683
+ pageDescBySpan = {
684
+ ...data,
685
+ rowIndexOfSpanEnd: data.rowIndex + data.rowSpan - 1
686
+ };
687
+ if (!tableNode._breaksBySpan) {
688
+ tableNode._breaksBySpan = [];
689
+ }
690
+ tableNode._breaksBySpan.push(pageDescBySpan);
691
+ }
692
+ pageDescBySpan.prevY = Math.max(pageDescBySpan.prevY, data.prevY);
693
+ pageDescBySpan.y = Math.min(pageDescBySpan.y, data.y);
694
+ let pageDesc = this._getPageBreak(pageBreaks, data.prevPage);
695
+ if (pageDesc) {
696
+ this._resolveBreakY(pageDesc, pageDescBySpan, pageDesc);
697
+ }
698
+ }
699
+ }
700
+ /**
701
+ * Calculates the left offset for a column based on the specified gap values.
702
+ *
703
+ * @param {number} i - The index of the column for which the offset is being calculated.
704
+ * @param {Array<number>} gaps - An array of gap values for each column.
705
+ * @returns {number} The left offset for the column. Returns `gaps[i]` if it exists, otherwise `0`.
706
+ */
707
+ _colLeftOffset(i, gaps) {
708
+ if (gaps && gaps.length > i) {
709
+ return gaps[i];
710
+ }
711
+ return 0;
712
+ }
713
+
714
+ /**
715
+ * Retrieves the ending cell for a row span in case it exists in a specified table column.
716
+ *
717
+ * @param {Array<Array<object>>} tableBody - The table body, represented as a 2D array of cell objects.
718
+ * @param {number} rowIndex - The index of the starting row for the row span.
719
+ * @param {object} column - The column object containing row span information.
720
+ * @param {number} columnIndex - The index of the column within the row.
721
+ * @returns {object|null} The cell at the end of the row span if it exists; otherwise, `null`.
722
+ * @throws {Error} If the row span extends beyond the total row count.
723
+ */
724
+ _getRowSpanEndingCell(tableBody, rowIndex, column, columnIndex) {
725
+ if (column.rowSpan && column.rowSpan > 1) {
726
+ let endingRow = rowIndex + column.rowSpan - 1;
727
+ if (endingRow >= tableBody.length) {
728
+ throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
729
+ }
730
+ return tableBody[endingRow][columnIndex];
731
+ }
732
+ return null;
733
+ }
734
+ processRow({
735
+ marginX = [0, 0],
736
+ dontBreakRows = false,
737
+ rowsWithoutPageBreak = 0,
738
+ cells,
739
+ widths,
740
+ gaps,
741
+ tableNode,
742
+ tableBody,
743
+ rowIndex,
744
+ height
745
+ }) {
746
+ const isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
747
+ let pageBreaks = [];
748
+ let pageBreaksByRowSpan = [];
749
+ let positions = [];
750
+ let willBreakByHeight = false;
751
+ let verticalAlignmentCells = {};
752
+ widths = widths || cells;
753
+
754
+ // Check if row should break by height
755
+ if (!isUnbreakableRow && height > this.writer.context().availableHeight) {
756
+ willBreakByHeight = true;
757
+ }
758
+
759
+ // Use the marginX if we are in a top level table/column (not nested)
760
+ const marginXParent = this.nestedLevel === 1 ? marginX : null;
761
+ const _bottomByPage = tableNode ? tableNode._bottomByPage : null;
762
+ this.writer.context().beginColumnGroup(marginXParent, _bottomByPage);
763
+
764
+ // RTL table right-alignment: shift the starting x position to the right
765
+ // so that the table content aligns with the right-aligned grid
766
+ if (tableNode && tableNode.table && tableNode.table._rtl) {
767
+ let tableWidth = tableNode._offsets.total;
768
+ for (let w = 0; w < widths.length; w++) {
769
+ tableWidth += widths[w]._calcWidth;
770
+ }
771
+ let rtlOffset = this.writer.context().availableWidth - tableWidth;
772
+ if (rtlOffset > 0.5) {
773
+ this.writer.context().x += rtlOffset;
774
+ }
775
+ }
776
+ for (let i = 0, l = cells.length; i < l; i++) {
777
+ let cell = cells[i];
778
+ let cellIndexBegin = i;
779
+
780
+ // Page change handler
781
+ const storePageBreakClosure = data => {
782
+ const startsRowSpan = cell.rowSpan && cell.rowSpan > 1;
783
+ if (startsRowSpan) {
784
+ data.rowSpan = cell.rowSpan;
785
+ }
786
+ data.rowIndex = rowIndex;
787
+ this._storePageBreakData(data, startsRowSpan, pageBreaks, tableNode);
788
+ };
789
+ this.writer.addListener('pageChanged', storePageBreakClosure);
790
+ let width = widths[i]._calcWidth;
791
+ let leftOffset = this._colLeftOffset(i, gaps);
792
+ // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
793
+ let startingSpanCell = this._findStartingRowSpanCell(cells, i);
794
+ if (cell.colSpan && cell.colSpan > 1) {
795
+ for (let j = 1; j < cell.colSpan; j++) {
796
+ width += widths[++i]._calcWidth + gaps[i];
797
+ }
798
+ }
799
+
800
+ // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
801
+ const rowSpanRightEndingCell = this._getRowSpanEndingCell(tableBody, rowIndex, cell, i);
802
+ const rowSpanLeftEndingCell = this._getRowSpanEndingCell(tableBody, rowIndex, cell, cellIndexBegin);
803
+ if (rowSpanRightEndingCell) {
804
+ // We store a reference of the ending cell in the first cell of the rowspan
805
+ cell._endingCell = rowSpanRightEndingCell;
806
+ cell._endingCell._startingRowSpanY = cell._startingRowSpanY;
807
+ }
808
+ if (rowSpanLeftEndingCell) {
809
+ // We store a reference of the left ending cell in the first cell of the rowspan
810
+ cell._leftEndingCell = rowSpanLeftEndingCell;
811
+ cell._leftEndingCell._startingRowSpanY = cell._startingRowSpanY;
812
+ }
813
+
814
+ // If we are after a cell that started a rowspan
815
+ let endOfRowSpanCell = null;
816
+ if (startingSpanCell && startingSpanCell._endingCell) {
817
+ // Reference to the last cell of the rowspan
818
+ endOfRowSpanCell = startingSpanCell._endingCell;
819
+ // Store if we are in an unbreakable block when we save the context and the originalX
820
+ if (this.writer.transactionLevel > 0) {
821
+ endOfRowSpanCell._isUnbreakableContext = true;
822
+ endOfRowSpanCell._originalXOffset = this.writer.originalX;
823
+ }
824
+ }
825
+
826
+ // We pass the endingSpanCell reference to store the context just after processing rowspan cell
827
+ this.writer.context().beginColumn(width, leftOffset, endOfRowSpanCell);
828
+ if (!cell._span) {
829
+ this.processNode(cell, true);
830
+ this.writer.context().updateBottomByPage();
831
+ if (cell.verticalAlignment) {
832
+ verticalAlignmentCells[cellIndexBegin] = this.verticalAlignmentItemStack.length - 1;
833
+ }
834
+ addAll(positions, cell.positions);
835
+ } else if (cell._columnEndingContext) {
836
+ let discountY = 0;
837
+ if (dontBreakRows) {
838
+ // Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
839
+ const ctxBeforeRowSpanLastRow = this.writer.contextStack[this.writer.contextStack.length - 1];
840
+ discountY = ctxBeforeRowSpanLastRow.y - cell._startingRowSpanY;
841
+ }
842
+ let originalXOffset = 0;
843
+ // If context was saved from an unbreakable block and we are not in an unbreakable block anymore
844
+ // We have to sum the originalX (X before starting unbreakable block) to X
845
+ if (cell._isUnbreakableContext && !this.writer.transactionLevel) {
846
+ originalXOffset = cell._originalXOffset;
847
+ }
848
+ // row-span ending
849
+ // Recover the context after processing the rowspanned cell
850
+ this.writer.context().markEnding(cell, originalXOffset, discountY);
851
+ }
852
+ this.writer.removeListener('pageChanged', storePageBreakClosure);
853
+ }
854
+
855
+ // Check if last cell is part of a span
856
+ let endingSpanCell = null;
857
+ const lastColumn = cells.length > 0 ? cells[cells.length - 1] : null;
858
+ if (lastColumn) {
859
+ // Previous column cell has a rowspan
860
+ if (lastColumn._endingCell) {
861
+ endingSpanCell = lastColumn._endingCell;
862
+ // Previous column cell is part of a span
863
+ } else if (lastColumn._span === true) {
864
+ // We get the cell that started the span where we set a reference to the ending cell
865
+ const startingSpanCell = this._findStartingRowSpanCell(cells, cells.length);
866
+ if (startingSpanCell) {
867
+ // Context will be stored here (ending cell)
868
+ endingSpanCell = startingSpanCell._endingCell;
869
+ // Store if we are in an unbreakable block when we save the context and the originalX
870
+ if (this.writer.transactionLevel > 0) {
871
+ endingSpanCell._isUnbreakableContext = true;
872
+ endingSpanCell._originalXOffset = this.writer.originalX;
873
+ }
874
+ }
875
+ }
876
+ }
877
+
878
+ // If content did not break page, check if we should break by height
879
+ if (willBreakByHeight && !isUnbreakableRow && pageBreaks.length === 0) {
880
+ this.writer.context().moveDown(this.writer.context().availableHeight);
881
+ this.writer.moveToNextPage();
882
+ }
883
+ const bottomByPage = this.writer.context().completeColumnGroup(height, endingSpanCell);
884
+ if (tableNode) {
885
+ tableNode._bottomByPage = bottomByPage;
886
+ // If there are page breaks in this row, update data with prevY of last cell
887
+ this._updatePageBreaksData(pageBreaks, tableNode, rowIndex);
888
+ }
889
+ let rowHeight = this.writer.context().height;
890
+ for (let i = 0, l = cells.length; i < l; i++) {
891
+ let cell = cells[i];
892
+ if (!cell._span && cell.verticalAlignment) {
893
+ let itemBegin = this.verticalAlignmentItemStack[verticalAlignmentCells[i]].begin.item;
894
+ itemBegin.viewHeight = rowHeight;
895
+ itemBegin.nodeHeight = cell.__height;
896
+ itemBegin.cell = cell;
897
+ itemBegin.bottomY = this.writer.context().y;
898
+ itemBegin.isCellContentMultiPage = !itemBegin.cell.positions.every(item => item.pageNumber === itemBegin.cell.positions[0].pageNumber);
899
+ itemBegin.getViewHeight = function () {
900
+ if (this.cell._willBreak) {
901
+ return this.cell._bottomY - this.cell._rowTopPageY;
902
+ }
903
+ if (this.cell.rowSpan && this.cell.rowSpan > 1) {
904
+ if (dontBreakRows) {
905
+ let rowTopPageY = this.cell._leftEndingCell._startingRowSpanY + this.cell._leftEndingCell._rowTopPageYPadding;
906
+ return this.cell._leftEndingCell._rowTopPageY - rowTopPageY + this.cell._leftEndingCell._bottomY;
907
+ } else {
908
+ if (this.cell.positions[0].pageNumber !== this.cell._leftEndingCell._lastPageNumber) {
909
+ return this.bottomY - this.cell._leftEndingCell._bottomY;
910
+ }
911
+ return this.viewHeight + this.cell._leftEndingCell._bottomY - this.bottomY;
912
+ }
913
+ }
914
+ return this.viewHeight;
915
+ };
916
+ itemBegin.getNodeHeight = function () {
917
+ return this.nodeHeight;
918
+ };
919
+ let itemEnd = this.verticalAlignmentItemStack[verticalAlignmentCells[i]].end.item;
920
+ itemEnd.isCellContentMultiPage = itemBegin.isCellContentMultiPage;
921
+ }
922
+ }
923
+ return {
924
+ pageBreaksBySpan: pageBreaksByRowSpan,
925
+ pageBreaks: pageBreaks,
926
+ positions: positions
927
+ };
928
+ }
929
+
930
+ // lists
931
+ processList(orderedList, node) {
932
+ // Detect if list is RTL based on content
933
+ let listItems = orderedList ? node.ol : node.ul;
934
+ let isRTLList = this._isListRTL(listItems);
935
+ const addMarkerToFirstLeaf = line => {
936
+ // I'm not very happy with the way list processing is implemented
937
+ // (both code and algorithm should be rethinked)
938
+ if (nextMarker) {
939
+ let marker = nextMarker;
940
+ nextMarker = null;
941
+ if (marker.canvas) {
942
+ let vector = marker.canvas[0];
943
+ if (isRTLList) {
944
+ // RTL: place marker in the right gap area
945
+ // availableWidth is the right edge of content area, marker goes just past it
946
+ (0, _tools.offsetVector)(vector, this.writer.context().availableWidth + 10, 0);
947
+ } else {
948
+ (0, _tools.offsetVector)(vector, -marker._minWidth, 0);
949
+ }
950
+ this.writer.addVector(vector);
951
+ } else if (marker._inlines) {
952
+ let markerLine = new _Line.default(this.pageSize.width);
953
+ // Reset alignment and direction on marker inlines to prevent
954
+ // alignLine() from applying alignment offset on top of the
955
+ // manually calculated marker position
956
+ let markerInline = Object.assign({}, marker._inlines[0]);
957
+ markerInline.alignment = 'left';
958
+ markerInline.isRTL = false;
959
+ markerInline.direction = 'ltr';
960
+ markerLine.addInline(markerInline);
961
+ markerLine.listMarker = true;
962
+ if (isRTLList) {
963
+ // RTL: place text marker (number) in the right gap area with spacing
964
+ markerLine.x = this.writer.context().availableWidth + 3;
965
+ } else {
966
+ markerLine.x = -marker._minWidth;
967
+ }
968
+ markerLine.y = line.getAscenderHeight() - markerLine.getAscenderHeight();
969
+ this.writer.addLine(markerLine, true);
970
+ }
971
+ }
972
+ };
973
+ let items = orderedList ? node.ol : node.ul;
974
+ let gapSize = node._gapSize;
975
+ if (isRTLList) {
976
+ // RTL: reserve gap on the right side for marker
977
+ this.writer.context().addMargin(0, gapSize.width);
978
+ } else {
979
+ this.writer.context().addMargin(gapSize.width);
980
+ }
981
+ let nextMarker;
982
+ this.writer.addListener('lineAdded', addMarkerToFirstLeaf);
983
+ items.forEach(item => {
984
+ nextMarker = item.listMarker;
985
+ this.processNode(item);
986
+ addAll(node.positions, item.positions);
987
+ });
988
+ this.writer.removeListener('lineAdded', addMarkerToFirstLeaf);
989
+ if (isRTLList) {
990
+ this.writer.context().addMargin(0, -gapSize.width);
991
+ } else {
992
+ this.writer.context().addMargin(-gapSize.width);
993
+ }
994
+ }
995
+
996
+ /**
997
+ * Check if a list contains predominantly RTL content
998
+ * @param {Array} items - List items
999
+ * @returns {boolean}
1000
+ */
1001
+ _isListRTL(items) {
1002
+ if (!items || !Array.isArray(items)) return false;
1003
+ let rtlCount = 0;
1004
+ let total = 0;
1005
+ for (let i = 0; i < items.length; i++) {
1006
+ let item = items[i];
1007
+ let text = '';
1008
+ if (typeof item === 'string') {
1009
+ text = item;
1010
+ } else if (item && item.text) {
1011
+ text = typeof item.text === 'string' ? item.text : Array.isArray(item.text) ? item.text.map(t => typeof t === 'string' ? t : t && t.text || '').join('') : '';
1012
+ }
1013
+ if (text) {
1014
+ total++;
1015
+ if ((0, _rtlUtils.containsRTL)(text)) {
1016
+ rtlCount++;
1017
+ }
1018
+ }
1019
+ }
1020
+ return total > 0 && rtlCount / total >= 0.3;
1021
+ }
1022
+
1023
+ // tables
1024
+ processTable(tableNode) {
1025
+ this.nestedLevel++;
1026
+ let processor = new _TableProcessor.default(tableNode);
1027
+ processor.beginTable(this.writer);
1028
+ let rowHeights = tableNode.table.heights;
1029
+ for (let i = 0, l = tableNode.table.body.length; i < l; i++) {
1030
+ // if dontBreakRows and row starts a rowspan
1031
+ // we store the 'y' of the beginning of each rowSpan
1032
+ if (processor.dontBreakRows) {
1033
+ tableNode.table.body[i].forEach(cell => {
1034
+ if (cell.rowSpan && cell.rowSpan > 1) {
1035
+ cell._startingRowSpanY = this.writer.context().y;
1036
+ }
1037
+ });
1038
+ }
1039
+ processor.beginRow(i, this.writer);
1040
+ let height;
1041
+ if (typeof rowHeights === 'function') {
1042
+ height = rowHeights(i);
1043
+ } else if (Array.isArray(rowHeights)) {
1044
+ height = rowHeights[i];
1045
+ } else {
1046
+ height = rowHeights;
1047
+ }
1048
+ if (height === 'auto') {
1049
+ height = undefined;
1050
+ }
1051
+ const pageBeforeProcessing = this.writer.context().page;
1052
+ let result = this.processRow({
1053
+ marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
1054
+ dontBreakRows: processor.dontBreakRows,
1055
+ rowsWithoutPageBreak: processor.rowsWithoutPageBreak,
1056
+ cells: tableNode.table.body[i],
1057
+ widths: tableNode.table.widths,
1058
+ gaps: tableNode._offsets.offsets,
1059
+ tableBody: tableNode.table.body,
1060
+ tableNode,
1061
+ rowIndex: i,
1062
+ height
1063
+ });
1064
+ addAll(tableNode.positions, result.positions);
1065
+ if (!result.pageBreaks || result.pageBreaks.length === 0) {
1066
+ const breaksBySpan = tableNode && tableNode._breaksBySpan || null;
1067
+ const breakBySpanData = this._findSameRowPageBreakByRowSpanData(breaksBySpan, pageBeforeProcessing, i);
1068
+ if (breakBySpanData) {
1069
+ const finalBreakBySpanData = this._getPageBreakListBySpan(tableNode, breakBySpanData.prevPage, i);
1070
+ result.pageBreaks.push(finalBreakBySpanData);
1071
+ }
1072
+ }
1073
+ processor.endRow(i, this.writer, result.pageBreaks);
1074
+ }
1075
+ processor.endTable(this.writer);
1076
+ this.nestedLevel--;
1077
+ if (this.nestedLevel === 0) {
1078
+ this.writer.context().resetMarginXTopParent();
1079
+ }
1080
+ }
1081
+
1082
+ // leafs (texts)
1083
+ processLeaf(node) {
1084
+ let line = this.buildNextLine(node);
1085
+ if (line && (node.tocItem || node.id)) {
1086
+ line._node = node;
1087
+ }
1088
+ let currentHeight = line ? line.getHeight() : 0;
1089
+ let maxHeight = node.maxHeight || -1;
1090
+ if (line) {
1091
+ let nodeId = (0, _node.getNodeId)(node);
1092
+ if (nodeId) {
1093
+ line.id = nodeId;
1094
+ }
1095
+ }
1096
+ if (node._tocItemRef) {
1097
+ line._pageNodeRef = node._tocItemRef;
1098
+ }
1099
+ if (node._pageRef) {
1100
+ line._pageNodeRef = node._pageRef._nodeRef;
1101
+ }
1102
+ if (line && line.inlines && Array.isArray(line.inlines)) {
1103
+ for (let i = 0, l = line.inlines.length; i < l; i++) {
1104
+ if (line.inlines[i]._tocItemRef) {
1105
+ line.inlines[i]._pageNodeRef = line.inlines[i]._tocItemRef;
1106
+ }
1107
+ if (line.inlines[i]._pageRef) {
1108
+ line.inlines[i]._pageNodeRef = line.inlines[i]._pageRef._nodeRef;
1109
+ }
1110
+ }
1111
+ }
1112
+ while (line && (maxHeight === -1 || currentHeight < maxHeight)) {
1113
+ let positions = this.writer.addLine(line);
1114
+ node.positions.push(positions);
1115
+ line = this.buildNextLine(node);
1116
+ if (line) {
1117
+ currentHeight += line.getHeight();
1118
+ }
1119
+ }
1120
+ }
1121
+ processToc(node) {
1122
+ if (!node.toc._table && node.toc.hideEmpty === true) {
1123
+ return;
1124
+ }
1125
+ if (node.toc.title) {
1126
+ this.processNode(node.toc.title);
1127
+ }
1128
+ if (node.toc._table) {
1129
+ this.processNode(node.toc._table);
1130
+ }
1131
+ }
1132
+ buildNextLine(textNode) {
1133
+ function cloneInline(inline) {
1134
+ let newInline = inline.constructor();
1135
+ for (let key in inline) {
1136
+ newInline[key] = inline[key];
1137
+ }
1138
+ return newInline;
1139
+ }
1140
+ function findMaxFitLength(text, maxWidth, measureFn) {
1141
+ let low = 1;
1142
+ let high = text.length;
1143
+ let bestFit = 1;
1144
+ while (low <= high) {
1145
+ const mid = Math.floor((low + high) / 2);
1146
+ const part = text.substring(0, mid);
1147
+ const width = measureFn(part);
1148
+ if (width <= maxWidth) {
1149
+ bestFit = mid;
1150
+ low = mid + 1;
1151
+ } else {
1152
+ high = mid - 1;
1153
+ }
1154
+ }
1155
+ return bestFit;
1156
+ }
1157
+ if (!textNode._inlines || textNode._inlines.length === 0) {
1158
+ return null;
1159
+ }
1160
+ let line = new _Line.default(this.writer.context().availableWidth);
1161
+ const textInlines = new _TextInlines.default(null);
1162
+ let isForceContinue = false;
1163
+ while (textNode._inlines && textNode._inlines.length > 0 && (line.hasEnoughSpaceForInline(textNode._inlines[0], textNode._inlines.slice(1)) || isForceContinue)) {
1164
+ let isHardWrap = false;
1165
+ let inline = textNode._inlines.shift();
1166
+ isForceContinue = false;
1167
+ if (!inline.noWrap && inline.text.length > 1 && inline.width > line.getAvailableWidth()) {
1168
+ let maxChars = findMaxFitLength(inline.text, line.getAvailableWidth(), txt => textInlines.widthOfText(txt, inline));
1169
+ if (maxChars < inline.text.length) {
1170
+ let newInline = cloneInline(inline);
1171
+ newInline.text = inline.text.substr(maxChars);
1172
+ inline.text = inline.text.substr(0, maxChars);
1173
+ newInline.width = textInlines.widthOfText(newInline.text, newInline);
1174
+ inline.width = textInlines.widthOfText(inline.text, inline);
1175
+ textNode._inlines.unshift(newInline);
1176
+ isHardWrap = true;
1177
+ }
1178
+ }
1179
+ line.addInline(inline);
1180
+ isForceContinue = inline.noNewLine && !isHardWrap;
1181
+ }
1182
+ line.lastLineInParagraph = textNode._inlines.length === 0;
1183
+ return line;
1184
+ }
1185
+
1186
+ // images
1187
+ processImage(node) {
1188
+ let position = this.writer.addImage(node);
1189
+ node.positions.push(position);
1190
+ }
1191
+ processCanvas(node) {
1192
+ let positions = this.writer.addCanvas(node);
1193
+ addAll(node.positions, positions);
1194
+ }
1195
+ processSVG(node) {
1196
+ let position = this.writer.addSVG(node);
1197
+ node.positions.push(position);
1198
+ }
1199
+ processQr(node) {
1200
+ let position = this.writer.addQr(node);
1201
+ node.positions.push(position);
1202
+ }
1203
+ processAttachment(node) {
1204
+ let position = this.writer.addAttachment(node);
1205
+ node.positions.push(position);
1206
+ }
1207
+ }
1208
+ function decorateNode(node) {
1209
+ let x = node.x;
1210
+ let y = node.y;
1211
+ node.positions = [];
1212
+ if (Array.isArray(node.canvas)) {
1213
+ node.canvas.forEach(vector => {
1214
+ let x = vector.x;
1215
+ let y = vector.y;
1216
+ let x1 = vector.x1;
1217
+ let y1 = vector.y1;
1218
+ let x2 = vector.x2;
1219
+ let y2 = vector.y2;
1220
+ vector.resetXY = () => {
1221
+ vector.x = x;
1222
+ vector.y = y;
1223
+ vector.x1 = x1;
1224
+ vector.y1 = y1;
1225
+ vector.x2 = x2;
1226
+ vector.y2 = y2;
1227
+ };
1228
+ });
1229
+ }
1230
+ node.resetXY = () => {
1231
+ node.x = x;
1232
+ node.y = y;
1233
+ if (Array.isArray(node.canvas)) {
1234
+ node.canvas.forEach(vector => {
1235
+ vector.resetXY();
1236
+ });
1237
+ }
1238
+ };
1239
+ }
1240
+ var _default = exports.default = LayoutBuilder;