@kittl/pdfkit 0.17.3 → 0.17.5
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/.yarn/install-state.gz +0 -0
- package/.yarn/releases/yarn-4.13.0.cjs +940 -0
- package/.yarnrc.yml +3 -0
- package/CHANGELOG.md +14 -0
- package/README.md +11 -4
- package/js/pdfkit.es.js +631 -172
- package/js/pdfkit.es.js.map +1 -1
- package/js/pdfkit.js +629 -172
- package/js/pdfkit.js.map +1 -1
- package/js/pdfkit.standalone.js +21437 -42180
- package/js/writeSvg.es.js +3684 -0
- package/js/writeSvg.es.js.map +1 -0
- package/js/writeSvg.js +3686 -0
- package/js/writeSvg.js.map +1 -0
- package/js/write_svg.es.js +3706 -0
- package/js/write_svg.es.js.map +1 -0
- package/js/write_svg.js +3708 -0
- package/js/write_svg.js.map +1 -0
- package/package.json +22 -8
- package/types/pdfkit.d.ts +1160 -0
- package/types/write_svg.d.ts +76 -0
- package/js/pdfkit.es5.js +0 -6091
- package/js/pdfkit.es5.js.map +0 -1
- package/js/pdfkit.esnext.js +0 -5392
- package/js/pdfkit.esnext.js.map +0 -1
package/js/pdfkit.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
var stream = require('stream');
|
|
4
4
|
var zlib = require('zlib');
|
|
5
|
-
var
|
|
5
|
+
var utils = require('@noble/hashes/utils');
|
|
6
|
+
var md5 = require('js-md5');
|
|
7
|
+
var sha256 = require('@noble/hashes/sha256');
|
|
8
|
+
var aes = require('@noble/ciphers/aes');
|
|
6
9
|
var fs = require('fs');
|
|
7
10
|
var fontkit = require('fontkit');
|
|
8
11
|
var events = require('events');
|
|
9
12
|
var LineBreaker = require('linebreak');
|
|
10
|
-
var exif = require('jpeg-exif');
|
|
11
13
|
var PNG = require('png-js');
|
|
12
14
|
|
|
13
15
|
class PDFAbstractReference {
|
|
@@ -359,6 +361,7 @@ class PDFPage {
|
|
|
359
361
|
this._options = options;
|
|
360
362
|
this.size = options.size || 'letter';
|
|
361
363
|
this.layout = options.layout || 'portrait';
|
|
364
|
+
this.userUnit = options.userUnit || 1.0;
|
|
362
365
|
const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
|
|
363
366
|
this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
|
|
364
367
|
this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
|
|
@@ -374,7 +377,8 @@ class PDFPage {
|
|
|
374
377
|
Parent: this.document._root.data.Pages,
|
|
375
378
|
MediaBox: [0, 0, this.width, this.height],
|
|
376
379
|
Contents: this.content,
|
|
377
|
-
Resources: this.resources
|
|
380
|
+
Resources: this.resources,
|
|
381
|
+
UserUnit: this.userUnit
|
|
378
382
|
});
|
|
379
383
|
this.markings = [];
|
|
380
384
|
}
|
|
@@ -430,6 +434,7 @@ class PDFPage {
|
|
|
430
434
|
for (let color of Object.values(this.document.spotColors)) {
|
|
431
435
|
this.resources.data.ColorSpace[color.id] = color;
|
|
432
436
|
}
|
|
437
|
+
this.document._writeSpaceToResources(this.resources);
|
|
433
438
|
this.resources.end();
|
|
434
439
|
return this.content.end();
|
|
435
440
|
}
|
|
@@ -447,6 +452,58 @@ class PDFNameTree extends PDFTree {
|
|
|
447
452
|
}
|
|
448
453
|
}
|
|
449
454
|
|
|
455
|
+
function md5Hash(data) {
|
|
456
|
+
return new Uint8Array(md5.arrayBuffer(data));
|
|
457
|
+
}
|
|
458
|
+
function md5Hex(data) {
|
|
459
|
+
return md5(data);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function sha256Hash(data) {
|
|
463
|
+
return sha256.sha256(data);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function aesCbcEncrypt(data, key, iv, padding = true) {
|
|
467
|
+
return aes.cbc(key, iv, {
|
|
468
|
+
disablePadding: !padding
|
|
469
|
+
}).encrypt(data);
|
|
470
|
+
}
|
|
471
|
+
function aesEcbEncrypt(data, key) {
|
|
472
|
+
return aes.ecb(key, {
|
|
473
|
+
disablePadding: true
|
|
474
|
+
}).encrypt(data);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function rc4(data, key) {
|
|
478
|
+
const s = new Uint8Array(256);
|
|
479
|
+
for (let i = 0; i < 256; i++) {
|
|
480
|
+
s[i] = i;
|
|
481
|
+
}
|
|
482
|
+
let j = 0;
|
|
483
|
+
for (let i = 0; i < 256; i++) {
|
|
484
|
+
j = j + s[i] + key[i % key.length] & 0xff;
|
|
485
|
+
[s[i], s[j]] = [s[j], s[i]];
|
|
486
|
+
}
|
|
487
|
+
const output = new Uint8Array(data.length);
|
|
488
|
+
for (let i = 0, j = 0, k = 0; k < data.length; k++) {
|
|
489
|
+
i = i + 1 & 0xff;
|
|
490
|
+
j = j + s[i] & 0xff;
|
|
491
|
+
[s[i], s[j]] = [s[j], s[i]];
|
|
492
|
+
output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
|
|
493
|
+
}
|
|
494
|
+
return output;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function randomBytes(length) {
|
|
498
|
+
const bytes = new Uint8Array(length);
|
|
499
|
+
if (globalThis.crypto?.getRandomValues) {
|
|
500
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
501
|
+
} else {
|
|
502
|
+
require('crypto').randomFillSync(bytes);
|
|
503
|
+
}
|
|
504
|
+
return bytes;
|
|
505
|
+
}
|
|
506
|
+
|
|
450
507
|
function inRange(value, rangeGroup) {
|
|
451
508
|
if (value < rangeGroup[0]) return false;
|
|
452
509
|
let startRange = 0;
|
|
@@ -545,10 +602,10 @@ class PDFSecurity {
|
|
|
545
602
|
}
|
|
546
603
|
infoStr += `${key}: ${info[key].valueOf()}\n`;
|
|
547
604
|
}
|
|
548
|
-
return
|
|
605
|
+
return Buffer.from(md5Hash(infoStr));
|
|
549
606
|
}
|
|
550
607
|
static generateRandomWordArray(bytes) {
|
|
551
|
-
return
|
|
608
|
+
return randomBytes(bytes);
|
|
552
609
|
}
|
|
553
610
|
static create(document, options = {}) {
|
|
554
611
|
if (!options.ownerPassword && !options.userPassword) {
|
|
@@ -640,8 +697,8 @@ class PDFSecurity {
|
|
|
640
697
|
encDict.StrF = 'StdCF';
|
|
641
698
|
}
|
|
642
699
|
encDict.R = r;
|
|
643
|
-
encDict.O =
|
|
644
|
-
encDict.U =
|
|
700
|
+
encDict.O = Buffer.from(ownerPasswordEntry);
|
|
701
|
+
encDict.U = Buffer.from(userPasswordEntry);
|
|
645
702
|
encDict.P = permissions;
|
|
646
703
|
}
|
|
647
704
|
_setupEncryptionV5(encDict, options) {
|
|
@@ -651,10 +708,10 @@ class PDFSecurity {
|
|
|
651
708
|
const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
|
|
652
709
|
this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
|
|
653
710
|
const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
|
|
654
|
-
const userKeySalt =
|
|
711
|
+
const userKeySalt = userPasswordEntry.slice(40, 48);
|
|
655
712
|
const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
|
|
656
713
|
const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
|
|
657
|
-
const ownerKeySalt =
|
|
714
|
+
const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
|
|
658
715
|
const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
|
|
659
716
|
const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
|
|
660
717
|
encDict.V = 5;
|
|
@@ -669,36 +726,37 @@ class PDFSecurity {
|
|
|
669
726
|
encDict.StmF = 'StdCF';
|
|
670
727
|
encDict.StrF = 'StdCF';
|
|
671
728
|
encDict.R = 5;
|
|
672
|
-
encDict.O =
|
|
673
|
-
encDict.OE =
|
|
674
|
-
encDict.U =
|
|
675
|
-
encDict.UE =
|
|
729
|
+
encDict.O = Buffer.from(ownerPasswordEntry);
|
|
730
|
+
encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
|
|
731
|
+
encDict.U = Buffer.from(userPasswordEntry);
|
|
732
|
+
encDict.UE = Buffer.from(userEncryptionKeyEntry);
|
|
676
733
|
encDict.P = permissions;
|
|
677
|
-
encDict.Perms =
|
|
734
|
+
encDict.Perms = Buffer.from(permsEntry);
|
|
678
735
|
}
|
|
679
736
|
getEncryptFn(obj, gen) {
|
|
680
737
|
let digest;
|
|
681
738
|
if (this.version < 5) {
|
|
682
|
-
|
|
739
|
+
const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
|
|
740
|
+
digest = utils.concatBytes(this.encryptionKey, suffix);
|
|
683
741
|
}
|
|
684
742
|
if (this.version === 1 || this.version === 2) {
|
|
685
|
-
let key =
|
|
686
|
-
|
|
687
|
-
|
|
743
|
+
let key = md5Hash(digest);
|
|
744
|
+
const keyLen = Math.min(16, this.keyBits / 8 + 5);
|
|
745
|
+
key = key.slice(0, keyLen);
|
|
746
|
+
return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
|
|
688
747
|
}
|
|
689
748
|
let key;
|
|
690
749
|
if (this.version === 4) {
|
|
691
|
-
|
|
750
|
+
const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
|
|
751
|
+
key = md5Hash(utils.concatBytes(digest, saltMarker));
|
|
692
752
|
} else {
|
|
693
753
|
key = this.encryptionKey;
|
|
694
754
|
}
|
|
695
755
|
const iv = PDFSecurity.generateRandomWordArray(16);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
iv
|
|
756
|
+
return buffer => {
|
|
757
|
+
const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
|
|
758
|
+
return Buffer.from(utils.concatBytes(iv, encrypted));
|
|
700
759
|
};
|
|
701
|
-
return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
|
|
702
760
|
}
|
|
703
761
|
end() {
|
|
704
762
|
this.dictionary.end();
|
|
@@ -749,89 +807,97 @@ function getPermissionsR3(permissionObject = {}) {
|
|
|
749
807
|
return permissions;
|
|
750
808
|
}
|
|
751
809
|
function getUserPasswordR2(encryptionKey) {
|
|
752
|
-
return
|
|
810
|
+
return rc4(processPasswordR2R3R4(), encryptionKey);
|
|
753
811
|
}
|
|
754
812
|
function getUserPasswordR3R4(documentId, encryptionKey) {
|
|
755
|
-
const key = encryptionKey.
|
|
756
|
-
let cipher =
|
|
813
|
+
const key = encryptionKey.slice();
|
|
814
|
+
let cipher = md5Hash(utils.concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
|
|
757
815
|
for (let i = 0; i < 20; i++) {
|
|
758
|
-
const
|
|
759
|
-
for (let j = 0; j <
|
|
760
|
-
|
|
816
|
+
const xorKey = new Uint8Array(key.length);
|
|
817
|
+
for (let j = 0; j < key.length; j++) {
|
|
818
|
+
xorKey[j] = encryptionKey[j] ^ i;
|
|
761
819
|
}
|
|
762
|
-
cipher =
|
|
820
|
+
cipher = rc4(cipher, xorKey);
|
|
763
821
|
}
|
|
764
|
-
|
|
822
|
+
const result = new Uint8Array(32);
|
|
823
|
+
result.set(cipher);
|
|
824
|
+
return result;
|
|
765
825
|
}
|
|
766
826
|
function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
|
|
767
827
|
let digest = paddedOwnerPassword;
|
|
768
828
|
let round = r >= 3 ? 51 : 1;
|
|
769
829
|
for (let i = 0; i < round; i++) {
|
|
770
|
-
digest =
|
|
830
|
+
digest = md5Hash(digest);
|
|
771
831
|
}
|
|
772
|
-
const
|
|
773
|
-
key
|
|
832
|
+
const keyLen = keyBits / 8;
|
|
833
|
+
let key = digest.slice(0, keyLen);
|
|
774
834
|
let cipher = paddedUserPassword;
|
|
775
835
|
round = r >= 3 ? 20 : 1;
|
|
776
836
|
for (let i = 0; i < round; i++) {
|
|
777
|
-
const
|
|
778
|
-
for (let j = 0; j <
|
|
779
|
-
|
|
837
|
+
const xorKey = new Uint8Array(keyLen);
|
|
838
|
+
for (let j = 0; j < keyLen; j++) {
|
|
839
|
+
xorKey[j] = key[j] ^ i;
|
|
780
840
|
}
|
|
781
|
-
cipher =
|
|
841
|
+
cipher = rc4(cipher, xorKey);
|
|
782
842
|
}
|
|
783
843
|
return cipher;
|
|
784
844
|
}
|
|
785
845
|
function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
|
|
786
|
-
|
|
846
|
+
const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
|
|
847
|
+
let key = utils.concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
|
|
787
848
|
const round = r >= 3 ? 51 : 1;
|
|
849
|
+
const keyLen = keyBits / 8;
|
|
788
850
|
for (let i = 0; i < round; i++) {
|
|
789
|
-
key =
|
|
790
|
-
key
|
|
851
|
+
key = md5Hash(key);
|
|
852
|
+
key = key.slice(0, keyLen);
|
|
791
853
|
}
|
|
792
854
|
return key;
|
|
793
855
|
}
|
|
794
856
|
function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
|
|
795
857
|
const validationSalt = generateRandomWordArray(8);
|
|
796
858
|
const keySalt = generateRandomWordArray(8);
|
|
797
|
-
|
|
859
|
+
const hash = sha256Hash(utils.concatBytes(processedUserPassword, validationSalt));
|
|
860
|
+
return utils.concatBytes(hash, validationSalt, keySalt);
|
|
798
861
|
}
|
|
799
862
|
function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
|
|
800
|
-
const key =
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
padding: CryptoJS.pad.NoPadding,
|
|
804
|
-
iv: CryptoJS.lib.WordArray.create(null, 16)
|
|
805
|
-
};
|
|
806
|
-
return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
|
|
863
|
+
const key = sha256Hash(utils.concatBytes(processedUserPassword, userKeySalt));
|
|
864
|
+
const iv = new Uint8Array(16);
|
|
865
|
+
return aesCbcEncrypt(encryptionKey, key, iv, false);
|
|
807
866
|
}
|
|
808
867
|
function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
|
|
809
868
|
const validationSalt = generateRandomWordArray(8);
|
|
810
869
|
const keySalt = generateRandomWordArray(8);
|
|
811
|
-
|
|
870
|
+
const hash = sha256Hash(utils.concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
|
|
871
|
+
return utils.concatBytes(hash, validationSalt, keySalt);
|
|
812
872
|
}
|
|
813
873
|
function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
|
|
814
|
-
const key =
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
padding: CryptoJS.pad.NoPadding,
|
|
818
|
-
iv: CryptoJS.lib.WordArray.create(null, 16)
|
|
819
|
-
};
|
|
820
|
-
return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
|
|
874
|
+
const key = sha256Hash(utils.concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
|
|
875
|
+
const iv = new Uint8Array(16);
|
|
876
|
+
return aesCbcEncrypt(encryptionKey, key, iv, false);
|
|
821
877
|
}
|
|
822
878
|
function getEncryptionKeyR5(generateRandomWordArray) {
|
|
823
879
|
return generateRandomWordArray(32);
|
|
824
880
|
}
|
|
825
881
|
function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
882
|
+
const data = new Uint8Array(16);
|
|
883
|
+
data[0] = permissions & 0xff;
|
|
884
|
+
data[1] = permissions >> 8 & 0xff;
|
|
885
|
+
data[2] = permissions >> 16 & 0xff;
|
|
886
|
+
data[3] = permissions >> 24 & 0xff;
|
|
887
|
+
data[4] = 0xff;
|
|
888
|
+
data[5] = 0xff;
|
|
889
|
+
data[6] = 0xff;
|
|
890
|
+
data[7] = 0xff;
|
|
891
|
+
data[8] = 0x54;
|
|
892
|
+
data[9] = 0x61;
|
|
893
|
+
data[10] = 0x64;
|
|
894
|
+
data[11] = 0x62;
|
|
895
|
+
const randomPart = generateRandomWordArray(4);
|
|
896
|
+
data.set(randomPart, 12);
|
|
897
|
+
return aesEcbEncrypt(data, encryptionKey);
|
|
832
898
|
}
|
|
833
899
|
function processPasswordR2R3R4(password = '') {
|
|
834
|
-
const out =
|
|
900
|
+
const out = new Uint8Array(32);
|
|
835
901
|
const length = password.length;
|
|
836
902
|
let index = 0;
|
|
837
903
|
while (index < length && index < 32) {
|
|
@@ -846,26 +912,16 @@ function processPasswordR2R3R4(password = '') {
|
|
|
846
912
|
out[index] = PASSWORD_PADDING[index - length];
|
|
847
913
|
index++;
|
|
848
914
|
}
|
|
849
|
-
return
|
|
915
|
+
return out;
|
|
850
916
|
}
|
|
851
917
|
function processPasswordR5(password = '') {
|
|
852
918
|
password = unescape(encodeURIComponent(saslprep(password)));
|
|
853
919
|
const length = Math.min(127, password.length);
|
|
854
|
-
const out =
|
|
920
|
+
const out = new Uint8Array(length);
|
|
855
921
|
for (let i = 0; i < length; i++) {
|
|
856
922
|
out[i] = password.charCodeAt(i);
|
|
857
923
|
}
|
|
858
|
-
return
|
|
859
|
-
}
|
|
860
|
-
function lsbFirstWord(data) {
|
|
861
|
-
return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
|
|
862
|
-
}
|
|
863
|
-
function wordArrayToBuffer(wordArray) {
|
|
864
|
-
const byteArray = [];
|
|
865
|
-
for (let i = 0; i < wordArray.sigBytes; i++) {
|
|
866
|
-
byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
|
|
867
|
-
}
|
|
868
|
-
return Buffer.from(byteArray);
|
|
924
|
+
return out;
|
|
869
925
|
}
|
|
870
926
|
const PASSWORD_PADDING = [0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a];
|
|
871
927
|
|
|
@@ -885,14 +941,18 @@ class PDFGradient$1 {
|
|
|
885
941
|
}
|
|
886
942
|
color = this.doc._normalizeColor(color);
|
|
887
943
|
if (this.stops.length === 0) {
|
|
888
|
-
if (
|
|
889
|
-
this._colorSpace =
|
|
890
|
-
} else if (color.length === 4) {
|
|
891
|
-
this._colorSpace = 'DeviceCMYK';
|
|
892
|
-
} else if (color.length === 1) {
|
|
893
|
-
this._colorSpace = 'DeviceGray';
|
|
944
|
+
if (this._activeColorProfile) {
|
|
945
|
+
this._colorSpace = this._activeColorProfile.ref;
|
|
894
946
|
} else {
|
|
895
|
-
|
|
947
|
+
if (color.length === 3) {
|
|
948
|
+
this._colorSpace = 'DeviceRGB';
|
|
949
|
+
} else if (color.length === 4) {
|
|
950
|
+
this._colorSpace = 'DeviceCMYK';
|
|
951
|
+
} else if (color.length === 1) {
|
|
952
|
+
this._colorSpace = 'DeviceGray';
|
|
953
|
+
} else {
|
|
954
|
+
throw new Error('Unknown color space');
|
|
955
|
+
}
|
|
896
956
|
}
|
|
897
957
|
} else if (this._colorSpace === 'DeviceRGB' && color.length !== 3 || this._colorSpace === 'DeviceCMYK' && color.length !== 4 || this._colorSpace === 'DeviceGray' && color.length !== 1) {
|
|
898
958
|
throw new Error('All gradient stops must use the same color space');
|
|
@@ -1163,6 +1223,7 @@ var ColorMixin = {
|
|
|
1163
1223
|
this._opacityCount = 0;
|
|
1164
1224
|
this._patternCount = 0;
|
|
1165
1225
|
this._gradCount = 0;
|
|
1226
|
+
this._activeColorProfile = null;
|
|
1166
1227
|
},
|
|
1167
1228
|
_normalizeColor(color) {
|
|
1168
1229
|
if (typeof color === 'string') {
|
|
@@ -1222,6 +1283,13 @@ var ColorMixin = {
|
|
|
1222
1283
|
if (color instanceof SpotColor) {
|
|
1223
1284
|
return color.id;
|
|
1224
1285
|
}
|
|
1286
|
+
if (this._activeColorProfile) {
|
|
1287
|
+
const profile = this._activeColorProfile;
|
|
1288
|
+
if (profile.channels !== color.length) {
|
|
1289
|
+
throw Error("Profile channels don't match color channels");
|
|
1290
|
+
}
|
|
1291
|
+
return profile.label;
|
|
1292
|
+
}
|
|
1225
1293
|
return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
|
|
1226
1294
|
},
|
|
1227
1295
|
fillColor(color, opacity) {
|
|
@@ -1251,6 +1319,18 @@ var ColorMixin = {
|
|
|
1251
1319
|
this._doOpacity(null, opacity);
|
|
1252
1320
|
return this;
|
|
1253
1321
|
},
|
|
1322
|
+
beginColorSpace(label) {
|
|
1323
|
+
const profile = this._colorProfiles[label];
|
|
1324
|
+
if (!profile) {
|
|
1325
|
+
throw Error('Invalid color space label, the profile should be set first');
|
|
1326
|
+
}
|
|
1327
|
+
this._activeColorProfile = profile;
|
|
1328
|
+
return this;
|
|
1329
|
+
},
|
|
1330
|
+
endColorSpace() {
|
|
1331
|
+
this._activeColorProfile = null;
|
|
1332
|
+
return this;
|
|
1333
|
+
},
|
|
1254
1334
|
_doOpacity(fillOpacity, strokeOpacity) {
|
|
1255
1335
|
let dictionary, name;
|
|
1256
1336
|
if (fillOpacity == null && strokeOpacity == null) {
|
|
@@ -1473,86 +1553,176 @@ const parameters = {
|
|
|
1473
1553
|
Z: 0,
|
|
1474
1554
|
z: 0
|
|
1475
1555
|
};
|
|
1556
|
+
const isCommand = function (c) {
|
|
1557
|
+
return c in parameters;
|
|
1558
|
+
};
|
|
1559
|
+
const isWsp = function (c) {
|
|
1560
|
+
const codePoint = c.codePointAt(0);
|
|
1561
|
+
return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
|
|
1562
|
+
};
|
|
1563
|
+
const isDigit = function (c) {
|
|
1564
|
+
const codePoint = c.codePointAt(0);
|
|
1565
|
+
if (codePoint == null) {
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
return 48 <= codePoint && codePoint <= 57;
|
|
1569
|
+
};
|
|
1570
|
+
const readNumber = function (string, cursor) {
|
|
1571
|
+
let i = cursor;
|
|
1572
|
+
let value = '';
|
|
1573
|
+
let state = 'none';
|
|
1574
|
+
for (; i < string.length; i += 1) {
|
|
1575
|
+
const c = string[i];
|
|
1576
|
+
if (c === '+' || c === '-') {
|
|
1577
|
+
if (state === 'none') {
|
|
1578
|
+
state = 'sign';
|
|
1579
|
+
value += c;
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
if (state === 'e') {
|
|
1583
|
+
state = 'exponent_sign';
|
|
1584
|
+
value += c;
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (isDigit(c)) {
|
|
1589
|
+
if (state === 'none' || state === 'sign' || state === 'whole') {
|
|
1590
|
+
state = 'whole';
|
|
1591
|
+
value += c;
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
if (state === 'decimal_point' || state === 'decimal') {
|
|
1595
|
+
state = 'decimal';
|
|
1596
|
+
value += c;
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
|
|
1600
|
+
state = 'exponent';
|
|
1601
|
+
value += c;
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (c === '.') {
|
|
1606
|
+
if (state === 'none' || state === 'sign' || state === 'whole') {
|
|
1607
|
+
state = 'decimal_point';
|
|
1608
|
+
value += c;
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
if (c === 'E' || c === 'e') {
|
|
1613
|
+
if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
|
|
1614
|
+
state = 'e';
|
|
1615
|
+
value += c;
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
const number = Number.parseFloat(value);
|
|
1622
|
+
if (Number.isNaN(number)) {
|
|
1623
|
+
return [cursor, null];
|
|
1624
|
+
}
|
|
1625
|
+
return [i - 1, number];
|
|
1626
|
+
};
|
|
1476
1627
|
const parse = function (path) {
|
|
1477
|
-
|
|
1478
|
-
|
|
1628
|
+
const pathData = [];
|
|
1629
|
+
let command = null;
|
|
1479
1630
|
let args = [];
|
|
1480
|
-
let
|
|
1481
|
-
let
|
|
1482
|
-
let
|
|
1483
|
-
for (let
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1631
|
+
let argsCount = 0;
|
|
1632
|
+
let canHaveComma = false;
|
|
1633
|
+
let hadComma = false;
|
|
1634
|
+
for (let i = 0; i < path.length; i += 1) {
|
|
1635
|
+
const c = path.charAt(i);
|
|
1636
|
+
if (isWsp(c)) {
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
if (canHaveComma && c === ',') {
|
|
1640
|
+
if (hadComma) {
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
hadComma = true;
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (isCommand(c)) {
|
|
1647
|
+
if (hadComma) {
|
|
1648
|
+
return pathData;
|
|
1649
|
+
}
|
|
1650
|
+
if (command == null) {
|
|
1651
|
+
if (c !== 'M' && c !== 'm') {
|
|
1652
|
+
return pathData;
|
|
1653
|
+
}
|
|
1654
|
+
} else {
|
|
1655
|
+
if (args.length !== 0) {
|
|
1656
|
+
return pathData;
|
|
1489
1657
|
}
|
|
1490
|
-
ret[ret.length] = {
|
|
1491
|
-
cmd,
|
|
1492
|
-
args
|
|
1493
|
-
};
|
|
1494
|
-
args = [];
|
|
1495
|
-
curArg = '';
|
|
1496
|
-
foundDecimal = false;
|
|
1497
|
-
}
|
|
1498
|
-
cmd = c;
|
|
1499
|
-
} else if ([' ', ','].includes(c) || c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e' || c === '.' && foundDecimal) {
|
|
1500
|
-
if (curArg.length === 0) {
|
|
1501
|
-
continue;
|
|
1502
1658
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1659
|
+
command = c;
|
|
1660
|
+
args = [];
|
|
1661
|
+
argsCount = parameters[command];
|
|
1662
|
+
canHaveComma = false;
|
|
1663
|
+
if (argsCount === 0) {
|
|
1664
|
+
pathData.push({
|
|
1665
|
+
command,
|
|
1506
1666
|
args
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
if (command == null) {
|
|
1672
|
+
return pathData;
|
|
1673
|
+
}
|
|
1674
|
+
let newCursor = i;
|
|
1675
|
+
let number = null;
|
|
1676
|
+
if (command === 'A' || command === 'a') {
|
|
1677
|
+
const position = args.length;
|
|
1678
|
+
if (position === 0 || position === 1) {
|
|
1679
|
+
if (c !== '+' && c !== '-') {
|
|
1680
|
+
[newCursor, number] = readNumber(path, i);
|
|
1511
1681
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1682
|
+
}
|
|
1683
|
+
if (position === 2 || position === 5 || position === 6) {
|
|
1684
|
+
[newCursor, number] = readNumber(path, i);
|
|
1685
|
+
}
|
|
1686
|
+
if (position === 3 || position === 4) {
|
|
1687
|
+
if (c === '0') {
|
|
1688
|
+
number = 0;
|
|
1689
|
+
}
|
|
1690
|
+
if (c === '1') {
|
|
1691
|
+
number = 1;
|
|
1514
1692
|
}
|
|
1515
|
-
} else {
|
|
1516
|
-
args[args.length] = +curArg;
|
|
1517
1693
|
}
|
|
1518
|
-
foundDecimal = c === '.';
|
|
1519
|
-
curArg = ['-', '.'].includes(c) ? c : '';
|
|
1520
1694
|
} else {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1695
|
+
[newCursor, number] = readNumber(path, i);
|
|
1696
|
+
}
|
|
1697
|
+
if (number == null) {
|
|
1698
|
+
return pathData;
|
|
1699
|
+
}
|
|
1700
|
+
args.push(number);
|
|
1701
|
+
canHaveComma = true;
|
|
1702
|
+
hadComma = false;
|
|
1703
|
+
i = newCursor;
|
|
1704
|
+
if (args.length === argsCount) {
|
|
1705
|
+
pathData.push({
|
|
1706
|
+
command,
|
|
1531
1707
|
args
|
|
1532
|
-
};
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
cmd = 'L';
|
|
1708
|
+
});
|
|
1709
|
+
if (command === 'M') {
|
|
1710
|
+
command = 'L';
|
|
1536
1711
|
}
|
|
1537
|
-
if (
|
|
1538
|
-
|
|
1712
|
+
if (command === 'm') {
|
|
1713
|
+
command = 'l';
|
|
1539
1714
|
}
|
|
1540
|
-
|
|
1541
|
-
args[args.length] = +curArg;
|
|
1715
|
+
args = [];
|
|
1542
1716
|
}
|
|
1543
1717
|
}
|
|
1544
|
-
|
|
1545
|
-
cmd,
|
|
1546
|
-
args
|
|
1547
|
-
};
|
|
1548
|
-
return ret;
|
|
1718
|
+
return pathData;
|
|
1549
1719
|
};
|
|
1550
1720
|
const apply = function (commands, doc) {
|
|
1551
1721
|
cx = cy = px = py = sx = sy = 0;
|
|
1552
1722
|
for (let i = 0; i < commands.length; i++) {
|
|
1553
1723
|
const c = commands[i];
|
|
1554
|
-
if (typeof runners[c.
|
|
1555
|
-
runners[c.
|
|
1724
|
+
if (typeof runners[c.command] === 'function') {
|
|
1725
|
+
runners[c.command](doc, c.args);
|
|
1556
1726
|
}
|
|
1557
1727
|
}
|
|
1558
1728
|
};
|
|
@@ -2584,7 +2754,7 @@ begincmap
|
|
|
2584
2754
|
1 begincodespacerange
|
|
2585
2755
|
<0000><ffff>
|
|
2586
2756
|
endcodespacerange
|
|
2587
|
-
|
|
2757
|
+
${ranges.length} beginbfrange
|
|
2588
2758
|
${ranges.join('\n')}
|
|
2589
2759
|
endbfrange
|
|
2590
2760
|
endcmap
|
|
@@ -2763,6 +2933,7 @@ class LineWrapper extends events.EventEmitter {
|
|
|
2763
2933
|
this.document = document;
|
|
2764
2934
|
this.horizontalScaling = options.horizontalScaling || 100;
|
|
2765
2935
|
this.indent = (options.indent || 0) * this.horizontalScaling / 100;
|
|
2936
|
+
this.indentAllLines = options.indentAllLines || false;
|
|
2766
2937
|
this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
|
|
2767
2938
|
this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
|
|
2768
2939
|
this.columns = options.columns || 1;
|
|
@@ -3004,7 +3175,13 @@ class LineWrapper extends events.EventEmitter {
|
|
|
3004
3175
|
this.column = 1;
|
|
3005
3176
|
this.startY = this.document.page.margins.top;
|
|
3006
3177
|
this.maxY = this.document.page.maxY();
|
|
3007
|
-
this.
|
|
3178
|
+
if (this.indentAllLines) {
|
|
3179
|
+
const indent = this.continuedX || this.indent;
|
|
3180
|
+
this.document.x += indent;
|
|
3181
|
+
this.lineWidth -= indent;
|
|
3182
|
+
} else {
|
|
3183
|
+
this.document.x = this.startX;
|
|
3184
|
+
}
|
|
3008
3185
|
if (this.document._fillColor) {
|
|
3009
3186
|
this.document.fillColor(...this.document._fillColor);
|
|
3010
3187
|
}
|
|
@@ -3408,7 +3585,11 @@ var TextMixin = {
|
|
|
3408
3585
|
}
|
|
3409
3586
|
const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
|
|
3410
3587
|
if (options.link != null) {
|
|
3411
|
-
|
|
3588
|
+
const linkOptions = {};
|
|
3589
|
+
if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
|
|
3590
|
+
linkOptions.structParent = this._currentStructureElement;
|
|
3591
|
+
}
|
|
3592
|
+
this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
|
|
3412
3593
|
}
|
|
3413
3594
|
if (options.goTo != null) {
|
|
3414
3595
|
this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
|
|
@@ -3537,6 +3718,47 @@ var TextMixin = {
|
|
|
3537
3718
|
}
|
|
3538
3719
|
};
|
|
3539
3720
|
|
|
3721
|
+
const parseExifOrientation = data => {
|
|
3722
|
+
if (!data || data.length < 20) return null;
|
|
3723
|
+
let pos = 2;
|
|
3724
|
+
while (pos < data.length - 4) {
|
|
3725
|
+
while (pos < data.length && data[pos] !== 0xff) pos++;
|
|
3726
|
+
if (pos >= data.length - 4) return null;
|
|
3727
|
+
const marker = data.readUInt16BE(pos);
|
|
3728
|
+
pos += 2;
|
|
3729
|
+
if (marker === 0xffda) return null;
|
|
3730
|
+
if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
|
|
3731
|
+
if (pos + 2 > data.length) return null;
|
|
3732
|
+
const segmentLength = data.readUInt16BE(pos);
|
|
3733
|
+
if (marker === 0xffe1 && pos + 8 <= data.length) {
|
|
3734
|
+
const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
|
|
3735
|
+
if (exifHeader === 'Exif\x00\x00') {
|
|
3736
|
+
const tiffStart = pos + 8;
|
|
3737
|
+
if (tiffStart + 8 > data.length) return null;
|
|
3738
|
+
const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
|
|
3739
|
+
const isLittleEndian = byteOrder === 'II';
|
|
3740
|
+
if (!isLittleEndian && byteOrder !== 'MM') return null;
|
|
3741
|
+
const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
|
|
3742
|
+
const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
|
|
3743
|
+
if (read16(tiffStart + 2) !== 42) return null;
|
|
3744
|
+
const ifdPos = tiffStart + read32(tiffStart + 4);
|
|
3745
|
+
if (ifdPos + 2 > data.length) return null;
|
|
3746
|
+
const entryCount = read16(ifdPos);
|
|
3747
|
+
for (let i = 0; i < entryCount; i++) {
|
|
3748
|
+
const entryPos = ifdPos + 2 + i * 12;
|
|
3749
|
+
if (entryPos + 12 > data.length) return null;
|
|
3750
|
+
if (read16(entryPos) === 0x0112) {
|
|
3751
|
+
const value = read16(entryPos + 8);
|
|
3752
|
+
return value >= 1 && value <= 8 ? value : null;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
return null;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
pos += segmentLength;
|
|
3759
|
+
}
|
|
3760
|
+
return null;
|
|
3761
|
+
};
|
|
3540
3762
|
const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
|
|
3541
3763
|
const COLOR_SPACE_MAP = {
|
|
3542
3764
|
1: 'DeviceGray',
|
|
@@ -3551,9 +3773,11 @@ class JPEG {
|
|
|
3551
3773
|
if (this.data.readUInt16BE(0) !== 0xffd8) {
|
|
3552
3774
|
throw 'SOI not found in JPEG';
|
|
3553
3775
|
}
|
|
3554
|
-
this.orientation =
|
|
3776
|
+
this.orientation = parseExifOrientation(this.data) || 1;
|
|
3555
3777
|
let pos = 2;
|
|
3556
3778
|
while (pos < this.data.length) {
|
|
3779
|
+
while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
|
|
3780
|
+
if (pos >= this.data.length) break;
|
|
3557
3781
|
marker = this.data.readUInt16BE(pos);
|
|
3558
3782
|
pos += 2;
|
|
3559
3783
|
if (MARKERS.includes(marker)) {
|
|
@@ -3705,12 +3929,16 @@ class PNGImage {
|
|
|
3705
3929
|
}
|
|
3706
3930
|
loadIndexedAlphaChannel() {
|
|
3707
3931
|
const transparency = this.image.transparency.indexed;
|
|
3932
|
+
const isInterlaced = this.image.interlaceMethod === 1;
|
|
3708
3933
|
return this.image.decodePixels(pixels => {
|
|
3709
3934
|
const alphaChannel = Buffer.alloc(this.width * this.height);
|
|
3710
3935
|
let i = 0;
|
|
3711
3936
|
for (let j = 0, end = pixels.length; j < end; j++) {
|
|
3712
3937
|
alphaChannel[i++] = transparency[pixels[j]];
|
|
3713
3938
|
}
|
|
3939
|
+
if (isInterlaced) {
|
|
3940
|
+
this.imgData = zlib.deflateSync(Buffer.from(pixels));
|
|
3941
|
+
}
|
|
3714
3942
|
this.alphaChannel = zlib.deflateSync(alphaChannel);
|
|
3715
3943
|
return this.finalize();
|
|
3716
3944
|
});
|
|
@@ -3723,16 +3951,108 @@ class PNGImage {
|
|
|
3723
3951
|
}
|
|
3724
3952
|
}
|
|
3725
3953
|
|
|
3954
|
+
class BitmapImage {
|
|
3955
|
+
constructor(data, label, properties) {
|
|
3956
|
+
this.label = label;
|
|
3957
|
+
this.data = data;
|
|
3958
|
+
this.width = properties.width;
|
|
3959
|
+
this.height = properties.height;
|
|
3960
|
+
this.channels = properties.channels;
|
|
3961
|
+
this.colorSpace = properties.colorSpace;
|
|
3962
|
+
this.hasAlphaChannel = properties.hasAlphaChannel;
|
|
3963
|
+
this.obj = null;
|
|
3964
|
+
}
|
|
3965
|
+
embed(document) {
|
|
3966
|
+
if (this.obj) {
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
this.document = document;
|
|
3970
|
+
if (!(this.data instanceof Uint8Array && this.data.length === this.channels * this.width * this.height)) {
|
|
3971
|
+
throw Error("Invalid bitmap, data doesn't match the given properties.");
|
|
3972
|
+
}
|
|
3973
|
+
if (this.colorSpace) {
|
|
3974
|
+
const profile = this.document._colorProfiles[this.colorSpace];
|
|
3975
|
+
if (!profile) {
|
|
3976
|
+
throw Error("PDFDocument doesn't support bitmap color space.");
|
|
3977
|
+
}
|
|
3978
|
+
const channels = this.hasAlphaChannel ? this.channels - 1 : this.channels;
|
|
3979
|
+
if (profile.channels !== channels) {
|
|
3980
|
+
throw Error("Color profile doesn't support image channels");
|
|
3981
|
+
}
|
|
3982
|
+
this.colorSpace = profile.ref;
|
|
3983
|
+
} else {
|
|
3984
|
+
if (!this.hasAlphaChannel) {
|
|
3985
|
+
this.colorSpace = this.channels === 4 ? 'DeviceCMYK' : 'DeviceRGB';
|
|
3986
|
+
} else {
|
|
3987
|
+
this.colorSpace = this.channels === 5 ? 'DeviceCMYK' : 'DeviceRGB';
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
let pixelData = this.data;
|
|
3991
|
+
let alphaData = undefined;
|
|
3992
|
+
if (this.hasAlphaChannel) {
|
|
3993
|
+
const pixelChannels = this.channels - 1;
|
|
3994
|
+
pixelData = new Uint8Array(pixelChannels * this.width * this.height);
|
|
3995
|
+
alphaData = new Uint8Array(this.width * this.height);
|
|
3996
|
+
if (this.channels === 4) {
|
|
3997
|
+
for (let src = 0, dst = 0, a = 0; a < this.data.length; src += 4, dst += 3, a++) {
|
|
3998
|
+
pixelData[dst] = this.data[src];
|
|
3999
|
+
pixelData[dst + 1] = this.data[src + 1];
|
|
4000
|
+
pixelData[dst + 2] = this.data[src + 2];
|
|
4001
|
+
alphaData[a] = this.data[src + 3];
|
|
4002
|
+
}
|
|
4003
|
+
} else if (this.channels === 5) {
|
|
4004
|
+
for (let src = 0, dst = 0, a = 0; a < this.data.length; src += 5, dst += 4, a++) {
|
|
4005
|
+
pixelData[dst] = this.data[src];
|
|
4006
|
+
pixelData[dst + 1] = this.data[src + 1];
|
|
4007
|
+
pixelData[dst + 2] = this.data[src + 2];
|
|
4008
|
+
pixelData[dst + 3] = this.data[src + 3];
|
|
4009
|
+
alphaData[a] = this.data[src + 4];
|
|
4010
|
+
}
|
|
4011
|
+
} else {
|
|
4012
|
+
for (let src = 0, dst = 0, a = 0; a < this.data.length; src += this.channels, dst += pixelChannels, a++) {
|
|
4013
|
+
for (let j = 0; j < pixelChannels; j++) {
|
|
4014
|
+
pixelData[dst + j] = this.data[src + j];
|
|
4015
|
+
}
|
|
4016
|
+
alphaData[a] = this.data[src + pixelChannels];
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
this.obj = this.document.ref({
|
|
4021
|
+
Type: 'XObject',
|
|
4022
|
+
Subtype: 'Image',
|
|
4023
|
+
BitsPerComponent: 8,
|
|
4024
|
+
Width: this.width,
|
|
4025
|
+
Height: this.height,
|
|
4026
|
+
ColorSpace: this.colorSpace
|
|
4027
|
+
});
|
|
4028
|
+
if (alphaData) {
|
|
4029
|
+
const sMask = this.document.ref({
|
|
4030
|
+
Type: 'XObject',
|
|
4031
|
+
Subtype: 'Image',
|
|
4032
|
+
Height: this.height,
|
|
4033
|
+
Width: this.width,
|
|
4034
|
+
BitsPerComponent: 8,
|
|
4035
|
+
ColorSpace: 'DeviceGray',
|
|
4036
|
+
Decode: [0, 1]
|
|
4037
|
+
});
|
|
4038
|
+
sMask.end(alphaData);
|
|
4039
|
+
this.obj.data['SMask'] = sMask;
|
|
4040
|
+
}
|
|
4041
|
+
this.obj.end(pixelData);
|
|
4042
|
+
this.data = null;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
|
|
3726
4046
|
class PDFImage {
|
|
3727
|
-
static open(src, label) {
|
|
4047
|
+
static open(src, label, properties) {
|
|
3728
4048
|
let data;
|
|
3729
|
-
if (Buffer.isBuffer(src)) {
|
|
4049
|
+
if (src instanceof Uint8Array || Buffer.isBuffer(src)) {
|
|
3730
4050
|
data = src;
|
|
3731
4051
|
} else if (src instanceof ArrayBuffer) {
|
|
3732
4052
|
data = Buffer.from(new Uint8Array(src));
|
|
3733
4053
|
} else {
|
|
3734
4054
|
const split = src.split(',');
|
|
3735
|
-
if (split[0].startsWith('data:') && split[0].endsWith(';base64
|
|
4055
|
+
if (split[0].startsWith('data:') && split[0].endsWith(';base64')) {
|
|
3736
4056
|
if (split.length === 1) {
|
|
3737
4057
|
throw Error('Empty base64');
|
|
3738
4058
|
}
|
|
@@ -3744,6 +4064,9 @@ class PDFImage {
|
|
|
3744
4064
|
}
|
|
3745
4065
|
}
|
|
3746
4066
|
}
|
|
4067
|
+
if (properties?.isBitmap) {
|
|
4068
|
+
return new BitmapImage(data, label, properties);
|
|
4069
|
+
}
|
|
3747
4070
|
if (data[0] === 0xff && data[1] === 0xd8) {
|
|
3748
4071
|
return new JPEG(data, label);
|
|
3749
4072
|
} else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
|
|
@@ -3927,13 +4250,13 @@ var ImagesMixin = {
|
|
|
3927
4250
|
this.restore();
|
|
3928
4251
|
return this;
|
|
3929
4252
|
},
|
|
3930
|
-
openImage(src) {
|
|
4253
|
+
openImage(src, properties) {
|
|
3931
4254
|
let image;
|
|
3932
4255
|
if (typeof src === 'string') {
|
|
3933
4256
|
image = this._imageRegistry[src];
|
|
3934
4257
|
}
|
|
3935
4258
|
if (!image) {
|
|
3936
|
-
image = PDFImage.open(src, `I${++this._imageCount}
|
|
4259
|
+
image = PDFImage.open(src, `I${++this._imageCount}`, properties);
|
|
3937
4260
|
if (typeof src === 'string') {
|
|
3938
4261
|
this._imageRegistry[src] = image;
|
|
3939
4262
|
}
|
|
@@ -3942,6 +4265,12 @@ var ImagesMixin = {
|
|
|
3942
4265
|
}
|
|
3943
4266
|
};
|
|
3944
4267
|
|
|
4268
|
+
class PDFAnnotationReference {
|
|
4269
|
+
constructor(annotationRef) {
|
|
4270
|
+
this.annotationRef = annotationRef;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
|
|
3945
4274
|
var AnnotationsMixin = {
|
|
3946
4275
|
annotate(x, y, w, h, options) {
|
|
3947
4276
|
options.Type = 'Annot';
|
|
@@ -3959,12 +4288,18 @@ var AnnotationsMixin = {
|
|
|
3959
4288
|
if (typeof options.Dest === 'string') {
|
|
3960
4289
|
options.Dest = new String(options.Dest);
|
|
3961
4290
|
}
|
|
4291
|
+
const structParent = options.structParent;
|
|
4292
|
+
delete options.structParent;
|
|
3962
4293
|
for (let key in options) {
|
|
3963
4294
|
const val = options[key];
|
|
3964
4295
|
options[key[0].toUpperCase() + key.slice(1)] = val;
|
|
3965
4296
|
}
|
|
3966
4297
|
const ref = this.ref(options);
|
|
3967
4298
|
this.page.annotations.push(ref);
|
|
4299
|
+
if (structParent && typeof structParent.add === 'function') {
|
|
4300
|
+
const annotRef = new PDFAnnotationReference(ref);
|
|
4301
|
+
structParent.add(annotRef);
|
|
4302
|
+
}
|
|
3968
4303
|
ref.end();
|
|
3969
4304
|
return this;
|
|
3970
4305
|
},
|
|
@@ -4008,6 +4343,9 @@ var AnnotationsMixin = {
|
|
|
4008
4343
|
});
|
|
4009
4344
|
options.A.end();
|
|
4010
4345
|
}
|
|
4346
|
+
if (options.structParent && !options.Contents) {
|
|
4347
|
+
options.Contents = new String('');
|
|
4348
|
+
}
|
|
4011
4349
|
return this.annotate(x, y, w, h, options);
|
|
4012
4350
|
},
|
|
4013
4351
|
_markup(x, y, w, h, options = {}) {
|
|
@@ -4079,15 +4417,26 @@ var AnnotationsMixin = {
|
|
|
4079
4417
|
}
|
|
4080
4418
|
};
|
|
4081
4419
|
|
|
4420
|
+
const DEFAULT_OPTIONS = {
|
|
4421
|
+
top: 0,
|
|
4422
|
+
left: 0,
|
|
4423
|
+
zoom: 0,
|
|
4424
|
+
fit: true,
|
|
4425
|
+
pageNumber: null,
|
|
4426
|
+
expanded: false
|
|
4427
|
+
};
|
|
4082
4428
|
class PDFOutline {
|
|
4083
|
-
constructor(document, parent, title, dest, options = {
|
|
4084
|
-
expanded: false
|
|
4085
|
-
}) {
|
|
4429
|
+
constructor(document, parent, title, dest, options = DEFAULT_OPTIONS) {
|
|
4086
4430
|
this.document = document;
|
|
4087
4431
|
this.options = options;
|
|
4088
4432
|
this.outlineData = {};
|
|
4089
4433
|
if (dest !== null) {
|
|
4090
|
-
|
|
4434
|
+
const destWidth = dest.data.MediaBox[2];
|
|
4435
|
+
const destHeight = dest.data.MediaBox[3];
|
|
4436
|
+
const top = destHeight - (options.top || 0);
|
|
4437
|
+
const left = destWidth - (options.left || 0);
|
|
4438
|
+
const zoom = options.zoom || 0;
|
|
4439
|
+
this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
|
|
4091
4440
|
}
|
|
4092
4441
|
if (parent !== null) {
|
|
4093
4442
|
this.outlineData['Parent'] = parent;
|
|
@@ -4098,10 +4447,10 @@ class PDFOutline {
|
|
|
4098
4447
|
this.dictionary = this.document.ref(this.outlineData);
|
|
4099
4448
|
this.children = [];
|
|
4100
4449
|
}
|
|
4101
|
-
addItem(title, options = {
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
const result = new PDFOutline(this.document, this.dictionary, title,
|
|
4450
|
+
addItem(title, options = DEFAULT_OPTIONS) {
|
|
4451
|
+
const pages = this.document._root.data.Pages.data.Kids;
|
|
4452
|
+
const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
|
|
4453
|
+
const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
|
|
4105
4454
|
this.children.push(result);
|
|
4106
4455
|
return result;
|
|
4107
4456
|
}
|
|
@@ -4137,7 +4486,7 @@ var OutlineMixin = {
|
|
|
4137
4486
|
this.outline.endOutline();
|
|
4138
4487
|
if (this.outline.children.length > 0) {
|
|
4139
4488
|
this._root.data.Outlines = this.outline.dictionary;
|
|
4140
|
-
return this._root.data.PageMode = 'UseOutlines';
|
|
4489
|
+
return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
|
|
4141
4490
|
}
|
|
4142
4491
|
}
|
|
4143
4492
|
};
|
|
@@ -4208,6 +4557,9 @@ class PDFStructureElement {
|
|
|
4208
4557
|
if (child instanceof PDFStructureContent) {
|
|
4209
4558
|
this._addContentToParentTree(child);
|
|
4210
4559
|
}
|
|
4560
|
+
if (child instanceof PDFAnnotationReference) {
|
|
4561
|
+
this._addAnnotationToParentTree(child.annotationRef);
|
|
4562
|
+
}
|
|
4211
4563
|
if (typeof child === 'function' && this._attached) {
|
|
4212
4564
|
child = this._contentForClosure(child);
|
|
4213
4565
|
}
|
|
@@ -4223,6 +4575,12 @@ class PDFStructureElement {
|
|
|
4223
4575
|
pageStructParents[mcid] = this.dictionary;
|
|
4224
4576
|
});
|
|
4225
4577
|
}
|
|
4578
|
+
_addAnnotationToParentTree(annotRef) {
|
|
4579
|
+
const parentTreeKey = this.document.createStructParentTreeNextKey();
|
|
4580
|
+
annotRef.data.StructParent = parentTreeKey;
|
|
4581
|
+
const parentTree = this.document.getStructParentTree();
|
|
4582
|
+
parentTree.add(parentTreeKey, this.dictionary);
|
|
4583
|
+
}
|
|
4226
4584
|
setParent(parentRef) {
|
|
4227
4585
|
if (this.dictionary.data.P) {
|
|
4228
4586
|
throw new Error(`Structure element added to more than one parent`);
|
|
@@ -4254,11 +4612,17 @@ class PDFStructureElement {
|
|
|
4254
4612
|
this._flush();
|
|
4255
4613
|
}
|
|
4256
4614
|
_isValidChild(child) {
|
|
4257
|
-
return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
|
|
4615
|
+
return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
|
|
4258
4616
|
}
|
|
4259
4617
|
_contentForClosure(closure) {
|
|
4260
4618
|
const content = this.document.markStructureContent(this.dictionary.data.S);
|
|
4619
|
+
const prevStructElement = this.document._currentStructureElement;
|
|
4620
|
+
this.document._currentStructureElement = this;
|
|
4621
|
+
const wasEnded = this._ended;
|
|
4622
|
+
this._ended = false;
|
|
4261
4623
|
closure();
|
|
4624
|
+
this._ended = wasEnded;
|
|
4625
|
+
this.document._currentStructureElement = prevStructElement;
|
|
4262
4626
|
this.document.endMarkedContent();
|
|
4263
4627
|
this._addContentToParentTree(content);
|
|
4264
4628
|
return content;
|
|
@@ -4311,6 +4675,15 @@ class PDFStructureElement {
|
|
|
4311
4675
|
}
|
|
4312
4676
|
});
|
|
4313
4677
|
}
|
|
4678
|
+
if (child instanceof PDFAnnotationReference) {
|
|
4679
|
+
const pageRef = this.document.page.dictionary;
|
|
4680
|
+
const objr = {
|
|
4681
|
+
Type: 'OBJR',
|
|
4682
|
+
Obj: child.annotationRef,
|
|
4683
|
+
Pg: pageRef
|
|
4684
|
+
};
|
|
4685
|
+
this.dictionary.data.K.push(objr);
|
|
4686
|
+
}
|
|
4314
4687
|
}
|
|
4315
4688
|
}
|
|
4316
4689
|
|
|
@@ -4404,6 +4777,13 @@ var MarkingsMixin = {
|
|
|
4404
4777
|
endMarkedContent() {
|
|
4405
4778
|
this.page.markings.pop();
|
|
4406
4779
|
this.addContent('EMC');
|
|
4780
|
+
if (this._textOptions) {
|
|
4781
|
+
delete this._textOptions.link;
|
|
4782
|
+
delete this._textOptions.goTo;
|
|
4783
|
+
delete this._textOptions.destination;
|
|
4784
|
+
delete this._textOptions.underline;
|
|
4785
|
+
delete this._textOptions.strike;
|
|
4786
|
+
}
|
|
4407
4787
|
return this;
|
|
4408
4788
|
},
|
|
4409
4789
|
struct(type, options = {}, children = null) {
|
|
@@ -4850,7 +5230,7 @@ var AttachmentsMixin = {
|
|
|
4850
5230
|
if (options.type) {
|
|
4851
5231
|
refBody.Subtype = options.type.replace('/', '#2F');
|
|
4852
5232
|
}
|
|
4853
|
-
const checksum =
|
|
5233
|
+
const checksum = md5Hex(new Uint8Array(data));
|
|
4854
5234
|
refBody.Params.CheckSum = new String(checksum);
|
|
4855
5235
|
refBody.Params.Size = data.byteLength;
|
|
4856
5236
|
let ref;
|
|
@@ -4910,6 +5290,9 @@ var PDFA = {
|
|
|
4910
5290
|
this._addColorOutputIntent();
|
|
4911
5291
|
},
|
|
4912
5292
|
_addColorOutputIntent() {
|
|
5293
|
+
if (this._root.data.OutputIntents && this._root.data.OutputIntents.length !== 0) {
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
4913
5296
|
const iccProfile = fs.readFileSync(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
|
|
4914
5297
|
const colorProfileRef = this.ref({
|
|
4915
5298
|
Length: iccProfile.length,
|
|
@@ -5493,7 +5876,7 @@ function renderRow(row, rowIndex) {
|
|
|
5493
5876
|
function renderCell(cell, rowStruct) {
|
|
5494
5877
|
const cellRenderer = () => {
|
|
5495
5878
|
if (cell.backgroundColor != null) {
|
|
5496
|
-
this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(
|
|
5879
|
+
this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
|
|
5497
5880
|
}
|
|
5498
5881
|
renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
|
|
5499
5882
|
if (cell.debug) {
|
|
@@ -5554,20 +5937,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
|
|
|
5554
5937
|
const doc = this.document;
|
|
5555
5938
|
if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
|
|
5556
5939
|
if (border.top > 0) {
|
|
5557
|
-
doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(
|
|
5940
|
+
doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
|
|
5558
5941
|
}
|
|
5559
5942
|
} else {
|
|
5560
5943
|
if (border.top > 0) {
|
|
5561
|
-
doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(
|
|
5944
|
+
doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
|
|
5562
5945
|
}
|
|
5563
5946
|
if (border.right > 0) {
|
|
5564
|
-
doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(
|
|
5947
|
+
doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
|
|
5565
5948
|
}
|
|
5566
5949
|
if (border.bottom > 0) {
|
|
5567
|
-
doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(
|
|
5950
|
+
doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
|
|
5568
5951
|
}
|
|
5569
5952
|
if (border.left > 0) {
|
|
5570
|
-
doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(
|
|
5953
|
+
doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
|
|
5571
5954
|
}
|
|
5572
5955
|
}
|
|
5573
5956
|
}
|
|
@@ -5729,6 +6112,72 @@ var MetadataMixin = {
|
|
|
5729
6112
|
}
|
|
5730
6113
|
};
|
|
5731
6114
|
|
|
6115
|
+
class ICCProfile {
|
|
6116
|
+
constructor(label, data, channels, alternate) {
|
|
6117
|
+
this.label = label;
|
|
6118
|
+
this.data = data;
|
|
6119
|
+
this.channels = channels;
|
|
6120
|
+
this.alternate = alternate;
|
|
6121
|
+
this.ref = null;
|
|
6122
|
+
this.streamRef = null;
|
|
6123
|
+
}
|
|
6124
|
+
embed(document) {
|
|
6125
|
+
if (this.ref) {
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6128
|
+
this.document = document;
|
|
6129
|
+
this.streamRef = this.document.ref({
|
|
6130
|
+
Alternate: this.alternate,
|
|
6131
|
+
N: this.channels,
|
|
6132
|
+
Length: this.data.length
|
|
6133
|
+
});
|
|
6134
|
+
this.streamRef.end(this.data);
|
|
6135
|
+
this.ref = this.document.ref([`ICCBased ${this.streamRef}`]);
|
|
6136
|
+
this.ref.end();
|
|
6137
|
+
this.data = null;
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
|
|
6141
|
+
var ColorSpaceMixin = {
|
|
6142
|
+
initColorSpace() {
|
|
6143
|
+
this._colorProfiles = {};
|
|
6144
|
+
},
|
|
6145
|
+
iccProfile(label, data, channels, alternate) {
|
|
6146
|
+
const profile = new ICCProfile(label, data, channels, alternate);
|
|
6147
|
+
profile.embed(this);
|
|
6148
|
+
this._colorProfiles[label] = profile;
|
|
6149
|
+
return this;
|
|
6150
|
+
},
|
|
6151
|
+
_writeSpaceToResources(resources) {
|
|
6152
|
+
resources.data.ColorSpace = resources.data.ColorSpace || {};
|
|
6153
|
+
Object.entries(this._colorProfiles).forEach(([k, v]) => {
|
|
6154
|
+
resources.data.ColorSpace[k] = v.ref;
|
|
6155
|
+
});
|
|
6156
|
+
}
|
|
6157
|
+
};
|
|
6158
|
+
|
|
6159
|
+
var OutputIntent = {
|
|
6160
|
+
initOutputIntent() {
|
|
6161
|
+
this._root.data.OutputIntents = this._root.data.OutputIntents || [];
|
|
6162
|
+
},
|
|
6163
|
+
outputIntent(type, s, info, outputConditionIdentifier, destOutputProfileLabel) {
|
|
6164
|
+
const profile = this._colorProfiles[destOutputProfileLabel];
|
|
6165
|
+
if (profile) {
|
|
6166
|
+
throw Error('Invalid color profile label, the profile should be set first');
|
|
6167
|
+
}
|
|
6168
|
+
const intentRef = this.ref({
|
|
6169
|
+
Type: type,
|
|
6170
|
+
S: s,
|
|
6171
|
+
Info: info,
|
|
6172
|
+
OutputConditionIdentifier: outputConditionIdentifier,
|
|
6173
|
+
DestOutputProfile: profile.ref
|
|
6174
|
+
});
|
|
6175
|
+
intentRef.end();
|
|
6176
|
+
this._root.data.OutputIntents.push(intentRef);
|
|
6177
|
+
return this;
|
|
6178
|
+
}
|
|
6179
|
+
};
|
|
6180
|
+
|
|
5732
6181
|
class PDFDocument extends stream.Readable {
|
|
5733
6182
|
constructor(options = {}) {
|
|
5734
6183
|
super(options);
|
|
@@ -5774,8 +6223,13 @@ class PDFDocument extends stream.Readable {
|
|
|
5774
6223
|
if (this.options.lang) {
|
|
5775
6224
|
this._root.data.Lang = new String(this.options.lang);
|
|
5776
6225
|
}
|
|
6226
|
+
if (this.options.pageLayout) {
|
|
6227
|
+
const layout = this.options.pageLayout;
|
|
6228
|
+
this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
|
|
6229
|
+
}
|
|
5777
6230
|
this.page = null;
|
|
5778
6231
|
this.initMetadata();
|
|
6232
|
+
this.initColorSpace();
|
|
5779
6233
|
this.initColor();
|
|
5780
6234
|
this.initVector();
|
|
5781
6235
|
this.initFonts(options.font);
|
|
@@ -5785,6 +6239,7 @@ class PDFDocument extends stream.Readable {
|
|
|
5785
6239
|
this.initMarkings(options);
|
|
5786
6240
|
this.initTables();
|
|
5787
6241
|
this.initSubset(options);
|
|
6242
|
+
this.initOutputIntent();
|
|
5788
6243
|
this.info = {
|
|
5789
6244
|
Producer: 'PDFKit',
|
|
5790
6245
|
Creator: 'PDFKit',
|
|
@@ -5983,6 +6438,7 @@ const mixin = methods => {
|
|
|
5983
6438
|
Object.assign(PDFDocument.prototype, methods);
|
|
5984
6439
|
};
|
|
5985
6440
|
mixin(MetadataMixin);
|
|
6441
|
+
mixin(ColorSpaceMixin);
|
|
5986
6442
|
mixin(ColorMixin);
|
|
5987
6443
|
mixin(VectorMixin);
|
|
5988
6444
|
mixin(FontsMixin);
|
|
@@ -5995,6 +6451,7 @@ mixin(AcroFormMixin);
|
|
|
5995
6451
|
mixin(AttachmentsMixin);
|
|
5996
6452
|
mixin(SubsetMixin);
|
|
5997
6453
|
mixin(TableMixin);
|
|
6454
|
+
mixin(OutputIntent);
|
|
5998
6455
|
PDFDocument.LineWrapper = LineWrapper;
|
|
5999
6456
|
|
|
6000
6457
|
module.exports = PDFDocument;
|