@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,687 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _variableType = require("./helpers/variableType");
6
+ var _tools = require("./helpers/tools");
7
+ var _DocumentContext = _interopRequireDefault(require("./DocumentContext"));
8
+ var _events = require("events");
9
+ var _rtlUtils = require("./rtlUtils");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ /**
12
+ * A line/vector writer, which adds elements to current page and sets
13
+ * their positions based on the context
14
+ */
15
+ class ElementWriter extends _events.EventEmitter {
16
+ /**
17
+ * @param {DocumentContext} context
18
+ */
19
+ constructor(context) {
20
+ super();
21
+ this._context = context;
22
+ this.contextStack = [];
23
+ }
24
+
25
+ /**
26
+ * @returns {DocumentContext}
27
+ */
28
+ context() {
29
+ return this._context;
30
+ }
31
+ addLine(line, dontUpdateContextPosition, index) {
32
+ let height = line.getHeight();
33
+ let context = this.context();
34
+ let page = context.getCurrentPage();
35
+ let position = this.getCurrentPositionOnPage();
36
+ if (context.availableHeight < height || !page) {
37
+ return false;
38
+ }
39
+ line.x = context.x + (line.x || 0);
40
+ line.y = context.y + (line.y || 0);
41
+ this.alignLine(line);
42
+ addPageItem(page, {
43
+ type: 'line',
44
+ item: line
45
+ }, index);
46
+ this.emit('lineAdded', line);
47
+ if (!dontUpdateContextPosition) {
48
+ context.moveDown(height);
49
+ }
50
+ return position;
51
+ }
52
+ alignLine(line) {
53
+ // Skip alignment for list marker lines - their position is manually
54
+ // calculated in processList and should not be affected by inherited
55
+ // alignment or direction properties
56
+ if (line.listMarker) {
57
+ return;
58
+ }
59
+ let width = this.context().availableWidth;
60
+ let lineWidth = line.getWidth();
61
+ let alignment = line.inlines && line.inlines.length > 0 && line.inlines[0].alignment;
62
+ let isRTL = line.isRTL && line.isRTL();
63
+ let offset = 0;
64
+
65
+ // For RTL lines, apply special handling
66
+ if (isRTL) {
67
+ if (!alignment || alignment === 'left') {
68
+ alignment = 'right';
69
+ }
70
+ this.adjustRTLInlines(line, width);
71
+ }
72
+ switch (alignment) {
73
+ case 'right':
74
+ offset = width - lineWidth;
75
+ break;
76
+ case 'center':
77
+ offset = (width - lineWidth) / 2;
78
+ break;
79
+ }
80
+ if (offset) {
81
+ line.x = (line.x || 0) + offset;
82
+ }
83
+ if (alignment === 'justify' && !line.newLineForced && !line.lastLineInParagraph && line.inlines.length > 1) {
84
+ let additionalSpacing = (width - lineWidth) / (line.inlines.length - 1);
85
+ for (let i = 1, l = line.inlines.length; i < l; i++) {
86
+ offset = i * additionalSpacing;
87
+ line.inlines[i].x += offset;
88
+ line.inlines[i].justifyShift = additionalSpacing;
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Adjust RTL inline positioning - reorder inlines for proper visual display.
95
+ *
96
+ * Implements a simplified Unicode Bidirectional Algorithm (UBA):
97
+ * 0. Pre-split inlines at RTL↔neutral boundaries so punctuation like "/" between
98
+ * Arabic and Latin text is treated as a separate neutral inline
99
+ * 1. Classify each inline as RTL, LTR, or neutral
100
+ * 2. Group consecutive same-direction inlines into directional "runs"
101
+ * 3. Resolve neutral runs: attach to adjacent run based on surrounding context
102
+ * 4. Reverse the order of runs (base direction is RTL)
103
+ * 5. Within each LTR run keep order; within each RTL run reverse inlines
104
+ * 6. Recalculate x positions
105
+ *
106
+ * This preserves the positional relationship between adjacent text and
107
+ * punctuation (e.g. "العربية/arabic" keeps the "/" attached correctly).
108
+ *
109
+ * @param {object} line - Line containing RTL text
110
+ */
111
+ adjustRTLInlines(line) {
112
+ if (!line.inlines || line.inlines.length === 0) {
113
+ return;
114
+ }
115
+ const LTR_REGEX = /[A-Za-z\u00C0-\u024F\u1E00-\u1EFF]/;
116
+ const NUMBER_PUNCTUATION_REGEX = /^(\d+)([.:/\-)(]+)(\s*)$/;
117
+ // Characters that are "boundary neutral" — separators/punctuation between scripts
118
+ const BOUNDARY_NEUTRAL = /[/\\\-()[\]{}<>:;.,!?@#$%^&*_=+|~`'"،؛؟\s]/;
119
+
120
+ // --- Step 0: Pre-split inlines at RTL↔neutral and LTR↔neutral boundaries ---
121
+ // e.g. "العربية/" → ["العربية", "/"] and "hello-" → ["hello", "-"]
122
+ let splitInlines = [];
123
+ line.inlines.forEach(inline => {
124
+ let text = inline.text;
125
+ if (!text || text.length === 0) {
126
+ splitInlines.push(inline);
127
+ return;
128
+ }
129
+ let hasStrongRTL = (0, _rtlUtils.containsRTL)(text);
130
+ let hasStrongLTR = LTR_REGEX.test(text);
131
+
132
+ // Only split if the inline has strong directional chars AND trailing/leading neutrals
133
+ if ((hasStrongRTL || hasStrongLTR) && text.length > 1) {
134
+ // Split trailing neutral characters (e.g. "العربية/" → "العربية" + "/")
135
+ let trailingStart = text.length;
136
+ while (trailingStart > 0) {
137
+ let ch = text[trailingStart - 1];
138
+ if (BOUNDARY_NEUTRAL.test(ch) && !(0, _rtlUtils.containsRTL)(ch) && !LTR_REGEX.test(ch)) {
139
+ trailingStart--;
140
+ } else {
141
+ break;
142
+ }
143
+ }
144
+
145
+ // Split leading neutral characters (e.g. "/العربية" → "/" + "العربية")
146
+ let leadingEnd = 0;
147
+ while (leadingEnd < text.length) {
148
+ let ch = text[leadingEnd];
149
+ if (BOUNDARY_NEUTRAL.test(ch) && !(0, _rtlUtils.containsRTL)(ch) && !LTR_REGEX.test(ch)) {
150
+ leadingEnd++;
151
+ } else {
152
+ break;
153
+ }
154
+ }
155
+
156
+ // Only split if there's a meaningful core left
157
+ if ((leadingEnd > 0 || trailingStart < text.length) && leadingEnd < trailingStart) {
158
+ let leadingText = text.slice(0, leadingEnd);
159
+ let coreText = text.slice(leadingEnd, trailingStart);
160
+ let trailingText = text.slice(trailingStart);
161
+ if (leadingText) {
162
+ let clone = Object.assign({}, inline);
163
+ clone.text = leadingText;
164
+ clone.width = inline.font ? inline.font.widthOfString(leadingText, inline.fontSize, inline.fontFeatures) + (inline.characterSpacing || 0) * (leadingText.length - 1) : 0;
165
+ clone._isSplit = true;
166
+ splitInlines.push(clone);
167
+ }
168
+ if (coreText) {
169
+ let clone = Object.assign({}, inline);
170
+ clone.text = coreText;
171
+ clone.width = inline.font ? inline.font.widthOfString(coreText, inline.fontSize, inline.fontFeatures) + (inline.characterSpacing || 0) * (coreText.length - 1) : 0;
172
+ clone._isSplit = true;
173
+ splitInlines.push(clone);
174
+ }
175
+ if (trailingText) {
176
+ let clone = Object.assign({}, inline);
177
+ clone.text = trailingText;
178
+ clone.width = inline.font ? inline.font.widthOfString(trailingText, inline.fontSize, inline.fontFeatures) + (inline.characterSpacing || 0) * (trailingText.length - 1) : 0;
179
+ clone._isSplit = true;
180
+ splitInlines.push(clone);
181
+ }
182
+ } else {
183
+ splitInlines.push(inline);
184
+ }
185
+ } else {
186
+ splitInlines.push(inline);
187
+ }
188
+ });
189
+
190
+ // --- Step 1: Classify each inline ---
191
+ const classified = splitInlines.map(inline => {
192
+ let hasStrongLTR = LTR_REGEX.test(inline.text);
193
+ let hasStrongRTL = (0, _rtlUtils.containsRTL)(inline.text);
194
+ let dir;
195
+ if (hasStrongRTL && hasStrongLTR) {
196
+ // Mixed — treat as RTL (predominant for RTL lines)
197
+ dir = 'rtl';
198
+ } else if (hasStrongRTL) {
199
+ dir = 'rtl';
200
+ } else if (hasStrongLTR) {
201
+ dir = 'ltr';
202
+ } else {
203
+ dir = 'neutral'; // punctuation, digits, spaces only
204
+ }
205
+ return {
206
+ inline,
207
+ dir
208
+ };
209
+ });
210
+
211
+ // --- Step 2: Build directional runs (groups of consecutive same-direction) ---
212
+ let runs = [];
213
+ let currentRun = null;
214
+ classified.forEach(item => {
215
+ if (!currentRun || currentRun.dir !== item.dir) {
216
+ currentRun = {
217
+ dir: item.dir,
218
+ inlines: []
219
+ };
220
+ runs.push(currentRun);
221
+ }
222
+ currentRun.inlines.push(item.inline);
223
+ });
224
+
225
+ // --- Step 3: Resolve neutral runs ---
226
+ // Step 3a: Bracket pair resolution (UBA rule N0).
227
+ // Find matching bracket pairs across runs. If the content between
228
+ // a "(" neutral run and a ")" neutral run is predominantly one direction,
229
+ // merge the opening bracket, content, and closing bracket into that direction.
230
+ const OPEN_BRACKETS = /[(\\[{<]/;
231
+ // const CLOSE_BRACKETS = /[)\]}>]/;
232
+ const BRACKET_MATCH = {
233
+ '(': ')',
234
+ '[': ']',
235
+ '{': '}',
236
+ '<': '>'
237
+ };
238
+ for (let i = 0; i < runs.length; i++) {
239
+ if (runs[i].dir !== 'neutral') continue;
240
+
241
+ // Check if this neutral run contains an opening bracket
242
+ let openBracket = null;
243
+ for (let k = 0; k < runs[i].inlines.length; k++) {
244
+ let txt = runs[i].inlines[k].text.trim();
245
+ if (OPEN_BRACKETS.test(txt)) {
246
+ openBracket = txt.match(OPEN_BRACKETS)[0];
247
+ break;
248
+ }
249
+ }
250
+ if (!openBracket) continue;
251
+ let closeBracket = BRACKET_MATCH[openBracket];
252
+
253
+ // Search forward for the matching closing bracket
254
+ for (let j = i + 1; j < runs.length; j++) {
255
+ if (runs[j].dir === 'neutral') {
256
+ let hasClose = false;
257
+ for (let k = 0; k < runs[j].inlines.length; k++) {
258
+ if (runs[j].inlines[k].text.indexOf(closeBracket) >= 0) {
259
+ hasClose = true;
260
+ break;
261
+ }
262
+ }
263
+ if (!hasClose) continue;
264
+
265
+ // Found matching close bracket at run j.
266
+ // Determine predominant direction of content between i and j
267
+ let innerLtr = 0,
268
+ innerRtl = 0;
269
+ for (let m = i + 1; m < j; m++) {
270
+ if (runs[m].dir === 'ltr') innerLtr += runs[m].inlines.length;else if (runs[m].dir === 'rtl') innerRtl += runs[m].inlines.length;
271
+ }
272
+
273
+ // Resolve bracket pair to inner content direction, or LTR if neutral-only
274
+ let pairDir = innerLtr >= innerRtl ? 'ltr' : 'rtl';
275
+
276
+ // Set the direction for the opening and closing bracket runs
277
+ runs[i].dir = pairDir;
278
+ runs[j].dir = pairDir;
279
+ break; // only match the first closing bracket
280
+ }
281
+ }
282
+ }
283
+
284
+ // Step 3b: General neutral resolution.
285
+ // A neutral run takes the direction of its neighbors. If both neighbors
286
+ // agree, use that direction. If they disagree, use the base direction (RTL).
287
+ // If only one neighbor exists, use that neighbor's resolved direction.
288
+ for (let i = 0; i < runs.length; i++) {
289
+ if (runs[i].dir !== 'neutral') continue;
290
+ let prevDir = null;
291
+ for (let j = i - 1; j >= 0; j--) {
292
+ if (runs[j].dir !== 'neutral') {
293
+ prevDir = runs[j].dir;
294
+ break;
295
+ }
296
+ }
297
+ let nextDir = null;
298
+ for (let j = i + 1; j < runs.length; j++) {
299
+ if (runs[j].dir !== 'neutral') {
300
+ nextDir = runs[j].dir;
301
+ break;
302
+ }
303
+ }
304
+ if (prevDir && nextDir) {
305
+ runs[i].dir = prevDir === nextDir ? prevDir : 'rtl';
306
+ } else if (prevDir) {
307
+ runs[i].dir = prevDir;
308
+ } else if (nextDir) {
309
+ runs[i].dir = nextDir;
310
+ } else {
311
+ runs[i].dir = 'rtl'; // all neutral → base direction
312
+ }
313
+ }
314
+
315
+ // --- Step 3c: Merge adjacent runs that now share the same direction ---
316
+ let merged = [runs[0]];
317
+ for (let i = 1; i < runs.length; i++) {
318
+ let last = merged[merged.length - 1];
319
+ if (last.dir === runs[i].dir) {
320
+ last.inlines = last.inlines.concat(runs[i].inlines);
321
+ } else {
322
+ merged.push(runs[i]);
323
+ }
324
+ }
325
+ runs = merged;
326
+
327
+ // --- Step 4: Reverse run order (base direction is RTL) ---
328
+ runs.reverse();
329
+
330
+ // --- Step 5: Within each RTL run, reverse the inline order ---
331
+ runs.forEach(run => {
332
+ if (run.dir === 'rtl') {
333
+ run.inlines.reverse();
334
+ }
335
+ // LTR runs keep their original inline order
336
+ });
337
+
338
+ // --- Step 6: Flatten, apply bracket mirroring, recalculate x positions ---
339
+ // UBA Rule L4: after reordering, mirror bracket glyphs in RTL context
340
+ let reorderedInlines = [];
341
+ let currentX = 0;
342
+ const MIRROR_MAP = {
343
+ '(': ')',
344
+ ')': '(',
345
+ '[': ']',
346
+ ']': '[',
347
+ '{': '}',
348
+ '}': '{',
349
+ '<': '>',
350
+ '>': '<'
351
+ };
352
+ runs.forEach(run => {
353
+ run.inlines.forEach(inline => {
354
+ // Apply context-aware bracket mirroring for RTL inlines that contain Arabic text
355
+ if (run.dir === 'rtl' && (0, _rtlUtils.containsRTL)(inline.text)) {
356
+ inline.text = (0, _rtlUtils.fixArabicTextUsingReplace)(inline.text);
357
+ }
358
+
359
+ // UBA Rule L4: Mirror standalone bracket characters in RTL runs.
360
+ // After Step 5 reversed the inline order, brackets like "(" and ")"
361
+ // are in swapped positions. Mirroring the glyph restores correct visuals.
362
+ // e.g. reversed ")" at position 0 → mirror to "(" → visually correct.
363
+ if (run.dir === 'rtl' && !(0, _rtlUtils.containsRTL)(inline.text) && !LTR_REGEX.test(inline.text)) {
364
+ let mirrored = '';
365
+ for (let c = 0; c < inline.text.length; c++) {
366
+ let ch = inline.text[c];
367
+ mirrored += MIRROR_MAP[ch] !== undefined ? MIRROR_MAP[ch] : ch;
368
+ }
369
+ inline.text = mirrored;
370
+ }
371
+
372
+ // Fix number+punctuation rendering in RTL context
373
+ if (run.dir === 'rtl' && NUMBER_PUNCTUATION_REGEX.test(inline.text)) {
374
+ inline.text = inline.text.replace(NUMBER_PUNCTUATION_REGEX, ' $3$2$1');
375
+ }
376
+ inline.x = currentX;
377
+ currentX += inline.width;
378
+ reorderedInlines.push(inline);
379
+ });
380
+ });
381
+ line.inlines = reorderedInlines;
382
+ }
383
+ addImage(image, index) {
384
+ let context = this.context();
385
+ let page = context.getCurrentPage();
386
+ let position = this.getCurrentPositionOnPage();
387
+ if (!page || image.absolutePosition === undefined && context.availableHeight < image._height && page.items.length > 0) {
388
+ return false;
389
+ }
390
+ if (image._x === undefined) {
391
+ image._x = image.x || 0;
392
+ }
393
+ image.x = context.x + image._x;
394
+ image.y = context.y;
395
+ this.alignImage(image);
396
+ addPageItem(page, {
397
+ type: 'image',
398
+ item: image
399
+ }, index);
400
+ context.moveDown(image._height);
401
+ return position;
402
+ }
403
+ addCanvas(node, index) {
404
+ let context = this.context();
405
+ let page = context.getCurrentPage();
406
+ let positions = [];
407
+ let height = node._minHeight;
408
+ if (!page || node.absolutePosition === undefined && context.availableHeight < height) {
409
+ // TODO: support for canvas larger than a page
410
+ // TODO: support for other overflow methods
411
+
412
+ return false;
413
+ }
414
+ this.alignCanvas(node);
415
+ node.canvas.forEach(function (vector) {
416
+ let position = this.addVector(vector, false, false, index);
417
+ positions.push(position);
418
+ if (index !== undefined) {
419
+ index++;
420
+ }
421
+ }, this);
422
+ context.moveDown(height);
423
+ return positions;
424
+ }
425
+ addSVG(image, index) {
426
+ // TODO: same as addImage
427
+ let context = this.context();
428
+ let page = context.getCurrentPage();
429
+ let position = this.getCurrentPositionOnPage();
430
+ if (!page || image.absolutePosition === undefined && context.availableHeight < image._height && page.items.length > 0) {
431
+ return false;
432
+ }
433
+ if (image._x === undefined) {
434
+ image._x = image.x || 0;
435
+ }
436
+ image.x = context.x + image._x;
437
+ image.y = context.y;
438
+ this.alignImage(image);
439
+ addPageItem(page, {
440
+ type: 'svg',
441
+ item: image
442
+ }, index);
443
+ context.moveDown(image._height);
444
+ return position;
445
+ }
446
+ addQr(qr, index) {
447
+ let context = this.context();
448
+ let page = context.getCurrentPage();
449
+ let position = this.getCurrentPositionOnPage();
450
+ if (!page || qr.absolutePosition === undefined && context.availableHeight < qr._height) {
451
+ return false;
452
+ }
453
+ if (qr._x === undefined) {
454
+ qr._x = qr.x || 0;
455
+ }
456
+ qr.x = context.x + qr._x;
457
+ qr.y = context.y;
458
+ this.alignImage(qr);
459
+ for (let i = 0, l = qr._canvas.length; i < l; i++) {
460
+ let vector = qr._canvas[i];
461
+ vector.x += qr.x;
462
+ vector.y += qr.y;
463
+ this.addVector(vector, true, true, index);
464
+ }
465
+ context.moveDown(qr._height);
466
+ return position;
467
+ }
468
+ addAttachment(attachment, index) {
469
+ let context = this.context();
470
+ let page = context.getCurrentPage();
471
+ let position = this.getCurrentPositionOnPage();
472
+ if (!page || attachment.absolutePosition === undefined && context.availableHeight < attachment._height && page.items.length > 0) {
473
+ return false;
474
+ }
475
+ if (attachment._x === undefined) {
476
+ attachment._x = attachment.x || 0;
477
+ }
478
+ attachment.x = context.x + attachment._x;
479
+ attachment.y = context.y;
480
+ addPageItem(page, {
481
+ type: 'attachment',
482
+ item: attachment
483
+ }, index);
484
+ context.moveDown(attachment._height);
485
+ return position;
486
+ }
487
+ alignImage(image) {
488
+ let width = this.context().availableWidth;
489
+ let imageWidth = image._minWidth;
490
+ let offset = 0;
491
+ switch (image._alignment) {
492
+ case 'right':
493
+ offset = width - imageWidth;
494
+ break;
495
+ case 'center':
496
+ offset = (width - imageWidth) / 2;
497
+ break;
498
+ }
499
+ if (offset) {
500
+ image.x = (image.x || 0) + offset;
501
+ }
502
+ }
503
+ alignCanvas(node) {
504
+ let width = this.context().availableWidth;
505
+ let canvasWidth = node._minWidth;
506
+ let offset = 0;
507
+ switch (node._alignment) {
508
+ case 'right':
509
+ offset = width - canvasWidth;
510
+ break;
511
+ case 'center':
512
+ offset = (width - canvasWidth) / 2;
513
+ break;
514
+ }
515
+ if (offset) {
516
+ node.canvas.forEach(vector => {
517
+ (0, _tools.offsetVector)(vector, offset, 0);
518
+ });
519
+ }
520
+ }
521
+ addVector(vector, ignoreContextX, ignoreContextY, index, forcePage) {
522
+ let context = this.context();
523
+ let page = context.getCurrentPage();
524
+ if ((0, _variableType.isNumber)(forcePage)) {
525
+ page = context.pages[forcePage];
526
+ }
527
+ let position = this.getCurrentPositionOnPage();
528
+ if (page) {
529
+ (0, _tools.offsetVector)(vector, ignoreContextX ? 0 : context.x, ignoreContextY ? 0 : context.y);
530
+ addPageItem(page, {
531
+ type: 'vector',
532
+ item: vector
533
+ }, index);
534
+ return position;
535
+ }
536
+ }
537
+ beginClip(width, height) {
538
+ let ctx = this.context();
539
+ let page = ctx.getCurrentPage();
540
+ page.items.push({
541
+ type: 'beginClip',
542
+ item: {
543
+ x: ctx.x,
544
+ y: ctx.y,
545
+ width: width,
546
+ height: height
547
+ }
548
+ });
549
+ return true;
550
+ }
551
+ endClip() {
552
+ let ctx = this.context();
553
+ let page = ctx.getCurrentPage();
554
+ page.items.push({
555
+ type: 'endClip'
556
+ });
557
+ return true;
558
+ }
559
+ beginVerticalAlignment(verticalAlignment) {
560
+ let page = this.context().getCurrentPage();
561
+ let item = {
562
+ type: 'beginVerticalAlignment',
563
+ item: {
564
+ verticalAlignment: verticalAlignment
565
+ }
566
+ };
567
+ page.items.push(item);
568
+ return item;
569
+ }
570
+ endVerticalAlignment(verticalAlignment) {
571
+ let page = this.context().getCurrentPage();
572
+ let item = {
573
+ type: 'endVerticalAlignment',
574
+ item: {
575
+ verticalAlignment: verticalAlignment
576
+ }
577
+ };
578
+ page.items.push(item);
579
+ return item;
580
+ }
581
+ addFragment(block, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition) {
582
+ let ctx = this.context();
583
+ let page = ctx.getCurrentPage();
584
+ if (!useBlockXOffset && block.height > ctx.availableHeight) {
585
+ return false;
586
+ }
587
+ block.items.forEach(item => {
588
+ switch (item.type) {
589
+ case 'line':
590
+ var l = item.item.clone();
591
+ if (l._node) {
592
+ l._node.positions[0].pageNumber = ctx.page + 1;
593
+ }
594
+ l.x = (l.x || 0) + (useBlockXOffset ? block.xOffset || 0 : ctx.x);
595
+ l.y = (l.y || 0) + (useBlockYOffset ? block.yOffset || 0 : ctx.y);
596
+ page.items.push({
597
+ type: 'line',
598
+ item: l
599
+ });
600
+ break;
601
+ case 'vector':
602
+ var v = (0, _tools.pack)(item.item);
603
+ (0, _tools.offsetVector)(v, useBlockXOffset ? block.xOffset || 0 : ctx.x, useBlockYOffset ? block.yOffset || 0 : ctx.y);
604
+ if (v._isFillColorFromUnbreakable) {
605
+ // If the item is a fillColor from an unbreakable block
606
+ // We have to add it at the beginning of the items body array of the page
607
+ delete v._isFillColorFromUnbreakable;
608
+ const endOfBackgroundItemsIndex = ctx.backgroundLength[ctx.page];
609
+ page.items.splice(endOfBackgroundItemsIndex, 0, {
610
+ type: 'vector',
611
+ item: v
612
+ });
613
+ } else {
614
+ page.items.push({
615
+ type: 'vector',
616
+ item: v
617
+ });
618
+ }
619
+ break;
620
+ case 'image':
621
+ case 'svg':
622
+ case 'beginClip':
623
+ case 'endClip':
624
+ case 'beginVerticalAlignment':
625
+ case 'endVerticalAlignment':
626
+ var img = (0, _tools.pack)(item.item);
627
+ img.x = (img.x || 0) + (useBlockXOffset ? block.xOffset || 0 : ctx.x);
628
+ img.y = (img.y || 0) + (useBlockYOffset ? block.yOffset || 0 : ctx.y);
629
+ page.items.push({
630
+ type: item.type,
631
+ item: img
632
+ });
633
+ break;
634
+ }
635
+ });
636
+ if (!dontUpdateContextPosition) {
637
+ ctx.moveDown(block.height);
638
+ }
639
+ return true;
640
+ }
641
+
642
+ /**
643
+ * Pushes the provided context onto the stack or creates a new one
644
+ *
645
+ * pushContext(context) - pushes the provided context and makes it current
646
+ * pushContext(width, height) - creates and pushes a new context with the specified width and height
647
+ * pushContext() - creates a new context for unbreakable blocks (with current availableWidth and full-page-height)
648
+ *
649
+ * @param {DocumentContext|number} contextOrWidth
650
+ * @param {number} height
651
+ */
652
+ pushContext(contextOrWidth, height) {
653
+ if (contextOrWidth === undefined) {
654
+ height = this.context().getCurrentPage().height - this.context().pageMargins.top - this.context().pageMargins.bottom;
655
+ contextOrWidth = this.context().availableWidth;
656
+ }
657
+ if ((0, _variableType.isNumber)(contextOrWidth)) {
658
+ let width = contextOrWidth;
659
+ contextOrWidth = new _DocumentContext.default();
660
+ contextOrWidth.addPage({
661
+ width: width,
662
+ height: height
663
+ }, {
664
+ left: 0,
665
+ right: 0,
666
+ top: 0,
667
+ bottom: 0
668
+ });
669
+ }
670
+ this.contextStack.push(this.context());
671
+ this._context = contextOrWidth;
672
+ }
673
+ popContext() {
674
+ this._context = this.contextStack.pop();
675
+ }
676
+ getCurrentPositionOnPage() {
677
+ return (this.contextStack[0] || this.context()).getCurrentPosition();
678
+ }
679
+ }
680
+ function addPageItem(page, item, index) {
681
+ if (index === null || index === undefined || index < 0 || index > page.items.length) {
682
+ page.items.push(item);
683
+ } else {
684
+ page.items.splice(index, 0, item);
685
+ }
686
+ }
687
+ var _default = exports.default = ElementWriter;