@kittl/pdfkit 0.17.3 → 0.17.4

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/js/pdfkit.es.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import stream from 'stream';
2
2
  import zlib from 'zlib';
3
- import CryptoJS from 'crypto-js';
3
+ import { concatBytes } from '@noble/hashes/utils';
4
+ import md5 from 'js-md5';
5
+ import { sha256 } from '@noble/hashes/sha256';
6
+ import { cbc, ecb } from '@noble/ciphers/aes';
4
7
  import fs from 'fs';
5
8
  import * as fontkit from 'fontkit';
6
9
  import { EventEmitter } from 'events';
7
10
  import LineBreaker from 'linebreak';
8
- import exif from 'jpeg-exif';
9
11
  import PNG from 'png-js';
10
12
 
11
13
  class PDFAbstractReference {
@@ -363,6 +365,7 @@ class PDFPage {
363
365
  this._options = options;
364
366
  this.size = options.size || 'letter';
365
367
  this.layout = options.layout || 'portrait';
368
+ this.userUnit = options.userUnit || 1.0;
366
369
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
367
370
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
368
371
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
@@ -378,7 +381,8 @@ class PDFPage {
378
381
  Parent: this.document._root.data.Pages,
379
382
  MediaBox: [0, 0, this.width, this.height],
380
383
  Contents: this.content,
381
- Resources: this.resources
384
+ Resources: this.resources,
385
+ UserUnit: this.userUnit
382
386
  });
383
387
  this.markings = [];
384
388
  }
@@ -434,6 +438,7 @@ class PDFPage {
434
438
  for (let color of Object.values(this.document.spotColors)) {
435
439
  this.resources.data.ColorSpace[color.id] = color;
436
440
  }
441
+ this.document._writeSpaceToResources(this.resources);
437
442
  this.resources.end();
438
443
  return this.content.end();
439
444
  }
@@ -451,6 +456,59 @@ class PDFNameTree extends PDFTree {
451
456
  }
452
457
  }
453
458
 
459
+ function md5Hash(data) {
460
+ return new Uint8Array(md5.arrayBuffer(data));
461
+ }
462
+ function md5Hex(data) {
463
+ return md5(data);
464
+ }
465
+
466
+ function sha256Hash(data) {
467
+ return sha256(data);
468
+ }
469
+
470
+ function aesCbcEncrypt(data, key, iv) {
471
+ let padding = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
472
+ return cbc(key, iv, {
473
+ disablePadding: !padding
474
+ }).encrypt(data);
475
+ }
476
+ function aesEcbEncrypt(data, key) {
477
+ return ecb(key, {
478
+ disablePadding: true
479
+ }).encrypt(data);
480
+ }
481
+
482
+ function rc4(data, key) {
483
+ const s = new Uint8Array(256);
484
+ for (let i = 0; i < 256; i++) {
485
+ s[i] = i;
486
+ }
487
+ let j = 0;
488
+ for (let i = 0; i < 256; i++) {
489
+ j = j + s[i] + key[i % key.length] & 0xff;
490
+ [s[i], s[j]] = [s[j], s[i]];
491
+ }
492
+ const output = new Uint8Array(data.length);
493
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
494
+ i = i + 1 & 0xff;
495
+ j = j + s[i] & 0xff;
496
+ [s[i], s[j]] = [s[j], s[i]];
497
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
498
+ }
499
+ return output;
500
+ }
501
+
502
+ function randomBytes(length) {
503
+ const bytes = new Uint8Array(length);
504
+ if (globalThis.crypto?.getRandomValues) {
505
+ globalThis.crypto.getRandomValues(bytes);
506
+ } else {
507
+ require('crypto').randomFillSync(bytes);
508
+ }
509
+ return bytes;
510
+ }
511
+
454
512
  function inRange(value, rangeGroup) {
455
513
  if (value < rangeGroup[0]) return false;
456
514
  let startRange = 0;
@@ -551,10 +609,10 @@ class PDFSecurity {
551
609
  }
552
610
  infoStr += `${key}: ${info[key].valueOf()}\n`;
553
611
  }
554
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
612
+ return Buffer.from(md5Hash(infoStr));
555
613
  }
556
614
  static generateRandomWordArray(bytes) {
557
- return CryptoJS.lib.WordArray.random(bytes);
615
+ return randomBytes(bytes);
558
616
  }
559
617
  static create(document) {
560
618
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -648,8 +706,8 @@ class PDFSecurity {
648
706
  encDict.StrF = 'StdCF';
649
707
  }
650
708
  encDict.R = r;
651
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
652
- encDict.U = wordArrayToBuffer(userPasswordEntry);
709
+ encDict.O = Buffer.from(ownerPasswordEntry);
710
+ encDict.U = Buffer.from(userPasswordEntry);
653
711
  encDict.P = permissions;
654
712
  }
655
713
  _setupEncryptionV5(encDict, options) {
@@ -659,10 +717,10 @@ class PDFSecurity {
659
717
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
660
718
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
661
719
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
662
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
720
+ const userKeySalt = userPasswordEntry.slice(40, 48);
663
721
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
664
722
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
665
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
723
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
666
724
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
667
725
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
668
726
  encDict.V = 5;
@@ -677,36 +735,37 @@ class PDFSecurity {
677
735
  encDict.StmF = 'StdCF';
678
736
  encDict.StrF = 'StdCF';
679
737
  encDict.R = 5;
680
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
681
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
682
- encDict.U = wordArrayToBuffer(userPasswordEntry);
683
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
738
+ encDict.O = Buffer.from(ownerPasswordEntry);
739
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
740
+ encDict.U = Buffer.from(userPasswordEntry);
741
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
684
742
  encDict.P = permissions;
685
- encDict.Perms = wordArrayToBuffer(permsEntry);
743
+ encDict.Perms = Buffer.from(permsEntry);
686
744
  }
687
745
  getEncryptFn(obj, gen) {
688
746
  let digest;
689
747
  if (this.version < 5) {
690
- digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
748
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
749
+ digest = concatBytes(this.encryptionKey, suffix);
691
750
  }
692
751
  if (this.version === 1 || this.version === 2) {
693
- let key = CryptoJS.MD5(digest);
694
- key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
695
- return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
752
+ let key = md5Hash(digest);
753
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
754
+ key = key.slice(0, keyLen);
755
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
696
756
  }
697
757
  let key;
698
758
  if (this.version === 4) {
699
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
759
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
760
+ key = md5Hash(concatBytes(digest, saltMarker));
700
761
  } else {
701
762
  key = this.encryptionKey;
702
763
  }
703
764
  const iv = PDFSecurity.generateRandomWordArray(16);
704
- const options = {
705
- mode: CryptoJS.mode.CBC,
706
- padding: CryptoJS.pad.Pkcs7,
707
- iv
765
+ return buffer => {
766
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
767
+ return Buffer.from(concatBytes(iv, encrypted));
708
768
  };
709
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
710
769
  }
711
770
  end() {
712
771
  this.dictionary.end();
@@ -759,90 +818,98 @@ function getPermissionsR3() {
759
818
  return permissions;
760
819
  }
761
820
  function getUserPasswordR2(encryptionKey) {
762
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
821
+ return rc4(processPasswordR2R3R4(), encryptionKey);
763
822
  }
764
823
  function getUserPasswordR3R4(documentId, encryptionKey) {
765
- const key = encryptionKey.clone();
766
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
824
+ const key = encryptionKey.slice();
825
+ let cipher = md5Hash(concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
767
826
  for (let i = 0; i < 20; i++) {
768
- const xorRound = Math.ceil(key.sigBytes / 4);
769
- for (let j = 0; j < xorRound; j++) {
770
- key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
827
+ const xorKey = new Uint8Array(key.length);
828
+ for (let j = 0; j < key.length; j++) {
829
+ xorKey[j] = encryptionKey[j] ^ i;
771
830
  }
772
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
831
+ cipher = rc4(cipher, xorKey);
773
832
  }
774
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
833
+ const result = new Uint8Array(32);
834
+ result.set(cipher);
835
+ return result;
775
836
  }
776
837
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
777
838
  let digest = paddedOwnerPassword;
778
839
  let round = r >= 3 ? 51 : 1;
779
840
  for (let i = 0; i < round; i++) {
780
- digest = CryptoJS.MD5(digest);
841
+ digest = md5Hash(digest);
781
842
  }
782
- const key = digest.clone();
783
- key.sigBytes = keyBits / 8;
843
+ const keyLen = keyBits / 8;
844
+ let key = digest.slice(0, keyLen);
784
845
  let cipher = paddedUserPassword;
785
846
  round = r >= 3 ? 20 : 1;
786
847
  for (let i = 0; i < round; i++) {
787
- const xorRound = Math.ceil(key.sigBytes / 4);
788
- for (let j = 0; j < xorRound; j++) {
789
- key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
848
+ const xorKey = new Uint8Array(keyLen);
849
+ for (let j = 0; j < keyLen; j++) {
850
+ xorKey[j] = key[j] ^ i;
790
851
  }
791
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
852
+ cipher = rc4(cipher, xorKey);
792
853
  }
793
854
  return cipher;
794
855
  }
795
856
  function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
796
- let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
857
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
858
+ let key = concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
797
859
  const round = r >= 3 ? 51 : 1;
860
+ const keyLen = keyBits / 8;
798
861
  for (let i = 0; i < round; i++) {
799
- key = CryptoJS.MD5(key);
800
- key.sigBytes = keyBits / 8;
862
+ key = md5Hash(key);
863
+ key = key.slice(0, keyLen);
801
864
  }
802
865
  return key;
803
866
  }
804
867
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
805
868
  const validationSalt = generateRandomWordArray(8);
806
869
  const keySalt = generateRandomWordArray(8);
807
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
870
+ const hash = sha256Hash(concatBytes(processedUserPassword, validationSalt));
871
+ return concatBytes(hash, validationSalt, keySalt);
808
872
  }
809
873
  function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
810
- const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
811
- const options = {
812
- mode: CryptoJS.mode.CBC,
813
- padding: CryptoJS.pad.NoPadding,
814
- iv: CryptoJS.lib.WordArray.create(null, 16)
815
- };
816
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
874
+ const key = sha256Hash(concatBytes(processedUserPassword, userKeySalt));
875
+ const iv = new Uint8Array(16);
876
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
817
877
  }
818
878
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
819
879
  const validationSalt = generateRandomWordArray(8);
820
880
  const keySalt = generateRandomWordArray(8);
821
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
881
+ const hash = sha256Hash(concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
882
+ return concatBytes(hash, validationSalt, keySalt);
822
883
  }
823
884
  function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
824
- const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
825
- const options = {
826
- mode: CryptoJS.mode.CBC,
827
- padding: CryptoJS.pad.NoPadding,
828
- iv: CryptoJS.lib.WordArray.create(null, 16)
829
- };
830
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
885
+ const key = sha256Hash(concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
886
+ const iv = new Uint8Array(16);
887
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
831
888
  }
832
889
  function getEncryptionKeyR5(generateRandomWordArray) {
833
890
  return generateRandomWordArray(32);
834
891
  }
835
892
  function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
836
- const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
837
- const options = {
838
- mode: CryptoJS.mode.ECB,
839
- padding: CryptoJS.pad.NoPadding
840
- };
841
- return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
893
+ const data = new Uint8Array(16);
894
+ data[0] = permissions & 0xff;
895
+ data[1] = permissions >> 8 & 0xff;
896
+ data[2] = permissions >> 16 & 0xff;
897
+ data[3] = permissions >> 24 & 0xff;
898
+ data[4] = 0xff;
899
+ data[5] = 0xff;
900
+ data[6] = 0xff;
901
+ data[7] = 0xff;
902
+ data[8] = 0x54;
903
+ data[9] = 0x61;
904
+ data[10] = 0x64;
905
+ data[11] = 0x62;
906
+ const randomPart = generateRandomWordArray(4);
907
+ data.set(randomPart, 12);
908
+ return aesEcbEncrypt(data, encryptionKey);
842
909
  }
843
910
  function processPasswordR2R3R4() {
844
911
  let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
845
- const out = Buffer.alloc(32);
912
+ const out = new Uint8Array(32);
846
913
  const length = password.length;
847
914
  let index = 0;
848
915
  while (index < length && index < 32) {
@@ -857,27 +924,17 @@ function processPasswordR2R3R4() {
857
924
  out[index] = PASSWORD_PADDING[index - length];
858
925
  index++;
859
926
  }
860
- return CryptoJS.lib.WordArray.create(out);
927
+ return out;
861
928
  }
862
929
  function processPasswordR5() {
863
930
  let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
864
931
  password = unescape(encodeURIComponent(saslprep(password)));
865
932
  const length = Math.min(127, password.length);
866
- const out = Buffer.alloc(length);
933
+ const out = new Uint8Array(length);
867
934
  for (let i = 0; i < length; i++) {
868
935
  out[i] = password.charCodeAt(i);
869
936
  }
870
- return CryptoJS.lib.WordArray.create(out);
871
- }
872
- function lsbFirstWord(data) {
873
- return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
874
- }
875
- function wordArrayToBuffer(wordArray) {
876
- const byteArray = [];
877
- for (let i = 0; i < wordArray.sigBytes; i++) {
878
- byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
879
- }
880
- return Buffer.from(byteArray);
937
+ return out;
881
938
  }
882
939
  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];
883
940
 
@@ -1175,6 +1232,7 @@ var ColorMixin = {
1175
1232
  this._opacityCount = 0;
1176
1233
  this._patternCount = 0;
1177
1234
  this._gradCount = 0;
1235
+ this._activeColorProfile = null;
1178
1236
  },
1179
1237
  _normalizeColor(color) {
1180
1238
  if (typeof color === 'string') {
@@ -1194,7 +1252,7 @@ var ColorMixin = {
1194
1252
  if (color.length === 3) {
1195
1253
  color = color.map(part => part / 255);
1196
1254
  } else if (color.length === 4) {
1197
- color = color.map(part => part / 100);
1255
+ color = color.map(part => part / 255);
1198
1256
  }
1199
1257
  return color;
1200
1258
  }
@@ -1234,6 +1292,13 @@ var ColorMixin = {
1234
1292
  if (color instanceof SpotColor) {
1235
1293
  return color.id;
1236
1294
  }
1295
+ if (this._activeColorProfile) {
1296
+ const profile = this._activeColorProfile;
1297
+ if (profile.channels !== color.length) {
1298
+ throw Error("Profile channels don't match color channels");
1299
+ }
1300
+ return profile.label;
1301
+ }
1237
1302
  return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1238
1303
  },
1239
1304
  fillColor(color, opacity) {
@@ -1263,6 +1328,18 @@ var ColorMixin = {
1263
1328
  this._doOpacity(null, opacity);
1264
1329
  return this;
1265
1330
  },
1331
+ beingColorProfile(label) {
1332
+ const profile = this._colorProfiles[label];
1333
+ if (!profile) {
1334
+ throw Error('Invalid color space label, the profile should be set first');
1335
+ }
1336
+ this._activeColorProfile = profile;
1337
+ return this;
1338
+ },
1339
+ endColorProfile() {
1340
+ this._activeColorProfile = null;
1341
+ return this;
1342
+ },
1266
1343
  _doOpacity(fillOpacity, strokeOpacity) {
1267
1344
  let dictionary, name;
1268
1345
  if (fillOpacity == null && strokeOpacity == null) {
@@ -1485,86 +1562,176 @@ const parameters = {
1485
1562
  Z: 0,
1486
1563
  z: 0
1487
1564
  };
1565
+ const isCommand = function (c) {
1566
+ return c in parameters;
1567
+ };
1568
+ const isWsp = function (c) {
1569
+ const codePoint = c.codePointAt(0);
1570
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1571
+ };
1572
+ const isDigit = function (c) {
1573
+ const codePoint = c.codePointAt(0);
1574
+ if (codePoint == null) {
1575
+ return false;
1576
+ }
1577
+ return 48 <= codePoint && codePoint <= 57;
1578
+ };
1579
+ const readNumber = function (string, cursor) {
1580
+ let i = cursor;
1581
+ let value = '';
1582
+ let state = 'none';
1583
+ for (; i < string.length; i += 1) {
1584
+ const c = string[i];
1585
+ if (c === '+' || c === '-') {
1586
+ if (state === 'none') {
1587
+ state = 'sign';
1588
+ value += c;
1589
+ continue;
1590
+ }
1591
+ if (state === 'e') {
1592
+ state = 'exponent_sign';
1593
+ value += c;
1594
+ continue;
1595
+ }
1596
+ }
1597
+ if (isDigit(c)) {
1598
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1599
+ state = 'whole';
1600
+ value += c;
1601
+ continue;
1602
+ }
1603
+ if (state === 'decimal_point' || state === 'decimal') {
1604
+ state = 'decimal';
1605
+ value += c;
1606
+ continue;
1607
+ }
1608
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1609
+ state = 'exponent';
1610
+ value += c;
1611
+ continue;
1612
+ }
1613
+ }
1614
+ if (c === '.') {
1615
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1616
+ state = 'decimal_point';
1617
+ value += c;
1618
+ continue;
1619
+ }
1620
+ }
1621
+ if (c === 'E' || c === 'e') {
1622
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1623
+ state = 'e';
1624
+ value += c;
1625
+ continue;
1626
+ }
1627
+ }
1628
+ break;
1629
+ }
1630
+ const number = Number.parseFloat(value);
1631
+ if (Number.isNaN(number)) {
1632
+ return [cursor, null];
1633
+ }
1634
+ return [i - 1, number];
1635
+ };
1488
1636
  const parse = function (path) {
1489
- let cmd;
1490
- const ret = [];
1637
+ const pathData = [];
1638
+ let command = null;
1491
1639
  let args = [];
1492
- let curArg = '';
1493
- let foundDecimal = false;
1494
- let params = 0;
1495
- for (let c of path) {
1496
- if (parameters[c] != null) {
1497
- params = parameters[c];
1498
- if (cmd) {
1499
- if (curArg.length > 0) {
1500
- args[args.length] = +curArg;
1640
+ let argsCount = 0;
1641
+ let canHaveComma = false;
1642
+ let hadComma = false;
1643
+ for (let i = 0; i < path.length; i += 1) {
1644
+ const c = path.charAt(i);
1645
+ if (isWsp(c)) {
1646
+ continue;
1647
+ }
1648
+ if (canHaveComma && c === ',') {
1649
+ if (hadComma) {
1650
+ break;
1651
+ }
1652
+ hadComma = true;
1653
+ continue;
1654
+ }
1655
+ if (isCommand(c)) {
1656
+ if (hadComma) {
1657
+ return pathData;
1658
+ }
1659
+ if (command == null) {
1660
+ if (c !== 'M' && c !== 'm') {
1661
+ return pathData;
1501
1662
  }
1502
- ret[ret.length] = {
1503
- cmd,
1663
+ } else {
1664
+ if (args.length !== 0) {
1665
+ return pathData;
1666
+ }
1667
+ }
1668
+ command = c;
1669
+ args = [];
1670
+ argsCount = parameters[command];
1671
+ canHaveComma = false;
1672
+ if (argsCount === 0) {
1673
+ pathData.push({
1674
+ command,
1504
1675
  args
1505
- };
1506
- args = [];
1507
- curArg = '';
1508
- foundDecimal = false;
1676
+ });
1509
1677
  }
1510
- cmd = c;
1511
- } else if ([' ', ','].includes(c) || c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e' || c === '.' && foundDecimal) {
1512
- if (curArg.length === 0) {
1513
- continue;
1678
+ continue;
1679
+ }
1680
+ if (command == null) {
1681
+ return pathData;
1682
+ }
1683
+ let newCursor = i;
1684
+ let number = null;
1685
+ if (command === 'A' || command === 'a') {
1686
+ const position = args.length;
1687
+ if (position === 0 || position === 1) {
1688
+ if (c !== '+' && c !== '-') {
1689
+ [newCursor, number] = readNumber(path, i);
1690
+ }
1514
1691
  }
1515
- if (args.length === params) {
1516
- ret[ret.length] = {
1517
- cmd,
1518
- args
1519
- };
1520
- args = [+curArg];
1521
- if (cmd === 'M') {
1522
- cmd = 'L';
1692
+ if (position === 2 || position === 5 || position === 6) {
1693
+ [newCursor, number] = readNumber(path, i);
1694
+ }
1695
+ if (position === 3 || position === 4) {
1696
+ if (c === '0') {
1697
+ number = 0;
1523
1698
  }
1524
- if (cmd === 'm') {
1525
- cmd = 'l';
1699
+ if (c === '1') {
1700
+ number = 1;
1526
1701
  }
1527
- } else {
1528
- args[args.length] = +curArg;
1529
1702
  }
1530
- foundDecimal = c === '.';
1531
- curArg = ['-', '.'].includes(c) ? c : '';
1532
1703
  } else {
1533
- curArg += c;
1534
- if (c === '.') {
1535
- foundDecimal = true;
1536
- }
1537
- }
1538
- }
1539
- if (curArg.length > 0) {
1540
- if (args.length === params) {
1541
- ret[ret.length] = {
1542
- cmd,
1704
+ [newCursor, number] = readNumber(path, i);
1705
+ }
1706
+ if (number == null) {
1707
+ return pathData;
1708
+ }
1709
+ args.push(number);
1710
+ canHaveComma = true;
1711
+ hadComma = false;
1712
+ i = newCursor;
1713
+ if (args.length === argsCount) {
1714
+ pathData.push({
1715
+ command,
1543
1716
  args
1544
- };
1545
- args = [+curArg];
1546
- if (cmd === 'M') {
1547
- cmd = 'L';
1717
+ });
1718
+ if (command === 'M') {
1719
+ command = 'L';
1548
1720
  }
1549
- if (cmd === 'm') {
1550
- cmd = 'l';
1721
+ if (command === 'm') {
1722
+ command = 'l';
1551
1723
  }
1552
- } else {
1553
- args[args.length] = +curArg;
1724
+ args = [];
1554
1725
  }
1555
1726
  }
1556
- ret[ret.length] = {
1557
- cmd,
1558
- args
1559
- };
1560
- return ret;
1727
+ return pathData;
1561
1728
  };
1562
1729
  const apply = function (commands, doc) {
1563
1730
  cx = cy = px = py = sx = sy = 0;
1564
1731
  for (let i = 0; i < commands.length; i++) {
1565
1732
  const c = commands[i];
1566
- if (typeof runners[c.cmd] === 'function') {
1567
- runners[c.cmd](doc, c.args);
1733
+ if (typeof runners[c.command] === 'function') {
1734
+ runners[c.command](doc, c.args);
1568
1735
  }
1569
1736
  }
1570
1737
  };
@@ -2603,7 +2770,7 @@ begincmap
2603
2770
  1 begincodespacerange
2604
2771
  <0000><ffff>
2605
2772
  endcodespacerange
2606
- 1 beginbfrange
2773
+ ${ranges.length} beginbfrange
2607
2774
  ${ranges.join('\n')}
2608
2775
  endbfrange
2609
2776
  endcmap
@@ -2788,6 +2955,7 @@ class LineWrapper extends EventEmitter {
2788
2955
  this.document = document;
2789
2956
  this.horizontalScaling = options.horizontalScaling || 100;
2790
2957
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2958
+ this.indentAllLines = options.indentAllLines || false;
2791
2959
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2792
2960
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2793
2961
  this.columns = options.columns || 1;
@@ -3029,7 +3197,13 @@ class LineWrapper extends EventEmitter {
3029
3197
  this.column = 1;
3030
3198
  this.startY = this.document.page.margins.top;
3031
3199
  this.maxY = this.document.page.maxY();
3032
- this.document.x = this.startX;
3200
+ if (this.indentAllLines) {
3201
+ const indent = this.continuedX || this.indent;
3202
+ this.document.x += indent;
3203
+ this.lineWidth -= indent;
3204
+ } else {
3205
+ this.document.x = this.startX;
3206
+ }
3033
3207
  if (this.document._fillColor) {
3034
3208
  this.document.fillColor(...this.document._fillColor);
3035
3209
  }
@@ -3439,7 +3613,11 @@ var TextMixin = {
3439
3613
  }
3440
3614
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3441
3615
  if (options.link != null) {
3442
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3616
+ const linkOptions = {};
3617
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3618
+ linkOptions.structParent = this._currentStructureElement;
3619
+ }
3620
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3443
3621
  }
3444
3622
  if (options.goTo != null) {
3445
3623
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3568,6 +3746,47 @@ var TextMixin = {
3568
3746
  }
3569
3747
  };
3570
3748
 
3749
+ const parseExifOrientation = data => {
3750
+ if (!data || data.length < 20) return null;
3751
+ let pos = 2;
3752
+ while (pos < data.length - 4) {
3753
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3754
+ if (pos >= data.length - 4) return null;
3755
+ const marker = data.readUInt16BE(pos);
3756
+ pos += 2;
3757
+ if (marker === 0xffda) return null;
3758
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3759
+ if (pos + 2 > data.length) return null;
3760
+ const segmentLength = data.readUInt16BE(pos);
3761
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3762
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3763
+ if (exifHeader === 'Exif\x00\x00') {
3764
+ const tiffStart = pos + 8;
3765
+ if (tiffStart + 8 > data.length) return null;
3766
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3767
+ const isLittleEndian = byteOrder === 'II';
3768
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3769
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3770
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3771
+ if (read16(tiffStart + 2) !== 42) return null;
3772
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3773
+ if (ifdPos + 2 > data.length) return null;
3774
+ const entryCount = read16(ifdPos);
3775
+ for (let i = 0; i < entryCount; i++) {
3776
+ const entryPos = ifdPos + 2 + i * 12;
3777
+ if (entryPos + 12 > data.length) return null;
3778
+ if (read16(entryPos) === 0x0112) {
3779
+ const value = read16(entryPos + 8);
3780
+ return value >= 1 && value <= 8 ? value : null;
3781
+ }
3782
+ }
3783
+ return null;
3784
+ }
3785
+ }
3786
+ pos += segmentLength;
3787
+ }
3788
+ return null;
3789
+ };
3571
3790
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3572
3791
  const COLOR_SPACE_MAP = {
3573
3792
  1: 'DeviceGray',
@@ -3582,9 +3801,11 @@ class JPEG {
3582
3801
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3583
3802
  throw 'SOI not found in JPEG';
3584
3803
  }
3585
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3804
+ this.orientation = parseExifOrientation(this.data) || 1;
3586
3805
  let pos = 2;
3587
3806
  while (pos < this.data.length) {
3807
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3808
+ if (pos >= this.data.length) break;
3588
3809
  marker = this.data.readUInt16BE(pos);
3589
3810
  pos += 2;
3590
3811
  if (MARKERS.includes(marker)) {
@@ -3736,12 +3957,16 @@ class PNGImage {
3736
3957
  }
3737
3958
  loadIndexedAlphaChannel() {
3738
3959
  const transparency = this.image.transparency.indexed;
3960
+ const isInterlaced = this.image.interlaceMethod === 1;
3739
3961
  return this.image.decodePixels(pixels => {
3740
3962
  const alphaChannel = Buffer.alloc(this.width * this.height);
3741
3963
  let i = 0;
3742
3964
  for (let j = 0, end = pixels.length; j < end; j++) {
3743
3965
  alphaChannel[i++] = transparency[pixels[j]];
3744
3966
  }
3967
+ if (isInterlaced) {
3968
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3969
+ }
3745
3970
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3746
3971
  return this.finalize();
3747
3972
  });
@@ -3763,7 +3988,7 @@ class PDFImage {
3763
3988
  data = Buffer.from(new Uint8Array(src));
3764
3989
  } else {
3765
3990
  const split = src.split(',');
3766
- if (split[0].startsWith('data:') && split[0].endsWith(';base64,')) {
3991
+ if (split[0].startsWith('data:') && split[0].endsWith(';base64')) {
3767
3992
  if (split.length === 1) {
3768
3993
  throw Error('Empty base64');
3769
3994
  }
@@ -3974,6 +4199,12 @@ var ImagesMixin = {
3974
4199
  }
3975
4200
  };
3976
4201
 
4202
+ class PDFAnnotationReference {
4203
+ constructor(annotationRef) {
4204
+ this.annotationRef = annotationRef;
4205
+ }
4206
+ }
4207
+
3977
4208
  var AnnotationsMixin = {
3978
4209
  annotate(x, y, w, h, options) {
3979
4210
  options.Type = 'Annot';
@@ -3991,12 +4222,18 @@ var AnnotationsMixin = {
3991
4222
  if (typeof options.Dest === 'string') {
3992
4223
  options.Dest = new String(options.Dest);
3993
4224
  }
4225
+ const structParent = options.structParent;
4226
+ delete options.structParent;
3994
4227
  for (let key in options) {
3995
4228
  const val = options[key];
3996
4229
  options[key[0].toUpperCase() + key.slice(1)] = val;
3997
4230
  }
3998
4231
  const ref = this.ref(options);
3999
4232
  this.page.annotations.push(ref);
4233
+ if (structParent && typeof structParent.add === 'function') {
4234
+ const annotRef = new PDFAnnotationReference(ref);
4235
+ structParent.add(annotRef);
4236
+ }
4000
4237
  ref.end();
4001
4238
  return this;
4002
4239
  },
@@ -4043,6 +4280,9 @@ var AnnotationsMixin = {
4043
4280
  });
4044
4281
  options.A.end();
4045
4282
  }
4283
+ if (options.structParent && !options.Contents) {
4284
+ options.Contents = new String('');
4285
+ }
4046
4286
  return this.annotate(x, y, w, h, options);
4047
4287
  },
4048
4288
  _markup(x, y, w, h) {
@@ -4124,16 +4364,27 @@ var AnnotationsMixin = {
4124
4364
  }
4125
4365
  };
4126
4366
 
4367
+ const DEFAULT_OPTIONS = {
4368
+ top: 0,
4369
+ left: 0,
4370
+ zoom: 0,
4371
+ fit: true,
4372
+ pageNumber: null,
4373
+ expanded: false
4374
+ };
4127
4375
  class PDFOutline {
4128
4376
  constructor(document, parent, title, dest) {
4129
- let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
4130
- expanded: false
4131
- };
4377
+ let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : DEFAULT_OPTIONS;
4132
4378
  this.document = document;
4133
4379
  this.options = options;
4134
4380
  this.outlineData = {};
4135
4381
  if (dest !== null) {
4136
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4382
+ const destWidth = dest.data.MediaBox[2];
4383
+ const destHeight = dest.data.MediaBox[3];
4384
+ const top = destHeight - (options.top || 0);
4385
+ const left = destWidth - (options.left || 0);
4386
+ const zoom = options.zoom || 0;
4387
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4137
4388
  }
4138
4389
  if (parent !== null) {
4139
4390
  this.outlineData['Parent'] = parent;
@@ -4145,10 +4396,10 @@ class PDFOutline {
4145
4396
  this.children = [];
4146
4397
  }
4147
4398
  addItem(title) {
4148
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
4149
- expanded: false
4150
- };
4151
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4399
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_OPTIONS;
4400
+ const pages = this.document._root.data.Pages.data.Kids;
4401
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4402
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4152
4403
  this.children.push(result);
4153
4404
  return result;
4154
4405
  }
@@ -4184,7 +4435,7 @@ var OutlineMixin = {
4184
4435
  this.outline.endOutline();
4185
4436
  if (this.outline.children.length > 0) {
4186
4437
  this._root.data.Outlines = this.outline.dictionary;
4187
- return this._root.data.PageMode = 'UseOutlines';
4438
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4188
4439
  }
4189
4440
  }
4190
4441
  };
@@ -4257,6 +4508,9 @@ class PDFStructureElement {
4257
4508
  if (child instanceof PDFStructureContent) {
4258
4509
  this._addContentToParentTree(child);
4259
4510
  }
4511
+ if (child instanceof PDFAnnotationReference) {
4512
+ this._addAnnotationToParentTree(child.annotationRef);
4513
+ }
4260
4514
  if (typeof child === 'function' && this._attached) {
4261
4515
  child = this._contentForClosure(child);
4262
4516
  }
@@ -4273,6 +4527,12 @@ class PDFStructureElement {
4273
4527
  pageStructParents[mcid] = this.dictionary;
4274
4528
  });
4275
4529
  }
4530
+ _addAnnotationToParentTree(annotRef) {
4531
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4532
+ annotRef.data.StructParent = parentTreeKey;
4533
+ const parentTree = this.document.getStructParentTree();
4534
+ parentTree.add(parentTreeKey, this.dictionary);
4535
+ }
4276
4536
  setParent(parentRef) {
4277
4537
  if (this.dictionary.data.P) {
4278
4538
  throw new Error(`Structure element added to more than one parent`);
@@ -4304,11 +4564,17 @@ class PDFStructureElement {
4304
4564
  this._flush();
4305
4565
  }
4306
4566
  _isValidChild(child) {
4307
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4567
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4308
4568
  }
4309
4569
  _contentForClosure(closure) {
4310
4570
  const content = this.document.markStructureContent(this.dictionary.data.S);
4571
+ const prevStructElement = this.document._currentStructureElement;
4572
+ this.document._currentStructureElement = this;
4573
+ const wasEnded = this._ended;
4574
+ this._ended = false;
4311
4575
  closure();
4576
+ this._ended = wasEnded;
4577
+ this.document._currentStructureElement = prevStructElement;
4312
4578
  this.document.endMarkedContent();
4313
4579
  this._addContentToParentTree(content);
4314
4580
  return content;
@@ -4362,6 +4628,15 @@ class PDFStructureElement {
4362
4628
  }
4363
4629
  });
4364
4630
  }
4631
+ if (child instanceof PDFAnnotationReference) {
4632
+ const pageRef = this.document.page.dictionary;
4633
+ const objr = {
4634
+ Type: 'OBJR',
4635
+ Obj: child.annotationRef,
4636
+ Pg: pageRef
4637
+ };
4638
+ this.dictionary.data.K.push(objr);
4639
+ }
4365
4640
  }
4366
4641
  }
4367
4642
 
@@ -4457,6 +4732,13 @@ var MarkingsMixin = {
4457
4732
  endMarkedContent() {
4458
4733
  this.page.markings.pop();
4459
4734
  this.addContent('EMC');
4735
+ if (this._textOptions) {
4736
+ delete this._textOptions.link;
4737
+ delete this._textOptions.goTo;
4738
+ delete this._textOptions.destination;
4739
+ delete this._textOptions.underline;
4740
+ delete this._textOptions.strike;
4741
+ }
4460
4742
  return this;
4461
4743
  },
4462
4744
  struct(type) {
@@ -4915,7 +5197,7 @@ var AttachmentsMixin = {
4915
5197
  if (options.type) {
4916
5198
  refBody.Subtype = options.type.replace('/', '#2F');
4917
5199
  }
4918
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5200
+ const checksum = md5Hex(new Uint8Array(data));
4919
5201
  refBody.Params.CheckSum = new String(checksum);
4920
5202
  refBody.Params.Size = data.byteLength;
4921
5203
  let ref;
@@ -4975,6 +5257,9 @@ var PDFA = {
4975
5257
  this._addColorOutputIntent();
4976
5258
  },
4977
5259
  _addColorOutputIntent() {
5260
+ if (this._root.data.OutputIntents && this._root.data.OutputIntents.length !== 0) {
5261
+ return;
5262
+ }
4978
5263
  const iccProfile = fs.readFileSync(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
4979
5264
  const colorProfileRef = this.ref({
4980
5265
  Length: iccProfile.length,
@@ -5567,7 +5852,7 @@ function renderRow(row, rowIndex) {
5567
5852
  function renderCell(cell, rowStruct) {
5568
5853
  const cellRenderer = () => {
5569
5854
  if (cell.backgroundColor != null) {
5570
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5855
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5571
5856
  }
5572
5857
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5573
5858
  if (cell.debug) {
@@ -5631,20 +5916,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5631
5916
  const doc = this.document;
5632
5917
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5633
5918
  if (border.top > 0) {
5634
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5919
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5635
5920
  }
5636
5921
  } else {
5637
5922
  if (border.top > 0) {
5638
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5923
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5639
5924
  }
5640
5925
  if (border.right > 0) {
5641
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5926
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5642
5927
  }
5643
5928
  if (border.bottom > 0) {
5644
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5929
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5645
5930
  }
5646
5931
  if (border.left > 0) {
5647
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5932
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5648
5933
  }
5649
5934
  }
5650
5935
  }
@@ -5810,6 +6095,74 @@ var MetadataMixin = {
5810
6095
  }
5811
6096
  };
5812
6097
 
6098
+ class ICCProfile {
6099
+ constructor(label, data, channels, alternate) {
6100
+ this.label = label;
6101
+ this.data = data;
6102
+ this.channels = channels;
6103
+ this.alternate = alternate;
6104
+ this.ref = null;
6105
+ this.streamRef = null;
6106
+ }
6107
+ embed(document) {
6108
+ if (this.ref) {
6109
+ return;
6110
+ }
6111
+ this.document = document;
6112
+ this.streamRef = this.document.ref({
6113
+ Alternate: this.alternate,
6114
+ N: this.channels,
6115
+ Length: this.data.length
6116
+ });
6117
+ this.streamRef.write(this.data);
6118
+ this.streamRef.end();
6119
+ this.ref = this.document.ref([`ICCBased ${this.streamRef}`]);
6120
+ this.ref.end();
6121
+ this.data = null;
6122
+ }
6123
+ }
6124
+
6125
+ var ColorSpaceMixin = {
6126
+ initColorSpace() {
6127
+ this._colorProfiles = {};
6128
+ },
6129
+ iccProfile(label, data, channels, alternate) {
6130
+ const profile = new ICCProfile(label, data, channels, alternate);
6131
+ profile.embed(this);
6132
+ this._colorProfiles[label] = profile;
6133
+ return this;
6134
+ },
6135
+ _writeSpaceToResources(resources) {
6136
+ resources.data.ColorSpace = resources.data.ColorSpace || {};
6137
+ Object.entries(this._colorProfiles).forEach(_ref => {
6138
+ let [k, v] = _ref;
6139
+ resources.data.ColorSpace[k] = v.ref;
6140
+ });
6141
+ }
6142
+ };
6143
+
6144
+ var OutputIntent = {
6145
+ initOutputIntent() {
6146
+ this._root.data.OutputIntents = this._root.data.OutputIntents || [];
6147
+ },
6148
+ outputIntent(type, s, info, outputConditionIdentifier, destOutputProfileLabel) {
6149
+ const profile = this._colorProfiles[destOutputProfileLabel];
6150
+ if (profile) {
6151
+ throw Error('Invalid color profile label, the profile should be set first');
6152
+ }
6153
+ const intentRef = this.ref({
6154
+ Type: type,
6155
+ S: s,
6156
+ Info: info,
6157
+ OutputConditionIdentifier: outputConditionIdentifier,
6158
+ DestOutputProfile: profile.ref
6159
+ });
6160
+ intentRef.end();
6161
+ this._root.data.OutputIntents.push(intentRef);
6162
+ return this;
6163
+ }
6164
+ };
6165
+
5813
6166
  class PDFDocument extends stream.Readable {
5814
6167
  constructor() {
5815
6168
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -5856,8 +6209,13 @@ class PDFDocument extends stream.Readable {
5856
6209
  if (this.options.lang) {
5857
6210
  this._root.data.Lang = new String(this.options.lang);
5858
6211
  }
6212
+ if (this.options.pageLayout) {
6213
+ const layout = this.options.pageLayout;
6214
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6215
+ }
5859
6216
  this.page = null;
5860
6217
  this.initMetadata();
6218
+ this.initColorSpace();
5861
6219
  this.initColor();
5862
6220
  this.initVector();
5863
6221
  this.initFonts(options.font);
@@ -5867,6 +6225,7 @@ class PDFDocument extends stream.Readable {
5867
6225
  this.initMarkings(options);
5868
6226
  this.initTables();
5869
6227
  this.initSubset(options);
6228
+ this.initOutputIntent();
5870
6229
  this.info = {
5871
6230
  Producer: 'PDFKit',
5872
6231
  Creator: 'PDFKit',
@@ -6068,6 +6427,7 @@ const mixin = methods => {
6068
6427
  Object.assign(PDFDocument.prototype, methods);
6069
6428
  };
6070
6429
  mixin(MetadataMixin);
6430
+ mixin(ColorSpaceMixin);
6071
6431
  mixin(ColorMixin);
6072
6432
  mixin(VectorMixin);
6073
6433
  mixin(FontsMixin);
@@ -6080,6 +6440,7 @@ mixin(AcroFormMixin);
6080
6440
  mixin(AttachmentsMixin);
6081
6441
  mixin(SubsetMixin);
6082
6442
  mixin(TableMixin);
6443
+ mixin(OutputIntent);
6083
6444
  PDFDocument.LineWrapper = LineWrapper;
6084
6445
 
6085
6446
  export { PDFDocument as default };