pdfkit 0.15.2 → 0.16.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
@@ -52,19 +52,39 @@ class PDFTree {
52
52
  out.push('>>');
53
53
  return out.join('\n');
54
54
  }
55
- _compareKeys( /*a, b*/
55
+ _compareKeys(/*a, b*/
56
56
  ) {
57
57
  throw new Error('Must be implemented by subclasses');
58
58
  }
59
59
  _keysName() {
60
60
  throw new Error('Must be implemented by subclasses');
61
61
  }
62
- _dataForKey( /*k*/
62
+ _dataForKey(/*k*/
63
63
  ) {
64
64
  throw new Error('Must be implemented by subclasses');
65
65
  }
66
66
  }
67
67
 
68
+ class SpotColor {
69
+ constructor(doc, name, C, M, Y, K) {
70
+ this.id = 'CS' + Object.keys(doc.spotColors).length;
71
+ this.name = name;
72
+ this.values = [C, M, Y, K];
73
+ this.ref = doc.ref(['Separation', this.name, 'DeviceCMYK', {
74
+ Range: [0, 1, 0, 1, 0, 1, 0, 1],
75
+ C0: [0, 0, 0, 0],
76
+ C1: this.values.map(value => value / 100),
77
+ FunctionType: 2,
78
+ Domain: [0, 1],
79
+ N: 1
80
+ }]);
81
+ this.ref.end();
82
+ }
83
+ toString() {
84
+ return `${this.ref.id} 0 R`;
85
+ }
86
+ }
87
+
68
88
  /*
69
89
  PDFObject - converts JavaScript types into their corresponding PDF types.
70
90
  By Devon Govett
@@ -136,7 +156,7 @@ class PDFObject {
136
156
  // Buffers are converted to PDF hex strings
137
157
  } else if (Buffer.isBuffer(object)) {
138
158
  return `<${object.toString('hex')}>`;
139
- } else if (object instanceof PDFAbstractReference || object instanceof PDFTree) {
159
+ } else if (object instanceof PDFAbstractReference || object instanceof PDFTree || object instanceof SpotColor) {
140
160
  return object.toString();
141
161
  } else if (object instanceof Date) {
142
162
  let string = `D:${pad(object.getUTCFullYear(), 4)}` + pad(object.getUTCMonth() + 1, 2) + pad(object.getUTCDate(), 2) + pad(object.getUTCHours(), 2) + pad(object.getUTCMinutes(), 2) + pad(object.getUTCSeconds(), 2) + 'Z';
@@ -190,7 +210,7 @@ class PDFReference extends PDFAbstractReference {
190
210
  this.buffer = [];
191
211
  }
192
212
  write(chunk) {
193
- if (!Buffer.isBuffer(chunk)) {
213
+ if (!(chunk instanceof Uint8Array)) {
194
214
  chunk = Buffer.from(chunk + '\n', 'binary');
195
215
  }
196
216
  this.uncompressedLength += chunk.length;
@@ -378,8 +398,20 @@ class PDFPage {
378
398
  write(chunk) {
379
399
  return this.content.write(chunk);
380
400
  }
401
+
402
+ // Set tab order if document is tagged for accessibility.
403
+ _setTabOrder() {
404
+ if (!this.dictionary.Tabs && this.document.hasMarkInfoDictionary()) {
405
+ this.dictionary.data.Tabs = 'S';
406
+ }
407
+ }
381
408
  end() {
409
+ this._setTabOrder();
382
410
  this.dictionary.end();
411
+ this.resources.data.ColorSpace = this.resources.data.ColorSpace || {};
412
+ for (let color of Object.values(this.document.spotColors)) {
413
+ this.resources.data.ColorSpace[color.id] = color;
414
+ }
383
415
  this.resources.end();
384
416
  return this.content.end();
385
417
  }
@@ -992,9 +1024,9 @@ function wordArrayToBuffer(wordArray) {
992
1024
  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];
993
1025
 
994
1026
  const {
995
- number
1027
+ number: number$2
996
1028
  } = PDFObject;
997
- class PDFGradient {
1029
+ class PDFGradient$1 {
998
1030
  constructor(doc) {
999
1031
  this.doc = doc;
1000
1032
  this.stops = [];
@@ -1081,7 +1113,7 @@ class PDFGradient {
1081
1113
  Type: 'Pattern',
1082
1114
  PatternType: 2,
1083
1115
  Shading: shader,
1084
- Matrix: this.matrix.map(number)
1116
+ Matrix: this.matrix.map(number$2)
1085
1117
  });
1086
1118
  pattern.end();
1087
1119
  if (this.stops.some(stop => stop[2] < 1)) {
@@ -1159,7 +1191,7 @@ class PDFGradient {
1159
1191
  return this.doc.addContent(`/${this.id} ${op}`);
1160
1192
  }
1161
1193
  }
1162
- class PDFLinearGradient extends PDFGradient {
1194
+ class PDFLinearGradient$1 extends PDFGradient$1 {
1163
1195
  constructor(doc, x1, y1, x2, y2) {
1164
1196
  super(doc);
1165
1197
  this.x1 = x1;
@@ -1177,10 +1209,10 @@ class PDFLinearGradient extends PDFGradient {
1177
1209
  });
1178
1210
  }
1179
1211
  opacityGradient() {
1180
- return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
1212
+ return new PDFLinearGradient$1(this.doc, this.x1, this.y1, this.x2, this.y2);
1181
1213
  }
1182
1214
  }
1183
- class PDFRadialGradient extends PDFGradient {
1215
+ class PDFRadialGradient$1 extends PDFGradient$1 {
1184
1216
  constructor(doc, x1, y1, r1, x2, y2, r2) {
1185
1217
  super(doc);
1186
1218
  this.doc = doc;
@@ -1201,13 +1233,13 @@ class PDFRadialGradient extends PDFGradient {
1201
1233
  });
1202
1234
  }
1203
1235
  opacityGradient() {
1204
- return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1236
+ return new PDFRadialGradient$1(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1205
1237
  }
1206
1238
  }
1207
1239
  var Gradient = {
1208
- PDFGradient,
1209
- PDFLinearGradient,
1210
- PDFRadialGradient
1240
+ PDFGradient: PDFGradient$1,
1241
+ PDFLinearGradient: PDFLinearGradient$1,
1242
+ PDFRadialGradient: PDFRadialGradient$1
1211
1243
  };
1212
1244
 
1213
1245
  /*
@@ -1215,7 +1247,7 @@ PDF tiling pattern support. Uncolored only.
1215
1247
  */
1216
1248
 
1217
1249
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1218
- class PDFTilingPattern {
1250
+ class PDFTilingPattern$1 {
1219
1251
  constructor(doc, bBox, xStep, yStep, stream) {
1220
1252
  this.doc = doc;
1221
1253
  this.bBox = bBox;
@@ -1293,19 +1325,20 @@ class PDFTilingPattern {
1293
1325
  }
1294
1326
  }
1295
1327
  var pattern = {
1296
- PDFTilingPattern
1328
+ PDFTilingPattern: PDFTilingPattern$1
1297
1329
  };
1298
1330
 
1299
1331
  const {
1300
- PDFGradient: PDFGradient$1,
1301
- PDFLinearGradient: PDFLinearGradient$1,
1302
- PDFRadialGradient: PDFRadialGradient$1
1332
+ PDFGradient,
1333
+ PDFLinearGradient,
1334
+ PDFRadialGradient
1303
1335
  } = Gradient;
1304
1336
  const {
1305
- PDFTilingPattern: PDFTilingPattern$1
1337
+ PDFTilingPattern
1306
1338
  } = pattern;
1307
1339
  var ColorMixin = {
1308
1340
  initColor() {
1341
+ this.spotColors = {};
1309
1342
  // The opacity dictionaries
1310
1343
  this._opacityRegistry = {};
1311
1344
  this._opacityCount = 0;
@@ -1322,6 +1355,8 @@ var ColorMixin = {
1322
1355
  color = [hex >> 16, hex >> 8 & 0xff, hex & 0xff];
1323
1356
  } else if (namedColors[color]) {
1324
1357
  color = namedColors[color];
1358
+ } else if (this.spotColors[color]) {
1359
+ return this.spotColors[color];
1325
1360
  }
1326
1361
  }
1327
1362
  if (Array.isArray(color)) {
@@ -1337,11 +1372,11 @@ var ColorMixin = {
1337
1372
  return null;
1338
1373
  },
1339
1374
  _setColor(color, stroke) {
1340
- if (color instanceof PDFGradient$1) {
1375
+ if (color instanceof PDFGradient) {
1341
1376
  color.apply(stroke);
1342
1377
  return true;
1343
1378
  // see if tiling pattern, decode & apply it it
1344
- } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern$1) {
1379
+ } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern) {
1345
1380
  color[0].apply(stroke, color[1]);
1346
1381
  return true;
1347
1382
  }
@@ -1356,8 +1391,12 @@ var ColorMixin = {
1356
1391
  const op = stroke ? 'SCN' : 'scn';
1357
1392
  const space = this._getColorSpace(color);
1358
1393
  this._setColorSpace(space, stroke);
1359
- color = color.join(' ');
1360
- this.addContent(`${color} ${op}`);
1394
+ if (color instanceof SpotColor) {
1395
+ this.page.colorSpaces[color.id] = color.ref;
1396
+ this.addContent(`1 ${op}`);
1397
+ } else {
1398
+ this.addContent(`${color.join(' ')} ${op}`);
1399
+ }
1361
1400
  return true;
1362
1401
  },
1363
1402
  _setColorSpace(space, stroke) {
@@ -1365,6 +1404,9 @@ var ColorMixin = {
1365
1404
  return this.addContent(`/${space} ${op}`);
1366
1405
  },
1367
1406
  _getColorSpace(color) {
1407
+ if (color instanceof SpotColor) {
1408
+ return color.id;
1409
+ }
1368
1410
  return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1369
1411
  },
1370
1412
  fillColor(color, opacity) {
@@ -1431,13 +1473,18 @@ var ColorMixin = {
1431
1473
  return this.addContent(`/${name} gs`);
1432
1474
  },
1433
1475
  linearGradient(x1, y1, x2, y2) {
1434
- return new PDFLinearGradient$1(this, x1, y1, x2, y2);
1476
+ return new PDFLinearGradient(this, x1, y1, x2, y2);
1435
1477
  },
1436
1478
  radialGradient(x1, y1, r1, x2, y2, r2) {
1437
- return new PDFRadialGradient$1(this, x1, y1, r1, x2, y2, r2);
1479
+ return new PDFRadialGradient(this, x1, y1, r1, x2, y2, r2);
1438
1480
  },
1439
1481
  pattern(bbox, xStep, yStep, stream) {
1440
- return new PDFTilingPattern$1(this, bbox, xStep, yStep, stream);
1482
+ return new PDFTilingPattern(this, bbox, xStep, yStep, stream);
1483
+ },
1484
+ addSpotColor(name, C, M, Y, K) {
1485
+ const color = new SpotColor(this, name, C, M, Y, K);
1486
+ this.spotColors[name] = color;
1487
+ return this;
1441
1488
  }
1442
1489
  };
1443
1490
  var namedColors = {
@@ -2677,7 +2724,7 @@ class EmbeddedFont extends PDFFont {
2677
2724
  if (isCFF) {
2678
2725
  fontFile.data.Subtype = 'CIDFontType0C';
2679
2726
  }
2680
- this.subset.encodeStream().on('data', data => fontFile.write(data)).on('end', () => fontFile.end());
2727
+ fontFile.end(this.subset.encode());
2681
2728
  const familyClass = ((this.font['OS/2'] != null ? this.font['OS/2'].sFamilyClass : undefined) || 0) >> 8;
2682
2729
  let flags = 0;
2683
2730
  if (this.font.post.isFixedPitch) {
@@ -2696,7 +2743,7 @@ class EmbeddedFont extends PDFFont {
2696
2743
 
2697
2744
  // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2698
2745
  const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)).join('');
2699
- const name = tag + '+' + this.font.postscriptName;
2746
+ const name = tag + '+' + this.font.postscriptName.replaceAll(' ', '_');
2700
2747
  const {
2701
2748
  bbox
2702
2749
  } = this.font;
@@ -2818,12 +2865,10 @@ class PDFFontFactory {
2818
2865
  }
2819
2866
  src = fs.readFileSync(src);
2820
2867
  }
2821
- if (Buffer.isBuffer(src)) {
2868
+ if (src instanceof Uint8Array) {
2822
2869
  font = fontkit.create(src, family);
2823
- } else if (src instanceof Uint8Array) {
2824
- font = fontkit.create(Buffer.from(src), family);
2825
2870
  } else if (src instanceof ArrayBuffer) {
2826
- font = fontkit.create(Buffer.from(new Uint8Array(src)), family);
2871
+ font = fontkit.create(new Uint8Array(src), family);
2827
2872
  }
2828
2873
  if (font == null) {
2829
2874
  throw new Error('Not a supported font format or standard PDF font.');
@@ -2832,6 +2877,18 @@ class PDFFontFactory {
2832
2877
  }
2833
2878
  }
2834
2879
 
2880
+ const isEqualFont = (font1, font2) => {
2881
+ // compare font checksum
2882
+ if (font1.font._tables?.head?.checkSumAdjustment !== font2.font._tables?.head?.checkSumAdjustment) {
2883
+ return false;
2884
+ }
2885
+
2886
+ // compare font name table
2887
+ if (JSON.stringify(font1.font._tables?.name?.records) !== JSON.stringify(font2.font._tables?.name?.records)) {
2888
+ return false;
2889
+ }
2890
+ return true;
2891
+ };
2835
2892
  var FontsMixin = {
2836
2893
  initFonts(defaultFont = 'Helvetica') {
2837
2894
  // Lookup table for embedded fonts
@@ -2884,7 +2941,7 @@ var FontsMixin = {
2884
2941
 
2885
2942
  // check for existing font familes with the same name already in the PDF
2886
2943
  // useful if the font was passed as a buffer
2887
- if (font = this._fontFamilies[this._font.name]) {
2944
+ if ((font = this._fontFamilies[this._font.name]) && isEqualFont(this._font, font)) {
2888
2945
  this._font = font;
2889
2946
  return this;
2890
2947
  }
@@ -2923,12 +2980,13 @@ class LineWrapper extends events.EventEmitter {
2923
2980
  constructor(document, options) {
2924
2981
  super();
2925
2982
  this.document = document;
2926
- this.indent = options.indent || 0;
2927
- this.characterSpacing = options.characterSpacing || 0;
2928
- this.wordSpacing = options.wordSpacing === 0;
2983
+ this.horizontalScaling = options.horizontalScaling || 100;
2984
+ this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2985
+ this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2986
+ this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2929
2987
  this.columns = options.columns || 1;
2930
- this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
2931
- this.lineWidth = (options.width - this.columnGap * (this.columns - 1)) / this.columns;
2988
+ this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100; // 1/4 inch
2989
+ this.lineWidth = (options.width * this.horizontalScaling / 100 - this.columnGap * (this.columns - 1)) / this.columns;
2932
2990
  this.spaceLeft = this.lineWidth;
2933
2991
  this.startX = this.document.x;
2934
2992
  this.startY = this.document.y;
@@ -2953,6 +3011,14 @@ class LineWrapper extends events.EventEmitter {
2953
3011
  const indent = this.continuedX || this.indent;
2954
3012
  this.document.x += indent;
2955
3013
  this.lineWidth -= indent;
3014
+
3015
+ // if indentAllLines is set to true
3016
+ // we're not resetting the indentation for this paragraph after the first line
3017
+ if (options.indentAllLines) {
3018
+ return;
3019
+ }
3020
+
3021
+ // otherwise we start the next line without indent
2956
3022
  return this.once('line', () => {
2957
3023
  this.document.x -= indent;
2958
3024
  this.lineWidth += indent;
@@ -3063,14 +3129,15 @@ class LineWrapper extends events.EventEmitter {
3063
3129
  }
3064
3130
  wrap(text, options) {
3065
3131
  // override options from previous continued fragments
3132
+ this.horizontalScaling = options.horizontalScaling || 100;
3066
3133
  if (options.indent != null) {
3067
- this.indent = options.indent;
3134
+ this.indent = options.indent * this.horizontalScaling / 100;
3068
3135
  }
3069
3136
  if (options.characterSpacing != null) {
3070
- this.characterSpacing = options.characterSpacing;
3137
+ this.characterSpacing = options.characterSpacing * this.horizontalScaling / 100;
3071
3138
  }
3072
3139
  if (options.wordSpacing != null) {
3073
- this.wordSpacing = options.wordSpacing;
3140
+ this.wordSpacing = options.wordSpacing * this.horizontalScaling / 100;
3074
3141
  }
3075
3142
  if (options.ellipsis != null) {
3076
3143
  this.ellipsis = options.ellipsis;
@@ -3228,7 +3295,7 @@ class LineWrapper extends events.EventEmitter {
3228
3295
  }
3229
3296
 
3230
3297
  const {
3231
- number: number$2
3298
+ number
3232
3299
  } = PDFObject;
3233
3300
  var TextMixin = {
3234
3301
  initText() {
@@ -3297,7 +3364,8 @@ var TextMixin = {
3297
3364
  return this._text(text, x, y, options, this._line);
3298
3365
  },
3299
3366
  widthOfString(string, options = {}) {
3300
- return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1);
3367
+ const horizontalScaling = options.horizontalScaling || 100;
3368
+ return (this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1)) * horizontalScaling / 100;
3301
3369
  },
3302
3370
  heightOfString(text, options) {
3303
3371
  const {
@@ -3470,7 +3538,7 @@ var TextMixin = {
3470
3538
  this._fragment(text, this.x, this.y, options);
3471
3539
  const lineGap = options.lineGap || this._lineGap || 0;
3472
3540
  if (!wrapper) {
3473
- return this.x += this.widthOfString(text);
3541
+ return this.x += this.widthOfString(text, options);
3474
3542
  } else {
3475
3543
  return this.y += this.currentLineHeight(true) + lineGap;
3476
3544
  }
@@ -3486,6 +3554,7 @@ var TextMixin = {
3486
3554
  const align = options.align || 'left';
3487
3555
  let wordSpacing = options.wordSpacing || 0;
3488
3556
  const characterSpacing = options.characterSpacing || 0;
3557
+ const horizontalScaling = options.horizontalScaling || 100;
3489
3558
 
3490
3559
  // text alignments
3491
3560
  if (options.width) {
@@ -3612,10 +3681,10 @@ var TextMixin = {
3612
3681
  this.addContent('BT');
3613
3682
 
3614
3683
  // text position
3615
- this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3684
+ this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3616
3685
 
3617
3686
  // font and font size
3618
- this.addContent(`/${this._font.id} ${number$2(this._fontSize)} Tf`);
3687
+ this.addContent(`/${this._font.id} ${number(this._fontSize)} Tf`);
3619
3688
 
3620
3689
  // rendering mode
3621
3690
  const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
@@ -3625,7 +3694,12 @@ var TextMixin = {
3625
3694
 
3626
3695
  // Character spacing
3627
3696
  if (characterSpacing) {
3628
- this.addContent(`${number$2(characterSpacing)} Tc`);
3697
+ this.addContent(`${number(characterSpacing)} Tc`);
3698
+ }
3699
+
3700
+ // Horizontal scaling
3701
+ if (horizontalScaling !== 100) {
3702
+ this.addContent(`${horizontalScaling} Tz`);
3629
3703
  }
3630
3704
 
3631
3705
  // Add the actual text
@@ -3667,7 +3741,7 @@ var TextMixin = {
3667
3741
  if (last < cur) {
3668
3742
  const hex = encoded.slice(last, cur).join('');
3669
3743
  const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3670
- commands.push(`<${hex}> ${number$2(-advance)}`);
3744
+ commands.push(`<${hex}> ${number(-advance)}`);
3671
3745
  }
3672
3746
  return last = cur;
3673
3747
  };
@@ -3689,13 +3763,13 @@ var TextMixin = {
3689
3763
  flush(i);
3690
3764
 
3691
3765
  // Move the text position and flush just the current character
3692
- this.addContent(`1 0 0 1 ${number$2(x + pos.xOffset * scale)} ${number$2(y + pos.yOffset * scale)} Tm`);
3766
+ this.addContent(`1 0 0 1 ${number(x + pos.xOffset * scale)} ${number(y + pos.yOffset * scale)} Tm`);
3693
3767
  flush(i + 1);
3694
3768
  hadOffset = true;
3695
3769
  } else {
3696
3770
  // If the last character had an offset, reset the text position
3697
3771
  if (hadOffset) {
3698
- this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3772
+ this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3699
3773
  hadOffset = false;
3700
3774
  }
3701
3775
 
@@ -4198,7 +4272,9 @@ var AnnotationsMixin = {
4198
4272
  note(x, y, w, h, contents, options = {}) {
4199
4273
  options.Subtype = 'Text';
4200
4274
  options.Contents = new String(contents);
4201
- options.Name = 'Comment';
4275
+ if (options.Name == null) {
4276
+ options.Name = 'Comment';
4277
+ }
4202
4278
  if (options.color == null) {
4203
4279
  options.color = [243, 223, 92];
4204
4280
  }
@@ -4701,6 +4777,9 @@ var MarkingsMixin = {
4701
4777
  }
4702
4778
  return this._root.data.MarkInfo;
4703
4779
  },
4780
+ hasMarkInfoDictionary() {
4781
+ return !!this._root.data.MarkInfo;
4782
+ },
4704
4783
  getStructTreeRoot() {
4705
4784
  if (!this._root.data.StructTreeRoot) {
4706
4785
  this._root.data.StructTreeRoot = this.ref({