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.js CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  var stream = require('stream');
4
4
  var zlib = require('zlib');
5
- var CryptoJS = require('crypto-js');
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
  }
@@ -447,6 +451,58 @@ class PDFNameTree extends PDFTree {
447
451
  }
448
452
  }
449
453
 
454
+ function md5Hash(data) {
455
+ return new Uint8Array(md5.arrayBuffer(data));
456
+ }
457
+ function md5Hex(data) {
458
+ return md5(data);
459
+ }
460
+
461
+ function sha256Hash(data) {
462
+ return sha256.sha256(data);
463
+ }
464
+
465
+ function aesCbcEncrypt(data, key, iv, padding = true) {
466
+ return aes.cbc(key, iv, {
467
+ disablePadding: !padding
468
+ }).encrypt(data);
469
+ }
470
+ function aesEcbEncrypt(data, key) {
471
+ return aes.ecb(key, {
472
+ disablePadding: true
473
+ }).encrypt(data);
474
+ }
475
+
476
+ function rc4(data, key) {
477
+ const s = new Uint8Array(256);
478
+ for (let i = 0; i < 256; i++) {
479
+ s[i] = i;
480
+ }
481
+ let j = 0;
482
+ for (let i = 0; i < 256; i++) {
483
+ j = j + s[i] + key[i % key.length] & 0xff;
484
+ [s[i], s[j]] = [s[j], s[i]];
485
+ }
486
+ const output = new Uint8Array(data.length);
487
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
488
+ i = i + 1 & 0xff;
489
+ j = j + s[i] & 0xff;
490
+ [s[i], s[j]] = [s[j], s[i]];
491
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
492
+ }
493
+ return output;
494
+ }
495
+
496
+ function randomBytes(length) {
497
+ const bytes = new Uint8Array(length);
498
+ if (globalThis.crypto?.getRandomValues) {
499
+ globalThis.crypto.getRandomValues(bytes);
500
+ } else {
501
+ require('crypto').randomFillSync(bytes);
502
+ }
503
+ return bytes;
504
+ }
505
+
450
506
  function inRange(value, rangeGroup) {
451
507
  if (value < rangeGroup[0]) return false;
452
508
  let startRange = 0;
@@ -545,10 +601,10 @@ class PDFSecurity {
545
601
  }
546
602
  infoStr += `${key}: ${info[key].valueOf()}\n`;
547
603
  }
548
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
604
+ return Buffer.from(md5Hash(infoStr));
549
605
  }
550
606
  static generateRandomWordArray(bytes) {
551
- return CryptoJS.lib.WordArray.random(bytes);
607
+ return randomBytes(bytes);
552
608
  }
553
609
  static create(document, options = {}) {
554
610
  if (!options.ownerPassword && !options.userPassword) {
@@ -640,8 +696,8 @@ class PDFSecurity {
640
696
  encDict.StrF = 'StdCF';
641
697
  }
642
698
  encDict.R = r;
643
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
644
- encDict.U = wordArrayToBuffer(userPasswordEntry);
699
+ encDict.O = Buffer.from(ownerPasswordEntry);
700
+ encDict.U = Buffer.from(userPasswordEntry);
645
701
  encDict.P = permissions;
646
702
  }
647
703
  _setupEncryptionV5(encDict, options) {
@@ -651,10 +707,10 @@ class PDFSecurity {
651
707
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
652
708
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
653
709
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
654
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
710
+ const userKeySalt = userPasswordEntry.slice(40, 48);
655
711
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
656
712
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
657
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
713
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
658
714
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
659
715
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
660
716
  encDict.V = 5;
@@ -669,36 +725,37 @@ class PDFSecurity {
669
725
  encDict.StmF = 'StdCF';
670
726
  encDict.StrF = 'StdCF';
671
727
  encDict.R = 5;
672
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
673
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
674
- encDict.U = wordArrayToBuffer(userPasswordEntry);
675
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
728
+ encDict.O = Buffer.from(ownerPasswordEntry);
729
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
730
+ encDict.U = Buffer.from(userPasswordEntry);
731
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
676
732
  encDict.P = permissions;
677
- encDict.Perms = wordArrayToBuffer(permsEntry);
733
+ encDict.Perms = Buffer.from(permsEntry);
678
734
  }
679
735
  getEncryptFn(obj, gen) {
680
736
  let digest;
681
737
  if (this.version < 5) {
682
- digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
738
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
739
+ digest = utils.concatBytes(this.encryptionKey, suffix);
683
740
  }
684
741
  if (this.version === 1 || this.version === 2) {
685
- let key = CryptoJS.MD5(digest);
686
- key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
687
- return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
742
+ let key = md5Hash(digest);
743
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
744
+ key = key.slice(0, keyLen);
745
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
688
746
  }
689
747
  let key;
690
748
  if (this.version === 4) {
691
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
749
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
750
+ key = md5Hash(utils.concatBytes(digest, saltMarker));
692
751
  } else {
693
752
  key = this.encryptionKey;
694
753
  }
695
754
  const iv = PDFSecurity.generateRandomWordArray(16);
696
- const options = {
697
- mode: CryptoJS.mode.CBC,
698
- padding: CryptoJS.pad.Pkcs7,
699
- iv
755
+ return buffer => {
756
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
757
+ return Buffer.from(utils.concatBytes(iv, encrypted));
700
758
  };
701
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
702
759
  }
703
760
  end() {
704
761
  this.dictionary.end();
@@ -749,89 +806,97 @@ function getPermissionsR3(permissionObject = {}) {
749
806
  return permissions;
750
807
  }
751
808
  function getUserPasswordR2(encryptionKey) {
752
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
809
+ return rc4(processPasswordR2R3R4(), encryptionKey);
753
810
  }
754
811
  function getUserPasswordR3R4(documentId, encryptionKey) {
755
- const key = encryptionKey.clone();
756
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
812
+ const key = encryptionKey.slice();
813
+ let cipher = md5Hash(utils.concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
757
814
  for (let i = 0; i < 20; i++) {
758
- const xorRound = Math.ceil(key.sigBytes / 4);
759
- for (let j = 0; j < xorRound; j++) {
760
- key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
815
+ const xorKey = new Uint8Array(key.length);
816
+ for (let j = 0; j < key.length; j++) {
817
+ xorKey[j] = encryptionKey[j] ^ i;
761
818
  }
762
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
819
+ cipher = rc4(cipher, xorKey);
763
820
  }
764
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
821
+ const result = new Uint8Array(32);
822
+ result.set(cipher);
823
+ return result;
765
824
  }
766
825
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
767
826
  let digest = paddedOwnerPassword;
768
827
  let round = r >= 3 ? 51 : 1;
769
828
  for (let i = 0; i < round; i++) {
770
- digest = CryptoJS.MD5(digest);
829
+ digest = md5Hash(digest);
771
830
  }
772
- const key = digest.clone();
773
- key.sigBytes = keyBits / 8;
831
+ const keyLen = keyBits / 8;
832
+ let key = digest.slice(0, keyLen);
774
833
  let cipher = paddedUserPassword;
775
834
  round = r >= 3 ? 20 : 1;
776
835
  for (let i = 0; i < round; i++) {
777
- const xorRound = Math.ceil(key.sigBytes / 4);
778
- for (let j = 0; j < xorRound; j++) {
779
- key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
836
+ const xorKey = new Uint8Array(keyLen);
837
+ for (let j = 0; j < keyLen; j++) {
838
+ xorKey[j] = key[j] ^ i;
780
839
  }
781
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
840
+ cipher = rc4(cipher, xorKey);
782
841
  }
783
842
  return cipher;
784
843
  }
785
844
  function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
786
- let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
845
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
846
+ let key = utils.concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
787
847
  const round = r >= 3 ? 51 : 1;
848
+ const keyLen = keyBits / 8;
788
849
  for (let i = 0; i < round; i++) {
789
- key = CryptoJS.MD5(key);
790
- key.sigBytes = keyBits / 8;
850
+ key = md5Hash(key);
851
+ key = key.slice(0, keyLen);
791
852
  }
792
853
  return key;
793
854
  }
794
855
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
795
856
  const validationSalt = generateRandomWordArray(8);
796
857
  const keySalt = generateRandomWordArray(8);
797
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
858
+ const hash = sha256Hash(utils.concatBytes(processedUserPassword, validationSalt));
859
+ return utils.concatBytes(hash, validationSalt, keySalt);
798
860
  }
799
861
  function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
800
- const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
801
- const options = {
802
- mode: CryptoJS.mode.CBC,
803
- padding: CryptoJS.pad.NoPadding,
804
- iv: CryptoJS.lib.WordArray.create(null, 16)
805
- };
806
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
862
+ const key = sha256Hash(utils.concatBytes(processedUserPassword, userKeySalt));
863
+ const iv = new Uint8Array(16);
864
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
807
865
  }
808
866
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
809
867
  const validationSalt = generateRandomWordArray(8);
810
868
  const keySalt = generateRandomWordArray(8);
811
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
869
+ const hash = sha256Hash(utils.concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
870
+ return utils.concatBytes(hash, validationSalt, keySalt);
812
871
  }
813
872
  function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
814
- const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
815
- const options = {
816
- mode: CryptoJS.mode.CBC,
817
- padding: CryptoJS.pad.NoPadding,
818
- iv: CryptoJS.lib.WordArray.create(null, 16)
819
- };
820
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
873
+ const key = sha256Hash(utils.concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
874
+ const iv = new Uint8Array(16);
875
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
821
876
  }
822
877
  function getEncryptionKeyR5(generateRandomWordArray) {
823
878
  return generateRandomWordArray(32);
824
879
  }
825
880
  function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
826
- const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
827
- const options = {
828
- mode: CryptoJS.mode.ECB,
829
- padding: CryptoJS.pad.NoPadding
830
- };
831
- return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
881
+ const data = new Uint8Array(16);
882
+ data[0] = permissions & 0xff;
883
+ data[1] = permissions >> 8 & 0xff;
884
+ data[2] = permissions >> 16 & 0xff;
885
+ data[3] = permissions >> 24 & 0xff;
886
+ data[4] = 0xff;
887
+ data[5] = 0xff;
888
+ data[6] = 0xff;
889
+ data[7] = 0xff;
890
+ data[8] = 0x54;
891
+ data[9] = 0x61;
892
+ data[10] = 0x64;
893
+ data[11] = 0x62;
894
+ const randomPart = generateRandomWordArray(4);
895
+ data.set(randomPart, 12);
896
+ return aesEcbEncrypt(data, encryptionKey);
832
897
  }
833
898
  function processPasswordR2R3R4(password = '') {
834
- const out = Buffer.alloc(32);
899
+ const out = new Uint8Array(32);
835
900
  const length = password.length;
836
901
  let index = 0;
837
902
  while (index < length && index < 32) {
@@ -846,26 +911,16 @@ function processPasswordR2R3R4(password = '') {
846
911
  out[index] = PASSWORD_PADDING[index - length];
847
912
  index++;
848
913
  }
849
- return CryptoJS.lib.WordArray.create(out);
914
+ return out;
850
915
  }
851
916
  function processPasswordR5(password = '') {
852
917
  password = unescape(encodeURIComponent(saslprep(password)));
853
918
  const length = Math.min(127, password.length);
854
- const out = Buffer.alloc(length);
919
+ const out = new Uint8Array(length);
855
920
  for (let i = 0; i < length; i++) {
856
921
  out[i] = password.charCodeAt(i);
857
922
  }
858
- return CryptoJS.lib.WordArray.create(out);
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);
923
+ return out;
869
924
  }
870
925
  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
926
 
@@ -1473,86 +1528,176 @@ const parameters = {
1473
1528
  Z: 0,
1474
1529
  z: 0
1475
1530
  };
1531
+ const isCommand = function (c) {
1532
+ return c in parameters;
1533
+ };
1534
+ const isWsp = function (c) {
1535
+ const codePoint = c.codePointAt(0);
1536
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1537
+ };
1538
+ const isDigit = function (c) {
1539
+ const codePoint = c.codePointAt(0);
1540
+ if (codePoint == null) {
1541
+ return false;
1542
+ }
1543
+ return 48 <= codePoint && codePoint <= 57;
1544
+ };
1545
+ const readNumber = function (string, cursor) {
1546
+ let i = cursor;
1547
+ let value = '';
1548
+ let state = 'none';
1549
+ for (; i < string.length; i += 1) {
1550
+ const c = string[i];
1551
+ if (c === '+' || c === '-') {
1552
+ if (state === 'none') {
1553
+ state = 'sign';
1554
+ value += c;
1555
+ continue;
1556
+ }
1557
+ if (state === 'e') {
1558
+ state = 'exponent_sign';
1559
+ value += c;
1560
+ continue;
1561
+ }
1562
+ }
1563
+ if (isDigit(c)) {
1564
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1565
+ state = 'whole';
1566
+ value += c;
1567
+ continue;
1568
+ }
1569
+ if (state === 'decimal_point' || state === 'decimal') {
1570
+ state = 'decimal';
1571
+ value += c;
1572
+ continue;
1573
+ }
1574
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1575
+ state = 'exponent';
1576
+ value += c;
1577
+ continue;
1578
+ }
1579
+ }
1580
+ if (c === '.') {
1581
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1582
+ state = 'decimal_point';
1583
+ value += c;
1584
+ continue;
1585
+ }
1586
+ }
1587
+ if (c === 'E' || c === 'e') {
1588
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1589
+ state = 'e';
1590
+ value += c;
1591
+ continue;
1592
+ }
1593
+ }
1594
+ break;
1595
+ }
1596
+ const number = Number.parseFloat(value);
1597
+ if (Number.isNaN(number)) {
1598
+ return [cursor, null];
1599
+ }
1600
+ return [i - 1, number];
1601
+ };
1476
1602
  const parse = function (path) {
1477
- let cmd;
1478
- const ret = [];
1603
+ const pathData = [];
1604
+ let command = null;
1479
1605
  let args = [];
1480
- let curArg = '';
1481
- let foundDecimal = false;
1482
- let params = 0;
1483
- for (let c of path) {
1484
- if (parameters[c] != null) {
1485
- params = parameters[c];
1486
- if (cmd) {
1487
- if (curArg.length > 0) {
1488
- args[args.length] = +curArg;
1606
+ let argsCount = 0;
1607
+ let canHaveComma = false;
1608
+ let hadComma = false;
1609
+ for (let i = 0; i < path.length; i += 1) {
1610
+ const c = path.charAt(i);
1611
+ if (isWsp(c)) {
1612
+ continue;
1613
+ }
1614
+ if (canHaveComma && c === ',') {
1615
+ if (hadComma) {
1616
+ break;
1617
+ }
1618
+ hadComma = true;
1619
+ continue;
1620
+ }
1621
+ if (isCommand(c)) {
1622
+ if (hadComma) {
1623
+ return pathData;
1624
+ }
1625
+ if (command == null) {
1626
+ if (c !== 'M' && c !== 'm') {
1627
+ return pathData;
1628
+ }
1629
+ } else {
1630
+ if (args.length !== 0) {
1631
+ return pathData;
1489
1632
  }
1490
- ret[ret.length] = {
1491
- cmd,
1633
+ }
1634
+ command = c;
1635
+ args = [];
1636
+ argsCount = parameters[command];
1637
+ canHaveComma = false;
1638
+ if (argsCount === 0) {
1639
+ pathData.push({
1640
+ command,
1492
1641
  args
1493
- };
1494
- args = [];
1495
- curArg = '';
1496
- foundDecimal = false;
1642
+ });
1497
1643
  }
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;
1644
+ continue;
1645
+ }
1646
+ if (command == null) {
1647
+ return pathData;
1648
+ }
1649
+ let newCursor = i;
1650
+ let number = null;
1651
+ if (command === 'A' || command === 'a') {
1652
+ const position = args.length;
1653
+ if (position === 0 || position === 1) {
1654
+ if (c !== '+' && c !== '-') {
1655
+ [newCursor, number] = readNumber(path, i);
1656
+ }
1502
1657
  }
1503
- if (args.length === params) {
1504
- ret[ret.length] = {
1505
- cmd,
1506
- args
1507
- };
1508
- args = [+curArg];
1509
- if (cmd === 'M') {
1510
- cmd = 'L';
1658
+ if (position === 2 || position === 5 || position === 6) {
1659
+ [newCursor, number] = readNumber(path, i);
1660
+ }
1661
+ if (position === 3 || position === 4) {
1662
+ if (c === '0') {
1663
+ number = 0;
1511
1664
  }
1512
- if (cmd === 'm') {
1513
- cmd = 'l';
1665
+ if (c === '1') {
1666
+ number = 1;
1514
1667
  }
1515
- } else {
1516
- args[args.length] = +curArg;
1517
1668
  }
1518
- foundDecimal = c === '.';
1519
- curArg = ['-', '.'].includes(c) ? c : '';
1520
1669
  } else {
1521
- curArg += c;
1522
- if (c === '.') {
1523
- foundDecimal = true;
1524
- }
1525
- }
1526
- }
1527
- if (curArg.length > 0) {
1528
- if (args.length === params) {
1529
- ret[ret.length] = {
1530
- cmd,
1670
+ [newCursor, number] = readNumber(path, i);
1671
+ }
1672
+ if (number == null) {
1673
+ return pathData;
1674
+ }
1675
+ args.push(number);
1676
+ canHaveComma = true;
1677
+ hadComma = false;
1678
+ i = newCursor;
1679
+ if (args.length === argsCount) {
1680
+ pathData.push({
1681
+ command,
1531
1682
  args
1532
- };
1533
- args = [+curArg];
1534
- if (cmd === 'M') {
1535
- cmd = 'L';
1683
+ });
1684
+ if (command === 'M') {
1685
+ command = 'L';
1536
1686
  }
1537
- if (cmd === 'm') {
1538
- cmd = 'l';
1687
+ if (command === 'm') {
1688
+ command = 'l';
1539
1689
  }
1540
- } else {
1541
- args[args.length] = +curArg;
1690
+ args = [];
1542
1691
  }
1543
1692
  }
1544
- ret[ret.length] = {
1545
- cmd,
1546
- args
1547
- };
1548
- return ret;
1693
+ return pathData;
1549
1694
  };
1550
1695
  const apply = function (commands, doc) {
1551
1696
  cx = cy = px = py = sx = sy = 0;
1552
1697
  for (let i = 0; i < commands.length; i++) {
1553
1698
  const c = commands[i];
1554
- if (typeof runners[c.cmd] === 'function') {
1555
- runners[c.cmd](doc, c.args);
1699
+ if (typeof runners[c.command] === 'function') {
1700
+ runners[c.command](doc, c.args);
1556
1701
  }
1557
1702
  }
1558
1703
  };
@@ -2584,7 +2729,7 @@ begincmap
2584
2729
  1 begincodespacerange
2585
2730
  <0000><ffff>
2586
2731
  endcodespacerange
2587
- 1 beginbfrange
2732
+ ${ranges.length} beginbfrange
2588
2733
  ${ranges.join('\n')}
2589
2734
  endbfrange
2590
2735
  endcmap
@@ -2763,6 +2908,7 @@ class LineWrapper extends events.EventEmitter {
2763
2908
  this.document = document;
2764
2909
  this.horizontalScaling = options.horizontalScaling || 100;
2765
2910
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2911
+ this.indentAllLines = options.indentAllLines || false;
2766
2912
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2767
2913
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2768
2914
  this.columns = options.columns || 1;
@@ -2956,12 +3102,14 @@ class LineWrapper extends events.EventEmitter {
2956
3102
  }
2957
3103
  emitLine();
2958
3104
  if (PDFNumber(this.document.y + lh) > this.maxY) {
3105
+ this.emit('sectionEnd', options, this);
2959
3106
  const shouldContinue = this.nextSection();
2960
3107
  if (!shouldContinue) {
2961
3108
  wc = 0;
2962
3109
  buffer = '';
2963
3110
  return false;
2964
3111
  }
3112
+ this.emit('sectionStart', options, this);
2965
3113
  }
2966
3114
  if (bk.required) {
2967
3115
  this.spaceLeft = this.lineWidth;
@@ -2994,7 +3142,6 @@ class LineWrapper extends events.EventEmitter {
2994
3142
  }
2995
3143
  }
2996
3144
  nextSection(options) {
2997
- this.emit('sectionEnd', options, this);
2998
3145
  if (++this.column > this.columns) {
2999
3146
  if (this.height != null) {
3000
3147
  return false;
@@ -3003,7 +3150,13 @@ class LineWrapper extends events.EventEmitter {
3003
3150
  this.column = 1;
3004
3151
  this.startY = this.document.page.margins.top;
3005
3152
  this.maxY = this.document.page.maxY();
3006
- this.document.x = this.startX;
3153
+ if (this.indentAllLines) {
3154
+ const indent = this.continuedX || this.indent;
3155
+ this.document.x += indent;
3156
+ this.lineWidth -= indent;
3157
+ } else {
3158
+ this.document.x = this.startX;
3159
+ }
3007
3160
  if (this.document._fillColor) {
3008
3161
  this.document.fillColor(...this.document._fillColor);
3009
3162
  }
@@ -3013,7 +3166,6 @@ class LineWrapper extends events.EventEmitter {
3013
3166
  this.document.y = this.startY;
3014
3167
  this.emit('columnBreak', options, this);
3015
3168
  }
3016
- this.emit('sectionStart', options, this);
3017
3169
  return true;
3018
3170
  }
3019
3171
  }
@@ -3021,6 +3173,15 @@ class LineWrapper extends events.EventEmitter {
3021
3173
  const {
3022
3174
  number
3023
3175
  } = PDFObject;
3176
+ function formatListLabel(n, listType) {
3177
+ if (listType === 'numbered') {
3178
+ return `${n}.`;
3179
+ }
3180
+ var letter = String.fromCharCode((n - 1) % 26 + 65);
3181
+ var times = Math.floor((n - 1) / 26 + 1);
3182
+ var text = Array(times + 1).join(letter);
3183
+ return `${text}.`;
3184
+ }
3024
3185
  var TextMixin = {
3025
3186
  initText() {
3026
3187
  this._line = this._line.bind(this);
@@ -3197,7 +3358,7 @@ var TextMixin = {
3197
3358
  this.y = y;
3198
3359
  return height;
3199
3360
  },
3200
- list(list, x, y, options, wrapper) {
3361
+ list(list, x, y, options) {
3201
3362
  options = this._initOptions(x, y, options);
3202
3363
  const listType = options.listType || 'bullet';
3203
3364
  const unit = Math.round(this._font.ascender / 1000 * this._fontSize);
@@ -3227,19 +3388,8 @@ var TextMixin = {
3227
3388
  }
3228
3389
  };
3229
3390
  flatten(list);
3230
- const label = function (n) {
3231
- switch (listType) {
3232
- case 'numbered':
3233
- return `${n}.`;
3234
- case 'lettered':
3235
- var letter = String.fromCharCode((n - 1) % 26 + 65);
3236
- var times = Math.floor((n - 1) / 26 + 1);
3237
- var text = Array(times + 1).join(letter);
3238
- return `${text}.`;
3239
- }
3240
- };
3241
3391
  const drawListItem = function (listItem, i) {
3242
- wrapper = new LineWrapper(this, options);
3392
+ const wrapper = new LineWrapper(this, options);
3243
3393
  wrapper.on('line', this._line);
3244
3394
  level = 1;
3245
3395
  wrapper.once('firstLine', () => {
@@ -3274,7 +3424,7 @@ var TextMixin = {
3274
3424
  break;
3275
3425
  case 'numbered':
3276
3426
  case 'lettered':
3277
- var text = label(numbers[i - 1]);
3427
+ var text = formatListLabel(numbers[i - 1], listType);
3278
3428
  this._fragment(text, this.x - indent, this.y, options);
3279
3429
  break;
3280
3430
  }
@@ -3342,11 +3492,11 @@ var TextMixin = {
3342
3492
  },
3343
3493
  _line(text, options = {}, wrapper) {
3344
3494
  this._fragment(text, this.x, this.y, options);
3345
- const lineGap = options.lineGap || this._lineGap || 0;
3346
- if (!wrapper) {
3347
- this.x += this.widthOfString(text, options);
3348
- } else {
3495
+ if (wrapper) {
3496
+ const lineGap = options.lineGap || this._lineGap || 0;
3349
3497
  this.y += this.currentLineHeight(true) + lineGap;
3498
+ } else {
3499
+ this.x += this.widthOfString(text, options);
3350
3500
  }
3351
3501
  },
3352
3502
  _fragment(text, x, y, options) {
@@ -3410,7 +3560,11 @@ var TextMixin = {
3410
3560
  }
3411
3561
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3412
3562
  if (options.link != null) {
3413
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3563
+ const linkOptions = {};
3564
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3565
+ linkOptions.structParent = this._currentStructureElement;
3566
+ }
3567
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3414
3568
  }
3415
3569
  if (options.goTo != null) {
3416
3570
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3539,6 +3693,47 @@ var TextMixin = {
3539
3693
  }
3540
3694
  };
3541
3695
 
3696
+ const parseExifOrientation = data => {
3697
+ if (!data || data.length < 20) return null;
3698
+ let pos = 2;
3699
+ while (pos < data.length - 4) {
3700
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3701
+ if (pos >= data.length - 4) return null;
3702
+ const marker = data.readUInt16BE(pos);
3703
+ pos += 2;
3704
+ if (marker === 0xffda) return null;
3705
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3706
+ if (pos + 2 > data.length) return null;
3707
+ const segmentLength = data.readUInt16BE(pos);
3708
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3709
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3710
+ if (exifHeader === 'Exif\x00\x00') {
3711
+ const tiffStart = pos + 8;
3712
+ if (tiffStart + 8 > data.length) return null;
3713
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3714
+ const isLittleEndian = byteOrder === 'II';
3715
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3716
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3717
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3718
+ if (read16(tiffStart + 2) !== 42) return null;
3719
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3720
+ if (ifdPos + 2 > data.length) return null;
3721
+ const entryCount = read16(ifdPos);
3722
+ for (let i = 0; i < entryCount; i++) {
3723
+ const entryPos = ifdPos + 2 + i * 12;
3724
+ if (entryPos + 12 > data.length) return null;
3725
+ if (read16(entryPos) === 0x0112) {
3726
+ const value = read16(entryPos + 8);
3727
+ return value >= 1 && value <= 8 ? value : null;
3728
+ }
3729
+ }
3730
+ return null;
3731
+ }
3732
+ }
3733
+ pos += segmentLength;
3734
+ }
3735
+ return null;
3736
+ };
3542
3737
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3543
3738
  const COLOR_SPACE_MAP = {
3544
3739
  1: 'DeviceGray',
@@ -3553,9 +3748,11 @@ class JPEG {
3553
3748
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3554
3749
  throw 'SOI not found in JPEG';
3555
3750
  }
3556
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3751
+ this.orientation = parseExifOrientation(this.data) || 1;
3557
3752
  let pos = 2;
3558
3753
  while (pos < this.data.length) {
3754
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3755
+ if (pos >= this.data.length) break;
3559
3756
  marker = this.data.readUInt16BE(pos);
3560
3757
  pos += 2;
3561
3758
  if (MARKERS.includes(marker)) {
@@ -3707,12 +3904,16 @@ class PNGImage {
3707
3904
  }
3708
3905
  loadIndexedAlphaChannel() {
3709
3906
  const transparency = this.image.transparency.indexed;
3907
+ const isInterlaced = this.image.interlaceMethod === 1;
3710
3908
  return this.image.decodePixels(pixels => {
3711
3909
  const alphaChannel = Buffer.alloc(this.width * this.height);
3712
3910
  let i = 0;
3713
3911
  for (let j = 0, end = pixels.length; j < end; j++) {
3714
3912
  alphaChannel[i++] = transparency[pixels[j]];
3715
3913
  }
3914
+ if (isInterlaced) {
3915
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3916
+ }
3716
3917
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3717
3918
  return this.finalize();
3718
3919
  });
@@ -3941,6 +4142,12 @@ var ImagesMixin = {
3941
4142
  }
3942
4143
  };
3943
4144
 
4145
+ class PDFAnnotationReference {
4146
+ constructor(annotationRef) {
4147
+ this.annotationRef = annotationRef;
4148
+ }
4149
+ }
4150
+
3944
4151
  var AnnotationsMixin = {
3945
4152
  annotate(x, y, w, h, options) {
3946
4153
  options.Type = 'Annot';
@@ -3958,12 +4165,18 @@ var AnnotationsMixin = {
3958
4165
  if (typeof options.Dest === 'string') {
3959
4166
  options.Dest = new String(options.Dest);
3960
4167
  }
4168
+ const structParent = options.structParent;
4169
+ delete options.structParent;
3961
4170
  for (let key in options) {
3962
4171
  const val = options[key];
3963
4172
  options[key[0].toUpperCase() + key.slice(1)] = val;
3964
4173
  }
3965
4174
  const ref = this.ref(options);
3966
4175
  this.page.annotations.push(ref);
4176
+ if (structParent && typeof structParent.add === 'function') {
4177
+ const annotRef = new PDFAnnotationReference(ref);
4178
+ structParent.add(annotRef);
4179
+ }
3967
4180
  ref.end();
3968
4181
  return this;
3969
4182
  },
@@ -4007,6 +4220,9 @@ var AnnotationsMixin = {
4007
4220
  });
4008
4221
  options.A.end();
4009
4222
  }
4223
+ if (options.structParent && !options.Contents) {
4224
+ options.Contents = new String('');
4225
+ }
4010
4226
  return this.annotate(x, y, w, h, options);
4011
4227
  },
4012
4228
  _markup(x, y, w, h, options = {}) {
@@ -4078,15 +4294,26 @@ var AnnotationsMixin = {
4078
4294
  }
4079
4295
  };
4080
4296
 
4297
+ const DEFAULT_OPTIONS = {
4298
+ top: 0,
4299
+ left: 0,
4300
+ zoom: 0,
4301
+ fit: true,
4302
+ pageNumber: null,
4303
+ expanded: false
4304
+ };
4081
4305
  class PDFOutline {
4082
- constructor(document, parent, title, dest, options = {
4083
- expanded: false
4084
- }) {
4306
+ constructor(document, parent, title, dest, options = DEFAULT_OPTIONS) {
4085
4307
  this.document = document;
4086
4308
  this.options = options;
4087
4309
  this.outlineData = {};
4088
4310
  if (dest !== null) {
4089
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4311
+ const destWidth = dest.data.MediaBox[2];
4312
+ const destHeight = dest.data.MediaBox[3];
4313
+ const top = destHeight - (options.top || 0);
4314
+ const left = destWidth - (options.left || 0);
4315
+ const zoom = options.zoom || 0;
4316
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4090
4317
  }
4091
4318
  if (parent !== null) {
4092
4319
  this.outlineData['Parent'] = parent;
@@ -4097,10 +4324,10 @@ class PDFOutline {
4097
4324
  this.dictionary = this.document.ref(this.outlineData);
4098
4325
  this.children = [];
4099
4326
  }
4100
- addItem(title, options = {
4101
- expanded: false
4102
- }) {
4103
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4327
+ addItem(title, options = DEFAULT_OPTIONS) {
4328
+ const pages = this.document._root.data.Pages.data.Kids;
4329
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4330
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4104
4331
  this.children.push(result);
4105
4332
  return result;
4106
4333
  }
@@ -4136,7 +4363,7 @@ var OutlineMixin = {
4136
4363
  this.outline.endOutline();
4137
4364
  if (this.outline.children.length > 0) {
4138
4365
  this._root.data.Outlines = this.outline.dictionary;
4139
- return this._root.data.PageMode = 'UseOutlines';
4366
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4140
4367
  }
4141
4368
  }
4142
4369
  };
@@ -4207,6 +4434,9 @@ class PDFStructureElement {
4207
4434
  if (child instanceof PDFStructureContent) {
4208
4435
  this._addContentToParentTree(child);
4209
4436
  }
4437
+ if (child instanceof PDFAnnotationReference) {
4438
+ this._addAnnotationToParentTree(child.annotationRef);
4439
+ }
4210
4440
  if (typeof child === 'function' && this._attached) {
4211
4441
  child = this._contentForClosure(child);
4212
4442
  }
@@ -4222,6 +4452,12 @@ class PDFStructureElement {
4222
4452
  pageStructParents[mcid] = this.dictionary;
4223
4453
  });
4224
4454
  }
4455
+ _addAnnotationToParentTree(annotRef) {
4456
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4457
+ annotRef.data.StructParent = parentTreeKey;
4458
+ const parentTree = this.document.getStructParentTree();
4459
+ parentTree.add(parentTreeKey, this.dictionary);
4460
+ }
4225
4461
  setParent(parentRef) {
4226
4462
  if (this.dictionary.data.P) {
4227
4463
  throw new Error(`Structure element added to more than one parent`);
@@ -4253,11 +4489,17 @@ class PDFStructureElement {
4253
4489
  this._flush();
4254
4490
  }
4255
4491
  _isValidChild(child) {
4256
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4492
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4257
4493
  }
4258
4494
  _contentForClosure(closure) {
4259
4495
  const content = this.document.markStructureContent(this.dictionary.data.S);
4496
+ const prevStructElement = this.document._currentStructureElement;
4497
+ this.document._currentStructureElement = this;
4498
+ const wasEnded = this._ended;
4499
+ this._ended = false;
4260
4500
  closure();
4501
+ this._ended = wasEnded;
4502
+ this.document._currentStructureElement = prevStructElement;
4261
4503
  this.document.endMarkedContent();
4262
4504
  this._addContentToParentTree(content);
4263
4505
  return content;
@@ -4310,6 +4552,15 @@ class PDFStructureElement {
4310
4552
  }
4311
4553
  });
4312
4554
  }
4555
+ if (child instanceof PDFAnnotationReference) {
4556
+ const pageRef = this.document.page.dictionary;
4557
+ const objr = {
4558
+ Type: 'OBJR',
4559
+ Obj: child.annotationRef,
4560
+ Pg: pageRef
4561
+ };
4562
+ this.dictionary.data.K.push(objr);
4563
+ }
4313
4564
  }
4314
4565
  }
4315
4566
 
@@ -4403,6 +4654,13 @@ var MarkingsMixin = {
4403
4654
  endMarkedContent() {
4404
4655
  this.page.markings.pop();
4405
4656
  this.addContent('EMC');
4657
+ if (this._textOptions) {
4658
+ delete this._textOptions.link;
4659
+ delete this._textOptions.goTo;
4660
+ delete this._textOptions.destination;
4661
+ delete this._textOptions.underline;
4662
+ delete this._textOptions.strike;
4663
+ }
4406
4664
  return this;
4407
4665
  },
4408
4666
  struct(type, options = {}, children = null) {
@@ -4849,7 +5107,7 @@ var AttachmentsMixin = {
4849
5107
  if (options.type) {
4850
5108
  refBody.Subtype = options.type.replace('/', '#2F');
4851
5109
  }
4852
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5110
+ const checksum = md5Hex(new Uint8Array(data));
4853
5111
  refBody.Params.CheckSum = new String(checksum);
4854
5112
  refBody.Params.Size = data.byteLength;
4855
5113
  let ref;
@@ -5492,7 +5750,7 @@ function renderRow(row, rowIndex) {
5492
5750
  function renderCell(cell, rowStruct) {
5493
5751
  const cellRenderer = () => {
5494
5752
  if (cell.backgroundColor != null) {
5495
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5753
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5496
5754
  }
5497
5755
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5498
5756
  if (cell.debug) {
@@ -5553,20 +5811,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5553
5811
  const doc = this.document;
5554
5812
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5555
5813
  if (border.top > 0) {
5556
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5814
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5557
5815
  }
5558
5816
  } else {
5559
5817
  if (border.top > 0) {
5560
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5818
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5561
5819
  }
5562
5820
  if (border.right > 0) {
5563
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5821
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5564
5822
  }
5565
5823
  if (border.bottom > 0) {
5566
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5824
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5567
5825
  }
5568
5826
  if (border.left > 0) {
5569
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5827
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5570
5828
  }
5571
5829
  }
5572
5830
  }
@@ -5773,6 +6031,10 @@ class PDFDocument extends stream.Readable {
5773
6031
  if (this.options.lang) {
5774
6032
  this._root.data.Lang = new String(this.options.lang);
5775
6033
  }
6034
+ if (this.options.pageLayout) {
6035
+ const layout = this.options.pageLayout;
6036
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6037
+ }
5776
6038
  this.page = null;
5777
6039
  this.initMetadata();
5778
6040
  this.initColor();