pdfkit 0.17.1 → 0.18.0

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
  }
@@ -451,6 +455,59 @@ class PDFNameTree extends PDFTree {
451
455
  }
452
456
  }
453
457
 
458
+ function md5Hash(data) {
459
+ return new Uint8Array(md5.arrayBuffer(data));
460
+ }
461
+ function md5Hex(data) {
462
+ return md5(data);
463
+ }
464
+
465
+ function sha256Hash(data) {
466
+ return sha256(data);
467
+ }
468
+
469
+ function aesCbcEncrypt(data, key, iv) {
470
+ let padding = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
471
+ return cbc(key, iv, {
472
+ disablePadding: !padding
473
+ }).encrypt(data);
474
+ }
475
+ function aesEcbEncrypt(data, key) {
476
+ return ecb(key, {
477
+ disablePadding: true
478
+ }).encrypt(data);
479
+ }
480
+
481
+ function rc4(data, key) {
482
+ const s = new Uint8Array(256);
483
+ for (let i = 0; i < 256; i++) {
484
+ s[i] = i;
485
+ }
486
+ let j = 0;
487
+ for (let i = 0; i < 256; i++) {
488
+ j = j + s[i] + key[i % key.length] & 0xff;
489
+ [s[i], s[j]] = [s[j], s[i]];
490
+ }
491
+ const output = new Uint8Array(data.length);
492
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
493
+ i = i + 1 & 0xff;
494
+ j = j + s[i] & 0xff;
495
+ [s[i], s[j]] = [s[j], s[i]];
496
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
497
+ }
498
+ return output;
499
+ }
500
+
501
+ function randomBytes(length) {
502
+ const bytes = new Uint8Array(length);
503
+ if (globalThis.crypto?.getRandomValues) {
504
+ globalThis.crypto.getRandomValues(bytes);
505
+ } else {
506
+ require('crypto').randomFillSync(bytes);
507
+ }
508
+ return bytes;
509
+ }
510
+
454
511
  function inRange(value, rangeGroup) {
455
512
  if (value < rangeGroup[0]) return false;
456
513
  let startRange = 0;
@@ -551,10 +608,10 @@ class PDFSecurity {
551
608
  }
552
609
  infoStr += `${key}: ${info[key].valueOf()}\n`;
553
610
  }
554
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
611
+ return Buffer.from(md5Hash(infoStr));
555
612
  }
556
613
  static generateRandomWordArray(bytes) {
557
- return CryptoJS.lib.WordArray.random(bytes);
614
+ return randomBytes(bytes);
558
615
  }
559
616
  static create(document) {
560
617
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -648,8 +705,8 @@ class PDFSecurity {
648
705
  encDict.StrF = 'StdCF';
649
706
  }
650
707
  encDict.R = r;
651
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
652
- encDict.U = wordArrayToBuffer(userPasswordEntry);
708
+ encDict.O = Buffer.from(ownerPasswordEntry);
709
+ encDict.U = Buffer.from(userPasswordEntry);
653
710
  encDict.P = permissions;
654
711
  }
655
712
  _setupEncryptionV5(encDict, options) {
@@ -659,10 +716,10 @@ class PDFSecurity {
659
716
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
660
717
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
661
718
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
662
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
719
+ const userKeySalt = userPasswordEntry.slice(40, 48);
663
720
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
664
721
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
665
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
722
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
666
723
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
667
724
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
668
725
  encDict.V = 5;
@@ -677,36 +734,37 @@ class PDFSecurity {
677
734
  encDict.StmF = 'StdCF';
678
735
  encDict.StrF = 'StdCF';
679
736
  encDict.R = 5;
680
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
681
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
682
- encDict.U = wordArrayToBuffer(userPasswordEntry);
683
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
737
+ encDict.O = Buffer.from(ownerPasswordEntry);
738
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
739
+ encDict.U = Buffer.from(userPasswordEntry);
740
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
684
741
  encDict.P = permissions;
685
- encDict.Perms = wordArrayToBuffer(permsEntry);
742
+ encDict.Perms = Buffer.from(permsEntry);
686
743
  }
687
744
  getEncryptFn(obj, gen) {
688
745
  let digest;
689
746
  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));
747
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
748
+ digest = concatBytes(this.encryptionKey, suffix);
691
749
  }
692
750
  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);
751
+ let key = md5Hash(digest);
752
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
753
+ key = key.slice(0, keyLen);
754
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
696
755
  }
697
756
  let key;
698
757
  if (this.version === 4) {
699
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
758
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
759
+ key = md5Hash(concatBytes(digest, saltMarker));
700
760
  } else {
701
761
  key = this.encryptionKey;
702
762
  }
703
763
  const iv = PDFSecurity.generateRandomWordArray(16);
704
- const options = {
705
- mode: CryptoJS.mode.CBC,
706
- padding: CryptoJS.pad.Pkcs7,
707
- iv
764
+ return buffer => {
765
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
766
+ return Buffer.from(concatBytes(iv, encrypted));
708
767
  };
709
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
710
768
  }
711
769
  end() {
712
770
  this.dictionary.end();
@@ -759,90 +817,98 @@ function getPermissionsR3() {
759
817
  return permissions;
760
818
  }
761
819
  function getUserPasswordR2(encryptionKey) {
762
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
820
+ return rc4(processPasswordR2R3R4(), encryptionKey);
763
821
  }
764
822
  function getUserPasswordR3R4(documentId, encryptionKey) {
765
- const key = encryptionKey.clone();
766
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
823
+ const key = encryptionKey.slice();
824
+ let cipher = md5Hash(concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
767
825
  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);
826
+ const xorKey = new Uint8Array(key.length);
827
+ for (let j = 0; j < key.length; j++) {
828
+ xorKey[j] = encryptionKey[j] ^ i;
771
829
  }
772
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
830
+ cipher = rc4(cipher, xorKey);
773
831
  }
774
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
832
+ const result = new Uint8Array(32);
833
+ result.set(cipher);
834
+ return result;
775
835
  }
776
836
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
777
837
  let digest = paddedOwnerPassword;
778
838
  let round = r >= 3 ? 51 : 1;
779
839
  for (let i = 0; i < round; i++) {
780
- digest = CryptoJS.MD5(digest);
840
+ digest = md5Hash(digest);
781
841
  }
782
- const key = digest.clone();
783
- key.sigBytes = keyBits / 8;
842
+ const keyLen = keyBits / 8;
843
+ let key = digest.slice(0, keyLen);
784
844
  let cipher = paddedUserPassword;
785
845
  round = r >= 3 ? 20 : 1;
786
846
  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);
847
+ const xorKey = new Uint8Array(keyLen);
848
+ for (let j = 0; j < keyLen; j++) {
849
+ xorKey[j] = key[j] ^ i;
790
850
  }
791
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
851
+ cipher = rc4(cipher, xorKey);
792
852
  }
793
853
  return cipher;
794
854
  }
795
855
  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));
856
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
857
+ let key = concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
797
858
  const round = r >= 3 ? 51 : 1;
859
+ const keyLen = keyBits / 8;
798
860
  for (let i = 0; i < round; i++) {
799
- key = CryptoJS.MD5(key);
800
- key.sigBytes = keyBits / 8;
861
+ key = md5Hash(key);
862
+ key = key.slice(0, keyLen);
801
863
  }
802
864
  return key;
803
865
  }
804
866
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
805
867
  const validationSalt = generateRandomWordArray(8);
806
868
  const keySalt = generateRandomWordArray(8);
807
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
869
+ const hash = sha256Hash(concatBytes(processedUserPassword, validationSalt));
870
+ return concatBytes(hash, validationSalt, keySalt);
808
871
  }
809
872
  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;
873
+ const key = sha256Hash(concatBytes(processedUserPassword, userKeySalt));
874
+ const iv = new Uint8Array(16);
875
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
817
876
  }
818
877
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
819
878
  const validationSalt = generateRandomWordArray(8);
820
879
  const keySalt = generateRandomWordArray(8);
821
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
880
+ const hash = sha256Hash(concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
881
+ return concatBytes(hash, validationSalt, keySalt);
822
882
  }
823
883
  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;
884
+ const key = sha256Hash(concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
885
+ const iv = new Uint8Array(16);
886
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
831
887
  }
832
888
  function getEncryptionKeyR5(generateRandomWordArray) {
833
889
  return generateRandomWordArray(32);
834
890
  }
835
891
  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;
892
+ const data = new Uint8Array(16);
893
+ data[0] = permissions & 0xff;
894
+ data[1] = permissions >> 8 & 0xff;
895
+ data[2] = permissions >> 16 & 0xff;
896
+ data[3] = permissions >> 24 & 0xff;
897
+ data[4] = 0xff;
898
+ data[5] = 0xff;
899
+ data[6] = 0xff;
900
+ data[7] = 0xff;
901
+ data[8] = 0x54;
902
+ data[9] = 0x61;
903
+ data[10] = 0x64;
904
+ data[11] = 0x62;
905
+ const randomPart = generateRandomWordArray(4);
906
+ data.set(randomPart, 12);
907
+ return aesEcbEncrypt(data, encryptionKey);
842
908
  }
843
909
  function processPasswordR2R3R4() {
844
910
  let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
845
- const out = Buffer.alloc(32);
911
+ const out = new Uint8Array(32);
846
912
  const length = password.length;
847
913
  let index = 0;
848
914
  while (index < length && index < 32) {
@@ -857,27 +923,17 @@ function processPasswordR2R3R4() {
857
923
  out[index] = PASSWORD_PADDING[index - length];
858
924
  index++;
859
925
  }
860
- return CryptoJS.lib.WordArray.create(out);
926
+ return out;
861
927
  }
862
928
  function processPasswordR5() {
863
929
  let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
864
930
  password = unescape(encodeURIComponent(saslprep(password)));
865
931
  const length = Math.min(127, password.length);
866
- const out = Buffer.alloc(length);
932
+ const out = new Uint8Array(length);
867
933
  for (let i = 0; i < length; i++) {
868
934
  out[i] = password.charCodeAt(i);
869
935
  }
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);
936
+ return out;
881
937
  }
882
938
  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
939
 
@@ -1485,86 +1541,176 @@ const parameters = {
1485
1541
  Z: 0,
1486
1542
  z: 0
1487
1543
  };
1544
+ const isCommand = function (c) {
1545
+ return c in parameters;
1546
+ };
1547
+ const isWsp = function (c) {
1548
+ const codePoint = c.codePointAt(0);
1549
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1550
+ };
1551
+ const isDigit = function (c) {
1552
+ const codePoint = c.codePointAt(0);
1553
+ if (codePoint == null) {
1554
+ return false;
1555
+ }
1556
+ return 48 <= codePoint && codePoint <= 57;
1557
+ };
1558
+ const readNumber = function (string, cursor) {
1559
+ let i = cursor;
1560
+ let value = '';
1561
+ let state = 'none';
1562
+ for (; i < string.length; i += 1) {
1563
+ const c = string[i];
1564
+ if (c === '+' || c === '-') {
1565
+ if (state === 'none') {
1566
+ state = 'sign';
1567
+ value += c;
1568
+ continue;
1569
+ }
1570
+ if (state === 'e') {
1571
+ state = 'exponent_sign';
1572
+ value += c;
1573
+ continue;
1574
+ }
1575
+ }
1576
+ if (isDigit(c)) {
1577
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1578
+ state = 'whole';
1579
+ value += c;
1580
+ continue;
1581
+ }
1582
+ if (state === 'decimal_point' || state === 'decimal') {
1583
+ state = 'decimal';
1584
+ value += c;
1585
+ continue;
1586
+ }
1587
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1588
+ state = 'exponent';
1589
+ value += c;
1590
+ continue;
1591
+ }
1592
+ }
1593
+ if (c === '.') {
1594
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1595
+ state = 'decimal_point';
1596
+ value += c;
1597
+ continue;
1598
+ }
1599
+ }
1600
+ if (c === 'E' || c === 'e') {
1601
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1602
+ state = 'e';
1603
+ value += c;
1604
+ continue;
1605
+ }
1606
+ }
1607
+ break;
1608
+ }
1609
+ const number = Number.parseFloat(value);
1610
+ if (Number.isNaN(number)) {
1611
+ return [cursor, null];
1612
+ }
1613
+ return [i - 1, number];
1614
+ };
1488
1615
  const parse = function (path) {
1489
- let cmd;
1490
- const ret = [];
1616
+ const pathData = [];
1617
+ let command = null;
1491
1618
  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;
1619
+ let argsCount = 0;
1620
+ let canHaveComma = false;
1621
+ let hadComma = false;
1622
+ for (let i = 0; i < path.length; i += 1) {
1623
+ const c = path.charAt(i);
1624
+ if (isWsp(c)) {
1625
+ continue;
1626
+ }
1627
+ if (canHaveComma && c === ',') {
1628
+ if (hadComma) {
1629
+ break;
1630
+ }
1631
+ hadComma = true;
1632
+ continue;
1633
+ }
1634
+ if (isCommand(c)) {
1635
+ if (hadComma) {
1636
+ return pathData;
1637
+ }
1638
+ if (command == null) {
1639
+ if (c !== 'M' && c !== 'm') {
1640
+ return pathData;
1501
1641
  }
1502
- ret[ret.length] = {
1503
- cmd,
1642
+ } else {
1643
+ if (args.length !== 0) {
1644
+ return pathData;
1645
+ }
1646
+ }
1647
+ command = c;
1648
+ args = [];
1649
+ argsCount = parameters[command];
1650
+ canHaveComma = false;
1651
+ if (argsCount === 0) {
1652
+ pathData.push({
1653
+ command,
1504
1654
  args
1505
- };
1506
- args = [];
1507
- curArg = '';
1508
- foundDecimal = false;
1655
+ });
1509
1656
  }
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;
1657
+ continue;
1658
+ }
1659
+ if (command == null) {
1660
+ return pathData;
1661
+ }
1662
+ let newCursor = i;
1663
+ let number = null;
1664
+ if (command === 'A' || command === 'a') {
1665
+ const position = args.length;
1666
+ if (position === 0 || position === 1) {
1667
+ if (c !== '+' && c !== '-') {
1668
+ [newCursor, number] = readNumber(path, i);
1669
+ }
1514
1670
  }
1515
- if (args.length === params) {
1516
- ret[ret.length] = {
1517
- cmd,
1518
- args
1519
- };
1520
- args = [+curArg];
1521
- if (cmd === 'M') {
1522
- cmd = 'L';
1671
+ if (position === 2 || position === 5 || position === 6) {
1672
+ [newCursor, number] = readNumber(path, i);
1673
+ }
1674
+ if (position === 3 || position === 4) {
1675
+ if (c === '0') {
1676
+ number = 0;
1523
1677
  }
1524
- if (cmd === 'm') {
1525
- cmd = 'l';
1678
+ if (c === '1') {
1679
+ number = 1;
1526
1680
  }
1527
- } else {
1528
- args[args.length] = +curArg;
1529
1681
  }
1530
- foundDecimal = c === '.';
1531
- curArg = ['-', '.'].includes(c) ? c : '';
1532
1682
  } 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,
1683
+ [newCursor, number] = readNumber(path, i);
1684
+ }
1685
+ if (number == null) {
1686
+ return pathData;
1687
+ }
1688
+ args.push(number);
1689
+ canHaveComma = true;
1690
+ hadComma = false;
1691
+ i = newCursor;
1692
+ if (args.length === argsCount) {
1693
+ pathData.push({
1694
+ command,
1543
1695
  args
1544
- };
1545
- args = [+curArg];
1546
- if (cmd === 'M') {
1547
- cmd = 'L';
1696
+ });
1697
+ if (command === 'M') {
1698
+ command = 'L';
1548
1699
  }
1549
- if (cmd === 'm') {
1550
- cmd = 'l';
1700
+ if (command === 'm') {
1701
+ command = 'l';
1551
1702
  }
1552
- } else {
1553
- args[args.length] = +curArg;
1703
+ args = [];
1554
1704
  }
1555
1705
  }
1556
- ret[ret.length] = {
1557
- cmd,
1558
- args
1559
- };
1560
- return ret;
1706
+ return pathData;
1561
1707
  };
1562
1708
  const apply = function (commands, doc) {
1563
1709
  cx = cy = px = py = sx = sy = 0;
1564
1710
  for (let i = 0; i < commands.length; i++) {
1565
1711
  const c = commands[i];
1566
- if (typeof runners[c.cmd] === 'function') {
1567
- runners[c.cmd](doc, c.args);
1712
+ if (typeof runners[c.command] === 'function') {
1713
+ runners[c.command](doc, c.args);
1568
1714
  }
1569
1715
  }
1570
1716
  };
@@ -2603,7 +2749,7 @@ begincmap
2603
2749
  1 begincodespacerange
2604
2750
  <0000><ffff>
2605
2751
  endcodespacerange
2606
- 1 beginbfrange
2752
+ ${ranges.length} beginbfrange
2607
2753
  ${ranges.join('\n')}
2608
2754
  endbfrange
2609
2755
  endcmap
@@ -2788,6 +2934,7 @@ class LineWrapper extends EventEmitter {
2788
2934
  this.document = document;
2789
2935
  this.horizontalScaling = options.horizontalScaling || 100;
2790
2936
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2937
+ this.indentAllLines = options.indentAllLines || false;
2791
2938
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2792
2939
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2793
2940
  this.columns = options.columns || 1;
@@ -2981,12 +3128,14 @@ class LineWrapper extends EventEmitter {
2981
3128
  }
2982
3129
  emitLine();
2983
3130
  if (PDFNumber(this.document.y + lh) > this.maxY) {
3131
+ this.emit('sectionEnd', options, this);
2984
3132
  const shouldContinue = this.nextSection();
2985
3133
  if (!shouldContinue) {
2986
3134
  wc = 0;
2987
3135
  buffer = '';
2988
3136
  return false;
2989
3137
  }
3138
+ this.emit('sectionStart', options, this);
2990
3139
  }
2991
3140
  if (bk.required) {
2992
3141
  this.spaceLeft = this.lineWidth;
@@ -3019,7 +3168,6 @@ class LineWrapper extends EventEmitter {
3019
3168
  }
3020
3169
  }
3021
3170
  nextSection(options) {
3022
- this.emit('sectionEnd', options, this);
3023
3171
  if (++this.column > this.columns) {
3024
3172
  if (this.height != null) {
3025
3173
  return false;
@@ -3028,7 +3176,13 @@ class LineWrapper extends EventEmitter {
3028
3176
  this.column = 1;
3029
3177
  this.startY = this.document.page.margins.top;
3030
3178
  this.maxY = this.document.page.maxY();
3031
- this.document.x = this.startX;
3179
+ if (this.indentAllLines) {
3180
+ const indent = this.continuedX || this.indent;
3181
+ this.document.x += indent;
3182
+ this.lineWidth -= indent;
3183
+ } else {
3184
+ this.document.x = this.startX;
3185
+ }
3032
3186
  if (this.document._fillColor) {
3033
3187
  this.document.fillColor(...this.document._fillColor);
3034
3188
  }
@@ -3038,7 +3192,6 @@ class LineWrapper extends EventEmitter {
3038
3192
  this.document.y = this.startY;
3039
3193
  this.emit('columnBreak', options, this);
3040
3194
  }
3041
- this.emit('sectionStart', options, this);
3042
3195
  return true;
3043
3196
  }
3044
3197
  }
@@ -3046,6 +3199,15 @@ class LineWrapper extends EventEmitter {
3046
3199
  const {
3047
3200
  number
3048
3201
  } = PDFObject;
3202
+ function formatListLabel(n, listType) {
3203
+ if (listType === 'numbered') {
3204
+ return `${n}.`;
3205
+ }
3206
+ var letter = String.fromCharCode((n - 1) % 26 + 65);
3207
+ var times = Math.floor((n - 1) / 26 + 1);
3208
+ var text = Array(times + 1).join(letter);
3209
+ return `${text}.`;
3210
+ }
3049
3211
  var TextMixin = {
3050
3212
  initText() {
3051
3213
  this._line = this._line.bind(this);
@@ -3223,7 +3385,7 @@ var TextMixin = {
3223
3385
  this.y = y;
3224
3386
  return height;
3225
3387
  },
3226
- list(list, x, y, options, wrapper) {
3388
+ list(list, x, y, options) {
3227
3389
  options = this._initOptions(x, y, options);
3228
3390
  const listType = options.listType || 'bullet';
3229
3391
  const unit = Math.round(this._font.ascender / 1000 * this._fontSize);
@@ -3253,19 +3415,8 @@ var TextMixin = {
3253
3415
  }
3254
3416
  };
3255
3417
  flatten(list);
3256
- const label = function (n) {
3257
- switch (listType) {
3258
- case 'numbered':
3259
- return `${n}.`;
3260
- case 'lettered':
3261
- var letter = String.fromCharCode((n - 1) % 26 + 65);
3262
- var times = Math.floor((n - 1) / 26 + 1);
3263
- var text = Array(times + 1).join(letter);
3264
- return `${text}.`;
3265
- }
3266
- };
3267
3418
  const drawListItem = function (listItem, i) {
3268
- wrapper = new LineWrapper(this, options);
3419
+ const wrapper = new LineWrapper(this, options);
3269
3420
  wrapper.on('line', this._line);
3270
3421
  level = 1;
3271
3422
  wrapper.once('firstLine', () => {
@@ -3300,7 +3451,7 @@ var TextMixin = {
3300
3451
  break;
3301
3452
  case 'numbered':
3302
3453
  case 'lettered':
3303
- var text = label(numbers[i - 1]);
3454
+ var text = formatListLabel(numbers[i - 1], listType);
3304
3455
  this._fragment(text, this.x - indent, this.y, options);
3305
3456
  break;
3306
3457
  }
@@ -3373,11 +3524,11 @@ var TextMixin = {
3373
3524
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3374
3525
  let wrapper = arguments.length > 2 ? arguments[2] : undefined;
3375
3526
  this._fragment(text, this.x, this.y, options);
3376
- const lineGap = options.lineGap || this._lineGap || 0;
3377
- if (!wrapper) {
3378
- this.x += this.widthOfString(text, options);
3379
- } else {
3527
+ if (wrapper) {
3528
+ const lineGap = options.lineGap || this._lineGap || 0;
3380
3529
  this.y += this.currentLineHeight(true) + lineGap;
3530
+ } else {
3531
+ this.x += this.widthOfString(text, options);
3381
3532
  }
3382
3533
  },
3383
3534
  _fragment(text, x, y, options) {
@@ -3441,7 +3592,11 @@ var TextMixin = {
3441
3592
  }
3442
3593
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3443
3594
  if (options.link != null) {
3444
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3595
+ const linkOptions = {};
3596
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3597
+ linkOptions.structParent = this._currentStructureElement;
3598
+ }
3599
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3445
3600
  }
3446
3601
  if (options.goTo != null) {
3447
3602
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3570,6 +3725,47 @@ var TextMixin = {
3570
3725
  }
3571
3726
  };
3572
3727
 
3728
+ const parseExifOrientation = data => {
3729
+ if (!data || data.length < 20) return null;
3730
+ let pos = 2;
3731
+ while (pos < data.length - 4) {
3732
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3733
+ if (pos >= data.length - 4) return null;
3734
+ const marker = data.readUInt16BE(pos);
3735
+ pos += 2;
3736
+ if (marker === 0xffda) return null;
3737
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3738
+ if (pos + 2 > data.length) return null;
3739
+ const segmentLength = data.readUInt16BE(pos);
3740
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3741
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3742
+ if (exifHeader === 'Exif\x00\x00') {
3743
+ const tiffStart = pos + 8;
3744
+ if (tiffStart + 8 > data.length) return null;
3745
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3746
+ const isLittleEndian = byteOrder === 'II';
3747
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3748
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3749
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3750
+ if (read16(tiffStart + 2) !== 42) return null;
3751
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3752
+ if (ifdPos + 2 > data.length) return null;
3753
+ const entryCount = read16(ifdPos);
3754
+ for (let i = 0; i < entryCount; i++) {
3755
+ const entryPos = ifdPos + 2 + i * 12;
3756
+ if (entryPos + 12 > data.length) return null;
3757
+ if (read16(entryPos) === 0x0112) {
3758
+ const value = read16(entryPos + 8);
3759
+ return value >= 1 && value <= 8 ? value : null;
3760
+ }
3761
+ }
3762
+ return null;
3763
+ }
3764
+ }
3765
+ pos += segmentLength;
3766
+ }
3767
+ return null;
3768
+ };
3573
3769
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3574
3770
  const COLOR_SPACE_MAP = {
3575
3771
  1: 'DeviceGray',
@@ -3584,9 +3780,11 @@ class JPEG {
3584
3780
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3585
3781
  throw 'SOI not found in JPEG';
3586
3782
  }
3587
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3783
+ this.orientation = parseExifOrientation(this.data) || 1;
3588
3784
  let pos = 2;
3589
3785
  while (pos < this.data.length) {
3786
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3787
+ if (pos >= this.data.length) break;
3590
3788
  marker = this.data.readUInt16BE(pos);
3591
3789
  pos += 2;
3592
3790
  if (MARKERS.includes(marker)) {
@@ -3738,12 +3936,16 @@ class PNGImage {
3738
3936
  }
3739
3937
  loadIndexedAlphaChannel() {
3740
3938
  const transparency = this.image.transparency.indexed;
3939
+ const isInterlaced = this.image.interlaceMethod === 1;
3741
3940
  return this.image.decodePixels(pixels => {
3742
3941
  const alphaChannel = Buffer.alloc(this.width * this.height);
3743
3942
  let i = 0;
3744
3943
  for (let j = 0, end = pixels.length; j < end; j++) {
3745
3944
  alphaChannel[i++] = transparency[pixels[j]];
3746
3945
  }
3946
+ if (isInterlaced) {
3947
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3948
+ }
3747
3949
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3748
3950
  return this.finalize();
3749
3951
  });
@@ -3973,6 +4175,12 @@ var ImagesMixin = {
3973
4175
  }
3974
4176
  };
3975
4177
 
4178
+ class PDFAnnotationReference {
4179
+ constructor(annotationRef) {
4180
+ this.annotationRef = annotationRef;
4181
+ }
4182
+ }
4183
+
3976
4184
  var AnnotationsMixin = {
3977
4185
  annotate(x, y, w, h, options) {
3978
4186
  options.Type = 'Annot';
@@ -3990,12 +4198,18 @@ var AnnotationsMixin = {
3990
4198
  if (typeof options.Dest === 'string') {
3991
4199
  options.Dest = new String(options.Dest);
3992
4200
  }
4201
+ const structParent = options.structParent;
4202
+ delete options.structParent;
3993
4203
  for (let key in options) {
3994
4204
  const val = options[key];
3995
4205
  options[key[0].toUpperCase() + key.slice(1)] = val;
3996
4206
  }
3997
4207
  const ref = this.ref(options);
3998
4208
  this.page.annotations.push(ref);
4209
+ if (structParent && typeof structParent.add === 'function') {
4210
+ const annotRef = new PDFAnnotationReference(ref);
4211
+ structParent.add(annotRef);
4212
+ }
3999
4213
  ref.end();
4000
4214
  return this;
4001
4215
  },
@@ -4042,6 +4256,9 @@ var AnnotationsMixin = {
4042
4256
  });
4043
4257
  options.A.end();
4044
4258
  }
4259
+ if (options.structParent && !options.Contents) {
4260
+ options.Contents = new String('');
4261
+ }
4045
4262
  return this.annotate(x, y, w, h, options);
4046
4263
  },
4047
4264
  _markup(x, y, w, h) {
@@ -4123,16 +4340,27 @@ var AnnotationsMixin = {
4123
4340
  }
4124
4341
  };
4125
4342
 
4343
+ const DEFAULT_OPTIONS = {
4344
+ top: 0,
4345
+ left: 0,
4346
+ zoom: 0,
4347
+ fit: true,
4348
+ pageNumber: null,
4349
+ expanded: false
4350
+ };
4126
4351
  class PDFOutline {
4127
4352
  constructor(document, parent, title, dest) {
4128
- let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
4129
- expanded: false
4130
- };
4353
+ let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : DEFAULT_OPTIONS;
4131
4354
  this.document = document;
4132
4355
  this.options = options;
4133
4356
  this.outlineData = {};
4134
4357
  if (dest !== null) {
4135
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4358
+ const destWidth = dest.data.MediaBox[2];
4359
+ const destHeight = dest.data.MediaBox[3];
4360
+ const top = destHeight - (options.top || 0);
4361
+ const left = destWidth - (options.left || 0);
4362
+ const zoom = options.zoom || 0;
4363
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4136
4364
  }
4137
4365
  if (parent !== null) {
4138
4366
  this.outlineData['Parent'] = parent;
@@ -4144,10 +4372,10 @@ class PDFOutline {
4144
4372
  this.children = [];
4145
4373
  }
4146
4374
  addItem(title) {
4147
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
4148
- expanded: false
4149
- };
4150
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4375
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_OPTIONS;
4376
+ const pages = this.document._root.data.Pages.data.Kids;
4377
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4378
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4151
4379
  this.children.push(result);
4152
4380
  return result;
4153
4381
  }
@@ -4183,7 +4411,7 @@ var OutlineMixin = {
4183
4411
  this.outline.endOutline();
4184
4412
  if (this.outline.children.length > 0) {
4185
4413
  this._root.data.Outlines = this.outline.dictionary;
4186
- return this._root.data.PageMode = 'UseOutlines';
4414
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4187
4415
  }
4188
4416
  }
4189
4417
  };
@@ -4256,6 +4484,9 @@ class PDFStructureElement {
4256
4484
  if (child instanceof PDFStructureContent) {
4257
4485
  this._addContentToParentTree(child);
4258
4486
  }
4487
+ if (child instanceof PDFAnnotationReference) {
4488
+ this._addAnnotationToParentTree(child.annotationRef);
4489
+ }
4259
4490
  if (typeof child === 'function' && this._attached) {
4260
4491
  child = this._contentForClosure(child);
4261
4492
  }
@@ -4272,6 +4503,12 @@ class PDFStructureElement {
4272
4503
  pageStructParents[mcid] = this.dictionary;
4273
4504
  });
4274
4505
  }
4506
+ _addAnnotationToParentTree(annotRef) {
4507
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4508
+ annotRef.data.StructParent = parentTreeKey;
4509
+ const parentTree = this.document.getStructParentTree();
4510
+ parentTree.add(parentTreeKey, this.dictionary);
4511
+ }
4275
4512
  setParent(parentRef) {
4276
4513
  if (this.dictionary.data.P) {
4277
4514
  throw new Error(`Structure element added to more than one parent`);
@@ -4303,11 +4540,17 @@ class PDFStructureElement {
4303
4540
  this._flush();
4304
4541
  }
4305
4542
  _isValidChild(child) {
4306
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4543
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4307
4544
  }
4308
4545
  _contentForClosure(closure) {
4309
4546
  const content = this.document.markStructureContent(this.dictionary.data.S);
4547
+ const prevStructElement = this.document._currentStructureElement;
4548
+ this.document._currentStructureElement = this;
4549
+ const wasEnded = this._ended;
4550
+ this._ended = false;
4310
4551
  closure();
4552
+ this._ended = wasEnded;
4553
+ this.document._currentStructureElement = prevStructElement;
4311
4554
  this.document.endMarkedContent();
4312
4555
  this._addContentToParentTree(content);
4313
4556
  return content;
@@ -4361,6 +4604,15 @@ class PDFStructureElement {
4361
4604
  }
4362
4605
  });
4363
4606
  }
4607
+ if (child instanceof PDFAnnotationReference) {
4608
+ const pageRef = this.document.page.dictionary;
4609
+ const objr = {
4610
+ Type: 'OBJR',
4611
+ Obj: child.annotationRef,
4612
+ Pg: pageRef
4613
+ };
4614
+ this.dictionary.data.K.push(objr);
4615
+ }
4364
4616
  }
4365
4617
  }
4366
4618
 
@@ -4456,6 +4708,13 @@ var MarkingsMixin = {
4456
4708
  endMarkedContent() {
4457
4709
  this.page.markings.pop();
4458
4710
  this.addContent('EMC');
4711
+ if (this._textOptions) {
4712
+ delete this._textOptions.link;
4713
+ delete this._textOptions.goTo;
4714
+ delete this._textOptions.destination;
4715
+ delete this._textOptions.underline;
4716
+ delete this._textOptions.strike;
4717
+ }
4459
4718
  return this;
4460
4719
  },
4461
4720
  struct(type) {
@@ -4914,7 +5173,7 @@ var AttachmentsMixin = {
4914
5173
  if (options.type) {
4915
5174
  refBody.Subtype = options.type.replace('/', '#2F');
4916
5175
  }
4917
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5176
+ const checksum = md5Hex(new Uint8Array(data));
4918
5177
  refBody.Params.CheckSum = new String(checksum);
4919
5178
  refBody.Params.Size = data.byteLength;
4920
5179
  let ref;
@@ -5566,7 +5825,7 @@ function renderRow(row, rowIndex) {
5566
5825
  function renderCell(cell, rowStruct) {
5567
5826
  const cellRenderer = () => {
5568
5827
  if (cell.backgroundColor != null) {
5569
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5828
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5570
5829
  }
5571
5830
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5572
5831
  if (cell.debug) {
@@ -5630,20 +5889,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5630
5889
  const doc = this.document;
5631
5890
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5632
5891
  if (border.top > 0) {
5633
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5892
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5634
5893
  }
5635
5894
  } else {
5636
5895
  if (border.top > 0) {
5637
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5896
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5638
5897
  }
5639
5898
  if (border.right > 0) {
5640
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5899
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5641
5900
  }
5642
5901
  if (border.bottom > 0) {
5643
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5902
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5644
5903
  }
5645
5904
  if (border.left > 0) {
5646
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5905
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5647
5906
  }
5648
5907
  }
5649
5908
  }
@@ -5855,6 +6114,10 @@ class PDFDocument extends stream.Readable {
5855
6114
  if (this.options.lang) {
5856
6115
  this._root.data.Lang = new String(this.options.lang);
5857
6116
  }
6117
+ if (this.options.pageLayout) {
6118
+ const layout = this.options.pageLayout;
6119
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6120
+ }
5858
6121
  this.page = null;
5859
6122
  this.initMetadata();
5860
6123
  this.initColor();