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.
- package/CHANGELOG.md +61 -1
- package/LICENSE +1 -1
- package/README.md +3 -1
- package/build/pdfmake.js +65039 -73250
- package/build/pdfmake.js.map +1 -1
- package/build/pdfmake.min.js +2 -2
- package/build/pdfmake.min.js.map +1 -1
- package/build/vfs_fonts.js +4 -4
- package/eslint.config.mjs +52 -0
- package/fonts/Roboto/Roboto-Italic.ttf +0 -0
- package/fonts/Roboto/Roboto-Medium.ttf +0 -0
- package/fonts/Roboto/Roboto-MediumItalic.ttf +0 -0
- package/fonts/Roboto/Roboto-Regular.ttf +0 -0
- package/js/3rd-party/svg-to-pdfkit/source.js +247 -922
- package/js/3rd-party/svg-to-pdfkit.js +2 -6
- package/js/DocMeasure.js +24 -148
- package/js/DocPreprocessor.js +8 -55
- package/js/DocumentContext.js +44 -62
- package/js/ElementWriter.js +38 -73
- package/js/LayoutBuilder.js +225 -177
- package/js/Line.js +7 -27
- package/js/OutputDocument.js +6 -16
- package/js/OutputDocumentServer.js +2 -9
- package/js/PDFDocument.js +24 -43
- package/js/PageElementWriter.js +12 -33
- package/js/PageSize.js +3 -17
- package/js/Printer.js +102 -49
- package/js/Renderer.js +37 -95
- package/js/SVGMeasure.js +3 -23
- package/js/StyleContextStack.js +13 -55
- package/js/TableProcessor.js +58 -124
- package/js/TextBreaker.js +4 -47
- package/js/TextDecorator.js +3 -41
- package/js/TextInlines.js +10 -52
- package/js/URLResolver.js +18 -24
- package/js/base.js +3 -20
- package/js/browser-extensions/OutputDocumentBrowser.js +7 -20
- package/js/browser-extensions/URLBrowserResolver.js +7 -20
- package/js/browser-extensions/fonts/Roboto.js +0 -4
- package/js/browser-extensions/index.js +2 -19
- package/js/browser-extensions/pdfMake.js +0 -14
- package/js/browser-extensions/standard-fonts/Courier.js +0 -4
- package/js/browser-extensions/standard-fonts/Helvetica.js +0 -4
- package/js/browser-extensions/standard-fonts/Symbol.js +0 -4
- package/js/browser-extensions/standard-fonts/Times.js +0 -4
- package/js/browser-extensions/standard-fonts/ZapfDingbats.js +0 -4
- package/js/columnCalculator.js +30 -24
- package/js/helpers/node.js +3 -27
- package/js/helpers/tools.js +0 -8
- package/js/helpers/variableType.js +15 -8
- package/js/index.js +0 -6
- package/js/qrEnc.js +133 -222
- package/js/standardPageSizes.js +2 -3
- package/js/tableLayouts.js +4 -33
- package/js/virtual-fs.js +4 -21
- package/package.json +25 -22
- package/src/DocMeasure.js +19 -6
- package/src/DocPreprocessor.js +6 -0
- package/src/DocumentContext.js +35 -20
- package/src/ElementWriter.js +41 -4
- package/src/LayoutBuilder.js +201 -18
- package/src/OutputDocument.js +1 -1
- package/src/PDFDocument.js +27 -1
- package/src/PageElementWriter.js +4 -0
- package/src/Printer.js +93 -7
- package/src/Renderer.js +35 -0
- package/src/StyleContextStack.js +3 -44
- package/src/TableProcessor.js +27 -5
- package/src/TextDecorator.js +1 -1
- package/src/URLResolver.js +16 -8
- package/src/browser-extensions/URLBrowserResolver.js +6 -3
- package/src/browser-extensions/index.js +1 -1
- package/src/browser-extensions/pdfMake.js +0 -14
- package/src/columnCalculator.js +24 -3
- package/src/helpers/variableType.js +11 -0
- package/src/qrEnc.js +5 -3
package/src/LayoutBuilder.js
CHANGED
|
@@ -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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
526
|
-
let column =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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) {
|
package/src/OutputDocument.js
CHANGED
package/src/PDFDocument.js
CHANGED
|
@@ -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',
|
package/src/PageElementWriter.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`);
|
package/src/StyleContextStack.js
CHANGED
|
@@ -79,50 +79,9 @@ class StyleContextStack {
|
|
|
79
79
|
this.push(styleNames[i]);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
/**
|