pdfmake 0.3.0-beta.1 → 0.3.0-beta.10

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 (76) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/LICENSE +1 -1
  3. package/README.md +3 -1
  4. package/build/pdfmake.js +65039 -73250
  5. package/build/pdfmake.js.map +1 -1
  6. package/build/pdfmake.min.js +2 -2
  7. package/build/pdfmake.min.js.map +1 -1
  8. package/build/vfs_fonts.js +4 -4
  9. package/eslint.config.mjs +52 -0
  10. package/fonts/Roboto/Roboto-Italic.ttf +0 -0
  11. package/fonts/Roboto/Roboto-Medium.ttf +0 -0
  12. package/fonts/Roboto/Roboto-MediumItalic.ttf +0 -0
  13. package/fonts/Roboto/Roboto-Regular.ttf +0 -0
  14. package/js/3rd-party/svg-to-pdfkit/source.js +247 -922
  15. package/js/3rd-party/svg-to-pdfkit.js +2 -6
  16. package/js/DocMeasure.js +24 -148
  17. package/js/DocPreprocessor.js +8 -55
  18. package/js/DocumentContext.js +44 -62
  19. package/js/ElementWriter.js +38 -73
  20. package/js/LayoutBuilder.js +225 -177
  21. package/js/Line.js +7 -27
  22. package/js/OutputDocument.js +6 -16
  23. package/js/OutputDocumentServer.js +2 -9
  24. package/js/PDFDocument.js +24 -43
  25. package/js/PageElementWriter.js +12 -33
  26. package/js/PageSize.js +3 -17
  27. package/js/Printer.js +102 -49
  28. package/js/Renderer.js +37 -95
  29. package/js/SVGMeasure.js +3 -23
  30. package/js/StyleContextStack.js +13 -55
  31. package/js/TableProcessor.js +58 -124
  32. package/js/TextBreaker.js +4 -47
  33. package/js/TextDecorator.js +3 -41
  34. package/js/TextInlines.js +10 -52
  35. package/js/URLResolver.js +18 -24
  36. package/js/base.js +3 -20
  37. package/js/browser-extensions/OutputDocumentBrowser.js +7 -20
  38. package/js/browser-extensions/URLBrowserResolver.js +7 -20
  39. package/js/browser-extensions/fonts/Roboto.js +0 -4
  40. package/js/browser-extensions/index.js +2 -19
  41. package/js/browser-extensions/pdfMake.js +0 -14
  42. package/js/browser-extensions/standard-fonts/Courier.js +0 -4
  43. package/js/browser-extensions/standard-fonts/Helvetica.js +0 -4
  44. package/js/browser-extensions/standard-fonts/Symbol.js +0 -4
  45. package/js/browser-extensions/standard-fonts/Times.js +0 -4
  46. package/js/browser-extensions/standard-fonts/ZapfDingbats.js +0 -4
  47. package/js/columnCalculator.js +30 -24
  48. package/js/helpers/node.js +3 -27
  49. package/js/helpers/tools.js +0 -8
  50. package/js/helpers/variableType.js +15 -8
  51. package/js/index.js +0 -6
  52. package/js/qrEnc.js +133 -222
  53. package/js/standardPageSizes.js +2 -3
  54. package/js/tableLayouts.js +4 -33
  55. package/js/virtual-fs.js +4 -21
  56. package/package.json +25 -22
  57. package/src/DocMeasure.js +19 -6
  58. package/src/DocPreprocessor.js +6 -0
  59. package/src/DocumentContext.js +35 -20
  60. package/src/ElementWriter.js +41 -4
  61. package/src/LayoutBuilder.js +201 -18
  62. package/src/OutputDocument.js +1 -1
  63. package/src/PDFDocument.js +27 -1
  64. package/src/PageElementWriter.js +4 -0
  65. package/src/Printer.js +93 -7
  66. package/src/Renderer.js +35 -0
  67. package/src/StyleContextStack.js +3 -44
  68. package/src/TableProcessor.js +27 -5
  69. package/src/TextDecorator.js +1 -1
  70. package/src/URLResolver.js +16 -8
  71. package/src/browser-extensions/URLBrowserResolver.js +6 -3
  72. package/src/browser-extensions/index.js +1 -1
  73. package/src/browser-extensions/pdfMake.js +0 -14
  74. package/src/columnCalculator.js +24 -3
  75. package/src/helpers/variableType.js +11 -0
  76. package/src/qrEnc.js +5 -3
@@ -32,6 +32,7 @@ class LayoutBuilder {
32
32
  this.pageMargins = pageMargins;
33
33
  this.svgMeasure = svgMeasure;
34
34
  this.tableLayouts = {};
35
+ this.nestedLevel = 0;
35
36
  }
36
37
 
37
38
  registerTableLayouts(tableLayouts) {
@@ -369,16 +370,50 @@ class LayoutBuilder {
369
370
  }
370
371
  }
371
372
 
372
- if (margin) {
373
- this.writer.context().moveDown(margin[1]);
373
+ const isDetachedBlock = node.relativePosition || node.absolutePosition;
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 top margin is bigger than available space, move to next page
379
+ // Necessary for nodes inside tables
380
+ if (availableHeight - margin[1] < 0) {
381
+ // Consume the whole available space
382
+ this.writer.context().moveDown(availableHeight);
383
+ this.writer.moveToNextPage(node.pageOrientation);
384
+ /**
385
+ * TODO - Something to consider:
386
+ * Right now the node starts at the top of next page (after header)
387
+ * Another option would be to apply just the top margin that has not been consumed in the page before
388
+ * It would something like: this.write.context().moveDown(margin[1] - availableHeight)
389
+ */
390
+ } else {
391
+ this.writer.context().moveDown(margin[1]);
392
+ }
393
+ // Apply lateral margins
374
394
  this.writer.context().addMargin(margin[0], margin[2]);
375
395
  }
376
-
377
396
  callback();
378
397
 
379
- if (margin) {
398
+ // Detached nodes have no margins, their position is only determined by 'x' and 'y'
399
+ if (margin && !isDetachedBlock) {
400
+ const availableHeight = this.writer.context().availableHeight;
401
+ // If bottom margin is bigger than available space, move to next page
402
+ // Necessary for nodes inside tables
403
+ if (availableHeight - margin[3] < 0) {
404
+ this.writer.context().moveDown(availableHeight);
405
+ this.writer.moveToNextPage(node.pageOrientation);
406
+ /**
407
+ * TODO - Something to consider:
408
+ * Right now next node starts at the top of next page (after header)
409
+ * Another option would be to apply the bottom margin that has not been consumed in the next page?
410
+ * It would something like: this.write.context().moveDown(margin[3] - availableHeight)
411
+ */
412
+ } else {
413
+ this.writer.context().moveDown(margin[3]);
414
+ }
415
+ // Apply lateral margins
380
416
  this.writer.context().addMargin(-margin[0], -margin[2]);
381
- this.writer.context().moveDown(margin[3]);
382
417
  }
383
418
 
384
419
  if (node.pageBreak === 'after') {
@@ -439,6 +474,8 @@ class LayoutBuilder {
439
474
  this.processCanvas(node);
440
475
  } else if (node.qr) {
441
476
  this.processQr(node);
477
+ } else if (node.attachment) {
478
+ this.processAttachment(node);
442
479
  } else if (!node._span) {
443
480
  throw new Error(`Unrecognized document structure: ${stringifyNode(node)}`);
444
481
  }
@@ -465,6 +502,7 @@ class LayoutBuilder {
465
502
 
466
503
  // columns
467
504
  processColumns(columnNode) {
505
+ this.nestedLevel++;
468
506
  let columns = columnNode.columns;
469
507
  let availableWidth = this.writer.context().availableWidth;
470
508
  let gaps = gapArray(columnNode._gap);
@@ -474,9 +512,17 @@ class LayoutBuilder {
474
512
  }
475
513
 
476
514
  ColumnCalculator.buildColumnWidths(columns, availableWidth);
477
- let result = this.processRow(columns, columns, gaps);
515
+ let result = this.processRow({
516
+ marginX: columnNode._margin ? [columnNode._margin[0], columnNode._margin[2]] : [0, 0],
517
+ cells: columns,
518
+ widths: columns,
519
+ gaps
520
+ });
478
521
  addAll(columnNode.positions, result.positions);
479
-
522
+ this.nestedLevel--;
523
+ if (this.nestedLevel === 0) {
524
+ this.writer.context().resetMarginXTopParent();
525
+ }
480
526
  function gapArray(gap) {
481
527
  if (!gap) {
482
528
  return null;
@@ -493,7 +539,38 @@ class LayoutBuilder {
493
539
  }
494
540
  }
495
541
 
496
- processRow(columns, widths, gaps, tableBody, tableRow, height) {
542
+ findStartingSpanCell(arr, i) {
543
+ let requiredColspan = 1;
544
+ for (let index = i - 1; index >= 0; index--) {
545
+ if (!arr[index]._span) {
546
+ if (arr[index].rowSpan > 1 && (arr[index].colSpan || 1) === requiredColspan) {
547
+ return arr[index];
548
+ } else {
549
+ return null;
550
+ }
551
+ }
552
+ requiredColspan++;
553
+ }
554
+ return null;
555
+ }
556
+
557
+ processRow({ marginX = [0, 0], dontBreakRows = false, rowsWithoutPageBreak = 0, cells, widths, gaps, tableBody, rowIndex, height }) {
558
+ const updatePageBreakData = (page, prevY) => {
559
+ let pageDesc;
560
+ // Find page break data for this row and page
561
+ for (let i = 0, l = pageBreaks.length; i < l; i++) {
562
+ let desc = pageBreaks[i];
563
+ if (desc.prevPage === page) {
564
+ pageDesc = desc;
565
+ break;
566
+ }
567
+ }
568
+ // If row has page break in this page, update prevY
569
+ if (pageDesc) {
570
+ pageDesc.prevY = Math.max(pageDesc.prevY, prevY);
571
+ }
572
+ };
573
+
497
574
  const storePageBreakData = data => {
498
575
  let pageDesc;
499
576
 
@@ -513,17 +590,25 @@ class LayoutBuilder {
513
590
  pageDesc.y = Math.min(pageDesc.y, data.y);
514
591
  };
515
592
 
593
+ const isUnbreakableRow = dontBreakRows || rowIndex <= rowsWithoutPageBreak - 1;
516
594
  let pageBreaks = [];
517
595
  let positions = [];
518
-
596
+ let willBreakByHeight = false;
519
597
  this.writer.addListener('pageChanged', storePageBreakData);
520
598
 
521
- widths = widths || columns;
599
+ // Check if row should break by height
600
+ if (!isUnbreakableRow && height > this.writer.context().availableHeight) {
601
+ willBreakByHeight = true;
602
+ }
603
+
604
+ widths = widths || cells;
605
+ // Use the marginX if we are in a top level table/column (not nested)
606
+ const marginXParent = this.nestedLevel === 1 ? marginX : null;
522
607
 
523
- this.writer.context().beginColumnGroup();
608
+ this.writer.context().beginColumnGroup(marginXParent);
524
609
 
525
- for (let i = 0, l = columns.length; i < l; i++) {
526
- let column = columns[i];
610
+ for (let i = 0, l = cells.length; i < l; i++) {
611
+ let column = cells[i];
527
612
  let width = widths[i]._calcWidth;
528
613
  let leftOffset = colLeftOffset(i);
529
614
 
@@ -533,18 +618,85 @@ class LayoutBuilder {
533
618
  }
534
619
  }
535
620
 
536
- this.writer.context().beginColumn(width, leftOffset, getEndingCell(column, i));
621
+ // if rowspan starts in this cell, we retrieve the last cell affected by the rowspan
622
+ const endingCell = getEndingCell(column, i);
623
+ if (endingCell) {
624
+ // We store a reference of the ending cell in the first cell of the rowspan
625
+ column._endingCell = endingCell;
626
+ column._endingCell._startingRowSpanY = column._startingRowSpanY;
627
+ }
628
+
629
+ // Check if exists and retrieve the cell that started the rowspan in case we are in the cell just after
630
+ let startingSpanCell = this.findStartingSpanCell(cells, i);
631
+ let endingSpanCell = null;
632
+ if (startingSpanCell && startingSpanCell._endingCell) {
633
+ // Reference to the last cell of the rowspan
634
+ endingSpanCell = startingSpanCell._endingCell;
635
+ // Store if we are in an unbreakable block when we save the context and the originalX
636
+ if (this.writer.transactionLevel > 0) {
637
+ endingSpanCell._isUnbreakableContext = true;
638
+ endingSpanCell._originalXOffset = this.writer.originalX;
639
+ }
640
+ }
641
+
642
+ // We pass the endingSpanCell reference to store the context just after processing rowspan cell
643
+ this.writer.context().beginColumn(width, leftOffset, endingSpanCell);
644
+
537
645
  if (!column._span) {
538
646
  this.processNode(column);
539
647
  addAll(positions, column.positions);
540
648
  } else if (column._columnEndingContext) {
649
+ let discountY = 0;
650
+ if (dontBreakRows) {
651
+ // Calculate how many points we have to discount to Y when dontBreakRows and rowSpan are combined
652
+ const ctxBeforeRowSpanLastRow = this.writer.contextStack[this.writer.contextStack.length - 1];
653
+ discountY = ctxBeforeRowSpanLastRow.y - column._startingRowSpanY;
654
+ }
655
+ let originalXOffset = 0;
656
+ // If context was saved from an unbreakable block and we are not in an unbreakable block anymore
657
+ // We have to sum the originalX (X before starting unbreakable block) to X
658
+ if (column._isUnbreakableContext && !this.writer.transactionLevel) {
659
+ originalXOffset = column._originalXOffset;
660
+ }
541
661
  // row-span ending
542
- this.writer.context().markEnding(column);
662
+ // Recover the context after processing the rowspanned cell
663
+ this.writer.context().markEnding(column, originalXOffset, discountY);
664
+ }
665
+ }
666
+
667
+ // Check if last cell is part of a span
668
+ let endingSpanCell = null;
669
+ const lastColumn = cells.length > 0 ? cells[cells.length - 1] : null;
670
+ if (lastColumn) {
671
+ // Previous column cell has a rowspan
672
+ if (lastColumn._endingCell) {
673
+ endingSpanCell = lastColumn._endingCell;
674
+ // Previous column cell is part of a span
675
+ } else if (lastColumn._span === true) {
676
+ // We get the cell that started the span where we set a reference to the ending cell
677
+ const startingSpanCell = this.findStartingSpanCell(cells, cells.length);
678
+ if (startingSpanCell) {
679
+ // Context will be stored here (ending cell)
680
+ endingSpanCell = startingSpanCell._endingCell;
681
+ // Store if we are in an unbreakable block when we save the context and the originalX
682
+ if (this.writer.transactionLevel > 0) {
683
+ endingSpanCell._isUnbreakableContext = true;
684
+ endingSpanCell._originalXOffset = this.writer.originalX;
685
+ }
686
+ }
543
687
  }
544
688
  }
545
689
 
546
- this.writer.context().completeColumnGroup(height);
690
+ // If there are page breaks in this row, update data with prevY of last cell
691
+ updatePageBreakData(this.writer.context().page, this.writer.context().y);
692
+
693
+ // If content did not break page, check if we should break by height
694
+ if (!isUnbreakableRow && pageBreaks.length === 0 && willBreakByHeight) {
695
+ this.writer.context().moveDown(this.writer.context().availableHeight);
696
+ this.writer.moveToNextPage();
697
+ }
547
698
 
699
+ this.writer.context().completeColumnGroup(height, endingSpanCell);
548
700
  this.writer.removeListener('pageChanged', storePageBreakData);
549
701
 
550
702
  return { pageBreaks: pageBreaks, positions: positions };
@@ -558,7 +710,7 @@ class LayoutBuilder {
558
710
 
559
711
  function getEndingCell(column, columnIndex) {
560
712
  if (column.rowSpan && column.rowSpan > 1) {
561
- let endingRow = tableRow + column.rowSpan - 1;
713
+ let endingRow = rowIndex + column.rowSpan - 1;
562
714
  if (endingRow >= tableBody.length) {
563
715
  throw new Error(`Row span for column ${columnIndex} (with indexes starting from 0) exceeded row count`);
564
716
  }
@@ -615,12 +767,23 @@ class LayoutBuilder {
615
767
 
616
768
  // tables
617
769
  processTable(tableNode) {
770
+ this.nestedLevel++;
618
771
  let processor = new TableProcessor(tableNode);
619
772
 
620
773
  processor.beginTable(this.writer);
621
774
 
622
775
  let rowHeights = tableNode.table.heights;
623
776
  for (let i = 0, l = tableNode.table.body.length; i < l; i++) {
777
+ // if dontBreakRows and row starts a rowspan
778
+ // we store the 'y' of the beginning of each rowSpan
779
+ if (processor.dontBreakRows) {
780
+ tableNode.table.body[i].forEach(cell => {
781
+ if (cell.rowSpan && cell.rowSpan > 1) {
782
+ cell._startingRowSpanY = this.writer.context().y;
783
+ }
784
+ });
785
+ }
786
+
624
787
  processor.beginRow(i, this.writer);
625
788
 
626
789
  let height;
@@ -636,13 +799,28 @@ class LayoutBuilder {
636
799
  height = undefined;
637
800
  }
638
801
 
639
- let result = this.processRow(tableNode.table.body[i], tableNode.table.widths, tableNode._offsets.offsets, tableNode.table.body, i, height);
802
+ let result = this.processRow({
803
+ marginX: tableNode._margin ? [tableNode._margin[0], tableNode._margin[2]] : [0, 0],
804
+ dontBreakRows: processor.dontBreakRows,
805
+ rowsWithoutPageBreak: processor.rowsWithoutPageBreak,
806
+ cells: tableNode.table.body[i],
807
+ widths: tableNode.table.widths,
808
+ gaps: tableNode._offsets.offsets,
809
+ tableBody: tableNode.table.body,
810
+ rowIndex: i,
811
+ height
812
+ });
813
+
640
814
  addAll(tableNode.positions, result.positions);
641
815
 
642
816
  processor.endRow(i, this.writer, result.pageBreaks);
643
817
  }
644
818
 
645
819
  processor.endTable(this.writer);
820
+ this.nestedLevel--;
821
+ if (this.nestedLevel === 0) {
822
+ this.writer.context().resetMarginXTopParent();
823
+ }
646
824
  }
647
825
 
648
826
  // leafs (texts)
@@ -774,6 +952,11 @@ class LayoutBuilder {
774
952
  let position = this.writer.addQr(node);
775
953
  node.positions.push(position);
776
954
  }
955
+
956
+ processAttachment(node) {
957
+ let position = this.writer.addAttachment(node);
958
+ node.positions.push(position);
959
+ }
777
960
  }
778
961
 
779
962
  function decorateNode(node) {
@@ -4,7 +4,7 @@ class OutputDocument {
4
4
  * @param {Promise<object>} pdfDocumentPromise
5
5
  */
6
6
  constructor(pdfDocumentPromise) {
7
- this.bufferSize = 9007199254740991;
7
+ this.bufferSize = 1073741824;
8
8
  this.pdfDocumentPromise = pdfDocumentPromise;
9
9
  this.bufferPromise = null;
10
10
  }
@@ -13,7 +13,7 @@ const typeName = (bold, italics) => {
13
13
  };
14
14
 
15
15
  class PDFDocument extends PDFKit {
16
- constructor(fonts = {}, images = {}, patterns = {}, options = {}, virtualfs = null) {
16
+ constructor(fonts = {}, images = {}, patterns = {}, attachments = {}, options = {}, virtualfs = null) {
17
17
  super(options);
18
18
 
19
19
  this.fonts = {};
@@ -41,6 +41,7 @@ class PDFDocument extends PDFKit {
41
41
 
42
42
 
43
43
  this.images = images;
44
+ this.attachments = attachments;
44
45
  this.virtualfs = virtualfs;
45
46
  }
46
47
 
@@ -134,6 +135,31 @@ class PDFDocument extends PDFKit {
134
135
  return null;
135
136
  }
136
137
 
138
+ provideAttachment(src) {
139
+ const checkRequired = obj => {
140
+ if (!obj) {
141
+ throw new Error('No attachment');
142
+ }
143
+ if (!obj.src) {
144
+ throw new Error('The "src" key is required for attachments');
145
+ }
146
+
147
+ return obj;
148
+ };
149
+
150
+ if (typeof src === 'object') {
151
+ return checkRequired(src);
152
+ }
153
+
154
+ let attachment = checkRequired(this.attachments[src]);
155
+
156
+ if (this.virtualfs && this.virtualfs.existsSync(attachment.src)) {
157
+ return this.virtualfs.readFileSync(attachment.src);
158
+ }
159
+
160
+ return attachment;
161
+ }
162
+
137
163
  setOpenActionAsPrint() {
138
164
  let printActionRef = this.ref({
139
165
  Type: 'Action',
@@ -35,6 +35,10 @@ class PageElementWriter extends ElementWriter {
35
35
  return this._fitOnPage(() => super.addQr(qr, index));
36
36
  }
37
37
 
38
+ addAttachment(attachment, index) {
39
+ return this._fitOnPage(() => super.addAttachment(attachment, index));
40
+ }
41
+
38
42
  addVector(vector, ignoreContextX, ignoreContextY, index) {
39
43
  return super.addVector(vector, ignoreContextX, ignoreContextY, index);
40
44
  }
package/src/Printer.js CHANGED
@@ -49,6 +49,7 @@ class PdfPrinter {
49
49
  docDefinition.version = docDefinition.version || '1.3';
50
50
  docDefinition.compress = typeof docDefinition.compress === 'boolean' ? docDefinition.compress : true;
51
51
  docDefinition.images = docDefinition.images || {};
52
+ docDefinition.attachments = docDefinition.attachments || {};
52
53
  docDefinition.pageMargins = isValue(docDefinition.pageMargins) ? docDefinition.pageMargins : 40;
53
54
  docDefinition.patterns = docDefinition.patterns || {};
54
55
 
@@ -61,6 +62,7 @@ class PdfPrinter {
61
62
  userPassword: docDefinition.userPassword,
62
63
  ownerPassword: docDefinition.ownerPassword,
63
64
  permissions: docDefinition.permissions,
65
+ lang: docDefinition.language,
64
66
  fontLayoutCache: typeof options.fontLayoutCache === 'boolean' ? options.fontLayoutCache : true,
65
67
  bufferPages: options.bufferPages || false,
66
68
  autoFirstPage: false,
@@ -68,7 +70,8 @@ class PdfPrinter {
68
70
  font: null
69
71
  };
70
72
 
71
- this.pdfKitDoc = new PDFDocument(this.fontDescriptors, docDefinition.images, docDefinition.patterns, pdfOptions, this.virtualfs);
73
+ this.pdfKitDoc = new PDFDocument(this.fontDescriptors, docDefinition.images, docDefinition.patterns, docDefinition.attachments, pdfOptions, this.virtualfs);
74
+ embedFiles(docDefinition, this.pdfKitDoc);
72
75
 
73
76
  const builder = new LayoutBuilder(pageSize, normalizePageMargin(docDefinition.pageMargins), new SVGMeasure());
74
77
 
@@ -108,6 +111,14 @@ class PdfPrinter {
108
111
  * @returns {Promise}
109
112
  */
110
113
  resolveUrls(docDefinition) {
114
+ const getExtendedUrl = url => {
115
+ if (typeof url === 'object') {
116
+ return { url: url.url, headers: url.headers };
117
+ }
118
+
119
+ return { url: url, headers: {} };
120
+ };
121
+
111
122
  return new Promise((resolve, reject) => {
112
123
  if (this.urlResolver === null) {
113
124
  resolve();
@@ -116,16 +127,48 @@ class PdfPrinter {
116
127
  for (let font in this.fontDescriptors) {
117
128
  if (this.fontDescriptors.hasOwnProperty(font)) {
118
129
  if (this.fontDescriptors[font].normal) {
119
- this.urlResolver.resolve(this.fontDescriptors[font].normal);
130
+ if (Array.isArray(this.fontDescriptors[font].normal)) { // TrueType Collection
131
+ let url = getExtendedUrl(this.fontDescriptors[font].normal[0]);
132
+ this.urlResolver.resolve(url.url, url.headers);
133
+ this.fontDescriptors[font].normal[0] = url.url;
134
+ } else {
135
+ let url = getExtendedUrl(this.fontDescriptors[font].normal);
136
+ this.urlResolver.resolve(url.url, url.headers);
137
+ this.fontDescriptors[font].normal = url.url;
138
+ }
120
139
  }
121
140
  if (this.fontDescriptors[font].bold) {
122
- this.urlResolver.resolve(this.fontDescriptors[font].bold);
141
+ if (Array.isArray(this.fontDescriptors[font].bold)) { // TrueType Collection
142
+ let url = getExtendedUrl(this.fontDescriptors[font].bold[0]);
143
+ this.urlResolver.resolve(url.url, url.headers);
144
+ this.fontDescriptors[font].bold[0] = url.url;
145
+ } else {
146
+ let url = getExtendedUrl(this.fontDescriptors[font].bold);
147
+ this.urlResolver.resolve(url.url, url.headers);
148
+ this.fontDescriptors[font].bold = url.url;
149
+ }
123
150
  }
124
151
  if (this.fontDescriptors[font].italics) {
125
- this.urlResolver.resolve(this.fontDescriptors[font].italics);
152
+ if (Array.isArray(this.fontDescriptors[font].italics)) { // TrueType Collection
153
+ let url = getExtendedUrl(this.fontDescriptors[font].italics[0]);
154
+ this.urlResolver.resolve(url.url, url.headers);
155
+ this.fontDescriptors[font].italics[0] = url.url;
156
+ } else {
157
+ let url = getExtendedUrl(this.fontDescriptors[font].italics);
158
+ this.urlResolver.resolve(url.url, url.headers);
159
+ this.fontDescriptors[font].italics = url.url;
160
+ }
126
161
  }
127
162
  if (this.fontDescriptors[font].bolditalics) {
128
- this.urlResolver.resolve(this.fontDescriptors[font].bolditalics);
163
+ if (Array.isArray(this.fontDescriptors[font].bolditalics)) { // TrueType Collection
164
+ let url = getExtendedUrl(this.fontDescriptors[font].bolditalics[0]);
165
+ this.urlResolver.resolve(url.url, url.headers);
166
+ this.fontDescriptors[font].bolditalics[0] = url.url;
167
+ } else {
168
+ let url = getExtendedUrl(this.fontDescriptors[font].bolditalics);
169
+ this.urlResolver.resolve(url.url, url.headers);
170
+ this.fontDescriptors[font].bolditalics = url.url;
171
+ }
129
172
  }
130
173
  }
131
174
  }
@@ -133,7 +176,29 @@ class PdfPrinter {
133
176
  if (docDefinition.images) {
134
177
  for (let image in docDefinition.images) {
135
178
  if (docDefinition.images.hasOwnProperty(image)) {
136
- this.urlResolver.resolve(docDefinition.images[image]);
179
+ let url = getExtendedUrl(docDefinition.images[image]);
180
+ this.urlResolver.resolve(url.url, url.headers);
181
+ docDefinition.images[image] = url.url;
182
+ }
183
+ }
184
+ }
185
+
186
+ if (docDefinition.attachments) {
187
+ for (let attachment in docDefinition.attachments) {
188
+ if (docDefinition.attachments.hasOwnProperty(attachment) && docDefinition.attachments[attachment].src) {
189
+ let url = getExtendedUrl(docDefinition.attachments[attachment].src);
190
+ this.urlResolver.resolve(url.url, url.headers);
191
+ docDefinition.attachments[attachment].src = url.url;
192
+ }
193
+ }
194
+ }
195
+
196
+ if (docDefinition.files) {
197
+ for (let file in docDefinition.files) {
198
+ if (docDefinition.files.hasOwnProperty(file) && docDefinition.files[file].src) {
199
+ let url = getExtendedUrl(docDefinition.files[file].src);
200
+ this.urlResolver.resolve(url.url, url.headers);
201
+ docDefinition.files[file].src = url.url;
137
202
  }
138
203
  }
139
204
  }
@@ -180,6 +245,23 @@ function createMetadata(docDefinition) {
180
245
  return info;
181
246
  }
182
247
 
248
+ function embedFiles(docDefinition, pdfKitDoc) {
249
+ if (docDefinition.files) {
250
+ for (const key in docDefinition.files) {
251
+ const file = docDefinition.files[key];
252
+
253
+ if (!file.src) return;
254
+
255
+ if (pdfKitDoc.virtualfs && pdfKitDoc.virtualfs.existsSync(file.src)) {
256
+ file.src = pdfKitDoc.virtualfs.readFileSync(file.src);
257
+ }
258
+
259
+ file.name = file.name || key;
260
+ pdfKitDoc.file(file.src, file);
261
+ }
262
+ }
263
+ }
264
+
183
265
  function calculatePageHeight(pages, margins) {
184
266
  function getItemHeight(item) {
185
267
  if (typeof item.item.getHeight === 'function') {
@@ -187,7 +269,11 @@ function calculatePageHeight(pages, margins) {
187
269
  } else if (item.item._height) {
188
270
  return item.item._height;
189
271
  } else if (item.type === 'vector') {
190
- return item.item.y1 > item.item.y2 ? item.item.y1 : item.item.y2;
272
+ if (typeof item.item.y1 !== 'undefined') {
273
+ return item.item.y1 > item.item.y2 ? item.item.y1 : item.item.y2;
274
+ } else {
275
+ return item.item.h;
276
+ }
191
277
  } else {
192
278
  // TODO: add support for next item types
193
279
  return 0;
package/src/Renderer.js CHANGED
@@ -78,6 +78,9 @@ class Renderer {
78
78
  case 'svg':
79
79
  this.renderSVG(item.item);
80
80
  break;
81
+ case 'attachment':
82
+ this.renderAttachment(item.item);
83
+ break;
81
84
  case 'beginClip':
82
85
  this.beginClip(item.item);
83
86
  break;
@@ -306,6 +309,27 @@ class Renderer {
306
309
  if (image.linkToDestination) {
307
310
  this.pdfDocument.goTo(image.x, image.y, image._width, image._height, image.linkToDestination);
308
311
  }
312
+ if (image.linkToFile) {
313
+ const attachment = this.pdfDocument.provideAttachment(image.linkToFile);
314
+ this.pdfDocument.fileAnnotation(
315
+ image.x,
316
+ image.y,
317
+ image._width,
318
+ image._height,
319
+ attachment,
320
+ // add empty rectangle as file annotation appearance with the same size as the rendered image
321
+ {
322
+ AP: {
323
+ N: {
324
+ Type: 'XObject',
325
+ Subtype: 'Form',
326
+ FormType: 1,
327
+ BBox: [image.x, image.y, image._width, image._height]
328
+ }
329
+ },
330
+ }
331
+ );
332
+ }
309
333
  }
310
334
 
311
335
  renderSVG(svg) {
@@ -326,6 +350,17 @@ class Renderer {
326
350
  SVGtoPDF(this.pdfDocument, svg.svg, svg.x, svg.y, options);
327
351
  }
328
352
 
353
+ renderAttachment(attachment) {
354
+ const file = this.pdfDocument.provideAttachment(attachment.attachment);
355
+
356
+ const options = {};
357
+ if (attachment.icon) {
358
+ options.Name = attachment.icon;
359
+ }
360
+
361
+ this.pdfDocument.fileAnnotation(attachment.x, attachment.y, attachment._width, attachment._height, file, options);
362
+ }
363
+
329
364
  beginClip(rect) {
330
365
  this.pdfDocument.save();
331
366
  this.pdfDocument.addContent(`${rect.x} ${rect.y} ${rect.width} ${rect.height} re`);
@@ -79,50 +79,9 @@ class StyleContextStack {
79
79
  this.push(styleNames[i]);
80
80
  }
81
81
 
82
- let styleProperties = [
83
- 'font',
84
- 'fontSize',
85
- 'fontFeatures',
86
- 'bold',
87
- 'italics',
88
- 'alignment',
89
- 'color',
90
- 'columnGap',
91
- 'fillColor',
92
- 'fillOpacity',
93
- 'decoration',
94
- 'decorationStyle',
95
- 'decorationColor',
96
- 'background',
97
- 'lineHeight',
98
- 'characterSpacing',
99
- 'noWrap',
100
- 'markerColor',
101
- 'leadingIndent',
102
- 'sup',
103
- 'sub'
104
- //'tableCellPadding'
105
- // 'cellBorder',
106
- // 'headerCellBorder',
107
- // 'oddRowCellBorder',
108
- // 'evenRowCellBorder',
109
- // 'tableBorder'
110
- ];
111
- let styleOverrideObject = {};
112
- let pushStyleOverrideObject = false;
113
-
114
- styleProperties.forEach(key => {
115
- if (isValue(item[key])) {
116
- styleOverrideObject[key] = item[key];
117
- pushStyleOverrideObject = true;
118
- }
119
- });
120
-
121
- if (pushStyleOverrideObject) {
122
- this.push(styleOverrideObject);
123
- }
124
-
125
- return styleNames.length + (pushStyleOverrideObject ? 1 : 0);
82
+ // rather than spend significant time making a styleOverrideObject, just add item
83
+ this.push(item);
84
+ return styleNames.length + 1;
126
85
  }
127
86
 
128
87
  /**