pdfkit 0.18.0 → 0.19.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
@@ -4,11 +4,10 @@ var stream = require('stream');
4
4
  var zlib = require('zlib');
5
5
  var utils = require('@noble/hashes/utils');
6
6
  var md5 = require('js-md5');
7
- var sha256 = require('@noble/hashes/sha256');
7
+ var sha2 = require('@noble/hashes/sha2');
8
8
  var aes = require('@noble/ciphers/aes');
9
9
  var fs = require('fs');
10
10
  var fontkit = require('fontkit');
11
- var events = require('events');
12
11
  var LineBreaker = require('linebreak');
13
12
  var PNG = require('png-js');
14
13
 
@@ -61,7 +60,7 @@ class SpotColor {
61
60
  this.id = 'CS' + Object.keys(doc.spotColors).length;
62
61
  this.name = name;
63
62
  this.values = [C, M, Y, K];
64
- this.ref = doc.ref(['Separation', this.name, 'DeviceCMYK', {
63
+ this.ref = doc.ref(['Separation', escapeName(this.name), 'DeviceCMYK', {
65
64
  Range: [0, 1, 0, 1, 0, 1, 0, 1],
66
65
  C0: [0, 0, 0, 0],
67
66
  C1: this.values.map(value => value / 100),
@@ -77,6 +76,10 @@ class SpotColor {
77
76
  }
78
77
 
79
78
  const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
79
+ const isSafeCharCode = code => {
80
+ if (code > 0x7f) return true;
81
+ return code > 0x20 && code !== 0x7f && code !== 0x23 && code !== 0x25 && code !== 0x28 && code !== 0x29 && code !== 0x2f && code !== 0x3c && code !== 0x3e && code !== 0x5b && code !== 0x5d && code !== 0x7b && code !== 0x7d;
82
+ };
80
83
  const escapableRe = /[\n\r\t\b\f()\\]/g;
81
84
  const escapable = {
82
85
  '\n': '\\n',
@@ -88,6 +91,18 @@ const escapable = {
88
91
  '(': '\\(',
89
92
  ')': '\\)'
90
93
  };
94
+ const escapeName = function (name) {
95
+ let escapedName = '';
96
+ for (const char of name) {
97
+ const code = char.charCodeAt(0);
98
+ if (isSafeCharCode(code)) {
99
+ escapedName += char;
100
+ } else {
101
+ escapedName += `#${code.toString(16).toUpperCase().padStart(2, '0')}`;
102
+ }
103
+ }
104
+ return escapedName;
105
+ };
91
106
  const swapBytes = function (buff) {
92
107
  const l = buff.length;
93
108
  if (l & 0x01) {
@@ -459,7 +474,7 @@ function md5Hex(data) {
459
474
  }
460
475
 
461
476
  function sha256Hash(data) {
462
- return sha256.sha256(data);
477
+ return sha2.sha256(data);
463
478
  }
464
479
 
465
480
  function aesCbcEncrypt(data, key, iv, padding = true) {
@@ -495,11 +510,7 @@ function rc4(data, key) {
495
510
 
496
511
  function randomBytes(length) {
497
512
  const bytes = new Uint8Array(length);
498
- if (globalThis.crypto?.getRandomValues) {
499
- globalThis.crypto.getRandomValues(bytes);
500
- } else {
501
- require('crypto').randomFillSync(bytes);
502
- }
513
+ globalThis.crypto.getRandomValues(bytes);
503
514
  return bytes;
504
515
  }
505
516
 
@@ -927,7 +938,7 @@ const PASSWORD_PADDING = [0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64,
927
938
  const {
928
939
  number: number$2
929
940
  } = PDFObject;
930
- class PDFGradient$1 {
941
+ let PDFGradient$1 = class PDFGradient {
931
942
  constructor(doc) {
932
943
  this.doc = doc;
933
944
  this.stops = [];
@@ -1085,8 +1096,8 @@ class PDFGradient$1 {
1085
1096
  const op = stroke ? 'SCN' : 'scn';
1086
1097
  return this.doc.addContent(`/${this.id} ${op}`);
1087
1098
  }
1088
- }
1089
- class PDFLinearGradient$1 extends PDFGradient$1 {
1099
+ };
1100
+ let PDFLinearGradient$1 = class PDFLinearGradient extends PDFGradient$1 {
1090
1101
  constructor(doc, x1, y1, x2, y2) {
1091
1102
  super(doc);
1092
1103
  this.x1 = x1;
@@ -1104,10 +1115,10 @@ class PDFLinearGradient$1 extends PDFGradient$1 {
1104
1115
  });
1105
1116
  }
1106
1117
  opacityGradient() {
1107
- return new PDFLinearGradient$1(this.doc, this.x1, this.y1, this.x2, this.y2);
1118
+ return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
1108
1119
  }
1109
- }
1110
- class PDFRadialGradient$1 extends PDFGradient$1 {
1120
+ };
1121
+ let PDFRadialGradient$1 = class PDFRadialGradient extends PDFGradient$1 {
1111
1122
  constructor(doc, x1, y1, r1, x2, y2, r2) {
1112
1123
  super(doc);
1113
1124
  this.doc = doc;
@@ -1128,9 +1139,9 @@ class PDFRadialGradient$1 extends PDFGradient$1 {
1128
1139
  });
1129
1140
  }
1130
1141
  opacityGradient() {
1131
- return new PDFRadialGradient$1(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1142
+ return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1132
1143
  }
1133
- }
1144
+ };
1134
1145
  var Gradient = {
1135
1146
  PDFGradient: PDFGradient$1,
1136
1147
  PDFLinearGradient: PDFLinearGradient$1,
@@ -1138,7 +1149,7 @@ var Gradient = {
1138
1149
  };
1139
1150
 
1140
1151
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1141
- class PDFTilingPattern$1 {
1152
+ let PDFTilingPattern$1 = class PDFTilingPattern {
1142
1153
  constructor(doc, bBox, xStep, yStep, stream) {
1143
1154
  this.doc = doc;
1144
1155
  this.bBox = bBox;
@@ -1198,7 +1209,7 @@ class PDFTilingPattern$1 {
1198
1209
  const op = stroke ? 'SCN' : 'scn';
1199
1210
  return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1200
1211
  }
1201
- }
1212
+ };
1202
1213
  var pattern = {
1203
1214
  PDFTilingPattern: PDFTilingPattern$1
1204
1215
  };
@@ -2007,21 +2018,42 @@ var VectorMixin = {
2007
2018
  rect(x, y, w, h) {
2008
2019
  return this.addContent(`${number$1(x)} ${number$1(y)} ${number$1(w)} ${number$1(h)} re`);
2009
2020
  },
2010
- roundedRect(x, y, w, h, r) {
2011
- if (r == null) {
2012
- r = 0;
2013
- }
2014
- r = Math.min(r, 0.5 * w, 0.5 * h);
2015
- const c = r * (1.0 - KAPPA);
2016
- this.moveTo(x + r, y);
2017
- this.lineTo(x + w - r, y);
2018
- this.bezierCurveTo(x + w - c, y, x + w, y + c, x + w, y + r);
2019
- this.lineTo(x + w, y + h - r);
2020
- this.bezierCurveTo(x + w, y + h - c, x + w - c, y + h, x + w - r, y + h);
2021
- this.lineTo(x + r, y + h);
2022
- this.bezierCurveTo(x + c, y + h, x, y + h - c, x, y + h - r);
2023
- this.lineTo(x, y + r);
2024
- this.bezierCurveTo(x, y + c, x + c, y, x + r, y);
2021
+ roundedRect(x, y, w, h, borderRadius) {
2022
+ if (borderRadius == null) {
2023
+ borderRadius = 0;
2024
+ }
2025
+ let radii;
2026
+ if (Array.isArray(borderRadius)) {
2027
+ radii = borderRadius.slice(0, 4);
2028
+ } else {
2029
+ radii = [borderRadius, borderRadius, borderRadius, borderRadius];
2030
+ }
2031
+ const limit = Math.min(0.5 * w, 0.5 * h);
2032
+ const rTL = Math.max(0, Math.min(radii[0] || 0, limit));
2033
+ const rTR = Math.max(0, Math.min(radii[1] || 0, limit));
2034
+ const rBR = Math.max(0, Math.min(radii[2] || 0, limit));
2035
+ const rBL = Math.max(0, Math.min(radii[3] || 0, limit));
2036
+ const cpTR = rTR * (1.0 - KAPPA);
2037
+ const cpBR = rBR * (1.0 - KAPPA);
2038
+ const cpBL = rBL * (1.0 - KAPPA);
2039
+ const cpTL = rTL * (1.0 - KAPPA);
2040
+ this.moveTo(x + rTL, y);
2041
+ this.lineTo(x + w - rTR, y);
2042
+ if (rTR > 0) {
2043
+ this.bezierCurveTo(x + w - cpTR, y, x + w, y + cpTR, x + w, y + rTR);
2044
+ }
2045
+ this.lineTo(x + w, y + h - rBR);
2046
+ if (rBR > 0) {
2047
+ this.bezierCurveTo(x + w, y + h - cpBR, x + w - cpBR, y + h, x + w - rBR, y + h);
2048
+ }
2049
+ this.lineTo(x + rBL, y + h);
2050
+ if (rBL > 0) {
2051
+ this.bezierCurveTo(x + cpBL, y + h, x, y + h - cpBL, x, y + h - rBL);
2052
+ }
2053
+ this.lineTo(x, y + rTL);
2054
+ if (rTL > 0) {
2055
+ this.bezierCurveTo(x, y + cpTL, x + cpTL, y, x + rTL, y);
2056
+ }
2025
2057
  return this.closePath();
2026
2058
  },
2027
2059
  ellipse(x, y, r1, r2) {
@@ -2313,7 +2345,7 @@ class AFMFont {
2313
2345
  if (match = line.match(/^Start(\w+)/)) {
2314
2346
  section = match[1];
2315
2347
  continue;
2316
- } else if (match = line.match(/^End(\w+)/)) {
2348
+ } else if (line.match(/^End(\w+)/)) {
2317
2349
  section = '';
2318
2350
  continue;
2319
2351
  }
@@ -2657,9 +2689,15 @@ class EmbeddedFont extends PDFFont {
2657
2689
  descriptor.data.FontFile2 = fontFile;
2658
2690
  }
2659
2691
  if (this.document.subset && this.document.subset === 1) {
2660
- const CIDSet = Buffer.from('FFFFFFFFC0', 'hex');
2692
+ const maxCID = this.widths.length - 1;
2693
+ const cidSetBuffer = Buffer.alloc(Math.ceil((maxCID + 1) / 8), 0);
2694
+ for (let cid = 0; cid <= maxCID; cid++) {
2695
+ if (this.widths[cid] != null) {
2696
+ cidSetBuffer[Math.floor(cid / 8)] |= 0x80 >> cid % 8;
2697
+ }
2698
+ }
2661
2699
  const CIDSetRef = this.document.ref();
2662
- CIDSetRef.write(CIDSet);
2700
+ CIDSetRef.write(cidSetBuffer);
2663
2701
  CIDSetRef.end();
2664
2702
  descriptor.data.CIDSet = CIDSetRef;
2665
2703
  }
@@ -2821,9 +2859,12 @@ var FontsMixin = {
2821
2859
  if (cacheKey) {
2822
2860
  this._fontFamilies[cacheKey] = this._font;
2823
2861
  }
2824
- if (this._font.name) {
2862
+ if (this._font.name && !this._fontFamilies[this._font.name]) {
2825
2863
  this._fontFamilies[this._font.name] = this._font;
2826
2864
  }
2865
+ if (!cacheKey && (!this._font.name || this._fontFamilies[this._font.name] !== this._font)) {
2866
+ this._fontFamilies[this._font.id] = this._font;
2867
+ }
2827
2868
  return this;
2828
2869
  },
2829
2870
  fontSize(_fontSize) {
@@ -2902,9 +2943,9 @@ var FontsMixin = {
2902
2943
 
2903
2944
  const SOFT_HYPHEN = '\u00AD';
2904
2945
  const HYPHEN = '-';
2905
- class LineWrapper extends events.EventEmitter {
2946
+ class LineWrapper {
2906
2947
  constructor(document, options) {
2907
- super();
2948
+ this._listeners = Object.create(null);
2908
2949
  this.document = document;
2909
2950
  this.horizontalScaling = options.horizontalScaling || 100;
2910
2951
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
@@ -2960,6 +3001,22 @@ class LineWrapper extends events.EventEmitter {
2960
3001
  });
2961
3002
  });
2962
3003
  }
3004
+ on(event, listener) {
3005
+ (this._listeners[event] || (this._listeners[event] = [])).push(listener);
3006
+ }
3007
+ once(event, listener) {
3008
+ const wrapper = (...args) => {
3009
+ const listeners = this._listeners[event];
3010
+ listeners.splice(listeners.indexOf(wrapper), 1);
3011
+ listener(...args);
3012
+ };
3013
+ this.on(event, wrapper);
3014
+ }
3015
+ emit(event, ...args) {
3016
+ const listeners = this._listeners[event];
3017
+ if (!listeners) return;
3018
+ for (const listener of listeners.slice()) listener(...args);
3019
+ }
2963
3020
  wordWidth(word) {
2964
3021
  return PDFNumber(this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing);
2965
3022
  }
@@ -3025,6 +3082,9 @@ class LineWrapper extends events.EventEmitter {
3025
3082
  }
3026
3083
  }
3027
3084
  wrap(text, options) {
3085
+ const {
3086
+ document
3087
+ } = this;
3028
3088
  this.horizontalScaling = options.horizontalScaling || 100;
3029
3089
  if (options.indent != null) {
3030
3090
  this.indent = options.indent * this.horizontalScaling / 100;
@@ -3038,24 +3098,20 @@ class LineWrapper extends events.EventEmitter {
3038
3098
  if (options.ellipsis != null) {
3039
3099
  this.ellipsis = options.ellipsis;
3040
3100
  }
3041
- const nextY = this.document.y + this.document.currentLineHeight(true);
3042
- if (this.document.y > this.maxY || nextY > this.maxY) {
3101
+ const nextY = document.y + document.currentLineHeight(true);
3102
+ if (document.y > this.maxY || nextY > this.maxY) {
3043
3103
  this.nextSection();
3044
3104
  }
3045
3105
  let buffer = '';
3046
3106
  let textWidth = 0;
3047
3107
  let wc = 0;
3048
3108
  let lc = 0;
3049
- let {
3050
- y
3051
- } = this.document;
3109
+ let continueY = document.y;
3052
3110
  const emitLine = () => {
3053
3111
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3054
3112
  options.wordCount = wc;
3055
3113
  options.lineWidth = this.lineWidth;
3056
- ({
3057
- y
3058
- } = this.document);
3114
+ continueY = document.y;
3059
3115
  this.emit('line', buffer, options, this);
3060
3116
  return lc++;
3061
3117
  };
@@ -3071,8 +3127,8 @@ class LineWrapper extends events.EventEmitter {
3071
3127
  wc++;
3072
3128
  }
3073
3129
  if (bk.required || !this.canFit(word, w)) {
3074
- const lh = this.document.currentLineHeight(true);
3075
- if (this.height != null && this.ellipsis && PDFNumber(this.document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3130
+ const lh = document.currentLineHeight(true);
3131
+ if (this.height != null && this.ellipsis && PDFNumber(document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3076
3132
  if (this.ellipsis === true) {
3077
3133
  this.ellipsis = '…';
3078
3134
  }
@@ -3101,7 +3157,7 @@ class LineWrapper extends events.EventEmitter {
3101
3157
  this.spaceLeft -= this.wordWidth(HYPHEN);
3102
3158
  }
3103
3159
  emitLine();
3104
- if (PDFNumber(this.document.y + lh) > this.maxY) {
3160
+ if (PDFNumber(document.y + lh) > this.maxY) {
3105
3161
  this.emit('sectionEnd', options, this);
3106
3162
  const shouldContinue = this.nextSection();
3107
3163
  if (!shouldContinue) {
@@ -3136,9 +3192,9 @@ class LineWrapper extends events.EventEmitter {
3136
3192
  this.continuedX = 0;
3137
3193
  }
3138
3194
  this.continuedX += options.textWidth || 0;
3139
- this.document.y = y;
3195
+ document.y = continueY;
3140
3196
  } else {
3141
- this.document.x = this.startX;
3197
+ document.x = this.startX;
3142
3198
  }
3143
3199
  }
3144
3200
  nextSection(options) {
@@ -3453,7 +3509,7 @@ var TextMixin = {
3453
3509
  return this;
3454
3510
  },
3455
3511
  _initOptions(x = {}, y, options = {}) {
3456
- if (typeof x === 'object') {
3512
+ if (x && typeof x === 'object') {
3457
3513
  options = x;
3458
3514
  x = null;
3459
3515
  }
@@ -3486,7 +3542,7 @@ var TextMixin = {
3486
3542
  if (result.columnGap == null) {
3487
3543
  result.columnGap = 18;
3488
3544
  }
3489
- result.rotation = Number(options.rotation ?? 0) % 360;
3545
+ result.rotation = Number(result.rotation ?? 0) % 360;
3490
3546
  if (result.rotation < 0) result.rotation += 360;
3491
3547
  return result;
3492
3548
  },
@@ -3693,28 +3749,42 @@ var TextMixin = {
3693
3749
  }
3694
3750
  };
3695
3751
 
3752
+ const toBinaryString = bytes => {
3753
+ const chunkSize = 0x8000;
3754
+ let out = '';
3755
+ for (let i = 0; i < bytes.length; i += chunkSize) {
3756
+ const end = Math.min(i + chunkSize, bytes.length);
3757
+ out += String.fromCharCode.apply(null, bytes.subarray(i, end));
3758
+ }
3759
+ return out;
3760
+ };
3761
+ const readUInt16BE = (bytes, offset = 0) => (bytes[offset] << 8 | bytes[offset + 1]) >>> 0;
3762
+ const readUInt16LE = (bytes, offset = 0) => (bytes[offset + 1] << 8 | bytes[offset]) >>> 0;
3763
+ const readUInt32BE = (bytes, offset = 0) => bytes[offset] * 0x1000000 + (bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
3764
+ const readUInt32LE = (bytes, offset = 0) => (bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16) + bytes[offset + 3] * 0x1000000 >>> 0;
3765
+
3696
3766
  const parseExifOrientation = data => {
3697
3767
  if (!data || data.length < 20) return null;
3698
3768
  let pos = 2;
3699
3769
  while (pos < data.length - 4) {
3700
3770
  while (pos < data.length && data[pos] !== 0xff) pos++;
3701
3771
  if (pos >= data.length - 4) return null;
3702
- const marker = data.readUInt16BE(pos);
3772
+ const marker = readUInt16BE(data, pos);
3703
3773
  pos += 2;
3704
3774
  if (marker === 0xffda) return null;
3705
3775
  if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3706
3776
  if (pos + 2 > data.length) return null;
3707
- const segmentLength = data.readUInt16BE(pos);
3777
+ const segmentLength = readUInt16BE(data, pos);
3708
3778
  if (marker === 0xffe1 && pos + 8 <= data.length) {
3709
- const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3779
+ const exifHeader = toBinaryString(data.subarray(pos + 2, pos + 8));
3710
3780
  if (exifHeader === 'Exif\x00\x00') {
3711
3781
  const tiffStart = pos + 8;
3712
3782
  if (tiffStart + 8 > data.length) return null;
3713
- const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3783
+ const byteOrder = toBinaryString(data.subarray(tiffStart, tiffStart + 2));
3714
3784
  const isLittleEndian = byteOrder === 'II';
3715
3785
  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);
3786
+ const read16 = isLittleEndian ? o => readUInt16LE(data, o) : o => readUInt16BE(data, o);
3787
+ const read32 = isLittleEndian ? o => readUInt32LE(data, o) : o => readUInt32BE(data, o);
3718
3788
  if (read16(tiffStart + 2) !== 42) return null;
3719
3789
  const ifdPos = tiffStart + read32(tiffStart + 4);
3720
3790
  if (ifdPos + 2 > data.length) return null;
@@ -3769,7 +3839,7 @@ class JPEG {
3769
3839
  pos += 2;
3770
3840
  this.width = this.data.readUInt16BE(pos);
3771
3841
  pos += 2;
3772
- const channels = this.data[pos++];
3842
+ const channels = this.data[pos + 1];
3773
3843
  this.colorSpace = COLOR_SPACE_MAP[channels];
3774
3844
  this.obj = null;
3775
3845
  }
@@ -3804,58 +3874,60 @@ class PNGImage {
3804
3874
  this.obj = null;
3805
3875
  }
3806
3876
  embed(document) {
3807
- let dataDecoded = false;
3808
3877
  this.document = document;
3809
3878
  if (this.obj) {
3810
3879
  return;
3811
3880
  }
3812
- const hasAlphaChannel = this.image.hasAlphaChannel;
3813
- const isInterlaced = this.image.interlaceMethod === 1;
3814
- this.obj = this.document.ref({
3881
+ const {
3882
+ image,
3883
+ height,
3884
+ width
3885
+ } = this;
3886
+ const hasAlphaChannel = image.hasAlphaChannel;
3887
+ const isInterlaced = image.interlaceMethod === 1;
3888
+ const obj = this.obj = document.ref({
3815
3889
  Type: 'XObject',
3816
3890
  Subtype: 'Image',
3817
- BitsPerComponent: hasAlphaChannel ? 8 : this.image.bits,
3818
- Width: this.width,
3819
- Height: this.height,
3891
+ BitsPerComponent: hasAlphaChannel ? 8 : image.bits,
3892
+ Width: width,
3893
+ Height: height,
3820
3894
  Filter: 'FlateDecode'
3821
3895
  });
3822
3896
  if (!hasAlphaChannel) {
3823
- const params = this.document.ref({
3897
+ const params = document.ref({
3824
3898
  Predictor: isInterlaced ? 1 : 15,
3825
- Colors: this.image.colors,
3826
- BitsPerComponent: this.image.bits,
3827
- Columns: this.width
3899
+ Colors: image.colors,
3900
+ BitsPerComponent: image.bits,
3901
+ Columns: width
3828
3902
  });
3829
- this.obj.data['DecodeParms'] = params;
3903
+ obj.data['DecodeParms'] = params;
3830
3904
  params.end();
3831
3905
  }
3832
- if (this.image.palette.length === 0) {
3833
- this.obj.data['ColorSpace'] = this.image.colorSpace;
3906
+ if (image.palette.length === 0) {
3907
+ obj.data['ColorSpace'] = image.colorSpace;
3834
3908
  } else {
3835
- const palette = this.document.ref();
3836
- palette.end(Buffer.from(this.image.palette));
3837
- this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3838
- }
3839
- if (this.image.transparency.grayscale != null) {
3840
- const val = this.image.transparency.grayscale;
3841
- this.obj.data['Mask'] = [val, val];
3842
- } else if (this.image.transparency.rgb) {
3909
+ const palette = document.ref();
3910
+ palette.end(Buffer.from(image.palette));
3911
+ obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', image.palette.length / 3 - 1, palette];
3912
+ }
3913
+ if (image.transparency.grayscale != null) {
3914
+ const val = image.transparency.grayscale;
3915
+ obj.data['Mask'] = [val, val];
3916
+ } else if (image.transparency.rgb) {
3843
3917
  const {
3844
3918
  rgb
3845
- } = this.image.transparency;
3919
+ } = image.transparency;
3846
3920
  const mask = [];
3847
3921
  for (let x of rgb) {
3848
3922
  mask.push(x, x);
3849
3923
  }
3850
- this.obj.data['Mask'] = mask;
3851
- } else if (this.image.transparency.indexed) {
3852
- dataDecoded = true;
3924
+ obj.data['Mask'] = mask;
3925
+ } else if (image.transparency.indexed) {
3853
3926
  return this.loadIndexedAlphaChannel();
3854
3927
  } else if (hasAlphaChannel) {
3855
- dataDecoded = true;
3856
3928
  return this.splitAlphaChannel();
3857
3929
  }
3858
- if (isInterlaced && !dataDecoded) {
3930
+ if (isInterlaced && true) {
3859
3931
  return this.decodeData();
3860
3932
  }
3861
3933
  this.finalize();
@@ -3946,7 +4018,7 @@ class PDFImage {
3946
4018
  }
3947
4019
  if (data[0] === 0xff && data[1] === 0xd8) {
3948
4020
  return new JPEG(data, label);
3949
- } else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
4021
+ } else if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) {
3950
4022
  return new PNGImage(data, label);
3951
4023
  } else {
3952
4024
  throw new Error('Unknown image format.');
@@ -4117,6 +4189,9 @@ var ImagesMixin = {
4117
4189
  this.y += h;
4118
4190
  }
4119
4191
  this.save();
4192
+ if (options.opacity != null) {
4193
+ this._doOpacity(options.opacity, null);
4194
+ }
4120
4195
  if (rotateAngle) {
4121
4196
  this.rotate(rotateAngle, {
4122
4197
  origin: [originX, originY]
@@ -4394,21 +4469,41 @@ class PDFStructureElement {
4394
4469
  children = options;
4395
4470
  options = {};
4396
4471
  }
4397
- if (typeof options.title !== 'undefined') {
4472
+ if (options.title) {
4398
4473
  data.T = new String(options.title);
4399
4474
  }
4400
- if (typeof options.lang !== 'undefined') {
4475
+ if (options.lang) {
4401
4476
  data.Lang = new String(options.lang);
4402
4477
  }
4403
- if (typeof options.alt !== 'undefined') {
4478
+ if (options.alt) {
4404
4479
  data.Alt = new String(options.alt);
4405
4480
  }
4406
- if (typeof options.expanded !== 'undefined') {
4481
+ if (options.expanded) {
4407
4482
  data.E = new String(options.expanded);
4408
4483
  }
4409
- if (typeof options.actual !== 'undefined') {
4484
+ if (options.actual) {
4410
4485
  data.ActualText = new String(options.actual);
4411
4486
  }
4487
+ const hasBbox = Array.isArray(options.bbox) && options.bbox.length === 4;
4488
+ const hasPlacement = typeof options.placement === 'string';
4489
+ if (hasBbox || hasPlacement) {
4490
+ const attrs = {
4491
+ O: 'Layout'
4492
+ };
4493
+ attrs.Placement = hasPlacement ? options.placement : 'Block';
4494
+ if (hasBbox) {
4495
+ const height = document.page.height;
4496
+ attrs.BBox = [options.bbox[0], height - options.bbox[3], options.bbox[2], height - options.bbox[1]];
4497
+ }
4498
+ data.A = attrs;
4499
+ }
4500
+ if (options.scope) {
4501
+ data.A = {
4502
+ ...(data.A || {}),
4503
+ O: 'Table',
4504
+ Scope: options.scope
4505
+ };
4506
+ }
4412
4507
  this._children = [];
4413
4508
  if (children) {
4414
4509
  if (!Array.isArray(children)) {
@@ -5082,7 +5177,7 @@ var AttachmentsMixin = {
5082
5177
  const match = /^data:(.*?);base64,(.*)$/.exec(src);
5083
5178
  if (match) {
5084
5179
  if (match[1]) {
5085
- refBody.Subtype = match[1].replace('/', '#2F');
5180
+ refBody.Subtype = escapeName(match[1]);
5086
5181
  }
5087
5182
  data = Buffer.from(match[2], 'base64');
5088
5183
  } else {
@@ -5105,7 +5200,7 @@ var AttachmentsMixin = {
5105
5200
  refBody.Params.ModDate = options.modifiedDate;
5106
5201
  }
5107
5202
  if (options.type) {
5108
- refBody.Subtype = options.type.replace('/', '#2F');
5203
+ refBody.Subtype = escapeName(options.type);
5109
5204
  }
5110
5205
  const checksum = md5Hex(new Uint8Array(data));
5111
5206
  refBody.Params.CheckSum = new String(checksum);
@@ -5960,7 +6055,7 @@ var MetadataMixin = {
5960
6055
  }
5961
6056
  this.appendXML(`
5962
6057
  <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
5963
- <pdf:Producer>${this.info.Creator}</pdf:Producer>`, false);
6058
+ <pdf:Producer>${this.info.Producer}</pdf:Producer>`, false);
5964
6059
  if (this.info.Keywords) {
5965
6060
  this.appendXML(`
5966
6061
  <pdf:Keywords>${this.info.Keywords}</pdf:Keywords>`, false);