UNPKG

189 kBJavaScriptView Raw
1import stream from 'stream';
2import zlib from 'zlib';
3import CryptoJS from 'crypto-js';
4import fs from 'fs';
5import fontkit from 'fontkit';
6import { EventEmitter } from 'events';
7import LineBreaker from 'linebreak';
8import exif from 'jpeg-exif';
9import PNG from 'png-js';
10
11/*
12PDFAbstractReference - abstract class for PDF reference
13*/
14
15class PDFAbstractReference {
16 toString() {
17 throw new Error('Must be implemented by subclasses');
18 }
19}
20
21/*
22PDFTree - abstract base class for name and number tree objects
23*/
24class PDFTree {
25 constructor() {
26 let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
27 this._items = {};
28 // disable /Limits output for this tree
29 this.limits = typeof options.limits === 'boolean' ? options.limits : true;
30 }
31 add(key, val) {
32 return this._items[key] = val;
33 }
34 get(key) {
35 return this._items[key];
36 }
37 toString() {
38 // Needs to be sorted by key
39 const sortedKeys = Object.keys(this._items).sort((a, b) => this._compareKeys(a, b));
40 const out = ['<<'];
41 if (this.limits && sortedKeys.length > 1) {
42 const first = sortedKeys[0],
43 last = sortedKeys[sortedKeys.length - 1];
44 out.push(` /Limits ${PDFObject.convert([this._dataForKey(first), this._dataForKey(last)])}`);
45 }
46 out.push(` /${this._keysName()} [`);
47 for (let key of sortedKeys) {
48 out.push(` ${PDFObject.convert(this._dataForKey(key))} ${PDFObject.convert(this._items[key])}`);
49 }
50 out.push(']');
51 out.push('>>');
52 return out.join('\n');
53 }
54 _compareKeys( /*a, b*/
55 ) {
56 throw new Error('Must be implemented by subclasses');
57 }
58 _keysName() {
59 throw new Error('Must be implemented by subclasses');
60 }
61 _dataForKey( /*k*/
62 ) {
63 throw new Error('Must be implemented by subclasses');
64 }
65}
66
67/*
68PDFObject - converts JavaScript types into their corresponding PDF types.
69By Devon Govett
70*/
71const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
72const escapableRe = /[\n\r\t\b\f()\\]/g;
73const escapable = {
74 '\n': '\\n',
75 '\r': '\\r',
76 '\t': '\\t',
77 '\b': '\\b',
78 '\f': '\\f',
79 '\\': '\\\\',
80 '(': '\\(',
81 ')': '\\)'
82};
83
84// Convert little endian UTF-16 to big endian
85const swapBytes = function (buff) {
86 const l = buff.length;
87 if (l & 0x01) {
88 throw new Error('Buffer length must be even');
89 } else {
90 for (let i = 0, end = l - 1; i < end; i += 2) {
91 const a = buff[i];
92 buff[i] = buff[i + 1];
93 buff[i + 1] = a;
94 }
95 }
96 return buff;
97};
98class PDFObject {
99 static convert(object) {
100 let encryptFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
101 // String literals are converted to the PDF name type
102 if (typeof object === 'string') {
103 return `/${object}`;
104
105 // String objects are converted to PDF strings (UTF-16)
106 } else if (object instanceof String) {
107 let string = object;
108 // Detect if this is a unicode string
109 let isUnicode = false;
110 for (let i = 0, end = string.length; i < end; i++) {
111 if (string.charCodeAt(i) > 0x7f) {
112 isUnicode = true;
113 break;
114 }
115 }
116
117 // If so, encode it as big endian UTF-16
118 let stringBuffer;
119 if (isUnicode) {
120 stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, 'utf16le'));
121 } else {
122 stringBuffer = Buffer.from(string.valueOf(), 'ascii');
123 }
124
125 // Encrypt the string when necessary
126 if (encryptFn) {
127 string = encryptFn(stringBuffer).toString('binary');
128 } else {
129 string = stringBuffer.toString('binary');
130 }
131
132 // Escape characters as required by the spec
133 string = string.replace(escapableRe, c => escapable[c]);
134 return `(${string})`;
135
136 // Buffers are converted to PDF hex strings
137 } else if (Buffer.isBuffer(object)) {
138 return `<${object.toString('hex')}>`;
139 } else if (object instanceof PDFAbstractReference || object instanceof PDFTree) {
140 return object.toString();
141 } else if (object instanceof Date) {
142 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';
143
144 // Encrypt the string when necessary
145 if (encryptFn) {
146 string = encryptFn(Buffer.from(string, 'ascii')).toString('binary');
147
148 // Escape characters as required by the spec
149 string = string.replace(escapableRe, c => escapable[c]);
150 }
151 return `(${string})`;
152 } else if (Array.isArray(object)) {
153 const items = object.map(e => PDFObject.convert(e, encryptFn)).join(' ');
154 return `[${items}]`;
155 } else if ({}.toString.call(object) === '[object Object]') {
156 const out = ['<<'];
157 for (let key in object) {
158 const val = object[key];
159 out.push(`/${key} ${PDFObject.convert(val, encryptFn)}`);
160 }
161 out.push('>>');
162 return out.join('\n');
163 } else if (typeof object === 'number') {
164 return PDFObject.number(object);
165 } else {
166 return `${object}`;
167 }
168 }
169 static number(n) {
170 if (n > -1e21 && n < 1e21) {
171 return Math.round(n * 1e6) / 1e6;
172 }
173 throw new Error(`unsupported number: ${n}`);
174 }
175}
176
177/*
178PDFReference - represents a reference to another object in the PDF object heirarchy
179By Devon Govett
180*/
181class PDFReference extends PDFAbstractReference {
182 constructor(document, id) {
183 let data = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
184 super();
185 this.document = document;
186 this.id = id;
187 this.data = data;
188 this.gen = 0;
189 this.compress = this.document.compress && !this.data.Filter;
190 this.uncompressedLength = 0;
191 this.buffer = [];
192 }
193 write(chunk) {
194 if (!Buffer.isBuffer(chunk)) {
195 chunk = Buffer.from(chunk + '\n', 'binary');
196 }
197 this.uncompressedLength += chunk.length;
198 if (this.data.Length == null) {
199 this.data.Length = 0;
200 }
201 this.buffer.push(chunk);
202 this.data.Length += chunk.length;
203 if (this.compress) {
204 return this.data.Filter = 'FlateDecode';
205 }
206 }
207 end(chunk) {
208 if (chunk) {
209 this.write(chunk);
210 }
211 return this.finalize();
212 }
213 finalize() {
214 this.offset = this.document._offset;
215 const encryptFn = this.document._security ? this.document._security.getEncryptFn(this.id, this.gen) : null;
216 if (this.buffer.length) {
217 this.buffer = Buffer.concat(this.buffer);
218 if (this.compress) {
219 this.buffer = zlib.deflateSync(this.buffer);
220 }
221 if (encryptFn) {
222 this.buffer = encryptFn(this.buffer);
223 }
224 this.data.Length = this.buffer.length;
225 }
226 this.document._write(`${this.id} ${this.gen} obj`);
227 this.document._write(PDFObject.convert(this.data, encryptFn));
228 if (this.buffer.length) {
229 this.document._write('stream');
230 this.document._write(this.buffer);
231 this.buffer = []; // free up memory
232 this.document._write('\nendstream');
233 }
234 this.document._write('endobj');
235 this.document._refEnd(this);
236 }
237 toString() {
238 return `${this.id} ${this.gen} R`;
239 }
240}
241
242/*
243PDFPage - represents a single page in the PDF document
244By Devon Govett
245*/
246
247const DEFAULT_MARGINS = {
248 top: 72,
249 left: 72,
250 bottom: 72,
251 right: 72
252};
253const SIZES = {
254 '4A0': [4767.87, 6740.79],
255 '2A0': [3370.39, 4767.87],
256 A0: [2383.94, 3370.39],
257 A1: [1683.78, 2383.94],
258 A2: [1190.55, 1683.78],
259 A3: [841.89, 1190.55],
260 A4: [595.28, 841.89],
261 A5: [419.53, 595.28],
262 A6: [297.64, 419.53],
263 A7: [209.76, 297.64],
264 A8: [147.4, 209.76],
265 A9: [104.88, 147.4],
266 A10: [73.7, 104.88],
267 B0: [2834.65, 4008.19],
268 B1: [2004.09, 2834.65],
269 B2: [1417.32, 2004.09],
270 B3: [1000.63, 1417.32],
271 B4: [708.66, 1000.63],
272 B5: [498.9, 708.66],
273 B6: [354.33, 498.9],
274 B7: [249.45, 354.33],
275 B8: [175.75, 249.45],
276 B9: [124.72, 175.75],
277 B10: [87.87, 124.72],
278 C0: [2599.37, 3676.54],
279 C1: [1836.85, 2599.37],
280 C2: [1298.27, 1836.85],
281 C3: [918.43, 1298.27],
282 C4: [649.13, 918.43],
283 C5: [459.21, 649.13],
284 C6: [323.15, 459.21],
285 C7: [229.61, 323.15],
286 C8: [161.57, 229.61],
287 C9: [113.39, 161.57],
288 C10: [79.37, 113.39],
289 RA0: [2437.8, 3458.27],
290 RA1: [1729.13, 2437.8],
291 RA2: [1218.9, 1729.13],
292 RA3: [864.57, 1218.9],
293 RA4: [609.45, 864.57],
294 SRA0: [2551.18, 3628.35],
295 SRA1: [1814.17, 2551.18],
296 SRA2: [1275.59, 1814.17],
297 SRA3: [907.09, 1275.59],
298 SRA4: [637.8, 907.09],
299 EXECUTIVE: [521.86, 756.0],
300 FOLIO: [612.0, 936.0],
301 LEGAL: [612.0, 1008.0],
302 LETTER: [612.0, 792.0],
303 TABLOID: [792.0, 1224.0]
304};
305class PDFPage {
306 constructor(document) {
307 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
308 this.document = document;
309 this.size = options.size || 'letter';
310 this.layout = options.layout || 'portrait';
311
312 // process margins
313 if (typeof options.margin === 'number') {
314 this.margins = {
315 top: options.margin,
316 left: options.margin,
317 bottom: options.margin,
318 right: options.margin
319 };
320
321 // default to 1 inch margins
322 } else {
323 this.margins = options.margins || DEFAULT_MARGINS;
324 }
325
326 // calculate page dimensions
327 const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
328 this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
329 this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
330 this.content = this.document.ref();
331
332 // Initialize the Font, XObject, and ExtGState dictionaries
333 this.resources = this.document.ref({
334 ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
335 });
336
337 // The page dictionary
338 this.dictionary = this.document.ref({
339 Type: 'Page',
340 Parent: this.document._root.data.Pages,
341 MediaBox: [0, 0, this.width, this.height],
342 Contents: this.content,
343 Resources: this.resources
344 });
345 this.markings = [];
346 }
347
348 // Lazily create these objects
349 get fonts() {
350 const data = this.resources.data;
351 return data.Font != null ? data.Font : data.Font = {};
352 }
353 get xobjects() {
354 const data = this.resources.data;
355 return data.XObject != null ? data.XObject : data.XObject = {};
356 }
357 get ext_gstates() {
358 const data = this.resources.data;
359 return data.ExtGState != null ? data.ExtGState : data.ExtGState = {};
360 }
361 get patterns() {
362 const data = this.resources.data;
363 return data.Pattern != null ? data.Pattern : data.Pattern = {};
364 }
365 get colorSpaces() {
366 const data = this.resources.data;
367 return data.ColorSpace || (data.ColorSpace = {});
368 }
369 get annotations() {
370 const data = this.dictionary.data;
371 return data.Annots != null ? data.Annots : data.Annots = [];
372 }
373 get structParentTreeKey() {
374 const data = this.dictionary.data;
375 return data.StructParents != null ? data.StructParents : data.StructParents = this.document.createStructParentTreeNextKey();
376 }
377 maxY() {
378 return this.height - this.margins.bottom;
379 }
380 write(chunk) {
381 return this.content.write(chunk);
382 }
383 end() {
384 this.dictionary.end();
385 this.resources.end();
386 return this.content.end();
387 }
388}
389
390/*
391PDFNameTree - represents a name tree object
392*/
393class PDFNameTree extends PDFTree {
394 _compareKeys(a, b) {
395 return a.localeCompare(b);
396 }
397 _keysName() {
398 return "Names";
399 }
400 _dataForKey(k) {
401 return new String(k);
402 }
403}
404
405/**
406 * Check if value is in a range group.
407 * @param {number} value
408 * @param {number[]} rangeGroup
409 * @returns {boolean}
410 */
411function inRange(value, rangeGroup) {
412 if (value < rangeGroup[0]) return false;
413 let startRange = 0;
414 let endRange = rangeGroup.length / 2;
415 while (startRange <= endRange) {
416 const middleRange = Math.floor((startRange + endRange) / 2);
417
418 // actual array index
419 const arrayIndex = middleRange * 2;
420
421 // Check if value is in range pointed by actual index
422 if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
423 return true;
424 }
425 if (value > rangeGroup[arrayIndex + 1]) {
426 // Search Right Side Of Array
427 startRange = middleRange + 1;
428 } else {
429 // Search Left Side Of Array
430 endRange = middleRange - 1;
431 }
432 }
433 return false;
434}
435
436// prettier-ignore-start
437/**
438 * A.1 Unassigned code points in Unicode 3.2
439 * @link https://tools.ietf.org/html/rfc3454#appendix-A.1
440 */
441const unassigned_code_points = [0x0221, 0x0221, 0x0234, 0x024f, 0x02ae, 0x02af, 0x02ef, 0x02ff, 0x0350, 0x035f, 0x0370, 0x0373, 0x0376, 0x0379, 0x037b, 0x037d, 0x037f, 0x0383, 0x038b, 0x038b, 0x038d, 0x038d, 0x03a2, 0x03a2, 0x03cf, 0x03cf, 0x03f7, 0x03ff, 0x0487, 0x0487, 0x04cf, 0x04cf, 0x04f6, 0x04f7, 0x04fa, 0x04ff, 0x0510, 0x0530, 0x0557, 0x0558, 0x0560, 0x0560, 0x0588, 0x0588, 0x058b, 0x0590, 0x05a2, 0x05a2, 0x05ba, 0x05ba, 0x05c5, 0x05cf, 0x05eb, 0x05ef, 0x05f5, 0x060b, 0x060d, 0x061a, 0x061c, 0x061e, 0x0620, 0x0620, 0x063b, 0x063f, 0x0656, 0x065f, 0x06ee, 0x06ef, 0x06ff, 0x06ff, 0x070e, 0x070e, 0x072d, 0x072f, 0x074b, 0x077f, 0x07b2, 0x0900, 0x0904, 0x0904, 0x093a, 0x093b, 0x094e, 0x094f, 0x0955, 0x0957, 0x0971, 0x0980, 0x0984, 0x0984, 0x098d, 0x098e, 0x0991, 0x0992, 0x09a9, 0x09a9, 0x09b1, 0x09b1, 0x09b3, 0x09b5, 0x09ba, 0x09bb, 0x09bd, 0x09bd, 0x09c5, 0x09c6, 0x09c9, 0x09ca, 0x09ce, 0x09d6, 0x09d8, 0x09db, 0x09de, 0x09de, 0x09e4, 0x09e5, 0x09fb, 0x0a01, 0x0a03, 0x0a04, 0x0a0b, 0x0a0e, 0x0a11, 0x0a12, 0x0a29, 0x0a29, 0x0a31, 0x0a31, 0x0a34, 0x0a34, 0x0a37, 0x0a37, 0x0a3a, 0x0a3b, 0x0a3d, 0x0a3d, 0x0a43, 0x0a46, 0x0a49, 0x0a4a, 0x0a4e, 0x0a58, 0x0a5d, 0x0a5d, 0x0a5f, 0x0a65, 0x0a75, 0x0a80, 0x0a84, 0x0a84, 0x0a8c, 0x0a8c, 0x0a8e, 0x0a8e, 0x0a92, 0x0a92, 0x0aa9, 0x0aa9, 0x0ab1, 0x0ab1, 0x0ab4, 0x0ab4, 0x0aba, 0x0abb, 0x0ac6, 0x0ac6, 0x0aca, 0x0aca, 0x0ace, 0x0acf, 0x0ad1, 0x0adf, 0x0ae1, 0x0ae5, 0x0af0, 0x0b00, 0x0b04, 0x0b04, 0x0b0d, 0x0b0e, 0x0b11, 0x0b12, 0x0b29, 0x0b29, 0x0b31, 0x0b31, 0x0b34, 0x0b35, 0x0b3a, 0x0b3b, 0x0b44, 0x0b46, 0x0b49, 0x0b4a, 0x0b4e, 0x0b55, 0x0b58, 0x0b5b, 0x0b5e, 0x0b5e, 0x0b62, 0x0b65, 0x0b71, 0x0b81, 0x0b84, 0x0b84, 0x0b8b, 0x0b8d, 0x0b91, 0x0b91, 0x0b96, 0x0b98, 0x0b9b, 0x0b9b, 0x0b9d, 0x0b9d, 0x0ba0, 0x0ba2, 0x0ba5, 0x0ba7, 0x0bab, 0x0bad, 0x0bb6, 0x0bb6, 0x0bba, 0x0bbd, 0x0bc3, 0x0bc5, 0x0bc9, 0x0bc9, 0x0bce, 0x0bd6, 0x0bd8, 0x0be6, 0x0bf3, 0x0c00, 0x0c04, 0x0c04, 0x0c0d, 0x0c0d, 0x0c11, 0x0c11, 0x0c29, 0x0c29, 0x0c34, 0x0c34, 0x0c3a, 0x0c3d, 0x0c45, 0x0c45, 0x0c49, 0x0c49, 0x0c4e, 0x0c54, 0x0c57, 0x0c5f, 0x0c62, 0x0c65, 0x0c70, 0x0c81, 0x0c84, 0x0c84, 0x0c8d, 0x0c8d, 0x0c91, 0x0c91, 0x0ca9, 0x0ca9, 0x0cb4, 0x0cb4, 0x0cba, 0x0cbd, 0x0cc5, 0x0cc5, 0x0cc9, 0x0cc9, 0x0cce, 0x0cd4, 0x0cd7, 0x0cdd, 0x0cdf, 0x0cdf, 0x0ce2, 0x0ce5, 0x0cf0, 0x0d01, 0x0d04, 0x0d04, 0x0d0d, 0x0d0d, 0x0d11, 0x0d11, 0x0d29, 0x0d29, 0x0d3a, 0x0d3d, 0x0d44, 0x0d45, 0x0d49, 0x0d49, 0x0d4e, 0x0d56, 0x0d58, 0x0d5f, 0x0d62, 0x0d65, 0x0d70, 0x0d81, 0x0d84, 0x0d84, 0x0d97, 0x0d99, 0x0db2, 0x0db2, 0x0dbc, 0x0dbc, 0x0dbe, 0x0dbf, 0x0dc7, 0x0dc9, 0x0dcb, 0x0dce, 0x0dd5, 0x0dd5, 0x0dd7, 0x0dd7, 0x0de0, 0x0df1, 0x0df5, 0x0e00, 0x0e3b, 0x0e3e, 0x0e5c, 0x0e80, 0x0e83, 0x0e83, 0x0e85, 0x0e86, 0x0e89, 0x0e89, 0x0e8b, 0x0e8c, 0x0e8e, 0x0e93, 0x0e98, 0x0e98, 0x0ea0, 0x0ea0, 0x0ea4, 0x0ea4, 0x0ea6, 0x0ea6, 0x0ea8, 0x0ea9, 0x0eac, 0x0eac, 0x0eba, 0x0eba, 0x0ebe, 0x0ebf, 0x0ec5, 0x0ec5, 0x0ec7, 0x0ec7, 0x0ece, 0x0ecf, 0x0eda, 0x0edb, 0x0ede, 0x0eff, 0x0f48, 0x0f48, 0x0f6b, 0x0f70, 0x0f8c, 0x0f8f, 0x0f98, 0x0f98, 0x0fbd, 0x0fbd, 0x0fcd, 0x0fce, 0x0fd0, 0x0fff, 0x1022, 0x1022, 0x1028, 0x1028, 0x102b, 0x102b, 0x1033, 0x1035, 0x103a, 0x103f, 0x105a, 0x109f, 0x10c6, 0x10cf, 0x10f9, 0x10fa, 0x10fc, 0x10ff, 0x115a, 0x115e, 0x11a3, 0x11a7, 0x11fa, 0x11ff, 0x1207, 0x1207, 0x1247, 0x1247, 0x1249, 0x1249, 0x124e, 0x124f, 0x1257, 0x1257, 0x1259, 0x1259, 0x125e, 0x125f, 0x1287, 0x1287, 0x1289, 0x1289, 0x128e, 0x128f, 0x12af, 0x12af, 0x12b1, 0x12b1, 0x12b6, 0x12b7, 0x12bf, 0x12bf, 0x12c1, 0x12c1, 0x12c6, 0x12c7, 0x12cf, 0x12cf, 0x12d7, 0x12d7, 0x12ef, 0x12ef, 0x130f, 0x130f, 0x1311, 0x1311, 0x1316, 0x1317, 0x131f, 0x131f, 0x1347, 0x1347, 0x135b, 0x1360, 0x137d, 0x139f, 0x13f5, 0x1400, 0x1677, 0x167f, 0x169d, 0x169f, 0x16f1, 0x16ff, 0x170d, 0x170d, 0x1715, 0x171f, 0x1737, 0x173f, 0x1754, 0x175f, 0x176d, 0x176d, 0x1771, 0x1771, 0x1774, 0x177f, 0x17dd, 0x17df, 0x17ea, 0x17ff, 0x180f, 0x180f, 0x181a, 0x181f, 0x1878, 0x187f, 0x18aa, 0x1dff, 0x1e9c, 0x1e9f, 0x1efa, 0x1eff, 0x1f16, 0x1f17, 0x1f1e, 0x1f1f, 0x1f46, 0x1f47, 0x1f4e, 0x1f4f, 0x1f58, 0x1f58, 0x1f5a, 0x1f5a, 0x1f5c, 0x1f5c, 0x1f5e, 0x1f5e, 0x1f7e, 0x1f7f, 0x1fb5, 0x1fb5, 0x1fc5, 0x1fc5, 0x1fd4, 0x1fd5, 0x1fdc, 0x1fdc, 0x1ff0, 0x1ff1, 0x1ff5, 0x1ff5, 0x1fff, 0x1fff, 0x2053, 0x2056, 0x2058, 0x205e, 0x2064, 0x2069, 0x2072, 0x2073, 0x208f, 0x209f, 0x20b2, 0x20cf, 0x20eb, 0x20ff, 0x213b, 0x213c, 0x214c, 0x2152, 0x2184, 0x218f, 0x23cf, 0x23ff, 0x2427, 0x243f, 0x244b, 0x245f, 0x24ff, 0x24ff, 0x2614, 0x2615, 0x2618, 0x2618, 0x267e, 0x267f, 0x268a, 0x2700, 0x2705, 0x2705, 0x270a, 0x270b, 0x2728, 0x2728, 0x274c, 0x274c, 0x274e, 0x274e, 0x2753, 0x2755, 0x2757, 0x2757, 0x275f, 0x2760, 0x2795, 0x2797, 0x27b0, 0x27b0, 0x27bf, 0x27cf, 0x27ec, 0x27ef, 0x2b00, 0x2e7f, 0x2e9a, 0x2e9a, 0x2ef4, 0x2eff, 0x2fd6, 0x2fef, 0x2ffc, 0x2fff, 0x3040, 0x3040, 0x3097, 0x3098, 0x3100, 0x3104, 0x312d, 0x3130, 0x318f, 0x318f, 0x31b8, 0x31ef, 0x321d, 0x321f, 0x3244, 0x3250, 0x327c, 0x327e, 0x32cc, 0x32cf, 0x32ff, 0x32ff, 0x3377, 0x337a, 0x33de, 0x33df, 0x33ff, 0x33ff, 0x4db6, 0x4dff, 0x9fa6, 0x9fff, 0xa48d, 0xa48f, 0xa4c7, 0xabff, 0xd7a4, 0xd7ff, 0xfa2e, 0xfa2f, 0xfa6b, 0xfaff, 0xfb07, 0xfb12, 0xfb18, 0xfb1c, 0xfb37, 0xfb37, 0xfb3d, 0xfb3d, 0xfb3f, 0xfb3f, 0xfb42, 0xfb42, 0xfb45, 0xfb45, 0xfbb2, 0xfbd2, 0xfd40, 0xfd4f, 0xfd90, 0xfd91, 0xfdc8, 0xfdcf, 0xfdfd, 0xfdff, 0xfe10, 0xfe1f, 0xfe24, 0xfe2f, 0xfe47, 0xfe48, 0xfe53, 0xfe53, 0xfe67, 0xfe67, 0xfe6c, 0xfe6f, 0xfe75, 0xfe75, 0xfefd, 0xfefe, 0xff00, 0xff00, 0xffbf, 0xffc1, 0xffc8, 0xffc9, 0xffd0, 0xffd1, 0xffd8, 0xffd9, 0xffdd, 0xffdf, 0xffe7, 0xffe7, 0xffef, 0xfff8, 0x10000, 0x102ff, 0x1031f, 0x1031f, 0x10324, 0x1032f, 0x1034b, 0x103ff, 0x10426, 0x10427, 0x1044e, 0x1cfff, 0x1d0f6, 0x1d0ff, 0x1d127, 0x1d129, 0x1d1de, 0x1d3ff, 0x1d455, 0x1d455, 0x1d49d, 0x1d49d, 0x1d4a0, 0x1d4a1, 0x1d4a3, 0x1d4a4, 0x1d4a7, 0x1d4a8, 0x1d4ad, 0x1d4ad, 0x1d4ba, 0x1d4ba, 0x1d4bc, 0x1d4bc, 0x1d4c1, 0x1d4c1, 0x1d4c4, 0x1d4c4, 0x1d506, 0x1d506, 0x1d50b, 0x1d50c, 0x1d515, 0x1d515, 0x1d51d, 0x1d51d, 0x1d53a, 0x1d53a, 0x1d53f, 0x1d53f, 0x1d545, 0x1d545, 0x1d547, 0x1d549, 0x1d551, 0x1d551, 0x1d6a4, 0x1d6a7, 0x1d7ca, 0x1d7cd, 0x1d800, 0x1fffd, 0x2a6d7, 0x2f7ff, 0x2fa1e, 0x2fffd, 0x30000, 0x3fffd, 0x40000, 0x4fffd, 0x50000, 0x5fffd, 0x60000, 0x6fffd, 0x70000, 0x7fffd, 0x80000, 0x8fffd, 0x90000, 0x9fffd, 0xa0000, 0xafffd, 0xb0000, 0xbfffd, 0xc0000, 0xcfffd, 0xd0000, 0xdfffd, 0xe0000, 0xe0000, 0xe0002, 0xe001f, 0xe0080, 0xefffd];
442// prettier-ignore-end
443
444const isUnassignedCodePoint = character => inRange(character, unassigned_code_points);
445
446// prettier-ignore-start
447/**
448 * B.1 Commonly mapped to nothing
449 * @link https://tools.ietf.org/html/rfc3454#appendix-B.1
450 */
451const commonly_mapped_to_nothing = [0x00ad, 0x00ad, 0x034f, 0x034f, 0x1806, 0x1806, 0x180b, 0x180b, 0x180c, 0x180c, 0x180d, 0x180d, 0x200b, 0x200b, 0x200c, 0x200c, 0x200d, 0x200d, 0x2060, 0x2060, 0xfe00, 0xfe00, 0xfe01, 0xfe01, 0xfe02, 0xfe02, 0xfe03, 0xfe03, 0xfe04, 0xfe04, 0xfe05, 0xfe05, 0xfe06, 0xfe06, 0xfe07, 0xfe07, 0xfe08, 0xfe08, 0xfe09, 0xfe09, 0xfe0a, 0xfe0a, 0xfe0b, 0xfe0b, 0xfe0c, 0xfe0c, 0xfe0d, 0xfe0d, 0xfe0e, 0xfe0e, 0xfe0f, 0xfe0f, 0xfeff, 0xfeff];
452// prettier-ignore-end
453
454const isCommonlyMappedToNothing = character => inRange(character, commonly_mapped_to_nothing);
455
456// prettier-ignore-start
457/**
458 * C.1.2 Non-ASCII space characters
459 * @link https://tools.ietf.org/html/rfc3454#appendix-C.1.2
460 */
461const non_ASCII_space_characters = [0x00a0, 0x00a0 /* NO-BREAK SPACE */, 0x1680, 0x1680 /* OGHAM SPACE MARK */, 0x2000, 0x2000 /* EN QUAD */, 0x2001, 0x2001 /* EM QUAD */, 0x2002, 0x2002 /* EN SPACE */, 0x2003, 0x2003 /* EM SPACE */, 0x2004, 0x2004 /* THREE-PER-EM SPACE */, 0x2005, 0x2005 /* FOUR-PER-EM SPACE */, 0x2006, 0x2006 /* SIX-PER-EM SPACE */, 0x2007, 0x2007 /* FIGURE SPACE */, 0x2008, 0x2008 /* PUNCTUATION SPACE */, 0x2009, 0x2009 /* THIN SPACE */, 0x200a, 0x200a /* HAIR SPACE */, 0x200b, 0x200b /* ZERO WIDTH SPACE */, 0x202f, 0x202f /* NARROW NO-BREAK SPACE */, 0x205f, 0x205f /* MEDIUM MATHEMATICAL SPACE */, 0x3000, 0x3000 /* IDEOGRAPHIC SPACE */];
462// prettier-ignore-end
463
464const isNonASCIISpaceCharacter = character => inRange(character, non_ASCII_space_characters);
465
466// prettier-ignore-start
467const non_ASCII_controls_characters = [
468/**
469 * C.2.2 Non-ASCII control characters
470 * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.2
471 */
4720x0080, 0x009f /* [CONTROL CHARACTERS] */, 0x06dd, 0x06dd /* ARABIC END OF AYAH */, 0x070f, 0x070f /* SYRIAC ABBREVIATION MARK */, 0x180e, 0x180e /* MONGOLIAN VOWEL SEPARATOR */, 0x200c, 0x200c /* ZERO WIDTH NON-JOINER */, 0x200d, 0x200d /* ZERO WIDTH JOINER */, 0x2028, 0x2028 /* LINE SEPARATOR */, 0x2029, 0x2029 /* PARAGRAPH SEPARATOR */, 0x2060, 0x2060 /* WORD JOINER */, 0x2061, 0x2061 /* FUNCTION APPLICATION */, 0x2062, 0x2062 /* INVISIBLE TIMES */, 0x2063, 0x2063 /* INVISIBLE SEPARATOR */, 0x206a, 0x206f /* [CONTROL CHARACTERS] */, 0xfeff, 0xfeff /* ZERO WIDTH NO-BREAK SPACE */, 0xfff9, 0xfffc /* [CONTROL CHARACTERS] */, 0x1d173, 0x1d17a /* [MUSICAL CONTROL CHARACTERS] */];
473const non_character_codepoints = [
474/**
475 * C.4 Non-character code points
476 * @link https://tools.ietf.org/html/rfc3454#appendix-C.4
477 */
4780xfdd0, 0xfdef /* [NONCHARACTER CODE POINTS] */, 0xfffe, 0xffff /* [NONCHARACTER CODE POINTS] */, 0x1fffe, 0x1ffff /* [NONCHARACTER CODE POINTS] */, 0x2fffe, 0x2ffff /* [NONCHARACTER CODE POINTS] */, 0x3fffe, 0x3ffff /* [NONCHARACTER CODE POINTS] */, 0x4fffe, 0x4ffff /* [NONCHARACTER CODE POINTS] */, 0x5fffe, 0x5ffff /* [NONCHARACTER CODE POINTS] */, 0x6fffe, 0x6ffff /* [NONCHARACTER CODE POINTS] */, 0x7fffe, 0x7ffff /* [NONCHARACTER CODE POINTS] */, 0x8fffe, 0x8ffff /* [NONCHARACTER CODE POINTS] */, 0x9fffe, 0x9ffff /* [NONCHARACTER CODE POINTS] */, 0xafffe, 0xaffff /* [NONCHARACTER CODE POINTS] */, 0xbfffe, 0xbffff /* [NONCHARACTER CODE POINTS] */, 0xcfffe, 0xcffff /* [NONCHARACTER CODE POINTS] */, 0xdfffe, 0xdffff /* [NONCHARACTER CODE POINTS] */, 0xefffe, 0xeffff /* [NONCHARACTER CODE POINTS] */, 0x10fffe, 0x10ffff /* [NONCHARACTER CODE POINTS] */];
479
480/**
481 * 2.3. Prohibited Output
482 */
483const prohibited_characters = [
484/**
485 * C.2.1 ASCII control characters
486 * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.1
487 */
4880, 0x001f /* [CONTROL CHARACTERS] */, 0x007f, 0x007f /* DELETE */,
489/**
490 * C.8 Change display properties or are deprecated
491 * @link https://tools.ietf.org/html/rfc3454#appendix-C.8
492 */
4930x0340, 0x0340 /* COMBINING GRAVE TONE MARK */, 0x0341, 0x0341 /* COMBINING ACUTE TONE MARK */, 0x200e, 0x200e /* LEFT-TO-RIGHT MARK */, 0x200f, 0x200f /* RIGHT-TO-LEFT MARK */, 0x202a, 0x202a /* LEFT-TO-RIGHT EMBEDDING */, 0x202b, 0x202b /* RIGHT-TO-LEFT EMBEDDING */, 0x202c, 0x202c /* POP DIRECTIONAL FORMATTING */, 0x202d, 0x202d /* LEFT-TO-RIGHT OVERRIDE */, 0x202e, 0x202e /* RIGHT-TO-LEFT OVERRIDE */, 0x206a, 0x206a /* INHIBIT SYMMETRIC SWAPPING */, 0x206b, 0x206b /* ACTIVATE SYMMETRIC SWAPPING */, 0x206c, 0x206c /* INHIBIT ARABIC FORM SHAPING */, 0x206d, 0x206d /* ACTIVATE ARABIC FORM SHAPING */, 0x206e, 0x206e /* NATIONAL DIGIT SHAPES */, 0x206f, 0x206f /* NOMINAL DIGIT SHAPES */,
494/**
495 * C.7 Inappropriate for canonical representation
496 * @link https://tools.ietf.org/html/rfc3454#appendix-C.7
497 */
4980x2ff0, 0x2ffb /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */,
499/**
500 * C.5 Surrogate codes
501 * @link https://tools.ietf.org/html/rfc3454#appendix-C.5
502 */
5030xd800, 0xdfff,
504/**
505 * C.3 Private use
506 * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
507 */
5080xe000, 0xf8ff /* [PRIVATE USE, PLANE 0] */,
509/**
510 * C.6 Inappropriate for plain text
511 * @link https://tools.ietf.org/html/rfc3454#appendix-C.6
512 */
5130xfff9, 0xfff9 /* INTERLINEAR ANNOTATION ANCHOR */, 0xfffa, 0xfffa /* INTERLINEAR ANNOTATION SEPARATOR */, 0xfffb, 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */, 0xfffc, 0xfffc /* OBJECT REPLACEMENT CHARACTER */, 0xfffd, 0xfffd /* REPLACEMENT CHARACTER */,
514/**
515 * C.9 Tagging characters
516 * @link https://tools.ietf.org/html/rfc3454#appendix-C.9
517 */
5180xe0001, 0xe0001 /* LANGUAGE TAG */, 0xe0020, 0xe007f /* [TAGGING CHARACTERS] */,
519/**
520 * C.3 Private use
521 * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
522 */
523
5240xf0000, 0xffffd /* [PRIVATE USE, PLANE 15] */, 0x100000, 0x10fffd /* [PRIVATE USE, PLANE 16] */];
525// prettier-ignore-end
526
527const isProhibitedCharacter = character => inRange(character, non_ASCII_space_characters) || inRange(character, prohibited_characters) || inRange(character, non_ASCII_controls_characters) || inRange(character, non_character_codepoints);
528
529// prettier-ignore-start
530/**
531 * D.1 Characters with bidirectional property "R" or "AL"
532 * @link https://tools.ietf.org/html/rfc3454#appendix-D.1
533 */
534const bidirectional_r_al = [0x05be, 0x05be, 0x05c0, 0x05c0, 0x05c3, 0x05c3, 0x05d0, 0x05ea, 0x05f0, 0x05f4, 0x061b, 0x061b, 0x061f, 0x061f, 0x0621, 0x063a, 0x0640, 0x064a, 0x066d, 0x066f, 0x0671, 0x06d5, 0x06dd, 0x06dd, 0x06e5, 0x06e6, 0x06fa, 0x06fe, 0x0700, 0x070d, 0x0710, 0x0710, 0x0712, 0x072c, 0x0780, 0x07a5, 0x07b1, 0x07b1, 0x200f, 0x200f, 0xfb1d, 0xfb1d, 0xfb1f, 0xfb28, 0xfb2a, 0xfb36, 0xfb38, 0xfb3c, 0xfb3e, 0xfb3e, 0xfb40, 0xfb41, 0xfb43, 0xfb44, 0xfb46, 0xfbb1, 0xfbd3, 0xfd3d, 0xfd50, 0xfd8f, 0xfd92, 0xfdc7, 0xfdf0, 0xfdfc, 0xfe70, 0xfe74, 0xfe76, 0xfefc];
535// prettier-ignore-end
536
537const isBidirectionalRAL = character => inRange(character, bidirectional_r_al);
538
539// prettier-ignore-start
540/**
541 * D.2 Characters with bidirectional property "L"
542 * @link https://tools.ietf.org/html/rfc3454#appendix-D.2
543 */
544const bidirectional_l = [0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, 0x00b5, 0x00b5, 0x00ba, 0x00ba, 0x00c0, 0x00d6, 0x00d8, 0x00f6, 0x00f8, 0x0220, 0x0222, 0x0233, 0x0250, 0x02ad, 0x02b0, 0x02b8, 0x02bb, 0x02c1, 0x02d0, 0x02d1, 0x02e0, 0x02e4, 0x02ee, 0x02ee, 0x037a, 0x037a, 0x0386, 0x0386, 0x0388, 0x038a, 0x038c, 0x038c, 0x038e, 0x03a1, 0x03a3, 0x03ce, 0x03d0, 0x03f5, 0x0400, 0x0482, 0x048a, 0x04ce, 0x04d0, 0x04f5, 0x04f8, 0x04f9, 0x0500, 0x050f, 0x0531, 0x0556, 0x0559, 0x055f, 0x0561, 0x0587, 0x0589, 0x0589, 0x0903, 0x0903, 0x0905, 0x0939, 0x093d, 0x0940, 0x0949, 0x094c, 0x0950, 0x0950, 0x0958, 0x0961, 0x0964, 0x0970, 0x0982, 0x0983, 0x0985, 0x098c, 0x098f, 0x0990, 0x0993, 0x09a8, 0x09aa, 0x09b0, 0x09b2, 0x09b2, 0x09b6, 0x09b9, 0x09be, 0x09c0, 0x09c7, 0x09c8, 0x09cb, 0x09cc, 0x09d7, 0x09d7, 0x09dc, 0x09dd, 0x09df, 0x09e1, 0x09e6, 0x09f1, 0x09f4, 0x09fa, 0x0a05, 0x0a0a, 0x0a0f, 0x0a10, 0x0a13, 0x0a28, 0x0a2a, 0x0a30, 0x0a32, 0x0a33, 0x0a35, 0x0a36, 0x0a38, 0x0a39, 0x0a3e, 0x0a40, 0x0a59, 0x0a5c, 0x0a5e, 0x0a5e, 0x0a66, 0x0a6f, 0x0a72, 0x0a74, 0x0a83, 0x0a83, 0x0a85, 0x0a8b, 0x0a8d, 0x0a8d, 0x0a8f, 0x0a91, 0x0a93, 0x0aa8, 0x0aaa, 0x0ab0, 0x0ab2, 0x0ab3, 0x0ab5, 0x0ab9, 0x0abd, 0x0ac0, 0x0ac9, 0x0ac9, 0x0acb, 0x0acc, 0x0ad0, 0x0ad0, 0x0ae0, 0x0ae0, 0x0ae6, 0x0aef, 0x0b02, 0x0b03, 0x0b05, 0x0b0c, 0x0b0f, 0x0b10, 0x0b13, 0x0b28, 0x0b2a, 0x0b30, 0x0b32, 0x0b33, 0x0b36, 0x0b39, 0x0b3d, 0x0b3e, 0x0b40, 0x0b40, 0x0b47, 0x0b48, 0x0b4b, 0x0b4c, 0x0b57, 0x0b57, 0x0b5c, 0x0b5d, 0x0b5f, 0x0b61, 0x0b66, 0x0b70, 0x0b83, 0x0b83, 0x0b85, 0x0b8a, 0x0b8e, 0x0b90, 0x0b92, 0x0b95, 0x0b99, 0x0b9a, 0x0b9c, 0x0b9c, 0x0b9e, 0x0b9f, 0x0ba3, 0x0ba4, 0x0ba8, 0x0baa, 0x0bae, 0x0bb5, 0x0bb7, 0x0bb9, 0x0bbe, 0x0bbf, 0x0bc1, 0x0bc2, 0x0bc6, 0x0bc8, 0x0bca, 0x0bcc, 0x0bd7, 0x0bd7, 0x0be7, 0x0bf2, 0x0c01, 0x0c03, 0x0c05, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c28, 0x0c2a, 0x0c33, 0x0c35, 0x0c39, 0x0c41, 0x0c44, 0x0c60, 0x0c61, 0x0c66, 0x0c6f, 0x0c82, 0x0c83, 0x0c85, 0x0c8c, 0x0c8e, 0x0c90, 0x0c92, 0x0ca8, 0x0caa, 0x0cb3, 0x0cb5, 0x0cb9, 0x0cbe, 0x0cbe, 0x0cc0, 0x0cc4, 0x0cc7, 0x0cc8, 0x0cca, 0x0ccb, 0x0cd5, 0x0cd6, 0x0cde, 0x0cde, 0x0ce0, 0x0ce1, 0x0ce6, 0x0cef, 0x0d02, 0x0d03, 0x0d05, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d28, 0x0d2a, 0x0d39, 0x0d3e, 0x0d40, 0x0d46, 0x0d48, 0x0d4a, 0x0d4c, 0x0d57, 0x0d57, 0x0d60, 0x0d61, 0x0d66, 0x0d6f, 0x0d82, 0x0d83, 0x0d85, 0x0d96, 0x0d9a, 0x0db1, 0x0db3, 0x0dbb, 0x0dbd, 0x0dbd, 0x0dc0, 0x0dc6, 0x0dcf, 0x0dd1, 0x0dd8, 0x0ddf, 0x0df2, 0x0df4, 0x0e01, 0x0e30, 0x0e32, 0x0e33, 0x0e40, 0x0e46, 0x0e4f, 0x0e5b, 0x0e81, 0x0e82, 0x0e84, 0x0e84, 0x0e87, 0x0e88, 0x0e8a, 0x0e8a, 0x0e8d, 0x0e8d, 0x0e94, 0x0e97, 0x0e99, 0x0e9f, 0x0ea1, 0x0ea3, 0x0ea5, 0x0ea5, 0x0ea7, 0x0ea7, 0x0eaa, 0x0eab, 0x0ead, 0x0eb0, 0x0eb2, 0x0eb3, 0x0ebd, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, 0x0ed0, 0x0ed9, 0x0edc, 0x0edd, 0x0f00, 0x0f17, 0x0f1a, 0x0f34, 0x0f36, 0x0f36, 0x0f38, 0x0f38, 0x0f3e, 0x0f47, 0x0f49, 0x0f6a, 0x0f7f, 0x0f7f, 0x0f85, 0x0f85, 0x0f88, 0x0f8b, 0x0fbe, 0x0fc5, 0x0fc7, 0x0fcc, 0x0fcf, 0x0fcf, 0x1000, 0x1021, 0x1023, 0x1027, 0x1029, 0x102a, 0x102c, 0x102c, 0x1031, 0x1031, 0x1038, 0x1038, 0x1040, 0x1057, 0x10a0, 0x10c5, 0x10d0, 0x10f8, 0x10fb, 0x10fb, 0x1100, 0x1159, 0x115f, 0x11a2, 0x11a8, 0x11f9, 0x1200, 0x1206, 0x1208, 0x1246, 0x1248, 0x1248, 0x124a, 0x124d, 0x1250, 0x1256, 0x1258, 0x1258, 0x125a, 0x125d, 0x1260, 0x1286, 0x1288, 0x1288, 0x128a, 0x128d, 0x1290, 0x12ae, 0x12b0, 0x12b0, 0x12b2, 0x12b5, 0x12b8, 0x12be, 0x12c0, 0x12c0, 0x12c2, 0x12c5, 0x12c8, 0x12ce, 0x12d0, 0x12d6, 0x12d8, 0x12ee, 0x12f0, 0x130e, 0x1310, 0x1310, 0x1312, 0x1315, 0x1318, 0x131e, 0x1320, 0x1346, 0x1348, 0x135a, 0x1361, 0x137c, 0x13a0, 0x13f4, 0x1401, 0x1676, 0x1681, 0x169a, 0x16a0, 0x16f0, 0x1700, 0x170c, 0x170e, 0x1711, 0x1720, 0x1731, 0x1735, 0x1736, 0x1740, 0x1751, 0x1760, 0x176c, 0x176e, 0x1770, 0x1780, 0x17b6, 0x17be, 0x17c5, 0x17c7, 0x17c8, 0x17d4, 0x17da, 0x17dc, 0x17dc, 0x17e0, 0x17e9, 0x1810, 0x1819, 0x1820, 0x1877, 0x1880, 0x18a8, 0x1e00, 0x1e9b, 0x1ea0, 0x1ef9, 0x1f00, 0x1f15, 0x1f18, 0x1f1d, 0x1f20, 0x1f45, 0x1f48, 0x1f4d, 0x1f50, 0x1f57, 0x1f59, 0x1f59, 0x1f5b, 0x1f5b, 0x1f5d, 0x1f5d, 0x1f5f, 0x1f7d, 0x1f80, 0x1fb4, 0x1fb6, 0x1fbc, 0x1fbe, 0x1fbe, 0x1fc2, 0x1fc4, 0x1fc6, 0x1fcc, 0x1fd0, 0x1fd3, 0x1fd6, 0x1fdb, 0x1fe0, 0x1fec, 0x1ff2, 0x1ff4, 0x1ff6, 0x1ffc, 0x200e, 0x200e, 0x2071, 0x2071, 0x207f, 0x207f, 0x2102, 0x2102, 0x2107, 0x2107, 0x210a, 0x2113, 0x2115, 0x2115, 0x2119, 0x211d, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212a, 0x212d, 0x212f, 0x2131, 0x2133, 0x2139, 0x213d, 0x213f, 0x2145, 0x2149, 0x2160, 0x2183, 0x2336, 0x237a, 0x2395, 0x2395, 0x249c, 0x24e9, 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303c, 0x3041, 0x3096, 0x309d, 0x309f, 0x30a1, 0x30fa, 0x30fc, 0x30ff, 0x3105, 0x312c, 0x3131, 0x318e, 0x3190, 0x31b7, 0x31f0, 0x321c, 0x3220, 0x3243, 0x3260, 0x327b, 0x327f, 0x32b0, 0x32c0, 0x32cb, 0x32d0, 0x32fe, 0x3300, 0x3376, 0x337b, 0x33dd, 0x33e0, 0x33fe, 0x3400, 0x4db5, 0x4e00, 0x9fa5, 0xa000, 0xa48c, 0xac00, 0xd7a3, 0xd800, 0xfa2d, 0xfa30, 0xfa6a, 0xfb00, 0xfb06, 0xfb13, 0xfb17, 0xff21, 0xff3a, 0xff41, 0xff5a, 0xff66, 0xffbe, 0xffc2, 0xffc7, 0xffca, 0xffcf, 0xffd2, 0xffd7, 0xffda, 0xffdc, 0x10300, 0x1031e, 0x10320, 0x10323, 0x10330, 0x1034a, 0x10400, 0x10425, 0x10428, 0x1044d, 0x1d000, 0x1d0f5, 0x1d100, 0x1d126, 0x1d12a, 0x1d166, 0x1d16a, 0x1d172, 0x1d183, 0x1d184, 0x1d18c, 0x1d1a9, 0x1d1ae, 0x1d1dd, 0x1d400, 0x1d454, 0x1d456, 0x1d49c, 0x1d49e, 0x1d49f, 0x1d4a2, 0x1d4a2, 0x1d4a5, 0x1d4a6, 0x1d4a9, 0x1d4ac, 0x1d4ae, 0x1d4b9, 0x1d4bb, 0x1d4bb, 0x1d4bd, 0x1d4c0, 0x1d4c2, 0x1d4c3, 0x1d4c5, 0x1d505, 0x1d507, 0x1d50a, 0x1d50d, 0x1d514, 0x1d516, 0x1d51c, 0x1d51e, 0x1d539, 0x1d53b, 0x1d53e, 0x1d540, 0x1d544, 0x1d546, 0x1d546, 0x1d54a, 0x1d550, 0x1d552, 0x1d6a3, 0x1d6a8, 0x1d7c9, 0x20000, 0x2a6d6, 0x2f800, 0x2fa1d, 0xf0000, 0xffffd, 0x100000, 0x10fffd];
545// prettier-ignore-end
546
547const isBidirectionalL = character => inRange(character, bidirectional_l);
548
549// 2.1. Mapping
550
551/**
552 * non-ASCII space characters [StringPrep, C.1.2] that can be
553 * mapped to SPACE (U+0020)
554 */
555const mapping2space = isNonASCIISpaceCharacter;
556
557/**
558 * the "commonly mapped to nothing" characters [StringPrep, B.1]
559 * that can be mapped to nothing.
560 */
561const mapping2nothing = isCommonlyMappedToNothing;
562
563// utils
564const getCodePoint = character => character.codePointAt(0);
565const first = x => x[0];
566const last = x => x[x.length - 1];
567
568/**
569 * Convert provided string into an array of Unicode Code Points.
570 * Based on https://stackoverflow.com/a/21409165/1556249
571 * and https://www.npmjs.com/package/code-point-at.
572 * @param {string} input
573 * @returns {number[]}
574 */
575function toCodePoints(input) {
576 const codepoints = [];
577 const size = input.length;
578 for (let i = 0; i < size; i += 1) {
579 const before = input.charCodeAt(i);
580 if (before >= 0xd800 && before <= 0xdbff && size > i + 1) {
581 const next = input.charCodeAt(i + 1);
582 if (next >= 0xdc00 && next <= 0xdfff) {
583 codepoints.push((before - 0xd800) * 0x400 + next - 0xdc00 + 0x10000);
584 i += 1;
585 continue;
586 }
587 }
588 codepoints.push(before);
589 }
590 return codepoints;
591}
592
593/**
594 * SASLprep.
595 * @param {string} input
596 * @param {Object} opts
597 * @param {boolean} opts.allowUnassigned
598 * @returns {string}
599 */
600function saslprep(input) {
601 let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
602 if (typeof input !== 'string') {
603 throw new TypeError('Expected string.');
604 }
605 if (input.length === 0) {
606 return '';
607 }
608
609 // 1. Map
610 const mapped_input = toCodePoints(input)
611 // 1.1 mapping to space
612 .map(character => mapping2space(character) ? 0x20 : character)
613 // 1.2 mapping to nothing
614 .filter(character => !mapping2nothing(character));
615
616 // 2. Normalize
617 const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
618 const normalized_map = toCodePoints(normalized_input);
619
620 // 3. Prohibit
621 const hasProhibited = normalized_map.some(isProhibitedCharacter);
622 if (hasProhibited) {
623 throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
624 }
625
626 // Unassigned Code Points
627 if (opts.allowUnassigned !== true) {
628 const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
629 if (hasUnassigned) {
630 throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
631 }
632 }
633
634 // 4. check bidi
635
636 const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
637 const hasBidiL = normalized_map.some(isBidirectionalL);
638
639 // 4.1 If a string contains any RandALCat character, the string MUST NOT
640 // contain any LCat character.
641 if (hasBidiRAL && hasBidiL) {
642 throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
643 }
644
645 /**
646 * 4.2 If a string contains any RandALCat character, a RandALCat
647 * character MUST be the first character of the string, and a
648 * RandALCat character MUST be the last character of the string.
649 */
650
651 const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
652 const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
653 if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
654 throw new Error('Bidirectional RandALCat character must be the first and the last' + ' character of the string, see https://tools.ietf.org/html/rfc3454#section-6');
655 }
656 return normalized_input;
657}
658
659/*
660 PDFSecurity - represents PDF security settings
661 By Yang Liu <hi@zesik.com>
662 */
663class PDFSecurity {
664 static generateFileID() {
665 let info = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
666 let infoStr = `${info.CreationDate.getTime()}\n`;
667 for (let key in info) {
668 // eslint-disable-next-line no-prototype-builtins
669 if (!info.hasOwnProperty(key)) {
670 continue;
671 }
672 infoStr += `${key}: ${info[key].valueOf()}\n`;
673 }
674 return wordArrayToBuffer(CryptoJS.MD5(infoStr));
675 }
676 static generateRandomWordArray(bytes) {
677 return CryptoJS.lib.WordArray.random(bytes);
678 }
679 static create(document) {
680 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
681 if (!options.ownerPassword && !options.userPassword) {
682 return null;
683 }
684 return new PDFSecurity(document, options);
685 }
686 constructor(document) {
687 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
688 if (!options.ownerPassword && !options.userPassword) {
689 throw new Error('None of owner password and user password is defined.');
690 }
691 this.document = document;
692 this._setupEncryption(options);
693 }
694 _setupEncryption(options) {
695 switch (options.pdfVersion) {
696 case '1.4':
697 case '1.5':
698 this.version = 2;
699 break;
700 case '1.6':
701 case '1.7':
702 this.version = 4;
703 break;
704 case '1.7ext3':
705 this.version = 5;
706 break;
707 default:
708 this.version = 1;
709 break;
710 }
711 const encDict = {
712 Filter: 'Standard'
713 };
714 switch (this.version) {
715 case 1:
716 case 2:
717 case 4:
718 this._setupEncryptionV1V2V4(this.version, encDict, options);
719 break;
720 case 5:
721 this._setupEncryptionV5(encDict, options);
722 break;
723 }
724 this.dictionary = this.document.ref(encDict);
725 }
726 _setupEncryptionV1V2V4(v, encDict, options) {
727 let r, permissions;
728 switch (v) {
729 case 1:
730 r = 2;
731 this.keyBits = 40;
732 permissions = getPermissionsR2(options.permissions);
733 break;
734 case 2:
735 r = 3;
736 this.keyBits = 128;
737 permissions = getPermissionsR3(options.permissions);
738 break;
739 case 4:
740 r = 4;
741 this.keyBits = 128;
742 permissions = getPermissionsR3(options.permissions);
743 break;
744 }
745 const paddedUserPassword = processPasswordR2R3R4(options.userPassword);
746 const paddedOwnerPassword = options.ownerPassword ? processPasswordR2R3R4(options.ownerPassword) : paddedUserPassword;
747 const ownerPasswordEntry = getOwnerPasswordR2R3R4(r, this.keyBits, paddedUserPassword, paddedOwnerPassword);
748 this.encryptionKey = getEncryptionKeyR2R3R4(r, this.keyBits, this.document._id, paddedUserPassword, ownerPasswordEntry, permissions);
749 let userPasswordEntry;
750 if (r === 2) {
751 userPasswordEntry = getUserPasswordR2(this.encryptionKey);
752 } else {
753 userPasswordEntry = getUserPasswordR3R4(this.document._id, this.encryptionKey);
754 }
755 encDict.V = v;
756 if (v >= 2) {
757 encDict.Length = this.keyBits;
758 }
759 if (v === 4) {
760 encDict.CF = {
761 StdCF: {
762 AuthEvent: 'DocOpen',
763 CFM: 'AESV2',
764 Length: this.keyBits / 8
765 }
766 };
767 encDict.StmF = 'StdCF';
768 encDict.StrF = 'StdCF';
769 }
770 encDict.R = r;
771 encDict.O = wordArrayToBuffer(ownerPasswordEntry);
772 encDict.U = wordArrayToBuffer(userPasswordEntry);
773 encDict.P = permissions;
774 }
775 _setupEncryptionV5(encDict, options) {
776 this.keyBits = 256;
777 const permissions = getPermissionsR3(options.permissions);
778 const processedUserPassword = processPasswordR5(options.userPassword);
779 const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
780 this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
781 const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
782 const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
783 const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
784 const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
785 const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
786 const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
787 const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
788 encDict.V = 5;
789 encDict.Length = this.keyBits;
790 encDict.CF = {
791 StdCF: {
792 AuthEvent: 'DocOpen',
793 CFM: 'AESV3',
794 Length: this.keyBits / 8
795 }
796 };
797 encDict.StmF = 'StdCF';
798 encDict.StrF = 'StdCF';
799 encDict.R = 5;
800 encDict.O = wordArrayToBuffer(ownerPasswordEntry);
801 encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
802 encDict.U = wordArrayToBuffer(userPasswordEntry);
803 encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
804 encDict.P = permissions;
805 encDict.Perms = wordArrayToBuffer(permsEntry);
806 }
807 getEncryptFn(obj, gen) {
808 let digest;
809 if (this.version < 5) {
810 digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
811 }
812 if (this.version === 1 || this.version === 2) {
813 let key = CryptoJS.MD5(digest);
814 key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
815 return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
816 }
817 let key;
818 if (this.version === 4) {
819 key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
820 } else {
821 key = this.encryptionKey;
822 }
823 const iv = PDFSecurity.generateRandomWordArray(16);
824 const options = {
825 mode: CryptoJS.mode.CBC,
826 padding: CryptoJS.pad.Pkcs7,
827 iv
828 };
829 return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
830 }
831 end() {
832 this.dictionary.end();
833 }
834}
835function getPermissionsR2() {
836 let permissionObject = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
837 let permissions = 0xffffffc0 >> 0;
838 if (permissionObject.printing) {
839 permissions |= 0b000000000100;
840 }
841 if (permissionObject.modifying) {
842 permissions |= 0b000000001000;
843 }
844 if (permissionObject.copying) {
845 permissions |= 0b000000010000;
846 }
847 if (permissionObject.annotating) {
848 permissions |= 0b000000100000;
849 }
850 return permissions;
851}
852function getPermissionsR3() {
853 let permissionObject = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
854 let permissions = 0xfffff0c0 >> 0;
855 if (permissionObject.printing === 'lowResolution') {
856 permissions |= 0b000000000100;
857 }
858 if (permissionObject.printing === 'highResolution') {
859 permissions |= 0b100000000100;
860 }
861 if (permissionObject.modifying) {
862 permissions |= 0b000000001000;
863 }
864 if (permissionObject.copying) {
865 permissions |= 0b000000010000;
866 }
867 if (permissionObject.annotating) {
868 permissions |= 0b000000100000;
869 }
870 if (permissionObject.fillingForms) {
871 permissions |= 0b000100000000;
872 }
873 if (permissionObject.contentAccessibility) {
874 permissions |= 0b001000000000;
875 }
876 if (permissionObject.documentAssembly) {
877 permissions |= 0b010000000000;
878 }
879 return permissions;
880}
881function getUserPasswordR2(encryptionKey) {
882 return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
883}
884function getUserPasswordR3R4(documentId, encryptionKey) {
885 const key = encryptionKey.clone();
886 let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
887 for (let i = 0; i < 20; i++) {
888 const xorRound = Math.ceil(key.sigBytes / 4);
889 for (let j = 0; j < xorRound; j++) {
890 key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
891 }
892 cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
893 }
894 return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
895}
896function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
897 let digest = paddedOwnerPassword;
898 let round = r >= 3 ? 51 : 1;
899 for (let i = 0; i < round; i++) {
900 digest = CryptoJS.MD5(digest);
901 }
902 const key = digest.clone();
903 key.sigBytes = keyBits / 8;
904 let cipher = paddedUserPassword;
905 round = r >= 3 ? 20 : 1;
906 for (let i = 0; i < round; i++) {
907 const xorRound = Math.ceil(key.sigBytes / 4);
908 for (let j = 0; j < xorRound; j++) {
909 key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
910 }
911 cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
912 }
913 return cipher;
914}
915function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
916 let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
917 const round = r >= 3 ? 51 : 1;
918 for (let i = 0; i < round; i++) {
919 key = CryptoJS.MD5(key);
920 key.sigBytes = keyBits / 8;
921 }
922 return key;
923}
924function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
925 const validationSalt = generateRandomWordArray(8);
926 const keySalt = generateRandomWordArray(8);
927 return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
928}
929function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
930 const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
931 const options = {
932 mode: CryptoJS.mode.CBC,
933 padding: CryptoJS.pad.NoPadding,
934 iv: CryptoJS.lib.WordArray.create(null, 16)
935 };
936 return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
937}
938function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
939 const validationSalt = generateRandomWordArray(8);
940 const keySalt = generateRandomWordArray(8);
941 return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
942}
943function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
944 const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
945 const options = {
946 mode: CryptoJS.mode.CBC,
947 padding: CryptoJS.pad.NoPadding,
948 iv: CryptoJS.lib.WordArray.create(null, 16)
949 };
950 return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
951}
952function getEncryptionKeyR5(generateRandomWordArray) {
953 return generateRandomWordArray(32);
954}
955function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
956 const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
957 const options = {
958 mode: CryptoJS.mode.ECB,
959 padding: CryptoJS.pad.NoPadding
960 };
961 return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
962}
963function processPasswordR2R3R4() {
964 let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
965 const out = Buffer.alloc(32);
966 const length = password.length;
967 let index = 0;
968 while (index < length && index < 32) {
969 const code = password.charCodeAt(index);
970 if (code > 0xff) {
971 throw new Error('Password contains one or more invalid characters.');
972 }
973 out[index] = code;
974 index++;
975 }
976 while (index < 32) {
977 out[index] = PASSWORD_PADDING[index - length];
978 index++;
979 }
980 return CryptoJS.lib.WordArray.create(out);
981}
982function processPasswordR5() {
983 let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
984 password = unescape(encodeURIComponent(saslprep(password)));
985 const length = Math.min(127, password.length);
986 const out = Buffer.alloc(length);
987 for (let i = 0; i < length; i++) {
988 out[i] = password.charCodeAt(i);
989 }
990 return CryptoJS.lib.WordArray.create(out);
991}
992function lsbFirstWord(data) {
993 return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
994}
995function wordArrayToBuffer(wordArray) {
996 const byteArray = [];
997 for (let i = 0; i < wordArray.sigBytes; i++) {
998 byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
999 }
1000 return Buffer.from(byteArray);
1001}
1002const 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];
1003
1004const {
1005 number
1006} = PDFObject;
1007class PDFGradient {
1008 constructor(doc) {
1009 this.doc = doc;
1010 this.stops = [];
1011 this.embedded = false;
1012 this.transform = [1, 0, 0, 1, 0, 0];
1013 }
1014 stop(pos, color, opacity) {
1015 if (opacity == null) {
1016 opacity = 1;
1017 }
1018 color = this.doc._normalizeColor(color);
1019 if (this.stops.length === 0) {
1020 if (color.length === 3) {
1021 this._colorSpace = 'DeviceRGB';
1022 } else if (color.length === 4) {
1023 this._colorSpace = 'DeviceCMYK';
1024 } else if (color.length === 1) {
1025 this._colorSpace = 'DeviceGray';
1026 } else {
1027 throw new Error('Unknown color space');
1028 }
1029 } else if (this._colorSpace === 'DeviceRGB' && color.length !== 3 || this._colorSpace === 'DeviceCMYK' && color.length !== 4 || this._colorSpace === 'DeviceGray' && color.length !== 1) {
1030 throw new Error('All gradient stops must use the same color space');
1031 }
1032 opacity = Math.max(0, Math.min(1, opacity));
1033 this.stops.push([pos, color, opacity]);
1034 return this;
1035 }
1036 setTransform(m11, m12, m21, m22, dx, dy) {
1037 this.transform = [m11, m12, m21, m22, dx, dy];
1038 return this;
1039 }
1040 embed(m) {
1041 let fn;
1042 const stopsLength = this.stops.length;
1043 if (stopsLength === 0) {
1044 return;
1045 }
1046 this.embedded = true;
1047 this.matrix = m;
1048
1049 // if the last stop comes before 100%, add a copy at 100%
1050 const last = this.stops[stopsLength - 1];
1051 if (last[0] < 1) {
1052 this.stops.push([1, last[1], last[2]]);
1053 }
1054 const bounds = [];
1055 const encode = [];
1056 const stops = [];
1057 for (let i = 0; i < stopsLength - 1; i++) {
1058 encode.push(0, 1);
1059 if (i + 2 !== stopsLength) {
1060 bounds.push(this.stops[i + 1][0]);
1061 }
1062 fn = this.doc.ref({
1063 FunctionType: 2,
1064 Domain: [0, 1],
1065 C0: this.stops[i + 0][1],
1066 C1: this.stops[i + 1][1],
1067 N: 1
1068 });
1069 stops.push(fn);
1070 fn.end();
1071 }
1072
1073 // if there are only two stops, we don't need a stitching function
1074 if (stopsLength === 1) {
1075 fn = stops[0];
1076 } else {
1077 fn = this.doc.ref({
1078 FunctionType: 3,
1079 // stitching function
1080 Domain: [0, 1],
1081 Functions: stops,
1082 Bounds: bounds,
1083 Encode: encode
1084 });
1085 fn.end();
1086 }
1087 this.id = `Sh${++this.doc._gradCount}`;
1088 const shader = this.shader(fn);
1089 shader.end();
1090 const pattern = this.doc.ref({
1091 Type: 'Pattern',
1092 PatternType: 2,
1093 Shading: shader,
1094 Matrix: this.matrix.map(number)
1095 });
1096 pattern.end();
1097 if (this.stops.some(stop => stop[2] < 1)) {
1098 let grad = this.opacityGradient();
1099 grad._colorSpace = 'DeviceGray';
1100 for (let stop of this.stops) {
1101 grad.stop(stop[0], [stop[2]]);
1102 }
1103 grad = grad.embed(this.matrix);
1104 const pageBBox = [0, 0, this.doc.page.width, this.doc.page.height];
1105 const form = this.doc.ref({
1106 Type: 'XObject',
1107 Subtype: 'Form',
1108 FormType: 1,
1109 BBox: pageBBox,
1110 Group: {
1111 Type: 'Group',
1112 S: 'Transparency',
1113 CS: 'DeviceGray'
1114 },
1115 Resources: {
1116 ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI'],
1117 Pattern: {
1118 Sh1: grad
1119 }
1120 }
1121 });
1122 form.write('/Pattern cs /Sh1 scn');
1123 form.end(`${pageBBox.join(' ')} re f`);
1124 const gstate = this.doc.ref({
1125 Type: 'ExtGState',
1126 SMask: {
1127 Type: 'Mask',
1128 S: 'Luminosity',
1129 G: form
1130 }
1131 });
1132 gstate.end();
1133 const opacityPattern = this.doc.ref({
1134 Type: 'Pattern',
1135 PatternType: 1,
1136 PaintType: 1,
1137 TilingType: 2,
1138 BBox: pageBBox,
1139 XStep: pageBBox[2],
1140 YStep: pageBBox[3],
1141 Resources: {
1142 ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI'],
1143 Pattern: {
1144 Sh1: pattern
1145 },
1146 ExtGState: {
1147 Gs1: gstate
1148 }
1149 }
1150 });
1151 opacityPattern.write('/Gs1 gs /Pattern cs /Sh1 scn');
1152 opacityPattern.end(`${pageBBox.join(' ')} re f`);
1153 this.doc.page.patterns[this.id] = opacityPattern;
1154 } else {
1155 this.doc.page.patterns[this.id] = pattern;
1156 }
1157 return pattern;
1158 }
1159 apply(stroke) {
1160 // apply gradient transform to existing document ctm
1161 const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1162 const [m11, m12, m21, m22, dx, dy] = this.transform;
1163 const m = [m0 * m11 + m2 * m12, m1 * m11 + m3 * m12, m0 * m21 + m2 * m22, m1 * m21 + m3 * m22, m0 * dx + m2 * dy + m4, m1 * dx + m3 * dy + m5];
1164 if (!this.embedded || m.join(' ') !== this.matrix.join(' ')) {
1165 this.embed(m);
1166 }
1167 this.doc._setColorSpace('Pattern', stroke);
1168 const op = stroke ? 'SCN' : 'scn';
1169 return this.doc.addContent(`/${this.id} ${op}`);
1170 }
1171}
1172class PDFLinearGradient extends PDFGradient {
1173 constructor(doc, x1, y1, x2, y2) {
1174 super(doc);
1175 this.x1 = x1;
1176 this.y1 = y1;
1177 this.x2 = x2;
1178 this.y2 = y2;
1179 }
1180 shader(fn) {
1181 return this.doc.ref({
1182 ShadingType: 2,
1183 ColorSpace: this._colorSpace,
1184 Coords: [this.x1, this.y1, this.x2, this.y2],
1185 Function: fn,
1186 Extend: [true, true]
1187 });
1188 }
1189 opacityGradient() {
1190 return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
1191 }
1192}
1193class PDFRadialGradient extends PDFGradient {
1194 constructor(doc, x1, y1, r1, x2, y2, r2) {
1195 super(doc);
1196 this.doc = doc;
1197 this.x1 = x1;
1198 this.y1 = y1;
1199 this.r1 = r1;
1200 this.x2 = x2;
1201 this.y2 = y2;
1202 this.r2 = r2;
1203 }
1204 shader(fn) {
1205 return this.doc.ref({
1206 ShadingType: 3,
1207 ColorSpace: this._colorSpace,
1208 Coords: [this.x1, this.y1, this.r1, this.x2, this.y2, this.r2],
1209 Function: fn,
1210 Extend: [true, true]
1211 });
1212 }
1213 opacityGradient() {
1214 return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1215 }
1216}
1217var Gradient = {
1218 PDFGradient,
1219 PDFLinearGradient,
1220 PDFRadialGradient
1221};
1222
1223/*
1224PDF tiling pattern support. Uncolored only.
1225 */
1226
1227const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1228class PDFTilingPattern {
1229 constructor(doc, bBox, xStep, yStep, stream) {
1230 this.doc = doc;
1231 this.bBox = bBox;
1232 this.xStep = xStep;
1233 this.yStep = yStep;
1234 this.stream = stream;
1235 }
1236 createPattern() {
1237 // no resources needed for our current usage
1238 // required entry
1239 const resources = this.doc.ref();
1240 resources.end();
1241 // apply default transform matrix (flipped in the default doc._ctm)
1242 // see document.js & gradient.js
1243 const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1244 const [m11, m12, m21, m22, dx, dy] = [1, 0, 0, 1, 0, 0];
1245 const m = [m0 * m11 + m2 * m12, m1 * m11 + m3 * m12, m0 * m21 + m2 * m22, m1 * m21 + m3 * m22, m0 * dx + m2 * dy + m4, m1 * dx + m3 * dy + m5];
1246 const pattern = this.doc.ref({
1247 Type: 'Pattern',
1248 PatternType: 1,
1249 // tiling
1250 PaintType: 2,
1251 // 1-colored, 2-uncolored
1252 TilingType: 2,
1253 // 2-no distortion
1254 BBox: this.bBox,
1255 XStep: this.xStep,
1256 YStep: this.yStep,
1257 Matrix: m.map(v => +v.toFixed(5)),
1258 Resources: resources
1259 });
1260 pattern.end(this.stream);
1261 return pattern;
1262 }
1263 embedPatternColorSpaces() {
1264 // map each pattern to an underlying color space
1265 // and embed on each page
1266 underlyingColorSpaces.forEach(csName => {
1267 const csId = this.getPatternColorSpaceId(csName);
1268 if (this.doc.page.colorSpaces[csId]) return;
1269 const cs = this.doc.ref(['Pattern', csName]);
1270 cs.end();
1271 this.doc.page.colorSpaces[csId] = cs;
1272 });
1273 }
1274 getPatternColorSpaceId(underlyingColorspace) {
1275 return `CsP${underlyingColorspace}`;
1276 }
1277 embed() {
1278 if (!this.id) {
1279 this.doc._patternCount = this.doc._patternCount + 1;
1280 this.id = 'P' + this.doc._patternCount;
1281 this.pattern = this.createPattern();
1282 }
1283
1284 // patterns are embedded in each page
1285 if (!this.doc.page.patterns[this.id]) {
1286 this.doc.page.patterns[this.id] = this.pattern;
1287 }
1288 }
1289 apply(stroke, patternColor) {
1290 // do any embedding/creating that might be needed
1291 this.embedPatternColorSpaces();
1292 this.embed();
1293 const normalizedColor = this.doc._normalizeColor(patternColor);
1294 if (!normalizedColor) throw Error(`invalid pattern color. (value: ${patternColor})`);
1295
1296 // select one of the pattern color spaces
1297 const csId = this.getPatternColorSpaceId(this.doc._getColorSpace(normalizedColor));
1298 this.doc._setColorSpace(csId, stroke);
1299
1300 // stroke/fill using the pattern and color (in the above underlying color space)
1301 const op = stroke ? 'SCN' : 'scn';
1302 return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1303 }
1304}
1305var pattern = {
1306 PDFTilingPattern
1307};
1308
1309const {
1310 PDFGradient: PDFGradient$1,
1311 PDFLinearGradient: PDFLinearGradient$1,
1312 PDFRadialGradient: PDFRadialGradient$1
1313} = Gradient;
1314const {
1315 PDFTilingPattern: PDFTilingPattern$1
1316} = pattern;
1317var ColorMixin = {
1318 initColor() {
1319 // The opacity dictionaries
1320 this._opacityRegistry = {};
1321 this._opacityCount = 0;
1322 this._patternCount = 0;
1323 return this._gradCount = 0;
1324 },
1325 _normalizeColor(color) {
1326 if (typeof color === 'string') {
1327 if (color.charAt(0) === '#') {
1328 if (color.length === 4) {
1329 color = color.replace(/#([0-9A-F])([0-9A-F])([0-9A-F])/i, '#$1$1$2$2$3$3');
1330 }
1331 const hex = parseInt(color.slice(1), 16);
1332 color = [hex >> 16, hex >> 8 & 0xff, hex & 0xff];
1333 } else if (namedColors[color]) {
1334 color = namedColors[color];
1335 }
1336 }
1337 if (Array.isArray(color)) {
1338 // RGB
1339 if (color.length === 3) {
1340 color = color.map(part => part / 255);
1341 // CMYK
1342 } else if (color.length === 4) {
1343 color = color.map(part => part / 100);
1344 }
1345 return color;
1346 }
1347 return null;
1348 },
1349 _setColor(color, stroke) {
1350 if (color instanceof PDFGradient$1) {
1351 color.apply(stroke);
1352 return true;
1353 // see if tiling pattern, decode & apply it it
1354 } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern$1) {
1355 color[0].apply(stroke, color[1]);
1356 return true;
1357 }
1358 // any other case should be a normal color and not a pattern
1359 return this._setColorCore(color, stroke);
1360 },
1361 _setColorCore(color, stroke) {
1362 color = this._normalizeColor(color);
1363 if (!color) {
1364 return false;
1365 }
1366 const op = stroke ? 'SCN' : 'scn';
1367 const space = this._getColorSpace(color);
1368 this._setColorSpace(space, stroke);
1369 color = color.join(' ');
1370 this.addContent(`${color} ${op}`);
1371 return true;
1372 },
1373 _setColorSpace(space, stroke) {
1374 const op = stroke ? 'CS' : 'cs';
1375 return this.addContent(`/${space} ${op}`);
1376 },
1377 _getColorSpace(color) {
1378 return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1379 },
1380 fillColor(color, opacity) {
1381 const set = this._setColor(color, false);
1382 if (set) {
1383 this.fillOpacity(opacity);
1384 }
1385
1386 // save this for text wrapper, which needs to reset
1387 // the fill color on new pages
1388 this._fillColor = [color, opacity];
1389 return this;
1390 },
1391 strokeColor(color, opacity) {
1392 const set = this._setColor(color, true);
1393 if (set) {
1394 this.strokeOpacity(opacity);
1395 }
1396 return this;
1397 },
1398 opacity(opacity) {
1399 this._doOpacity(opacity, opacity);
1400 return this;
1401 },
1402 fillOpacity(opacity) {
1403 this._doOpacity(opacity, null);
1404 return this;
1405 },
1406 strokeOpacity(opacity) {
1407 this._doOpacity(null, opacity);
1408 return this;
1409 },
1410 _doOpacity(fillOpacity, strokeOpacity) {
1411 let dictionary, name;
1412 if (fillOpacity == null && strokeOpacity == null) {
1413 return;
1414 }
1415 if (fillOpacity != null) {
1416 fillOpacity = Math.max(0, Math.min(1, fillOpacity));
1417 }
1418 if (strokeOpacity != null) {
1419 strokeOpacity = Math.max(0, Math.min(1, strokeOpacity));
1420 }
1421 const key = `${fillOpacity}_${strokeOpacity}`;
1422 if (this._opacityRegistry[key]) {
1423 [dictionary, name] = this._opacityRegistry[key];
1424 } else {
1425 dictionary = {
1426 Type: 'ExtGState'
1427 };
1428 if (fillOpacity != null) {
1429 dictionary.ca = fillOpacity;
1430 }
1431 if (strokeOpacity != null) {
1432 dictionary.CA = strokeOpacity;
1433 }
1434 dictionary = this.ref(dictionary);
1435 dictionary.end();
1436 const id = ++this._opacityCount;
1437 name = `Gs${id}`;
1438 this._opacityRegistry[key] = [dictionary, name];
1439 }
1440 this.page.ext_gstates[name] = dictionary;
1441 return this.addContent(`/${name} gs`);
1442 },
1443 linearGradient(x1, y1, x2, y2) {
1444 return new PDFLinearGradient$1(this, x1, y1, x2, y2);
1445 },
1446 radialGradient(x1, y1, r1, x2, y2, r2) {
1447 return new PDFRadialGradient$1(this, x1, y1, r1, x2, y2, r2);
1448 },
1449 pattern(bbox, xStep, yStep, stream) {
1450 return new PDFTilingPattern$1(this, bbox, xStep, yStep, stream);
1451 }
1452};
1453var namedColors = {
1454 aliceblue: [240, 248, 255],
1455 antiquewhite: [250, 235, 215],
1456 aqua: [0, 255, 255],
1457 aquamarine: [127, 255, 212],
1458 azure: [240, 255, 255],
1459 beige: [245, 245, 220],
1460 bisque: [255, 228, 196],
1461 black: [0, 0, 0],
1462 blanchedalmond: [255, 235, 205],
1463 blue: [0, 0, 255],
1464 blueviolet: [138, 43, 226],
1465 brown: [165, 42, 42],
1466 burlywood: [222, 184, 135],
1467 cadetblue: [95, 158, 160],
1468 chartreuse: [127, 255, 0],
1469 chocolate: [210, 105, 30],
1470 coral: [255, 127, 80],
1471 cornflowerblue: [100, 149, 237],
1472 cornsilk: [255, 248, 220],
1473 crimson: [220, 20, 60],
1474 cyan: [0, 255, 255],
1475 darkblue: [0, 0, 139],
1476 darkcyan: [0, 139, 139],
1477 darkgoldenrod: [184, 134, 11],
1478 darkgray: [169, 169, 169],
1479 darkgreen: [0, 100, 0],
1480 darkgrey: [169, 169, 169],
1481 darkkhaki: [189, 183, 107],
1482 darkmagenta: [139, 0, 139],
1483 darkolivegreen: [85, 107, 47],
1484 darkorange: [255, 140, 0],
1485 darkorchid: [153, 50, 204],
1486 darkred: [139, 0, 0],
1487 darksalmon: [233, 150, 122],
1488 darkseagreen: [143, 188, 143],
1489 darkslateblue: [72, 61, 139],
1490 darkslategray: [47, 79, 79],
1491 darkslategrey: [47, 79, 79],
1492 darkturquoise: [0, 206, 209],
1493 darkviolet: [148, 0, 211],
1494 deeppink: [255, 20, 147],
1495 deepskyblue: [0, 191, 255],
1496 dimgray: [105, 105, 105],
1497 dimgrey: [105, 105, 105],
1498 dodgerblue: [30, 144, 255],
1499 firebrick: [178, 34, 34],
1500 floralwhite: [255, 250, 240],
1501 forestgreen: [34, 139, 34],
1502 fuchsia: [255, 0, 255],
1503 gainsboro: [220, 220, 220],
1504 ghostwhite: [248, 248, 255],
1505 gold: [255, 215, 0],
1506 goldenrod: [218, 165, 32],
1507 gray: [128, 128, 128],
1508 grey: [128, 128, 128],
1509 green: [0, 128, 0],
1510 greenyellow: [173, 255, 47],
1511 honeydew: [240, 255, 240],
1512 hotpink: [255, 105, 180],
1513 indianred: [205, 92, 92],
1514 indigo: [75, 0, 130],
1515 ivory: [255, 255, 240],
1516 khaki: [240, 230, 140],
1517 lavender: [230, 230, 250],
1518 lavenderblush: [255, 240, 245],
1519 lawngreen: [124, 252, 0],
1520 lemonchiffon: [255, 250, 205],
1521 lightblue: [173, 216, 230],
1522 lightcoral: [240, 128, 128],
1523 lightcyan: [224, 255, 255],
1524 lightgoldenrodyellow: [250, 250, 210],
1525 lightgray: [211, 211, 211],
1526 lightgreen: [144, 238, 144],
1527 lightgrey: [211, 211, 211],
1528 lightpink: [255, 182, 193],
1529 lightsalmon: [255, 160, 122],
1530 lightseagreen: [32, 178, 170],
1531 lightskyblue: [135, 206, 250],
1532 lightslategray: [119, 136, 153],
1533 lightslategrey: [119, 136, 153],
1534 lightsteelblue: [176, 196, 222],
1535 lightyellow: [255, 255, 224],
1536 lime: [0, 255, 0],
1537 limegreen: [50, 205, 50],
1538 linen: [250, 240, 230],
1539 magenta: [255, 0, 255],
1540 maroon: [128, 0, 0],
1541 mediumaquamarine: [102, 205, 170],
1542 mediumblue: [0, 0, 205],
1543 mediumorchid: [186, 85, 211],
1544 mediumpurple: [147, 112, 219],
1545 mediumseagreen: [60, 179, 113],
1546 mediumslateblue: [123, 104, 238],
1547 mediumspringgreen: [0, 250, 154],
1548 mediumturquoise: [72, 209, 204],
1549 mediumvioletred: [199, 21, 133],
1550 midnightblue: [25, 25, 112],
1551 mintcream: [245, 255, 250],
1552 mistyrose: [255, 228, 225],
1553 moccasin: [255, 228, 181],
1554 navajowhite: [255, 222, 173],
1555 navy: [0, 0, 128],
1556 oldlace: [253, 245, 230],
1557 olive: [128, 128, 0],
1558 olivedrab: [107, 142, 35],
1559 orange: [255, 165, 0],
1560 orangered: [255, 69, 0],
1561 orchid: [218, 112, 214],
1562 palegoldenrod: [238, 232, 170],
1563 palegreen: [152, 251, 152],
1564 paleturquoise: [175, 238, 238],
1565 palevioletred: [219, 112, 147],
1566 papayawhip: [255, 239, 213],
1567 peachpuff: [255, 218, 185],
1568 peru: [205, 133, 63],
1569 pink: [255, 192, 203],
1570 plum: [221, 160, 221],
1571 powderblue: [176, 224, 230],
1572 purple: [128, 0, 128],
1573 red: [255, 0, 0],
1574 rosybrown: [188, 143, 143],
1575 royalblue: [65, 105, 225],
1576 saddlebrown: [139, 69, 19],
1577 salmon: [250, 128, 114],
1578 sandybrown: [244, 164, 96],
1579 seagreen: [46, 139, 87],
1580 seashell: [255, 245, 238],
1581 sienna: [160, 82, 45],
1582 silver: [192, 192, 192],
1583 skyblue: [135, 206, 235],
1584 slateblue: [106, 90, 205],
1585 slategray: [112, 128, 144],
1586 slategrey: [112, 128, 144],
1587 snow: [255, 250, 250],
1588 springgreen: [0, 255, 127],
1589 steelblue: [70, 130, 180],
1590 tan: [210, 180, 140],
1591 teal: [0, 128, 128],
1592 thistle: [216, 191, 216],
1593 tomato: [255, 99, 71],
1594 turquoise: [64, 224, 208],
1595 violet: [238, 130, 238],
1596 wheat: [245, 222, 179],
1597 white: [255, 255, 255],
1598 whitesmoke: [245, 245, 245],
1599 yellow: [255, 255, 0],
1600 yellowgreen: [154, 205, 50]
1601};
1602
1603let cx, cy, px, py, sx, sy;
1604cx = cy = px = py = sx = sy = 0;
1605const parameters = {
1606 A: 7,
1607 a: 7,
1608 C: 6,
1609 c: 6,
1610 H: 1,
1611 h: 1,
1612 L: 2,
1613 l: 2,
1614 M: 2,
1615 m: 2,
1616 Q: 4,
1617 q: 4,
1618 S: 4,
1619 s: 4,
1620 T: 2,
1621 t: 2,
1622 V: 1,
1623 v: 1,
1624 Z: 0,
1625 z: 0
1626};
1627const parse = function (path) {
1628 let cmd;
1629 const ret = [];
1630 let args = [];
1631 let curArg = '';
1632 let foundDecimal = false;
1633 let params = 0;
1634 for (let c of path) {
1635 if (parameters[c] != null) {
1636 params = parameters[c];
1637 if (cmd) {
1638 // save existing command
1639 if (curArg.length > 0) {
1640 args[args.length] = +curArg;
1641 }
1642 ret[ret.length] = {
1643 cmd,
1644 args
1645 };
1646 args = [];
1647 curArg = '';
1648 foundDecimal = false;
1649 }
1650 cmd = c;
1651 } else if ([' ', ','].includes(c) || c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e' || c === '.' && foundDecimal) {
1652 if (curArg.length === 0) {
1653 continue;
1654 }
1655 if (args.length === params) {
1656 // handle reused commands
1657 ret[ret.length] = {
1658 cmd,
1659 args
1660 };
1661 args = [+curArg];
1662
1663 // handle assumed commands
1664 if (cmd === 'M') {
1665 cmd = 'L';
1666 }
1667 if (cmd === 'm') {
1668 cmd = 'l';
1669 }
1670 } else {
1671 args[args.length] = +curArg;
1672 }
1673 foundDecimal = c === '.';
1674
1675 // fix for negative numbers or repeated decimals with no delimeter between commands
1676 curArg = ['-', '.'].includes(c) ? c : '';
1677 } else {
1678 curArg += c;
1679 if (c === '.') {
1680 foundDecimal = true;
1681 }
1682 }
1683 }
1684
1685 // add the last command
1686 if (curArg.length > 0) {
1687 if (args.length === params) {
1688 // handle reused commands
1689 ret[ret.length] = {
1690 cmd,
1691 args
1692 };
1693 args = [+curArg];
1694
1695 // handle assumed commands
1696 if (cmd === 'M') {
1697 cmd = 'L';
1698 }
1699 if (cmd === 'm') {
1700 cmd = 'l';
1701 }
1702 } else {
1703 args[args.length] = +curArg;
1704 }
1705 }
1706 ret[ret.length] = {
1707 cmd,
1708 args
1709 };
1710 return ret;
1711};
1712const apply = function (commands, doc) {
1713 // current point, control point, and subpath starting point
1714 cx = cy = px = py = sx = sy = 0;
1715
1716 // run the commands
1717 for (let i = 0; i < commands.length; i++) {
1718 const c = commands[i];
1719 if (typeof runners[c.cmd] === 'function') {
1720 runners[c.cmd](doc, c.args);
1721 }
1722 }
1723};
1724const runners = {
1725 M(doc, a) {
1726 cx = a[0];
1727 cy = a[1];
1728 px = py = null;
1729 sx = cx;
1730 sy = cy;
1731 return doc.moveTo(cx, cy);
1732 },
1733 m(doc, a) {
1734 cx += a[0];
1735 cy += a[1];
1736 px = py = null;
1737 sx = cx;
1738 sy = cy;
1739 return doc.moveTo(cx, cy);
1740 },
1741 C(doc, a) {
1742 cx = a[4];
1743 cy = a[5];
1744 px = a[2];
1745 py = a[3];
1746 return doc.bezierCurveTo(...a);
1747 },
1748 c(doc, a) {
1749 doc.bezierCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy, a[4] + cx, a[5] + cy);
1750 px = cx + a[2];
1751 py = cy + a[3];
1752 cx += a[4];
1753 return cy += a[5];
1754 },
1755 S(doc, a) {
1756 if (px === null) {
1757 px = cx;
1758 py = cy;
1759 }
1760 doc.bezierCurveTo(cx - (px - cx), cy - (py - cy), a[0], a[1], a[2], a[3]);
1761 px = a[0];
1762 py = a[1];
1763 cx = a[2];
1764 return cy = a[3];
1765 },
1766 s(doc, a) {
1767 if (px === null) {
1768 px = cx;
1769 py = cy;
1770 }
1771 doc.bezierCurveTo(cx - (px - cx), cy - (py - cy), cx + a[0], cy + a[1], cx + a[2], cy + a[3]);
1772 px = cx + a[0];
1773 py = cy + a[1];
1774 cx += a[2];
1775 return cy += a[3];
1776 },
1777 Q(doc, a) {
1778 px = a[0];
1779 py = a[1];
1780 cx = a[2];
1781 cy = a[3];
1782 return doc.quadraticCurveTo(a[0], a[1], cx, cy);
1783 },
1784 q(doc, a) {
1785 doc.quadraticCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy);
1786 px = cx + a[0];
1787 py = cy + a[1];
1788 cx += a[2];
1789 return cy += a[3];
1790 },
1791 T(doc, a) {
1792 if (px === null) {
1793 px = cx;
1794 py = cy;
1795 } else {
1796 px = cx - (px - cx);
1797 py = cy - (py - cy);
1798 }
1799 doc.quadraticCurveTo(px, py, a[0], a[1]);
1800 px = cx - (px - cx);
1801 py = cy - (py - cy);
1802 cx = a[0];
1803 return cy = a[1];
1804 },
1805 t(doc, a) {
1806 if (px === null) {
1807 px = cx;
1808 py = cy;
1809 } else {
1810 px = cx - (px - cx);
1811 py = cy - (py - cy);
1812 }
1813 doc.quadraticCurveTo(px, py, cx + a[0], cy + a[1]);
1814 cx += a[0];
1815 return cy += a[1];
1816 },
1817 A(doc, a) {
1818 solveArc(doc, cx, cy, a);
1819 cx = a[5];
1820 return cy = a[6];
1821 },
1822 a(doc, a) {
1823 a[5] += cx;
1824 a[6] += cy;
1825 solveArc(doc, cx, cy, a);
1826 cx = a[5];
1827 return cy = a[6];
1828 },
1829 L(doc, a) {
1830 cx = a[0];
1831 cy = a[1];
1832 px = py = null;
1833 return doc.lineTo(cx, cy);
1834 },
1835 l(doc, a) {
1836 cx += a[0];
1837 cy += a[1];
1838 px = py = null;
1839 return doc.lineTo(cx, cy);
1840 },
1841 H(doc, a) {
1842 cx = a[0];
1843 px = py = null;
1844 return doc.lineTo(cx, cy);
1845 },
1846 h(doc, a) {
1847 cx += a[0];
1848 px = py = null;
1849 return doc.lineTo(cx, cy);
1850 },
1851 V(doc, a) {
1852 cy = a[0];
1853 px = py = null;
1854 return doc.lineTo(cx, cy);
1855 },
1856 v(doc, a) {
1857 cy += a[0];
1858 px = py = null;
1859 return doc.lineTo(cx, cy);
1860 },
1861 Z(doc) {
1862 doc.closePath();
1863 cx = sx;
1864 return cy = sy;
1865 },
1866 z(doc) {
1867 doc.closePath();
1868 cx = sx;
1869 return cy = sy;
1870 }
1871};
1872const solveArc = function (doc, x, y, coords) {
1873 const [rx, ry, rot, large, sweep, ex, ey] = coords;
1874 const segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
1875 for (let seg of segs) {
1876 const bez = segmentToBezier(...seg);
1877 doc.bezierCurveTo(...bez);
1878 }
1879};
1880
1881// from Inkscape svgtopdf, thanks!
1882const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1883 const th = rotateX * (Math.PI / 180);
1884 const sin_th = Math.sin(th);
1885 const cos_th = Math.cos(th);
1886 rx = Math.abs(rx);
1887 ry = Math.abs(ry);
1888 px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
1889 py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
1890 let pl = px * px / (rx * rx) + py * py / (ry * ry);
1891 if (pl > 1) {
1892 pl = Math.sqrt(pl);
1893 rx *= pl;
1894 ry *= pl;
1895 }
1896 const a00 = cos_th / rx;
1897 const a01 = sin_th / rx;
1898 const a10 = -sin_th / ry;
1899 const a11 = cos_th / ry;
1900 const x0 = a00 * ox + a01 * oy;
1901 const y0 = a10 * ox + a11 * oy;
1902 const x1 = a00 * x + a01 * y;
1903 const y1 = a10 * x + a11 * y;
1904 const d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
1905 let sfactor_sq = 1 / d - 0.25;
1906 if (sfactor_sq < 0) {
1907 sfactor_sq = 0;
1908 }
1909 let sfactor = Math.sqrt(sfactor_sq);
1910 if (sweep === large) {
1911 sfactor = -sfactor;
1912 }
1913 const xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
1914 const yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
1915 const th0 = Math.atan2(y0 - yc, x0 - xc);
1916 const th1 = Math.atan2(y1 - yc, x1 - xc);
1917 let th_arc = th1 - th0;
1918 if (th_arc < 0 && sweep === 1) {
1919 th_arc += 2 * Math.PI;
1920 } else if (th_arc > 0 && sweep === 0) {
1921 th_arc -= 2 * Math.PI;
1922 }
1923 const segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
1924 const result = [];
1925 for (let i = 0; i < segments; i++) {
1926 const th2 = th0 + i * th_arc / segments;
1927 const th3 = th0 + (i + 1) * th_arc / segments;
1928 result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
1929 }
1930 return result;
1931};
1932const segmentToBezier = function (cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
1933 const a00 = cos_th * rx;
1934 const a01 = -sin_th * ry;
1935 const a10 = sin_th * rx;
1936 const a11 = cos_th * ry;
1937 const th_half = 0.5 * (th1 - th0);
1938 const t = 8 / 3 * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half);
1939 const x1 = cx + Math.cos(th0) - t * Math.sin(th0);
1940 const y1 = cy + Math.sin(th0) + t * Math.cos(th0);
1941 const x3 = cx + Math.cos(th1);
1942 const y3 = cy + Math.sin(th1);
1943 const x2 = x3 + t * Math.sin(th1);
1944 const y2 = y3 - t * Math.cos(th1);
1945 return [a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3];
1946};
1947class SVGPath {
1948 static apply(doc, path) {
1949 const commands = parse(path);
1950 apply(commands, doc);
1951 }
1952}
1953
1954const {
1955 number: number$1
1956} = PDFObject;
1957
1958// This constant is used to approximate a symmetrical arc using a cubic
1959// Bezier curve.
1960const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
1961var VectorMixin = {
1962 initVector() {
1963 this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
1964 return this._ctmStack = [];
1965 },
1966 save() {
1967 this._ctmStack.push(this._ctm.slice());
1968 // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
1969 return this.addContent('q');
1970 },
1971 restore() {
1972 this._ctm = this._ctmStack.pop() || [1, 0, 0, 1, 0, 0];
1973 return this.addContent('Q');
1974 },
1975 closePath() {
1976 return this.addContent('h');
1977 },
1978 lineWidth(w) {
1979 return this.addContent(`${number$1(w)} w`);
1980 },
1981 _CAP_STYLES: {
1982 BUTT: 0,
1983 ROUND: 1,
1984 SQUARE: 2
1985 },
1986 lineCap(c) {
1987 if (typeof c === 'string') {
1988 c = this._CAP_STYLES[c.toUpperCase()];
1989 }
1990 return this.addContent(`${c} J`);
1991 },
1992 _JOIN_STYLES: {
1993 MITER: 0,
1994 ROUND: 1,
1995 BEVEL: 2
1996 },
1997 lineJoin(j) {
1998 if (typeof j === 'string') {
1999 j = this._JOIN_STYLES[j.toUpperCase()];
2000 }
2001 return this.addContent(`${j} j`);
2002 },
2003 miterLimit(m) {
2004 return this.addContent(`${number$1(m)} M`);
2005 },
2006 dash(length) {
2007 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2008 const originalLength = length;
2009 if (!Array.isArray(length)) {
2010 length = [length, options.space || length];
2011 }
2012 const valid = length.every(x => Number.isFinite(x) && x > 0);
2013 if (!valid) {
2014 throw new Error(`dash(${JSON.stringify(originalLength)}, ${JSON.stringify(options)}) invalid, lengths must be numeric and greater than zero`);
2015 }
2016 length = length.map(number$1).join(' ');
2017 return this.addContent(`[${length}] ${number$1(options.phase || 0)} d`);
2018 },
2019 undash() {
2020 return this.addContent('[] 0 d');
2021 },
2022 moveTo(x, y) {
2023 return this.addContent(`${number$1(x)} ${number$1(y)} m`);
2024 },
2025 lineTo(x, y) {
2026 return this.addContent(`${number$1(x)} ${number$1(y)} l`);
2027 },
2028 bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
2029 return this.addContent(`${number$1(cp1x)} ${number$1(cp1y)} ${number$1(cp2x)} ${number$1(cp2y)} ${number$1(x)} ${number$1(y)} c`);
2030 },
2031 quadraticCurveTo(cpx, cpy, x, y) {
2032 return this.addContent(`${number$1(cpx)} ${number$1(cpy)} ${number$1(x)} ${number$1(y)} v`);
2033 },
2034 rect(x, y, w, h) {
2035 return this.addContent(`${number$1(x)} ${number$1(y)} ${number$1(w)} ${number$1(h)} re`);
2036 },
2037 roundedRect(x, y, w, h, r) {
2038 if (r == null) {
2039 r = 0;
2040 }
2041 r = Math.min(r, 0.5 * w, 0.5 * h);
2042
2043 // amount to inset control points from corners (see `ellipse`)
2044 const c = r * (1.0 - KAPPA);
2045 this.moveTo(x + r, y);
2046 this.lineTo(x + w - r, y);
2047 this.bezierCurveTo(x + w - c, y, x + w, y + c, x + w, y + r);
2048 this.lineTo(x + w, y + h - r);
2049 this.bezierCurveTo(x + w, y + h - c, x + w - c, y + h, x + w - r, y + h);
2050 this.lineTo(x + r, y + h);
2051 this.bezierCurveTo(x + c, y + h, x, y + h - c, x, y + h - r);
2052 this.lineTo(x, y + r);
2053 this.bezierCurveTo(x, y + c, x + c, y, x + r, y);
2054 return this.closePath();
2055 },
2056 ellipse(x, y, r1, r2) {
2057 // based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
2058 if (r2 == null) {
2059 r2 = r1;
2060 }
2061 x -= r1;
2062 y -= r2;
2063 const ox = r1 * KAPPA;
2064 const oy = r2 * KAPPA;
2065 const xe = x + r1 * 2;
2066 const ye = y + r2 * 2;
2067 const xm = x + r1;
2068 const ym = y + r2;
2069 this.moveTo(x, ym);
2070 this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
2071 this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
2072 this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
2073 this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
2074 return this.closePath();
2075 },
2076 circle(x, y, radius) {
2077 return this.ellipse(x, y, radius);
2078 },
2079 arc(x, y, radius, startAngle, endAngle, anticlockwise) {
2080 if (anticlockwise == null) {
2081 anticlockwise = false;
2082 }
2083 const TWO_PI = 2.0 * Math.PI;
2084 const HALF_PI = 0.5 * Math.PI;
2085 let deltaAng = endAngle - startAngle;
2086 if (Math.abs(deltaAng) > TWO_PI) {
2087 // draw only full circle if more than that is specified
2088 deltaAng = TWO_PI;
2089 } else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
2090 // necessary to flip direction of rendering
2091 const dir = anticlockwise ? -1 : 1;
2092 deltaAng = dir * TWO_PI + deltaAng;
2093 }
2094 const numSegs = Math.ceil(Math.abs(deltaAng) / HALF_PI);
2095 const segAng = deltaAng / numSegs;
2096 const handleLen = segAng / HALF_PI * KAPPA * radius;
2097 let curAng = startAngle;
2098
2099 // component distances between anchor point and control point
2100 let deltaCx = -Math.sin(curAng) * handleLen;
2101 let deltaCy = Math.cos(curAng) * handleLen;
2102
2103 // anchor point
2104 let ax = x + Math.cos(curAng) * radius;
2105 let ay = y + Math.sin(curAng) * radius;
2106
2107 // calculate and render segments
2108 this.moveTo(ax, ay);
2109 for (let segIdx = 0; segIdx < numSegs; segIdx++) {
2110 // starting control point
2111 const cp1x = ax + deltaCx;
2112 const cp1y = ay + deltaCy;
2113
2114 // step angle
2115 curAng += segAng;
2116
2117 // next anchor point
2118 ax = x + Math.cos(curAng) * radius;
2119 ay = y + Math.sin(curAng) * radius;
2120
2121 // next control point delta
2122 deltaCx = -Math.sin(curAng) * handleLen;
2123 deltaCy = Math.cos(curAng) * handleLen;
2124
2125 // ending control point
2126 const cp2x = ax - deltaCx;
2127 const cp2y = ay - deltaCy;
2128
2129 // render segment
2130 this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
2131 }
2132 return this;
2133 },
2134 polygon() {
2135 for (var _len = arguments.length, points = new Array(_len), _key = 0; _key < _len; _key++) {
2136 points[_key] = arguments[_key];
2137 }
2138 this.moveTo(...(points.shift() || []));
2139 for (let point of points) {
2140 this.lineTo(...(point || []));
2141 }
2142 return this.closePath();
2143 },
2144 path(path) {
2145 SVGPath.apply(this, path);
2146 return this;
2147 },
2148 _windingRule(rule) {
2149 if (/even-?odd/.test(rule)) {
2150 return '*';
2151 }
2152 return '';
2153 },
2154 fill(color, rule) {
2155 if (/(even-?odd)|(non-?zero)/.test(color)) {
2156 rule = color;
2157 color = null;
2158 }
2159 if (color) {
2160 this.fillColor(color);
2161 }
2162 return this.addContent(`f${this._windingRule(rule)}`);
2163 },
2164 stroke(color) {
2165 if (color) {
2166 this.strokeColor(color);
2167 }
2168 return this.addContent('S');
2169 },
2170 fillAndStroke(fillColor, strokeColor, rule) {
2171 if (strokeColor == null) {
2172 strokeColor = fillColor;
2173 }
2174 const isFillRule = /(even-?odd)|(non-?zero)/;
2175 if (isFillRule.test(fillColor)) {
2176 rule = fillColor;
2177 fillColor = null;
2178 }
2179 if (isFillRule.test(strokeColor)) {
2180 rule = strokeColor;
2181 strokeColor = fillColor;
2182 }
2183 if (fillColor) {
2184 this.fillColor(fillColor);
2185 this.strokeColor(strokeColor);
2186 }
2187 return this.addContent(`B${this._windingRule(rule)}`);
2188 },
2189 clip(rule) {
2190 return this.addContent(`W${this._windingRule(rule)} n`);
2191 },
2192 transform(m11, m12, m21, m22, dx, dy) {
2193 // keep track of the current transformation matrix
2194 if (m11 === 1 && m12 === 0 && m21 === 0 && m22 === 1 && dx === 0 && dy === 0) {
2195 // Ignore identity transforms
2196 return this;
2197 }
2198 const m = this._ctm;
2199 const [m0, m1, m2, m3, m4, m5] = m;
2200 m[0] = m0 * m11 + m2 * m12;
2201 m[1] = m1 * m11 + m3 * m12;
2202 m[2] = m0 * m21 + m2 * m22;
2203 m[3] = m1 * m21 + m3 * m22;
2204 m[4] = m0 * dx + m2 * dy + m4;
2205 m[5] = m1 * dx + m3 * dy + m5;
2206 const values = [m11, m12, m21, m22, dx, dy].map(v => number$1(v)).join(' ');
2207 return this.addContent(`${values} cm`);
2208 },
2209 translate(x, y) {
2210 return this.transform(1, 0, 0, 1, x, y);
2211 },
2212 rotate(angle) {
2213 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2214 let y;
2215 const rad = angle * Math.PI / 180;
2216 const cos = Math.cos(rad);
2217 const sin = Math.sin(rad);
2218 let x = y = 0;
2219 if (options.origin != null) {
2220 [x, y] = options.origin;
2221 const x1 = x * cos - y * sin;
2222 const y1 = x * sin + y * cos;
2223 x -= x1;
2224 y -= y1;
2225 }
2226 return this.transform(cos, sin, -sin, cos, x, y);
2227 },
2228 scale(xFactor, yFactor) {
2229 let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
2230 let y;
2231 if (yFactor == null) {
2232 yFactor = xFactor;
2233 }
2234 if (typeof yFactor === 'object') {
2235 options = yFactor;
2236 yFactor = xFactor;
2237 }
2238 let x = y = 0;
2239 if (options.origin != null) {
2240 [x, y] = options.origin;
2241 x -= xFactor * x;
2242 y -= yFactor * y;
2243 }
2244 return this.transform(xFactor, 0, 0, yFactor, x, y);
2245 }
2246};
2247
2248const WIN_ANSI_MAP = {
2249 402: 131,
2250 8211: 150,
2251 8212: 151,
2252 8216: 145,
2253 8217: 146,
2254 8218: 130,
2255 8220: 147,
2256 8221: 148,
2257 8222: 132,
2258 8224: 134,
2259 8225: 135,
2260 8226: 149,
2261 8230: 133,
2262 8364: 128,
2263 8240: 137,
2264 8249: 139,
2265 8250: 155,
2266 710: 136,
2267 8482: 153,
2268 338: 140,
2269 339: 156,
2270 732: 152,
2271 352: 138,
2272 353: 154,
2273 376: 159,
2274 381: 142,
2275 382: 158
2276};
2277const characters = `\
2278.notdef .notdef .notdef .notdef
2279.notdef .notdef .notdef .notdef
2280.notdef .notdef .notdef .notdef
2281.notdef .notdef .notdef .notdef
2282.notdef .notdef .notdef .notdef
2283.notdef .notdef .notdef .notdef
2284.notdef .notdef .notdef .notdef
2285.notdef .notdef .notdef .notdef
2286
2287space exclam quotedbl numbersign
2288dollar percent ampersand quotesingle
2289parenleft parenright asterisk plus
2290comma hyphen period slash
2291zero one two three
2292four five six seven
2293eight nine colon semicolon
2294less equal greater question
2295
2296at A B C
2297D E F G
2298H I J K
2299L M N O
2300P Q R S
2301T U V W
2302X Y Z bracketleft
2303backslash bracketright asciicircum underscore
2304
2305grave a b c
2306d e f g
2307h i j k
2308l m n o
2309p q r s
2310t u v w
2311x y z braceleft
2312bar braceright asciitilde .notdef
2313
2314Euro .notdef quotesinglbase florin
2315quotedblbase ellipsis dagger daggerdbl
2316circumflex perthousand Scaron guilsinglleft
2317OE .notdef Zcaron .notdef
2318.notdef quoteleft quoteright quotedblleft
2319quotedblright bullet endash emdash
2320tilde trademark scaron guilsinglright
2321oe .notdef zcaron ydieresis
2322
2323space exclamdown cent sterling
2324currency yen brokenbar section
2325dieresis copyright ordfeminine guillemotleft
2326logicalnot hyphen registered macron
2327degree plusminus twosuperior threesuperior
2328acute mu paragraph periodcentered
2329cedilla onesuperior ordmasculine guillemotright
2330onequarter onehalf threequarters questiondown
2331
2332Agrave Aacute Acircumflex Atilde
2333Adieresis Aring AE Ccedilla
2334Egrave Eacute Ecircumflex Edieresis
2335Igrave Iacute Icircumflex Idieresis
2336Eth Ntilde Ograve Oacute
2337Ocircumflex Otilde Odieresis multiply
2338Oslash Ugrave Uacute Ucircumflex
2339Udieresis Yacute Thorn germandbls
2340
2341agrave aacute acircumflex atilde
2342adieresis aring ae ccedilla
2343egrave eacute ecircumflex edieresis
2344igrave iacute icircumflex idieresis
2345eth ntilde ograve oacute
2346ocircumflex otilde odieresis divide
2347oslash ugrave uacute ucircumflex
2348udieresis yacute thorn ydieresis\
2349`.split(/\s+/);
2350class AFMFont {
2351 static open(filename) {
2352 return new AFMFont(fs.readFileSync(filename, 'utf8'));
2353 }
2354 constructor(contents) {
2355 this.contents = contents;
2356 this.attributes = {};
2357 this.glyphWidths = {};
2358 this.boundingBoxes = {};
2359 this.kernPairs = {};
2360 this.parse();
2361 // todo: remove charWidths since appears to not be used
2362 this.charWidths = new Array(256);
2363 for (let char = 0; char <= 255; char++) {
2364 this.charWidths[char] = this.glyphWidths[characters[char]];
2365 }
2366 this.bbox = this.attributes['FontBBox'].split(/\s+/).map(e => +e);
2367 this.ascender = +(this.attributes['Ascender'] || 0);
2368 this.descender = +(this.attributes['Descender'] || 0);
2369 this.xHeight = +(this.attributes['XHeight'] || 0);
2370 this.capHeight = +(this.attributes['CapHeight'] || 0);
2371 this.lineGap = this.bbox[3] - this.bbox[1] - (this.ascender - this.descender);
2372 }
2373 parse() {
2374 let section = '';
2375 for (let line of this.contents.split('\n')) {
2376 var match;
2377 var a;
2378 if (match = line.match(/^Start(\w+)/)) {
2379 section = match[1];
2380 continue;
2381 } else if (match = line.match(/^End(\w+)/)) {
2382 section = '';
2383 continue;
2384 }
2385 switch (section) {
2386 case 'FontMetrics':
2387 match = line.match(/(^\w+)\s+(.*)/);
2388 var key = match[1];
2389 var value = match[2];
2390 if (a = this.attributes[key]) {
2391 if (!Array.isArray(a)) {
2392 a = this.attributes[key] = [a];
2393 }
2394 a.push(value);
2395 } else {
2396 this.attributes[key] = value;
2397 }
2398 break;
2399 case 'CharMetrics':
2400 if (!/^CH?\s/.test(line)) {
2401 continue;
2402 }
2403 var name = line.match(/\bN\s+(\.?\w+)\s*;/)[1];
2404 this.glyphWidths[name] = +line.match(/\bWX\s+(\d+)\s*;/)[1];
2405 break;
2406 case 'KernPairs':
2407 match = line.match(/^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/);
2408 if (match) {
2409 this.kernPairs[match[1] + '\0' + match[2]] = parseInt(match[3]);
2410 }
2411 break;
2412 }
2413 }
2414 }
2415 encodeText(text) {
2416 const res = [];
2417 for (let i = 0, len = text.length; i < len; i++) {
2418 let char = text.charCodeAt(i);
2419 char = WIN_ANSI_MAP[char] || char;
2420 res.push(char.toString(16));
2421 }
2422 return res;
2423 }
2424 glyphsForString(string) {
2425 const glyphs = [];
2426 for (let i = 0, len = string.length; i < len; i++) {
2427 const charCode = string.charCodeAt(i);
2428 glyphs.push(this.characterToGlyph(charCode));
2429 }
2430 return glyphs;
2431 }
2432 characterToGlyph(character) {
2433 return characters[WIN_ANSI_MAP[character] || character] || '.notdef';
2434 }
2435 widthOfGlyph(glyph) {
2436 return this.glyphWidths[glyph] || 0;
2437 }
2438 getKernPair(left, right) {
2439 return this.kernPairs[left + '\0' + right] || 0;
2440 }
2441 advancesForGlyphs(glyphs) {
2442 const advances = [];
2443 for (let index = 0; index < glyphs.length; index++) {
2444 const left = glyphs[index];
2445 const right = glyphs[index + 1];
2446 advances.push(this.widthOfGlyph(left) + this.getKernPair(left, right));
2447 }
2448 return advances;
2449 }
2450}
2451
2452class PDFFont {
2453 constructor() {}
2454 encode() {
2455 throw new Error('Must be implemented by subclasses');
2456 }
2457 widthOfString() {
2458 throw new Error('Must be implemented by subclasses');
2459 }
2460 ref() {
2461 return this.dictionary != null ? this.dictionary : this.dictionary = this.document.ref();
2462 }
2463 finalize() {
2464 if (this.embedded || this.dictionary == null) {
2465 return;
2466 }
2467 this.embed();
2468 return this.embedded = true;
2469 }
2470 embed() {
2471 throw new Error('Must be implemented by subclasses');
2472 }
2473 lineHeight(size, includeGap) {
2474 if (includeGap == null) {
2475 includeGap = false;
2476 }
2477 const gap = includeGap ? this.lineGap : 0;
2478 return (this.ascender + gap - this.descender) / 1000 * size;
2479 }
2480}
2481
2482// This insanity is so bundlers can inline the font files
2483const STANDARD_FONTS = {
2484 Courier() {
2485 return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
2486 },
2487 'Courier-Bold'() {
2488 return fs.readFileSync(__dirname + '/data/Courier-Bold.afm', 'utf8');
2489 },
2490 'Courier-Oblique'() {
2491 return fs.readFileSync(__dirname + '/data/Courier-Oblique.afm', 'utf8');
2492 },
2493 'Courier-BoldOblique'() {
2494 return fs.readFileSync(__dirname + '/data/Courier-BoldOblique.afm', 'utf8');
2495 },
2496 Helvetica() {
2497 return fs.readFileSync(__dirname + '/data/Helvetica.afm', 'utf8');
2498 },
2499 'Helvetica-Bold'() {
2500 return fs.readFileSync(__dirname + '/data/Helvetica-Bold.afm', 'utf8');
2501 },
2502 'Helvetica-Oblique'() {
2503 return fs.readFileSync(__dirname + '/data/Helvetica-Oblique.afm', 'utf8');
2504 },
2505 'Helvetica-BoldOblique'() {
2506 return fs.readFileSync(__dirname + '/data/Helvetica-BoldOblique.afm', 'utf8');
2507 },
2508 'Times-Roman'() {
2509 return fs.readFileSync(__dirname + '/data/Times-Roman.afm', 'utf8');
2510 },
2511 'Times-Bold'() {
2512 return fs.readFileSync(__dirname + '/data/Times-Bold.afm', 'utf8');
2513 },
2514 'Times-Italic'() {
2515 return fs.readFileSync(__dirname + '/data/Times-Italic.afm', 'utf8');
2516 },
2517 'Times-BoldItalic'() {
2518 return fs.readFileSync(__dirname + '/data/Times-BoldItalic.afm', 'utf8');
2519 },
2520 Symbol() {
2521 return fs.readFileSync(__dirname + '/data/Symbol.afm', 'utf8');
2522 },
2523 ZapfDingbats() {
2524 return fs.readFileSync(__dirname + '/data/ZapfDingbats.afm', 'utf8');
2525 }
2526};
2527class StandardFont extends PDFFont {
2528 constructor(document, name, id) {
2529 super();
2530 this.document = document;
2531 this.name = name;
2532 this.id = id;
2533 this.font = new AFMFont(STANDARD_FONTS[this.name]());
2534 ({
2535 ascender: this.ascender,
2536 descender: this.descender,
2537 bbox: this.bbox,
2538 lineGap: this.lineGap,
2539 xHeight: this.xHeight,
2540 capHeight: this.capHeight
2541 } = this.font);
2542 }
2543 embed() {
2544 this.dictionary.data = {
2545 Type: 'Font',
2546 BaseFont: this.name,
2547 Subtype: 'Type1',
2548 Encoding: 'WinAnsiEncoding'
2549 };
2550 return this.dictionary.end();
2551 }
2552 encode(text) {
2553 const encoded = this.font.encodeText(text);
2554 const glyphs = this.font.glyphsForString(`${text}`);
2555 const advances = this.font.advancesForGlyphs(glyphs);
2556 const positions = [];
2557 for (let i = 0; i < glyphs.length; i++) {
2558 const glyph = glyphs[i];
2559 positions.push({
2560 xAdvance: advances[i],
2561 yAdvance: 0,
2562 xOffset: 0,
2563 yOffset: 0,
2564 advanceWidth: this.font.widthOfGlyph(glyph)
2565 });
2566 }
2567 return [encoded, positions];
2568 }
2569 widthOfString(string, size) {
2570 const glyphs = this.font.glyphsForString(`${string}`);
2571 const advances = this.font.advancesForGlyphs(glyphs);
2572 let width = 0;
2573 for (let advance of advances) {
2574 width += advance;
2575 }
2576 const scale = size / 1000;
2577 return width * scale;
2578 }
2579 static isStandardFont(name) {
2580 return name in STANDARD_FONTS;
2581 }
2582}
2583
2584const toHex = function (num) {
2585 return `0000${num.toString(16)}`.slice(-4);
2586};
2587class EmbeddedFont extends PDFFont {
2588 constructor(document, font, id) {
2589 super();
2590 this.document = document;
2591 this.font = font;
2592 this.id = id;
2593 this.subset = this.font.createSubset();
2594 this.unicode = [[0]];
2595 this.widths = [this.font.getGlyph(0).advanceWidth];
2596 this.name = this.font.postscriptName;
2597 this.scale = 1000 / this.font.unitsPerEm;
2598 this.ascender = this.font.ascent * this.scale;
2599 this.descender = this.font.descent * this.scale;
2600 this.xHeight = this.font.xHeight * this.scale;
2601 this.capHeight = this.font.capHeight * this.scale;
2602 this.lineGap = this.font.lineGap * this.scale;
2603 this.bbox = this.font.bbox;
2604 if (document.options.fontLayoutCache !== false) {
2605 this.layoutCache = Object.create(null);
2606 }
2607 }
2608 layoutRun(text, features) {
2609 const run = this.font.layout(text, features);
2610
2611 // Normalize position values
2612 for (let i = 0; i < run.positions.length; i++) {
2613 const position = run.positions[i];
2614 for (let key in position) {
2615 position[key] *= this.scale;
2616 }
2617 position.advanceWidth = run.glyphs[i].advanceWidth * this.scale;
2618 }
2619 return run;
2620 }
2621 layoutCached(text) {
2622 if (!this.layoutCache) {
2623 return this.layoutRun(text);
2624 }
2625 let cached;
2626 if (cached = this.layoutCache[text]) {
2627 return cached;
2628 }
2629 const run = this.layoutRun(text);
2630 this.layoutCache[text] = run;
2631 return run;
2632 }
2633 layout(text, features, onlyWidth) {
2634 // Skip the cache if any user defined features are applied
2635 if (features) {
2636 return this.layoutRun(text, features);
2637 }
2638 let glyphs = onlyWidth ? null : [];
2639 let positions = onlyWidth ? null : [];
2640 let advanceWidth = 0;
2641
2642 // Split the string by words to increase cache efficiency.
2643 // For this purpose, spaces and tabs are a good enough delimeter.
2644 let last = 0;
2645 let index = 0;
2646 while (index <= text.length) {
2647 var needle;
2648 if (index === text.length && last < index || (needle = text.charAt(index), [' ', '\t'].includes(needle))) {
2649 const run = this.layoutCached(text.slice(last, ++index));
2650 if (!onlyWidth) {
2651 glyphs = glyphs.concat(run.glyphs);
2652 positions = positions.concat(run.positions);
2653 }
2654 advanceWidth += run.advanceWidth;
2655 last = index;
2656 } else {
2657 index++;
2658 }
2659 }
2660 return {
2661 glyphs,
2662 positions,
2663 advanceWidth
2664 };
2665 }
2666 encode(text, features) {
2667 const {
2668 glyphs,
2669 positions
2670 } = this.layout(text, features);
2671 const res = [];
2672 for (let i = 0; i < glyphs.length; i++) {
2673 const glyph = glyphs[i];
2674 const gid = this.subset.includeGlyph(glyph.id);
2675 res.push(`0000${gid.toString(16)}`.slice(-4));
2676 if (this.widths[gid] == null) {
2677 this.widths[gid] = glyph.advanceWidth * this.scale;
2678 }
2679 if (this.unicode[gid] == null) {
2680 this.unicode[gid] = glyph.codePoints;
2681 }
2682 }
2683 return [res, positions];
2684 }
2685 widthOfString(string, size, features) {
2686 const width = this.layout(string, features, true).advanceWidth;
2687 const scale = size / 1000;
2688 return width * scale;
2689 }
2690 embed() {
2691 const isCFF = this.subset.cff != null;
2692 const fontFile = this.document.ref();
2693 if (isCFF) {
2694 fontFile.data.Subtype = 'CIDFontType0C';
2695 }
2696 this.subset.encodeStream().on('data', data => fontFile.write(data)).on('end', () => fontFile.end());
2697 const familyClass = ((this.font['OS/2'] != null ? this.font['OS/2'].sFamilyClass : undefined) || 0) >> 8;
2698 let flags = 0;
2699 if (this.font.post.isFixedPitch) {
2700 flags |= 1 << 0;
2701 }
2702 if (1 <= familyClass && familyClass <= 7) {
2703 flags |= 1 << 1;
2704 }
2705 flags |= 1 << 2; // assume the font uses non-latin characters
2706 if (familyClass === 10) {
2707 flags |= 1 << 3;
2708 }
2709 if (this.font.head.macStyle.italic) {
2710 flags |= 1 << 6;
2711 }
2712
2713 // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2714 const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)).join('');
2715 const name = tag + '+' + this.font.postscriptName;
2716 const {
2717 bbox
2718 } = this.font;
2719 const descriptor = this.document.ref({
2720 Type: 'FontDescriptor',
2721 FontName: name,
2722 Flags: flags,
2723 FontBBox: [bbox.minX * this.scale, bbox.minY * this.scale, bbox.maxX * this.scale, bbox.maxY * this.scale],
2724 ItalicAngle: this.font.italicAngle,
2725 Ascent: this.ascender,
2726 Descent: this.descender,
2727 CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
2728 XHeight: (this.font.xHeight || 0) * this.scale,
2729 StemV: 0
2730 }); // not sure how to calculate this
2731
2732 if (isCFF) {
2733 descriptor.data.FontFile3 = fontFile;
2734 } else {
2735 descriptor.data.FontFile2 = fontFile;
2736 }
2737 if (this.document.subset) {
2738 const CIDSet = Buffer.from('FFFFFFFFC0', 'hex');
2739 const CIDSetRef = this.document.ref();
2740 CIDSetRef.write(CIDSet);
2741 CIDSetRef.end();
2742 descriptor.data.CIDSet = CIDSetRef;
2743 }
2744 descriptor.end();
2745 const descendantFontData = {
2746 Type: 'Font',
2747 Subtype: 'CIDFontType0',
2748 BaseFont: name,
2749 CIDSystemInfo: {
2750 Registry: new String('Adobe'),
2751 Ordering: new String('Identity'),
2752 Supplement: 0
2753 },
2754 FontDescriptor: descriptor,
2755 W: [0, this.widths]
2756 };
2757 if (!isCFF) {
2758 descendantFontData.Subtype = 'CIDFontType2';
2759 descendantFontData.CIDToGIDMap = 'Identity';
2760 }
2761 const descendantFont = this.document.ref(descendantFontData);
2762 descendantFont.end();
2763 this.dictionary.data = {
2764 Type: 'Font',
2765 Subtype: 'Type0',
2766 BaseFont: name,
2767 Encoding: 'Identity-H',
2768 DescendantFonts: [descendantFont],
2769 ToUnicode: this.toUnicodeCmap()
2770 };
2771 return this.dictionary.end();
2772 }
2773
2774 // Maps the glyph ids encoded in the PDF back to unicode strings
2775 // Because of ligature substitutions and the like, there may be one or more
2776 // unicode characters represented by each glyph.
2777 toUnicodeCmap() {
2778 const cmap = this.document.ref();
2779 const entries = [];
2780 for (let codePoints of this.unicode) {
2781 const encoded = [];
2782
2783 // encode codePoints to utf16
2784 for (let value of codePoints) {
2785 if (value > 0xffff) {
2786 value -= 0x10000;
2787 encoded.push(toHex(value >>> 10 & 0x3ff | 0xd800));
2788 value = 0xdc00 | value & 0x3ff;
2789 }
2790 encoded.push(toHex(value));
2791 }
2792 entries.push(`<${encoded.join(' ')}>`);
2793 }
2794 const chunkSize = 256;
2795 const chunks = Math.ceil(entries.length / chunkSize);
2796 const ranges = [];
2797 for (let i = 0; i < chunks; i++) {
2798 const start = i * chunkSize;
2799 const end = Math.min((i + 1) * chunkSize, entries.length);
2800 ranges.push(`<${toHex(start)}> <${toHex(end - 1)}> [${entries.slice(start, end).join(' ')}]`);
2801 }
2802 cmap.end(`\
2803/CIDInit /ProcSet findresource begin
280412 dict begin
2805begincmap
2806/CIDSystemInfo <<
2807 /Registry (Adobe)
2808 /Ordering (UCS)
2809 /Supplement 0
2810>> def
2811/CMapName /Adobe-Identity-UCS def
2812/CMapType 2 def
28131 begincodespacerange
2814<0000><ffff>
2815endcodespacerange
28161 beginbfrange
2817${ranges.join('\n')}
2818endbfrange
2819endcmap
2820CMapName currentdict /CMap defineresource pop
2821end
2822end\
2823`);
2824 return cmap;
2825 }
2826}
2827
2828class PDFFontFactory {
2829 static open(document, src, family, id) {
2830 let font;
2831 if (typeof src === 'string') {
2832 if (StandardFont.isStandardFont(src)) {
2833 return new StandardFont(document, src, id);
2834 }
2835 src = fs.readFileSync(src);
2836 }
2837 if (Buffer.isBuffer(src)) {
2838 font = fontkit.create(src, family);
2839 } else if (src instanceof Uint8Array) {
2840 font = fontkit.create(Buffer.from(src), family);
2841 } else if (src instanceof ArrayBuffer) {
2842 font = fontkit.create(Buffer.from(new Uint8Array(src)), family);
2843 }
2844 if (font == null) {
2845 throw new Error('Not a supported font format or standard PDF font.');
2846 }
2847 return new EmbeddedFont(document, font, id);
2848 }
2849}
2850
2851var FontsMixin = {
2852 initFonts() {
2853 let defaultFont = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Helvetica';
2854 // Lookup table for embedded fonts
2855 this._fontFamilies = {};
2856 this._fontCount = 0;
2857
2858 // Font state
2859 this._fontSize = 12;
2860 this._font = null;
2861 this._registeredFonts = {};
2862
2863 // Set the default font
2864 if (defaultFont) {
2865 this.font(defaultFont);
2866 }
2867 },
2868 font(src, family, size) {
2869 let cacheKey, font;
2870 if (typeof family === 'number') {
2871 size = family;
2872 family = null;
2873 }
2874
2875 // check registered fonts if src is a string
2876 if (typeof src === 'string' && this._registeredFonts[src]) {
2877 cacheKey = src;
2878 ({
2879 src,
2880 family
2881 } = this._registeredFonts[src]);
2882 } else {
2883 cacheKey = family || src;
2884 if (typeof cacheKey !== 'string') {
2885 cacheKey = null;
2886 }
2887 }
2888 if (size != null) {
2889 this.fontSize(size);
2890 }
2891
2892 // fast path: check if the font is already in the PDF
2893 if (font = this._fontFamilies[cacheKey]) {
2894 this._font = font;
2895 return this;
2896 }
2897
2898 // load the font
2899 const id = `F${++this._fontCount}`;
2900 this._font = PDFFontFactory.open(this, src, family, id);
2901
2902 // check for existing font familes with the same name already in the PDF
2903 // useful if the font was passed as a buffer
2904 if (font = this._fontFamilies[this._font.name]) {
2905 this._font = font;
2906 return this;
2907 }
2908
2909 // save the font for reuse later
2910 if (cacheKey) {
2911 this._fontFamilies[cacheKey] = this._font;
2912 }
2913 if (this._font.name) {
2914 this._fontFamilies[this._font.name] = this._font;
2915 }
2916 return this;
2917 },
2918 fontSize(_fontSize) {
2919 this._fontSize = _fontSize;
2920 return this;
2921 },
2922 currentLineHeight(includeGap) {
2923 if (includeGap == null) {
2924 includeGap = false;
2925 }
2926 return this._font.lineHeight(this._fontSize, includeGap);
2927 },
2928 registerFont(name, src, family) {
2929 this._registeredFonts[name] = {
2930 src,
2931 family
2932 };
2933 return this;
2934 }
2935};
2936
2937const SOFT_HYPHEN = '\u00AD';
2938const HYPHEN = '-';
2939class LineWrapper extends EventEmitter {
2940 constructor(document, options) {
2941 super();
2942 this.document = document;
2943 this.indent = options.indent || 0;
2944 this.characterSpacing = options.characterSpacing || 0;
2945 this.wordSpacing = options.wordSpacing === 0;
2946 this.columns = options.columns || 1;
2947 this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
2948 this.lineWidth = (options.width - this.columnGap * (this.columns - 1)) / this.columns;
2949 this.spaceLeft = this.lineWidth;
2950 this.startX = this.document.x;
2951 this.startY = this.document.y;
2952 this.column = 1;
2953 this.ellipsis = options.ellipsis;
2954 this.continuedX = 0;
2955 this.features = options.features;
2956
2957 // calculate the maximum Y position the text can appear at
2958 if (options.height != null) {
2959 this.height = options.height;
2960 this.maxY = this.startY + options.height;
2961 } else {
2962 this.maxY = this.document.page.maxY();
2963 }
2964
2965 // handle paragraph indents
2966 this.on('firstLine', options => {
2967 // if this is the first line of the text segment, and
2968 // we're continuing where we left off, indent that much
2969 // otherwise use the user specified indent option
2970 const indent = this.continuedX || this.indent;
2971 this.document.x += indent;
2972 this.lineWidth -= indent;
2973 return this.once('line', () => {
2974 this.document.x -= indent;
2975 this.lineWidth += indent;
2976 if (options.continued && !this.continuedX) {
2977 this.continuedX = this.indent;
2978 }
2979 if (!options.continued) {
2980 return this.continuedX = 0;
2981 }
2982 });
2983 });
2984
2985 // handle left aligning last lines of paragraphs
2986 this.on('lastLine', options => {
2987 const {
2988 align
2989 } = options;
2990 if (align === 'justify') {
2991 options.align = 'left';
2992 }
2993 this.lastLine = true;
2994 return this.once('line', () => {
2995 this.document.y += options.paragraphGap || 0;
2996 options.align = align;
2997 return this.lastLine = false;
2998 });
2999 });
3000 }
3001 wordWidth(word) {
3002 return this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing;
3003 }
3004 canFit(word, w) {
3005 if (word[word.length - 1] != SOFT_HYPHEN) {
3006 return w <= this.spaceLeft;
3007 }
3008 return w + this.wordWidth(HYPHEN) <= this.spaceLeft;
3009 }
3010 eachWord(text, fn) {
3011 // setup a unicode line breaker
3012 let bk;
3013 const breaker = new LineBreaker(text);
3014 let last = null;
3015 const wordWidths = Object.create(null);
3016 while (bk = breaker.nextBreak()) {
3017 var shouldContinue;
3018 let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
3019 let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
3020
3021 // if the word is longer than the whole line, chop it up
3022 // TODO: break by grapheme clusters, not JS string characters
3023 if (w > this.lineWidth + this.continuedX) {
3024 // make some fake break objects
3025 let lbk = last;
3026 const fbk = {};
3027 while (word.length) {
3028 // fit as much of the word as possible into the space we have
3029 var l, mightGrow;
3030 if (w > this.spaceLeft) {
3031 // start our check at the end of our available space - this method is faster than a loop of each character and it resolves
3032 // an issue with long loops when processing massive words, such as a huge number of spaces
3033 l = Math.ceil(this.spaceLeft / (w / word.length));
3034 w = this.wordWidth(word.slice(0, l));
3035 mightGrow = w <= this.spaceLeft && l < word.length;
3036 } else {
3037 l = word.length;
3038 }
3039 let mustShrink = w > this.spaceLeft && l > 0;
3040 // shrink or grow word as necessary after our near-guess above
3041 while (mustShrink || mightGrow) {
3042 if (mustShrink) {
3043 w = this.wordWidth(word.slice(0, --l));
3044 mustShrink = w > this.spaceLeft && l > 0;
3045 } else {
3046 w = this.wordWidth(word.slice(0, ++l));
3047 mustShrink = w > this.spaceLeft && l > 0;
3048 mightGrow = w <= this.spaceLeft && l < word.length;
3049 }
3050 }
3051
3052 // check for the edge case where a single character cannot fit into a line.
3053 if (l === 0 && this.spaceLeft === this.lineWidth) {
3054 l = 1;
3055 }
3056
3057 // send a required break unless this is the last piece and a linebreak is not specified
3058 fbk.required = bk.required || l < word.length;
3059 shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
3060 lbk = {
3061 required: false
3062 };
3063
3064 // get the remaining piece of the word
3065 word = word.slice(l);
3066 w = this.wordWidth(word);
3067 if (shouldContinue === false) {
3068 break;
3069 }
3070 }
3071 } else {
3072 // otherwise just emit the break as it was given to us
3073 shouldContinue = fn(word, w, bk, last);
3074 }
3075 if (shouldContinue === false) {
3076 break;
3077 }
3078 last = bk;
3079 }
3080 }
3081 wrap(text, options) {
3082 // override options from previous continued fragments
3083 if (options.indent != null) {
3084 this.indent = options.indent;
3085 }
3086 if (options.characterSpacing != null) {
3087 this.characterSpacing = options.characterSpacing;
3088 }
3089 if (options.wordSpacing != null) {
3090 this.wordSpacing = options.wordSpacing;
3091 }
3092 if (options.ellipsis != null) {
3093 this.ellipsis = options.ellipsis;
3094 }
3095
3096 // make sure we're actually on the page
3097 // and that the first line of is never by
3098 // itself at the bottom of a page (orphans)
3099 const nextY = this.document.y + this.document.currentLineHeight(true);
3100 if (this.document.y > this.maxY || nextY > this.maxY) {
3101 this.nextSection();
3102 }
3103 let buffer = '';
3104 let textWidth = 0;
3105 let wc = 0;
3106 let lc = 0;
3107 let {
3108 y
3109 } = this.document; // used to reset Y pos if options.continued (below)
3110 const emitLine = () => {
3111 options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3112 options.wordCount = wc;
3113 options.lineWidth = this.lineWidth;
3114 ({
3115 y
3116 } = this.document);
3117 this.emit('line', buffer, options, this);
3118 return lc++;
3119 };
3120 this.emit('sectionStart', options, this);
3121 this.eachWord(text, (word, w, bk, last) => {
3122 if (last == null || last.required) {
3123 this.emit('firstLine', options, this);
3124 this.spaceLeft = this.lineWidth;
3125 }
3126 if (this.canFit(word, w)) {
3127 buffer += word;
3128 textWidth += w;
3129 wc++;
3130 }
3131 if (bk.required || !this.canFit(word, w)) {
3132 // if the user specified a max height and an ellipsis, and is about to pass the
3133 // max height and max columns after the next line, append the ellipsis
3134 const lh = this.document.currentLineHeight(true);
3135 if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
3136 if (this.ellipsis === true) {
3137 this.ellipsis = '…';
3138 } // map default ellipsis character
3139 buffer = buffer.replace(/\s+$/, '');
3140 textWidth = this.wordWidth(buffer + this.ellipsis);
3141
3142 // remove characters from the buffer until the ellipsis fits
3143 // to avoid infinite loop need to stop while-loop if buffer is empty string
3144 while (buffer && textWidth > this.lineWidth) {
3145 buffer = buffer.slice(0, -1).replace(/\s+$/, '');
3146 textWidth = this.wordWidth(buffer + this.ellipsis);
3147 }
3148 // need to add ellipsis only if there is enough space for it
3149 if (textWidth <= this.lineWidth) {
3150 buffer = buffer + this.ellipsis;
3151 }
3152 textWidth = this.wordWidth(buffer);
3153 }
3154 if (bk.required) {
3155 if (w > this.spaceLeft) {
3156 emitLine();
3157 buffer = word;
3158 textWidth = w;
3159 wc = 1;
3160 }
3161 this.emit('lastLine', options, this);
3162 }
3163
3164 // Previous entry is a soft hyphen - add visible hyphen.
3165 if (buffer[buffer.length - 1] == SOFT_HYPHEN) {
3166 buffer = buffer.slice(0, -1) + HYPHEN;
3167 this.spaceLeft -= this.wordWidth(HYPHEN);
3168 }
3169 emitLine();
3170
3171 // if we've reached the edge of the page,
3172 // continue on a new page or column
3173 if (this.document.y + lh > this.maxY) {
3174 const shouldContinue = this.nextSection();
3175
3176 // stop if we reached the maximum height
3177 if (!shouldContinue) {
3178 wc = 0;
3179 buffer = '';
3180 return false;
3181 }
3182 }
3183
3184 // reset the space left and buffer
3185 if (bk.required) {
3186 this.spaceLeft = this.lineWidth;
3187 buffer = '';
3188 textWidth = 0;
3189 return wc = 0;
3190 } else {
3191 // reset the space left and buffer
3192 this.spaceLeft = this.lineWidth - w;
3193 buffer = word;
3194 textWidth = w;
3195 return wc = 1;
3196 }
3197 } else {
3198 return this.spaceLeft -= w;
3199 }
3200 });
3201 if (wc > 0) {
3202 this.emit('lastLine', options, this);
3203 emitLine();
3204 }
3205 this.emit('sectionEnd', options, this);
3206
3207 // if the wrap is set to be continued, save the X position
3208 // to start the first line of the next segment at, and reset
3209 // the y position
3210 if (options.continued === true) {
3211 if (lc > 1) {
3212 this.continuedX = 0;
3213 }
3214 this.continuedX += options.textWidth || 0;
3215 return this.document.y = y;
3216 } else {
3217 return this.document.x = this.startX;
3218 }
3219 }
3220 nextSection(options) {
3221 this.emit('sectionEnd', options, this);
3222 if (++this.column > this.columns) {
3223 // if a max height was specified by the user, we're done.
3224 // otherwise, the default is to make a new page at the bottom.
3225 if (this.height != null) {
3226 return false;
3227 }
3228 this.document.continueOnNewPage();
3229 this.column = 1;
3230 this.startY = this.document.page.margins.top;
3231 this.maxY = this.document.page.maxY();
3232 this.document.x = this.startX;
3233 if (this.document._fillColor) {
3234 this.document.fillColor(...this.document._fillColor);
3235 }
3236 this.emit('pageBreak', options, this);
3237 } else {
3238 this.document.x += this.lineWidth + this.columnGap;
3239 this.document.y = this.startY;
3240 this.emit('columnBreak', options, this);
3241 }
3242 this.emit('sectionStart', options, this);
3243 return true;
3244 }
3245}
3246
3247const {
3248 number: number$2
3249} = PDFObject;
3250var TextMixin = {
3251 initText() {
3252 this._line = this._line.bind(this);
3253 // Current coordinates
3254 this.x = 0;
3255 this.y = 0;
3256 return this._lineGap = 0;
3257 },
3258 lineGap(_lineGap) {
3259 this._lineGap = _lineGap;
3260 return this;
3261 },
3262 moveDown(lines) {
3263 if (lines == null) {
3264 lines = 1;
3265 }
3266 this.y += this.currentLineHeight(true) * lines + this._lineGap;
3267 return this;
3268 },
3269 moveUp(lines) {
3270 if (lines == null) {
3271 lines = 1;
3272 }
3273 this.y -= this.currentLineHeight(true) * lines + this._lineGap;
3274 return this;
3275 },
3276 _text(text, x, y, options, lineCallback) {
3277 options = this._initOptions(x, y, options);
3278
3279 // Convert text to a string
3280 text = text == null ? '' : `${text}`;
3281
3282 // if the wordSpacing option is specified, remove multiple consecutive spaces
3283 if (options.wordSpacing) {
3284 text = text.replace(/\s{2,}/g, ' ');
3285 }
3286 const addStructure = () => {
3287 if (options.structParent) {
3288 options.structParent.add(this.struct(options.structType || 'P', [this.markStructureContent(options.structType || 'P')]));
3289 }
3290 };
3291
3292 // word wrapping
3293 if (options.width) {
3294 let wrapper = this._wrapper;
3295 if (!wrapper) {
3296 wrapper = new LineWrapper(this, options);
3297 wrapper.on('line', lineCallback);
3298 wrapper.on('firstLine', addStructure);
3299 }
3300 this._wrapper = options.continued ? wrapper : null;
3301 this._textOptions = options.continued ? options : null;
3302 wrapper.wrap(text, options);
3303
3304 // render paragraphs as single lines
3305 } else {
3306 for (let line of text.split('\n')) {
3307 addStructure();
3308 lineCallback(line, options);
3309 }
3310 }
3311 return this;
3312 },
3313 text(text, x, y, options) {
3314 return this._text(text, x, y, options, this._line);
3315 },
3316 widthOfString(string) {
3317 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3318 return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1);
3319 },
3320 heightOfString(text, options) {
3321 const {
3322 x,
3323 y
3324 } = this;
3325 options = this._initOptions(options);
3326 options.height = Infinity; // don't break pages
3327
3328 const lineGap = options.lineGap || this._lineGap || 0;
3329 this._text(text, this.x, this.y, options, () => {
3330 return this.y += this.currentLineHeight(true) + lineGap;
3331 });
3332 const height = this.y - y;
3333 this.x = x;
3334 this.y = y;
3335 return height;
3336 },
3337 list(list, x, y, options, wrapper) {
3338 options = this._initOptions(x, y, options);
3339 const listType = options.listType || 'bullet';
3340 const unit = Math.round(this._font.ascender / 1000 * this._fontSize);
3341 const midLine = unit / 2;
3342 const r = options.bulletRadius || unit / 3;
3343 const indent = options.textIndent || (listType === 'bullet' ? r * 5 : unit * 2);
3344 const itemIndent = options.bulletIndent || (listType === 'bullet' ? r * 8 : unit * 2);
3345 let level = 1;
3346 const items = [];
3347 const levels = [];
3348 const numbers = [];
3349 var flatten = function (list) {
3350 let n = 1;
3351 for (let i = 0; i < list.length; i++) {
3352 const item = list[i];
3353 if (Array.isArray(item)) {
3354 level++;
3355 flatten(item);
3356 level--;
3357 } else {
3358 items.push(item);
3359 levels.push(level);
3360 if (listType !== 'bullet') {
3361 numbers.push(n++);
3362 }
3363 }
3364 }
3365 };
3366 flatten(list);
3367 const label = function (n) {
3368 switch (listType) {
3369 case 'numbered':
3370 return `${n}.`;
3371 case 'lettered':
3372 var letter = String.fromCharCode((n - 1) % 26 + 65);
3373 var times = Math.floor((n - 1) / 26 + 1);
3374 var text = Array(times + 1).join(letter);
3375 return `${text}.`;
3376 }
3377 };
3378 const drawListItem = function (listItem) {
3379 wrapper = new LineWrapper(this, options);
3380 wrapper.on('line', this._line);
3381 level = 1;
3382 let i = 0;
3383 wrapper.once('firstLine', () => {
3384 let item, itemType, labelType, bodyType;
3385 if (options.structParent) {
3386 if (options.structTypes) {
3387 [itemType, labelType, bodyType] = options.structTypes;
3388 } else {
3389 [itemType, labelType, bodyType] = ['LI', 'Lbl', 'LBody'];
3390 }
3391 }
3392 if (itemType) {
3393 item = this.struct(itemType);
3394 options.structParent.add(item);
3395 } else if (options.structParent) {
3396 item = options.structParent;
3397 }
3398 let l;
3399 if ((l = levels[i++]) !== level) {
3400 const diff = itemIndent * (l - level);
3401 this.x += diff;
3402 wrapper.lineWidth -= diff;
3403 level = l;
3404 }
3405 if (item && (labelType || bodyType)) {
3406 item.add(this.struct(labelType || bodyType, [this.markStructureContent(labelType || bodyType)]));
3407 }
3408 switch (listType) {
3409 case 'bullet':
3410 this.circle(this.x - indent + r, this.y + midLine, r);
3411 this.fill();
3412 break;
3413 case 'numbered':
3414 case 'lettered':
3415 var text = label(numbers[i - 1]);
3416 this._fragment(text, this.x - indent, this.y, options);
3417 break;
3418 }
3419 if (item && labelType && bodyType) {
3420 item.add(this.struct(bodyType, [this.markStructureContent(bodyType)]));
3421 }
3422 if (item && item !== options.structParent) {
3423 item.end();
3424 }
3425 });
3426 wrapper.on('sectionStart', () => {
3427 const pos = indent + itemIndent * (level - 1);
3428 this.x += pos;
3429 return wrapper.lineWidth -= pos;
3430 });
3431 wrapper.on('sectionEnd', () => {
3432 const pos = indent + itemIndent * (level - 1);
3433 this.x -= pos;
3434 return wrapper.lineWidth += pos;
3435 });
3436 wrapper.wrap(listItem, options);
3437 };
3438 for (let i = 0; i < items.length; i++) {
3439 drawListItem.call(this, items[i]);
3440 }
3441 return this;
3442 },
3443 _initOptions() {
3444 let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
3445 let y = arguments.length > 1 ? arguments[1] : undefined;
3446 let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
3447 if (typeof x === 'object') {
3448 options = x;
3449 x = null;
3450 }
3451
3452 // clone options object
3453 const result = Object.assign({}, options);
3454
3455 // extend options with previous values for continued text
3456 if (this._textOptions) {
3457 for (let key in this._textOptions) {
3458 const val = this._textOptions[key];
3459 if (key !== 'continued') {
3460 if (result[key] === undefined) {
3461 result[key] = val;
3462 }
3463 }
3464 }
3465 }
3466
3467 // Update the current position
3468 if (x != null) {
3469 this.x = x;
3470 }
3471 if (y != null) {
3472 this.y = y;
3473 }
3474
3475 // wrap to margins if no x or y position passed
3476 if (result.lineBreak !== false) {
3477 if (result.width == null) {
3478 result.width = this.page.width - this.x - this.page.margins.right;
3479 }
3480 result.width = Math.max(result.width, 0);
3481 }
3482 if (!result.columns) {
3483 result.columns = 0;
3484 }
3485 if (result.columnGap == null) {
3486 result.columnGap = 18;
3487 } // 1/4 inch
3488
3489 return result;
3490 },
3491 _line(text) {
3492 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3493 let wrapper = arguments.length > 2 ? arguments[2] : undefined;
3494 this._fragment(text, this.x, this.y, options);
3495 const lineGap = options.lineGap || this._lineGap || 0;
3496 if (!wrapper) {
3497 return this.x += this.widthOfString(text);
3498 } else {
3499 return this.y += this.currentLineHeight(true) + lineGap;
3500 }
3501 },
3502 _fragment(text, x, y, options) {
3503 let dy, encoded, i, positions, textWidth, words;
3504 text = `${text}`.replace(/\n/g, '');
3505 if (text.length === 0) {
3506 return;
3507 }
3508
3509 // handle options
3510 const align = options.align || 'left';
3511 let wordSpacing = options.wordSpacing || 0;
3512 const characterSpacing = options.characterSpacing || 0;
3513
3514 // text alignments
3515 if (options.width) {
3516 switch (align) {
3517 case 'right':
3518 textWidth = this.widthOfString(text.replace(/\s+$/, ''), options);
3519 x += options.lineWidth - textWidth;
3520 break;
3521 case 'center':
3522 x += options.lineWidth / 2 - options.textWidth / 2;
3523 break;
3524 case 'justify':
3525 // calculate the word spacing value
3526 words = text.trim().split(/\s+/);
3527 textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3528 var spaceWidth = this.widthOfString(' ') + characterSpacing;
3529 wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3530 break;
3531 }
3532 }
3533
3534 // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3535 if (typeof options.baseline === 'number') {
3536 dy = -options.baseline;
3537 } else {
3538 switch (options.baseline) {
3539 case 'svg-middle':
3540 dy = 0.5 * this._font.xHeight;
3541 break;
3542 case 'middle':
3543 case 'svg-central':
3544 dy = 0.5 * (this._font.descender + this._font.ascender);
3545 break;
3546 case 'bottom':
3547 case 'ideographic':
3548 dy = this._font.descender;
3549 break;
3550 case 'alphabetic':
3551 dy = 0;
3552 break;
3553 case 'mathematical':
3554 dy = 0.5 * this._font.ascender;
3555 break;
3556 case 'hanging':
3557 dy = 0.8 * this._font.ascender;
3558 break;
3559 case 'top':
3560 dy = this._font.ascender;
3561 break;
3562 default:
3563 dy = this._font.ascender;
3564 }
3565 dy = dy / 1000 * this._fontSize;
3566 }
3567
3568 // calculate the actual rendered width of the string after word and character spacing
3569 const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3570
3571 // create link annotations if the link option is given
3572 if (options.link != null) {
3573 this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3574 }
3575 if (options.goTo != null) {
3576 this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
3577 }
3578 if (options.destination != null) {
3579 this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3580 }
3581
3582 // create underline
3583 if (options.underline) {
3584 this.save();
3585 if (!options.stroke) {
3586 this.strokeColor(...(this._fillColor || []));
3587 }
3588 const lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
3589 this.lineWidth(lineWidth);
3590 let lineY = y + this.currentLineHeight() - lineWidth;
3591 this.moveTo(x, lineY);
3592 this.lineTo(x + renderedWidth, lineY);
3593 this.stroke();
3594 this.restore();
3595 }
3596
3597 // create strikethrough line
3598 if (options.strike) {
3599 this.save();
3600 if (!options.stroke) {
3601 this.strokeColor(...(this._fillColor || []));
3602 }
3603 const lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
3604 this.lineWidth(lineWidth);
3605 let lineY = y + this.currentLineHeight() / 2;
3606 this.moveTo(x, lineY);
3607 this.lineTo(x + renderedWidth, lineY);
3608 this.stroke();
3609 this.restore();
3610 }
3611 this.save();
3612
3613 // oblique (angle in degrees or boolean)
3614 if (options.oblique) {
3615 let skew;
3616 if (typeof options.oblique === 'number') {
3617 skew = -Math.tan(options.oblique * Math.PI / 180);
3618 } else {
3619 skew = -0.25;
3620 }
3621 this.transform(1, 0, 0, 1, x, y);
3622 this.transform(1, 0, skew, 1, -skew * dy, 0);
3623 this.transform(1, 0, 0, 1, -x, -y);
3624 }
3625
3626 // flip coordinate system
3627 this.transform(1, 0, 0, -1, 0, this.page.height);
3628 y = this.page.height - y - dy;
3629
3630 // add current font to page if necessary
3631 if (this.page.fonts[this._font.id] == null) {
3632 this.page.fonts[this._font.id] = this._font.ref();
3633 }
3634
3635 // begin the text object
3636 this.addContent('BT');
3637
3638 // text position
3639 this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3640
3641 // font and font size
3642 this.addContent(`/${this._font.id} ${number$2(this._fontSize)} Tf`);
3643
3644 // rendering mode
3645 const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
3646 if (mode) {
3647 this.addContent(`${mode} Tr`);
3648 }
3649
3650 // Character spacing
3651 if (characterSpacing) {
3652 this.addContent(`${number$2(characterSpacing)} Tc`);
3653 }
3654
3655 // Add the actual text
3656 // If we have a word spacing value, we need to encode each word separately
3657 // since the normal Tw operator only works on character code 32, which isn't
3658 // used for embedded fonts.
3659 if (wordSpacing) {
3660 words = text.trim().split(/\s+/);
3661 wordSpacing += this.widthOfString(' ') + characterSpacing;
3662 wordSpacing *= 1000 / this._fontSize;
3663 encoded = [];
3664 positions = [];
3665 for (let word of words) {
3666 const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3667 encoded = encoded.concat(encodedWord);
3668 positions = positions.concat(positionsWord);
3669
3670 // add the word spacing to the end of the word
3671 // clone object because of cache
3672 const space = {};
3673 const object = positions[positions.length - 1];
3674 for (let key in object) {
3675 const val = object[key];
3676 space[key] = val;
3677 }
3678 space.xAdvance += wordSpacing;
3679 positions[positions.length - 1] = space;
3680 }
3681 } else {
3682 [encoded, positions] = this._font.encode(text, options.features);
3683 }
3684 const scale = this._fontSize / 1000;
3685 const commands = [];
3686 let last = 0;
3687 let hadOffset = false;
3688
3689 // Adds a segment of text to the TJ command buffer
3690 const addSegment = cur => {
3691 if (last < cur) {
3692 const hex = encoded.slice(last, cur).join('');
3693 const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3694 commands.push(`<${hex}> ${number$2(-advance)}`);
3695 }
3696 return last = cur;
3697 };
3698
3699 // Flushes the current TJ commands to the output stream
3700 const flush = i => {
3701 addSegment(i);
3702 if (commands.length > 0) {
3703 this.addContent(`[${commands.join(' ')}] TJ`);
3704 return commands.length = 0;
3705 }
3706 };
3707 for (i = 0; i < positions.length; i++) {
3708 // If we have an x or y offset, we have to break out of the current TJ command
3709 // so we can move the text position.
3710 const pos = positions[i];
3711 if (pos.xOffset || pos.yOffset) {
3712 // Flush the current buffer
3713 flush(i);
3714
3715 // Move the text position and flush just the current character
3716 this.addContent(`1 0 0 1 ${number$2(x + pos.xOffset * scale)} ${number$2(y + pos.yOffset * scale)} Tm`);
3717 flush(i + 1);
3718 hadOffset = true;
3719 } else {
3720 // If the last character had an offset, reset the text position
3721 if (hadOffset) {
3722 this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3723 hadOffset = false;
3724 }
3725
3726 // Group segments that don't have any advance adjustments
3727 if (pos.xAdvance - pos.advanceWidth !== 0) {
3728 addSegment(i + 1);
3729 }
3730 }
3731 x += pos.xAdvance * scale;
3732 }
3733
3734 // Flush any remaining commands
3735 flush(i);
3736
3737 // end the text object
3738 this.addContent('ET');
3739
3740 // restore flipped coordinate system
3741 return this.restore();
3742 }
3743};
3744
3745const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3746const COLOR_SPACE_MAP = {
3747 1: 'DeviceGray',
3748 3: 'DeviceRGB',
3749 4: 'DeviceCMYK'
3750};
3751class JPEG {
3752 constructor(data, label) {
3753 let marker;
3754 this.data = data;
3755 this.label = label;
3756 if (this.data.readUInt16BE(0) !== 0xffd8) {
3757 throw 'SOI not found in JPEG';
3758 }
3759
3760 // Parse the EXIF orientation
3761 this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3762 let pos = 2;
3763 while (pos < this.data.length) {
3764 marker = this.data.readUInt16BE(pos);
3765 pos += 2;
3766 if (MARKERS.includes(marker)) {
3767 break;
3768 }
3769 pos += this.data.readUInt16BE(pos);
3770 }
3771 if (!MARKERS.includes(marker)) {
3772 throw 'Invalid JPEG.';
3773 }
3774 pos += 2;
3775 this.bits = this.data[pos++];
3776 this.height = this.data.readUInt16BE(pos);
3777 pos += 2;
3778 this.width = this.data.readUInt16BE(pos);
3779 pos += 2;
3780 const channels = this.data[pos++];
3781 this.colorSpace = COLOR_SPACE_MAP[channels];
3782 this.obj = null;
3783 }
3784 embed(document) {
3785 if (this.obj) {
3786 return;
3787 }
3788 this.obj = document.ref({
3789 Type: 'XObject',
3790 Subtype: 'Image',
3791 BitsPerComponent: this.bits,
3792 Width: this.width,
3793 Height: this.height,
3794 ColorSpace: this.colorSpace,
3795 Filter: 'DCTDecode'
3796 });
3797
3798 // add extra decode params for CMYK images. By swapping the
3799 // min and max values from the default, we invert the colors. See
3800 // section 4.8.4 of the spec.
3801 if (this.colorSpace === 'DeviceCMYK') {
3802 this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3803 }
3804 this.obj.end(this.data);
3805
3806 // free memory
3807 return this.data = null;
3808 }
3809}
3810
3811class PNGImage {
3812 constructor(data, label) {
3813 this.label = label;
3814 this.image = new PNG(data);
3815 this.width = this.image.width;
3816 this.height = this.image.height;
3817 this.imgData = this.image.imgData;
3818 this.obj = null;
3819 }
3820 embed(document) {
3821 let dataDecoded = false;
3822 this.document = document;
3823 if (this.obj) {
3824 return;
3825 }
3826 const hasAlphaChannel = this.image.hasAlphaChannel;
3827 const isInterlaced = this.image.interlaceMethod === 1;
3828 this.obj = this.document.ref({
3829 Type: 'XObject',
3830 Subtype: 'Image',
3831 BitsPerComponent: hasAlphaChannel ? 8 : this.image.bits,
3832 Width: this.width,
3833 Height: this.height,
3834 Filter: 'FlateDecode'
3835 });
3836 if (!hasAlphaChannel) {
3837 const params = this.document.ref({
3838 Predictor: isInterlaced ? 1 : 15,
3839 Colors: this.image.colors,
3840 BitsPerComponent: this.image.bits,
3841 Columns: this.width
3842 });
3843 this.obj.data['DecodeParms'] = params;
3844 params.end();
3845 }
3846 if (this.image.palette.length === 0) {
3847 this.obj.data['ColorSpace'] = this.image.colorSpace;
3848 } else {
3849 // embed the color palette in the PDF as an object stream
3850 const palette = this.document.ref();
3851 palette.end(Buffer.from(this.image.palette));
3852
3853 // build the color space array for the image
3854 this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3855 }
3856
3857 // For PNG color types 0, 2 and 3, the transparency data is stored in
3858 // a dedicated PNG chunk.
3859 if (this.image.transparency.grayscale != null) {
3860 // Use Color Key Masking (spec section 4.8.5)
3861 // An array with N elements, where N is two times the number of color components.
3862 const val = this.image.transparency.grayscale;
3863 this.obj.data['Mask'] = [val, val];
3864 } else if (this.image.transparency.rgb) {
3865 // Use Color Key Masking (spec section 4.8.5)
3866 // An array with N elements, where N is two times the number of color components.
3867 const {
3868 rgb
3869 } = this.image.transparency;
3870 const mask = [];
3871 for (let x of rgb) {
3872 mask.push(x, x);
3873 }
3874 this.obj.data['Mask'] = mask;
3875 } else if (this.image.transparency.indexed) {
3876 // Create a transparency SMask for the image based on the data
3877 // in the PLTE and tRNS sections. See below for details on SMasks.
3878 dataDecoded = true;
3879 return this.loadIndexedAlphaChannel();
3880 } else if (hasAlphaChannel) {
3881 // For PNG color types 4 and 6, the transparency data is stored as a alpha
3882 // channel mixed in with the main image data. Separate this data out into an
3883 // SMask object and store it separately in the PDF.
3884 dataDecoded = true;
3885 return this.splitAlphaChannel();
3886 }
3887 if (isInterlaced && !dataDecoded) {
3888 return this.decodeData();
3889 }
3890 this.finalize();
3891 }
3892 finalize() {
3893 if (this.alphaChannel) {
3894 const sMask = this.document.ref({
3895 Type: 'XObject',
3896 Subtype: 'Image',
3897 Height: this.height,
3898 Width: this.width,
3899 BitsPerComponent: 8,
3900 Filter: 'FlateDecode',
3901 ColorSpace: 'DeviceGray',
3902 Decode: [0, 1]
3903 });
3904 sMask.end(this.alphaChannel);
3905 this.obj.data['SMask'] = sMask;
3906 }
3907
3908 // add the actual image data
3909 this.obj.end(this.imgData);
3910
3911 // free memory
3912 this.image = null;
3913 return this.imgData = null;
3914 }
3915 splitAlphaChannel() {
3916 return this.image.decodePixels(pixels => {
3917 let a, p;
3918 const colorCount = this.image.colors;
3919 const pixelCount = this.width * this.height;
3920 const imgData = Buffer.alloc(pixelCount * colorCount);
3921 const alphaChannel = Buffer.alloc(pixelCount);
3922 let i = p = a = 0;
3923 const len = pixels.length;
3924 // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
3925 const skipByteCount = this.image.bits === 16 ? 1 : 0;
3926 while (i < len) {
3927 for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
3928 imgData[p++] = pixels[i++];
3929 i += skipByteCount;
3930 }
3931 alphaChannel[a++] = pixels[i++];
3932 i += skipByteCount;
3933 }
3934 this.imgData = zlib.deflateSync(imgData);
3935 this.alphaChannel = zlib.deflateSync(alphaChannel);
3936 return this.finalize();
3937 });
3938 }
3939 loadIndexedAlphaChannel() {
3940 const transparency = this.image.transparency.indexed;
3941 return this.image.decodePixels(pixels => {
3942 const alphaChannel = Buffer.alloc(this.width * this.height);
3943 let i = 0;
3944 for (let j = 0, end = pixels.length; j < end; j++) {
3945 alphaChannel[i++] = transparency[pixels[j]];
3946 }
3947 this.alphaChannel = zlib.deflateSync(alphaChannel);
3948 return this.finalize();
3949 });
3950 }
3951 decodeData() {
3952 this.image.decodePixels(pixels => {
3953 this.imgData = zlib.deflateSync(pixels);
3954 this.finalize();
3955 });
3956 }
3957}
3958
3959/*
3960PDFImage - embeds images in PDF documents
3961By Devon Govett
3962*/
3963class PDFImage {
3964 static open(src, label) {
3965 let data;
3966 if (Buffer.isBuffer(src)) {
3967 data = src;
3968 } else if (src instanceof ArrayBuffer) {
3969 data = Buffer.from(new Uint8Array(src));
3970 } else {
3971 let match;
3972 if (match = /^data:.+?;base64,(.*)$/.exec(src)) {
3973 data = Buffer.from(match[1], 'base64');
3974 } else {
3975 data = fs.readFileSync(src);
3976 if (!data) {
3977 return;
3978 }
3979 }
3980 }
3981 if (data[0] === 0xff && data[1] === 0xd8) {
3982 return new JPEG(data, label);
3983 } else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
3984 return new PNGImage(data, label);
3985 } else {
3986 throw new Error('Unknown image format.');
3987 }
3988 }
3989}
3990
3991var ImagesMixin = {
3992 initImages() {
3993 this._imageRegistry = {};
3994 return this._imageCount = 0;
3995 },
3996 image(src, x, y) {
3997 let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
3998 let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
3999 if (typeof x === 'object') {
4000 options = x;
4001 x = null;
4002 }
4003
4004 // Ignore orientation based on document options or image options
4005 const ignoreOrientation = options.ignoreOrientation || options.ignoreOrientation !== false && this.options.ignoreOrientation;
4006 x = (left = x != null ? x : options.x) != null ? left : this.x;
4007 y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
4008 if (typeof src === 'string') {
4009 image = this._imageRegistry[src];
4010 }
4011 if (!image) {
4012 if (src.width && src.height) {
4013 image = src;
4014 } else {
4015 image = this.openImage(src);
4016 }
4017 }
4018 if (!image.obj) {
4019 image.embed(this);
4020 }
4021 if (this.page.xobjects[image.label] == null) {
4022 this.page.xobjects[image.label] = image.obj;
4023 }
4024 let {
4025 width,
4026 height
4027 } = image;
4028
4029 // If EXIF orientation calls for it, swap width and height
4030 if (!ignoreOrientation && image.orientation > 4) {
4031 [width, height] = [height, width];
4032 }
4033 let w = options.width || width;
4034 let h = options.height || height;
4035 if (options.width && !options.height) {
4036 const wp = w / width;
4037 w = width * wp;
4038 h = height * wp;
4039 } else if (options.height && !options.width) {
4040 const hp = h / height;
4041 w = width * hp;
4042 h = height * hp;
4043 } else if (options.scale) {
4044 w = width * options.scale;
4045 h = height * options.scale;
4046 } else if (options.fit) {
4047 [bw, bh] = options.fit;
4048 bp = bw / bh;
4049 ip = width / height;
4050 if (ip > bp) {
4051 w = bw;
4052 h = bw / ip;
4053 } else {
4054 h = bh;
4055 w = bh * ip;
4056 }
4057 } else if (options.cover) {
4058 [bw, bh] = options.cover;
4059 bp = bw / bh;
4060 ip = width / height;
4061 if (ip > bp) {
4062 h = bh;
4063 w = bh * ip;
4064 } else {
4065 w = bw;
4066 h = bw / ip;
4067 }
4068 }
4069 if (options.fit || options.cover) {
4070 if (options.align === 'center') {
4071 x = x + bw / 2 - w / 2;
4072 } else if (options.align === 'right') {
4073 x = x + bw - w;
4074 }
4075 if (options.valign === 'center') {
4076 y = y + bh / 2 - h / 2;
4077 } else if (options.valign === 'bottom') {
4078 y = y + bh - h;
4079 }
4080 }
4081 if (!ignoreOrientation) {
4082 switch (image.orientation) {
4083 // No orientation (need to flip image, though, because of the default transform matrix on the document)
4084 default:
4085 case 1:
4086 h = -h;
4087 y -= h;
4088 rotateAngle = 0;
4089 break;
4090 // Flip Horizontal
4091 case 2:
4092 w = -w;
4093 h = -h;
4094 x -= w;
4095 y -= h;
4096 rotateAngle = 0;
4097 break;
4098 // Rotate 180 degrees
4099 case 3:
4100 originX = x;
4101 originY = y;
4102 h = -h;
4103 x -= w;
4104 rotateAngle = 180;
4105 break;
4106 // Flip vertical
4107 case 4:
4108 // Do nothing, image will be flipped
4109
4110 break;
4111 // Flip horizontally and rotate 270 degrees CW
4112 case 5:
4113 originX = x;
4114 originY = y;
4115 [w, h] = [h, w];
4116 y -= h;
4117 rotateAngle = 90;
4118 break;
4119 // Rotate 90 degrees CW
4120 case 6:
4121 originX = x;
4122 originY = y;
4123 [w, h] = [h, w];
4124 h = -h;
4125 rotateAngle = 90;
4126 break;
4127 // Flip horizontally and rotate 90 degrees CW
4128 case 7:
4129 originX = x;
4130 originY = y;
4131 [w, h] = [h, w];
4132 h = -h;
4133 w = -w;
4134 x -= w;
4135 rotateAngle = 90;
4136 break;
4137 // Rotate 270 degrees CW
4138 case 8:
4139 originX = x;
4140 originY = y;
4141 [w, h] = [h, w];
4142 h = -h;
4143 x -= w;
4144 y -= h;
4145 rotateAngle = -90;
4146 break;
4147 }
4148 } else {
4149 h = -h;
4150 y -= h;
4151 rotateAngle = 0;
4152 }
4153
4154 // create link annotations if the link option is given
4155 if (options.link != null) {
4156 this.link(x, y, w, h, options.link);
4157 }
4158 if (options.goTo != null) {
4159 this.goTo(x, y, w, h, options.goTo);
4160 }
4161 if (options.destination != null) {
4162 this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4163 }
4164
4165 // Set the current y position to below the image if it is in the document flow
4166 if (this.y === y) {
4167 this.y += h;
4168 }
4169 this.save();
4170 if (rotateAngle) {
4171 this.rotate(rotateAngle, {
4172 origin: [originX, originY]
4173 });
4174 }
4175 this.transform(w, 0, 0, h, x, y);
4176 this.addContent(`/${image.label} Do`);
4177 this.restore();
4178 return this;
4179 },
4180 openImage(src) {
4181 let image;
4182 if (typeof src === 'string') {
4183 image = this._imageRegistry[src];
4184 }
4185 if (!image) {
4186 image = PDFImage.open(src, `I${++this._imageCount}`);
4187 if (typeof src === 'string') {
4188 this._imageRegistry[src] = image;
4189 }
4190 }
4191 return image;
4192 }
4193};
4194
4195var AnnotationsMixin = {
4196 annotate(x, y, w, h, options) {
4197 options.Type = 'Annot';
4198 options.Rect = this._convertRect(x, y, w, h);
4199 options.Border = [0, 0, 0];
4200 if (options.Subtype === 'Link' && typeof options.F === 'undefined') {
4201 options.F = 1 << 2; // Print Annotation Flag
4202 }
4203 if (options.Subtype !== 'Link') {
4204 if (options.C == null) {
4205 options.C = this._normalizeColor(options.color || [0, 0, 0]);
4206 }
4207 } // convert colors
4208 delete options.color;
4209 if (typeof options.Dest === 'string') {
4210 options.Dest = new String(options.Dest);
4211 }
4212
4213 // Capitalize keys
4214 for (let key in options) {
4215 const val = options[key];
4216 options[key[0].toUpperCase() + key.slice(1)] = val;
4217 }
4218 const ref = this.ref(options);
4219 this.page.annotations.push(ref);
4220 ref.end();
4221 return this;
4222 },
4223 note(x, y, w, h, contents) {
4224 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4225 options.Subtype = 'Text';
4226 options.Contents = new String(contents);
4227 options.Name = 'Comment';
4228 if (options.color == null) {
4229 options.color = [243, 223, 92];
4230 }
4231 return this.annotate(x, y, w, h, options);
4232 },
4233 goTo(x, y, w, h, name) {
4234 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4235 options.Subtype = 'Link';
4236 options.A = this.ref({
4237 S: 'GoTo',
4238 D: new String(name)
4239 });
4240 options.A.end();
4241 return this.annotate(x, y, w, h, options);
4242 },
4243 link(x, y, w, h, url) {
4244 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4245 options.Subtype = 'Link';
4246 if (typeof url === 'number') {
4247 // Link to a page in the document (the page must already exist)
4248 const pages = this._root.data.Pages.data;
4249 if (url >= 0 && url < pages.Kids.length) {
4250 options.A = this.ref({
4251 S: 'GoTo',
4252 D: [pages.Kids[url], 'XYZ', null, null, null]
4253 });
4254 options.A.end();
4255 } else {
4256 throw new Error(`The document has no page ${url}`);
4257 }
4258 } else {
4259 // Link to an external url
4260 options.A = this.ref({
4261 S: 'URI',
4262 URI: new String(url)
4263 });
4264 options.A.end();
4265 }
4266 return this.annotate(x, y, w, h, options);
4267 },
4268 _markup(x, y, w, h) {
4269 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4270 const [x1, y1, x2, y2] = this._convertRect(x, y, w, h);
4271 options.QuadPoints = [x1, y2, x2, y2, x1, y1, x2, y1];
4272 options.Contents = new String();
4273 return this.annotate(x, y, w, h, options);
4274 },
4275 highlight(x, y, w, h) {
4276 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4277 options.Subtype = 'Highlight';
4278 if (options.color == null) {
4279 options.color = [241, 238, 148];
4280 }
4281 return this._markup(x, y, w, h, options);
4282 },
4283 underline(x, y, w, h) {
4284 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4285 options.Subtype = 'Underline';
4286 return this._markup(x, y, w, h, options);
4287 },
4288 strike(x, y, w, h) {
4289 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4290 options.Subtype = 'StrikeOut';
4291 return this._markup(x, y, w, h, options);
4292 },
4293 lineAnnotation(x1, y1, x2, y2) {
4294 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4295 options.Subtype = 'Line';
4296 options.Contents = new String();
4297 options.L = [x1, this.page.height - y1, x2, this.page.height - y2];
4298 return this.annotate(x1, y1, x2, y2, options);
4299 },
4300 rectAnnotation(x, y, w, h) {
4301 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4302 options.Subtype = 'Square';
4303 options.Contents = new String();
4304 return this.annotate(x, y, w, h, options);
4305 },
4306 ellipseAnnotation(x, y, w, h) {
4307 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4308 options.Subtype = 'Circle';
4309 options.Contents = new String();
4310 return this.annotate(x, y, w, h, options);
4311 },
4312 textAnnotation(x, y, w, h, text) {
4313 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4314 options.Subtype = 'FreeText';
4315 options.Contents = new String(text);
4316 options.DA = new String();
4317 return this.annotate(x, y, w, h, options);
4318 },
4319 fileAnnotation(x, y, w, h) {
4320 let file = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4321 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4322 // create hidden file
4323 const filespec = this.file(file.src, Object.assign({
4324 hidden: true
4325 }, file));
4326 options.Subtype = 'FileAttachment';
4327 options.FS = filespec;
4328
4329 // add description from filespec unless description (Contents) has already been set
4330 if (options.Contents) {
4331 options.Contents = new String(options.Contents);
4332 } else if (filespec.data.Desc) {
4333 options.Contents = filespec.data.Desc;
4334 }
4335 return this.annotate(x, y, w, h, options);
4336 },
4337 _convertRect(x1, y1, w, h) {
4338 // flip y1 and y2
4339 let y2 = y1;
4340 y1 += h;
4341
4342 // make x2
4343 let x2 = x1 + w;
4344
4345 // apply current transformation matrix to points
4346 const [m0, m1, m2, m3, m4, m5] = this._ctm;
4347 x1 = m0 * x1 + m2 * y1 + m4;
4348 y1 = m1 * x1 + m3 * y1 + m5;
4349 x2 = m0 * x2 + m2 * y2 + m4;
4350 y2 = m1 * x2 + m3 * y2 + m5;
4351 return [x1, y1, x2, y2];
4352 }
4353};
4354
4355class PDFOutline {
4356 constructor(document, parent, title, dest) {
4357 let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
4358 expanded: false
4359 };
4360 this.document = document;
4361 this.options = options;
4362 this.outlineData = {};
4363 if (dest !== null) {
4364 this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4365 }
4366 if (parent !== null) {
4367 this.outlineData['Parent'] = parent;
4368 }
4369 if (title !== null) {
4370 this.outlineData['Title'] = new String(title);
4371 }
4372 this.dictionary = this.document.ref(this.outlineData);
4373 this.children = [];
4374 }
4375 addItem(title) {
4376 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
4377 expanded: false
4378 };
4379 const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4380 this.children.push(result);
4381 return result;
4382 }
4383 endOutline() {
4384 if (this.children.length > 0) {
4385 if (this.options.expanded) {
4386 this.outlineData.Count = this.children.length;
4387 }
4388 const first = this.children[0],
4389 last = this.children[this.children.length - 1];
4390 this.outlineData.First = first.dictionary;
4391 this.outlineData.Last = last.dictionary;
4392 for (let i = 0, len = this.children.length; i < len; i++) {
4393 const child = this.children[i];
4394 if (i > 0) {
4395 child.outlineData.Prev = this.children[i - 1].dictionary;
4396 }
4397 if (i < this.children.length - 1) {
4398 child.outlineData.Next = this.children[i + 1].dictionary;
4399 }
4400 child.endOutline();
4401 }
4402 }
4403 return this.dictionary.end();
4404 }
4405}
4406
4407var OutlineMixin = {
4408 initOutline() {
4409 return this.outline = new PDFOutline(this, null, null, null);
4410 },
4411 endOutline() {
4412 this.outline.endOutline();
4413 if (this.outline.children.length > 0) {
4414 this._root.data.Outlines = this.outline.dictionary;
4415 return this._root.data.PageMode = 'UseOutlines';
4416 }
4417 }
4418};
4419
4420/*
4421PDFStructureContent - a reference to a marked structure content
4422By Ben Schmidt
4423*/
4424
4425class PDFStructureContent {
4426 constructor(pageRef, mcid) {
4427 this.refs = [{
4428 pageRef,
4429 mcid
4430 }];
4431 }
4432 push(structContent) {
4433 structContent.refs.forEach(ref => this.refs.push(ref));
4434 }
4435}
4436
4437/*
4438PDFStructureElement - represents an element in the PDF logical structure tree
4439By Ben Schmidt
4440*/
4441class PDFStructureElement {
4442 constructor(document, type) {
4443 let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
4444 let children = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
4445 this.document = document;
4446 this._attached = false;
4447 this._ended = false;
4448 this._flushed = false;
4449 this.dictionary = document.ref({
4450 // Type: "StructElem",
4451 S: type
4452 });
4453 const data = this.dictionary.data;
4454 if (Array.isArray(options) || this._isValidChild(options)) {
4455 children = options;
4456 options = {};
4457 }
4458 if (typeof options.title !== 'undefined') {
4459 data.T = new String(options.title);
4460 }
4461 if (typeof options.lang !== 'undefined') {
4462 data.Lang = new String(options.lang);
4463 }
4464 if (typeof options.alt !== 'undefined') {
4465 data.Alt = new String(options.alt);
4466 }
4467 if (typeof options.expanded !== 'undefined') {
4468 data.E = new String(options.expanded);
4469 }
4470 if (typeof options.actual !== 'undefined') {
4471 data.ActualText = new String(options.actual);
4472 }
4473 this._children = [];
4474 if (children) {
4475 if (!Array.isArray(children)) {
4476 children = [children];
4477 }
4478 children.forEach(child => this.add(child));
4479 this.end();
4480 }
4481 }
4482 add(child) {
4483 if (this._ended) {
4484 throw new Error(`Cannot add child to already-ended structure element`);
4485 }
4486 if (!this._isValidChild(child)) {
4487 throw new Error(`Invalid structure element child`);
4488 }
4489 if (child instanceof PDFStructureElement) {
4490 child.setParent(this.dictionary);
4491 if (this._attached) {
4492 child.setAttached();
4493 }
4494 }
4495 if (child instanceof PDFStructureContent) {
4496 this._addContentToParentTree(child);
4497 }
4498 if (typeof child === 'function' && this._attached) {
4499 // _contentForClosure() adds the content to the parent tree
4500 child = this._contentForClosure(child);
4501 }
4502 this._children.push(child);
4503 return this;
4504 }
4505 _addContentToParentTree(content) {
4506 content.refs.forEach(_ref => {
4507 let {
4508 pageRef,
4509 mcid
4510 } = _ref;
4511 const pageStructParents = this.document.getStructParentTree().get(pageRef.data.StructParents);
4512 pageStructParents[mcid] = this.dictionary;
4513 });
4514 }
4515 setParent(parentRef) {
4516 if (this.dictionary.data.P) {
4517 throw new Error(`Structure element added to more than one parent`);
4518 }
4519 this.dictionary.data.P = parentRef;
4520 this._flush();
4521 }
4522 setAttached() {
4523 if (this._attached) {
4524 return;
4525 }
4526 this._children.forEach((child, index) => {
4527 if (child instanceof PDFStructureElement) {
4528 child.setAttached();
4529 }
4530 if (typeof child === 'function') {
4531 this._children[index] = this._contentForClosure(child);
4532 }
4533 });
4534 this._attached = true;
4535 this._flush();
4536 }
4537 end() {
4538 if (this._ended) {
4539 return;
4540 }
4541 this._children.filter(child => child instanceof PDFStructureElement).forEach(child => child.end());
4542 this._ended = true;
4543 this._flush();
4544 }
4545 _isValidChild(child) {
4546 return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4547 }
4548 _contentForClosure(closure) {
4549 const content = this.document.markStructureContent(this.dictionary.data.S);
4550 closure();
4551 this.document.endMarkedContent();
4552 this._addContentToParentTree(content);
4553 return content;
4554 }
4555 _isFlushable() {
4556 if (!this.dictionary.data.P || !this._ended) {
4557 return false;
4558 }
4559 return this._children.every(child => {
4560 if (typeof child === 'function') {
4561 return false;
4562 }
4563 if (child instanceof PDFStructureElement) {
4564 return child._isFlushable();
4565 }
4566 return true;
4567 });
4568 }
4569 _flush() {
4570 if (this._flushed || !this._isFlushable()) {
4571 return;
4572 }
4573 this.dictionary.data.K = [];
4574 this._children.forEach(child => this._flushChild(child));
4575 this.dictionary.end();
4576
4577 // free memory used by children; the dictionary itself may still be
4578 // referenced by a parent structure element or root, but we can
4579 // at least trim the tree here
4580 this._children = [];
4581 this.dictionary.data.K = null;
4582 this._flushed = true;
4583 }
4584 _flushChild(child) {
4585 if (child instanceof PDFStructureElement) {
4586 this.dictionary.data.K.push(child.dictionary);
4587 }
4588 if (child instanceof PDFStructureContent) {
4589 child.refs.forEach(_ref2 => {
4590 let {
4591 pageRef,
4592 mcid
4593 } = _ref2;
4594 if (!this.dictionary.data.Pg) {
4595 this.dictionary.data.Pg = pageRef;
4596 }
4597 if (this.dictionary.data.Pg === pageRef) {
4598 this.dictionary.data.K.push(mcid);
4599 } else {
4600 this.dictionary.data.K.push({
4601 Type: "MCR",
4602 Pg: pageRef,
4603 MCID: mcid
4604 });
4605 }
4606 });
4607 }
4608 }
4609}
4610
4611/*
4612PDFNumberTree - represents a number tree object
4613*/
4614class PDFNumberTree extends PDFTree {
4615 _compareKeys(a, b) {
4616 return parseInt(a) - parseInt(b);
4617 }
4618 _keysName() {
4619 return "Nums";
4620 }
4621 _dataForKey(k) {
4622 return parseInt(k);
4623 }
4624}
4625
4626/*
4627Markings mixin - support marked content sequences in content streams
4628By Ben Schmidt
4629*/
4630var MarkingsMixin = {
4631 initMarkings(options) {
4632 this.structChildren = [];
4633 if (options.tagged) {
4634 this.getMarkInfoDictionary().data.Marked = true;
4635 this.getStructTreeRoot();
4636 }
4637 },
4638 markContent(tag) {
4639 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
4640 if (tag === 'Artifact' || options && options.mcid) {
4641 let toClose = 0;
4642 this.page.markings.forEach(marking => {
4643 if (toClose || marking.structContent || marking.tag === 'Artifact') {
4644 toClose++;
4645 }
4646 });
4647 while (toClose--) {
4648 this.endMarkedContent();
4649 }
4650 }
4651 if (!options) {
4652 this.page.markings.push({
4653 tag
4654 });
4655 this.addContent(`/${tag} BMC`);
4656 return this;
4657 }
4658 this.page.markings.push({
4659 tag,
4660 options
4661 });
4662 const dictionary = {};
4663 if (typeof options.mcid !== 'undefined') {
4664 dictionary.MCID = options.mcid;
4665 }
4666 if (tag === 'Artifact') {
4667 if (typeof options.type === 'string') {
4668 dictionary.Type = options.type;
4669 }
4670 if (Array.isArray(options.bbox)) {
4671 dictionary.BBox = [options.bbox[0], this.page.height - options.bbox[3], options.bbox[2], this.page.height - options.bbox[1]];
4672 }
4673 if (Array.isArray(options.attached) && options.attached.every(val => typeof val === 'string')) {
4674 dictionary.Attached = options.attached;
4675 }
4676 }
4677 if (tag === 'Span') {
4678 if (options.lang) {
4679 dictionary.Lang = new String(options.lang);
4680 }
4681 if (options.alt) {
4682 dictionary.Alt = new String(options.alt);
4683 }
4684 if (options.expanded) {
4685 dictionary.E = new String(options.expanded);
4686 }
4687 if (options.actual) {
4688 dictionary.ActualText = new String(options.actual);
4689 }
4690 }
4691 this.addContent(`/${tag} ${PDFObject.convert(dictionary)} BDC`);
4692 return this;
4693 },
4694 markStructureContent(tag) {
4695 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
4696 const pageStructParents = this.getStructParentTree().get(this.page.structParentTreeKey);
4697 const mcid = pageStructParents.length;
4698 pageStructParents.push(null);
4699 this.markContent(tag, {
4700 ...options,
4701 mcid
4702 });
4703 const structContent = new PDFStructureContent(this.page.dictionary, mcid);
4704 this.page.markings.slice(-1)[0].structContent = structContent;
4705 return structContent;
4706 },
4707 endMarkedContent() {
4708 this.page.markings.pop();
4709 this.addContent('EMC');
4710 return this;
4711 },
4712 struct(type) {
4713 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
4714 let children = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
4715 return new PDFStructureElement(this, type, options, children);
4716 },
4717 addStructure(structElem) {
4718 const structTreeRoot = this.getStructTreeRoot();
4719 structElem.setParent(structTreeRoot);
4720 structElem.setAttached();
4721 this.structChildren.push(structElem);
4722 if (!structTreeRoot.data.K) {
4723 structTreeRoot.data.K = [];
4724 }
4725 structTreeRoot.data.K.push(structElem.dictionary);
4726 return this;
4727 },
4728 initPageMarkings(pageMarkings) {
4729 pageMarkings.forEach(marking => {
4730 if (marking.structContent) {
4731 const structContent = marking.structContent;
4732 const newStructContent = this.markStructureContent(marking.tag, marking.options);
4733 structContent.push(newStructContent);
4734 this.page.markings.slice(-1)[0].structContent = structContent;
4735 } else {
4736 this.markContent(marking.tag, marking.options);
4737 }
4738 });
4739 },
4740 endPageMarkings(page) {
4741 const pageMarkings = page.markings;
4742 pageMarkings.forEach(() => page.write('EMC'));
4743 page.markings = [];
4744 return pageMarkings;
4745 },
4746 getMarkInfoDictionary() {
4747 if (!this._root.data.MarkInfo) {
4748 this._root.data.MarkInfo = this.ref({});
4749 }
4750 return this._root.data.MarkInfo;
4751 },
4752 getStructTreeRoot() {
4753 if (!this._root.data.StructTreeRoot) {
4754 this._root.data.StructTreeRoot = this.ref({
4755 Type: 'StructTreeRoot',
4756 ParentTree: new PDFNumberTree(),
4757 ParentTreeNextKey: 0
4758 });
4759 }
4760 return this._root.data.StructTreeRoot;
4761 },
4762 getStructParentTree() {
4763 return this.getStructTreeRoot().data.ParentTree;
4764 },
4765 createStructParentTreeNextKey() {
4766 // initialise the MarkInfo dictionary
4767 this.getMarkInfoDictionary();
4768 const structTreeRoot = this.getStructTreeRoot();
4769 const key = structTreeRoot.data.ParentTreeNextKey++;
4770 structTreeRoot.data.ParentTree.add(key, []);
4771 return key;
4772 },
4773 endMarkings() {
4774 const structTreeRoot = this._root.data.StructTreeRoot;
4775 if (structTreeRoot) {
4776 structTreeRoot.end();
4777 this.structChildren.forEach(structElem => structElem.end());
4778 }
4779 if (this._root.data.MarkInfo) {
4780 this._root.data.MarkInfo.end();
4781 }
4782 }
4783};
4784
4785const FIELD_FLAGS = {
4786 readOnly: 1,
4787 required: 2,
4788 noExport: 4,
4789 multiline: 0x1000,
4790 password: 0x2000,
4791 toggleToOffButton: 0x4000,
4792 radioButton: 0x8000,
4793 pushButton: 0x10000,
4794 combo: 0x20000,
4795 edit: 0x40000,
4796 sort: 0x80000,
4797 multiSelect: 0x200000,
4798 noSpell: 0x400000
4799};
4800const FIELD_JUSTIFY = {
4801 left: 0,
4802 center: 1,
4803 right: 2
4804};
4805const VALUE_MAP = {
4806 value: 'V',
4807 defaultValue: 'DV'
4808};
4809const FORMAT_SPECIAL = {
4810 zip: '0',
4811 zipPlus4: '1',
4812 zip4: '1',
4813 phone: '2',
4814 ssn: '3'
4815};
4816const FORMAT_DEFAULT = {
4817 number: {
4818 nDec: 0,
4819 sepComma: false,
4820 negStyle: 'MinusBlack',
4821 currency: '',
4822 currencyPrepend: true
4823 },
4824 percent: {
4825 nDec: 0,
4826 sepComma: false
4827 }
4828};
4829var AcroFormMixin = {
4830 /**
4831 * Must call if adding AcroForms to a document. Must also call font() before
4832 * this method to set the default font.
4833 */
4834 initForm() {
4835 if (!this._font) {
4836 throw new Error('Must set a font before calling initForm method');
4837 }
4838 this._acroform = {
4839 fonts: {},
4840 defaultFont: this._font.name
4841 };
4842 this._acroform.fonts[this._font.id] = this._font.ref();
4843 let data = {
4844 Fields: [],
4845 NeedAppearances: true,
4846 DA: new String(`/${this._font.id} 0 Tf 0 g`),
4847 DR: {
4848 Font: {}
4849 }
4850 };
4851 data.DR.Font[this._font.id] = this._font.ref();
4852 const AcroForm = this.ref(data);
4853 this._root.data.AcroForm = AcroForm;
4854 return this;
4855 },
4856 /**
4857 * Called automatically by document.js
4858 */
4859 endAcroForm() {
4860 if (this._root.data.AcroForm) {
4861 if (!Object.keys(this._acroform.fonts).length && !this._acroform.defaultFont) {
4862 throw new Error('No fonts specified for PDF form');
4863 }
4864 let fontDict = this._root.data.AcroForm.data.DR.Font;
4865 Object.keys(this._acroform.fonts).forEach(name => {
4866 fontDict[name] = this._acroform.fonts[name];
4867 });
4868 this._root.data.AcroForm.data.Fields.forEach(fieldRef => {
4869 this._endChild(fieldRef);
4870 });
4871 this._root.data.AcroForm.end();
4872 }
4873 return this;
4874 },
4875 _endChild(ref) {
4876 if (Array.isArray(ref.data.Kids)) {
4877 ref.data.Kids.forEach(childRef => {
4878 this._endChild(childRef);
4879 });
4880 ref.end();
4881 }
4882 return this;
4883 },
4884 /**
4885 * Creates and adds a form field to the document. Form fields are intermediate
4886 * nodes in a PDF form that are used to specify form name heirarchy and form
4887 * value defaults.
4888 * @param {string} name - field name (T attribute in field dictionary)
4889 * @param {object} options - other attributes to include in field dictionary
4890 */
4891 formField(name) {
4892 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
4893 let fieldDict = this._fieldDict(name, null, options);
4894 let fieldRef = this.ref(fieldDict);
4895 this._addToParent(fieldRef);
4896 return fieldRef;
4897 },
4898 /**
4899 * Creates and adds a Form Annotation to the document. Form annotations are
4900 * called Widget annotations internally within a PDF file.
4901 * @param {string} name - form field name (T attribute of widget annotation
4902 * dictionary)
4903 * @param {number} x
4904 * @param {number} y
4905 * @param {number} w
4906 * @param {number} h
4907 * @param {object} options
4908 */
4909 formAnnotation(name, type, x, y, w, h) {
4910 let options = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {};
4911 let fieldDict = this._fieldDict(name, type, options);
4912 fieldDict.Subtype = 'Widget';
4913 if (fieldDict.F === undefined) {
4914 fieldDict.F = 4; // print the annotation
4915 }
4916
4917 // Add Field annot to page, and get it's ref
4918 this.annotate(x, y, w, h, fieldDict);
4919 let annotRef = this.page.annotations[this.page.annotations.length - 1];
4920 return this._addToParent(annotRef);
4921 },
4922 formText(name, x, y, w, h) {
4923 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4924 return this.formAnnotation(name, 'text', x, y, w, h, options);
4925 },
4926 formPushButton(name, x, y, w, h) {
4927 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4928 return this.formAnnotation(name, 'pushButton', x, y, w, h, options);
4929 },
4930 formCombo(name, x, y, w, h) {
4931 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4932 return this.formAnnotation(name, 'combo', x, y, w, h, options);
4933 },
4934 formList(name, x, y, w, h) {
4935 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4936 return this.formAnnotation(name, 'list', x, y, w, h, options);
4937 },
4938 formRadioButton(name, x, y, w, h) {
4939 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4940 return this.formAnnotation(name, 'radioButton', x, y, w, h, options);
4941 },
4942 formCheckbox(name, x, y, w, h) {
4943 let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4944 return this.formAnnotation(name, 'checkbox', x, y, w, h, options);
4945 },
4946 _addToParent(fieldRef) {
4947 let parent = fieldRef.data.Parent;
4948 if (parent) {
4949 if (!parent.data.Kids) {
4950 parent.data.Kids = [];
4951 }
4952 parent.data.Kids.push(fieldRef);
4953 } else {
4954 this._root.data.AcroForm.data.Fields.push(fieldRef);
4955 }
4956 return this;
4957 },
4958 _fieldDict(name, type) {
4959 let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
4960 if (!this._acroform) {
4961 throw new Error('Call document.initForms() method before adding form elements to document');
4962 }
4963 let opts = Object.assign({}, options);
4964 if (type !== null) {
4965 opts = this._resolveType(type, options);
4966 }
4967 opts = this._resolveFlags(opts);
4968 opts = this._resolveJustify(opts);
4969 opts = this._resolveFont(opts);
4970 opts = this._resolveStrings(opts);
4971 opts = this._resolveColors(opts);
4972 opts = this._resolveFormat(opts);
4973 opts.T = new String(name);
4974 if (opts.parent) {
4975 opts.Parent = opts.parent;
4976 delete opts.parent;
4977 }
4978 return opts;
4979 },
4980 _resolveType(type, opts) {
4981 if (type === 'text') {
4982 opts.FT = 'Tx';
4983 } else if (type === 'pushButton') {
4984 opts.FT = 'Btn';
4985 opts.pushButton = true;
4986 } else if (type === 'radioButton') {
4987 opts.FT = 'Btn';
4988 opts.radioButton = true;
4989 } else if (type === 'checkbox') {
4990 opts.FT = 'Btn';
4991 } else if (type === 'combo') {
4992 opts.FT = 'Ch';
4993 opts.combo = true;
4994 } else if (type === 'list') {
4995 opts.FT = 'Ch';
4996 } else {
4997 throw new Error(`Invalid form annotation type '${type}'`);
4998 }
4999 return opts;
5000 },
5001 _resolveFormat(opts) {
5002 const f = opts.format;
5003 if (f && f.type) {
5004 let fnKeystroke;
5005 let fnFormat;
5006 let params = '';
5007 if (FORMAT_SPECIAL[f.type] !== undefined) {
5008 fnKeystroke = `AFSpecial_Keystroke`;
5009 fnFormat = `AFSpecial_Format`;
5010 params = FORMAT_SPECIAL[f.type];
5011 } else {
5012 let format = f.type.charAt(0).toUpperCase() + f.type.slice(1);
5013 fnKeystroke = `AF${format}_Keystroke`;
5014 fnFormat = `AF${format}_Format`;
5015 if (f.type === 'date') {
5016 fnKeystroke += 'Ex';
5017 params = String(f.param);
5018 } else if (f.type === 'time') {
5019 params = String(f.param);
5020 } else if (f.type === 'number') {
5021 let p = Object.assign({}, FORMAT_DEFAULT.number, f);
5022 params = String([String(p.nDec), p.sepComma ? '0' : '1', '"' + p.negStyle + '"', 'null', '"' + p.currency + '"', String(p.currencyPrepend)].join(','));
5023 } else if (f.type === 'percent') {
5024 let p = Object.assign({}, FORMAT_DEFAULT.percent, f);
5025 params = String([String(p.nDec), p.sepComma ? '0' : '1'].join(','));
5026 }
5027 }
5028 opts.AA = opts.AA ? opts.AA : {};
5029 opts.AA.K = {
5030 S: 'JavaScript',
5031 JS: new String(`${fnKeystroke}(${params});`)
5032 };
5033 opts.AA.F = {
5034 S: 'JavaScript',
5035 JS: new String(`${fnFormat}(${params});`)
5036 };
5037 }
5038 delete opts.format;
5039 return opts;
5040 },
5041 _resolveColors(opts) {
5042 let color = this._normalizeColor(opts.backgroundColor);
5043 if (color) {
5044 if (!opts.MK) {
5045 opts.MK = {};
5046 }
5047 opts.MK.BG = color;
5048 }
5049 color = this._normalizeColor(opts.borderColor);
5050 if (color) {
5051 if (!opts.MK) {
5052 opts.MK = {};
5053 }
5054 opts.MK.BC = color;
5055 }
5056 delete opts.backgroundColor;
5057 delete opts.borderColor;
5058 return opts;
5059 },
5060 _resolveFlags(options) {
5061 let result = 0;
5062 Object.keys(options).forEach(key => {
5063 if (FIELD_FLAGS[key]) {
5064 if (options[key]) {
5065 result |= FIELD_FLAGS[key];
5066 }
5067 delete options[key];
5068 }
5069 });
5070 if (result !== 0) {
5071 options.Ff = options.Ff ? options.Ff : 0;
5072 options.Ff |= result;
5073 }
5074 return options;
5075 },
5076 _resolveJustify(options) {
5077 let result = 0;
5078 if (options.align !== undefined) {
5079 if (typeof FIELD_JUSTIFY[options.align] === 'number') {
5080 result = FIELD_JUSTIFY[options.align];
5081 }
5082 delete options.align;
5083 }
5084 if (result !== 0) {
5085 options.Q = result; // default
5086 }
5087 return options;
5088 },
5089 _resolveFont(options) {
5090 // add current font to document-level AcroForm dict if necessary
5091 if (this._acroform.fonts[this._font.id] === null) {
5092 this._acroform.fonts[this._font.id] = this._font.ref();
5093 }
5094
5095 // add current font to field's resource dict (RD) if not the default acroform font
5096 if (this._acroform.defaultFont !== this._font.name) {
5097 options.DR = {
5098 Font: {}
5099 };
5100
5101 // Get the fontSize option. If not set use auto sizing
5102 const fontSize = options.fontSize || 0;
5103 options.DR.Font[this._font.id] = this._font.ref();
5104 options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
5105 }
5106 return options;
5107 },
5108 _resolveStrings(options) {
5109 let select = [];
5110 function appendChoices(a) {
5111 if (Array.isArray(a)) {
5112 for (let idx = 0; idx < a.length; idx++) {
5113 if (typeof a[idx] === 'string') {
5114 select.push(new String(a[idx]));
5115 } else {
5116 select.push(a[idx]);
5117 }
5118 }
5119 }
5120 }
5121 appendChoices(options.Opt);
5122 if (options.select) {
5123 appendChoices(options.select);
5124 delete options.select;
5125 }
5126 if (select.length) {
5127 options.Opt = select;
5128 }
5129 Object.keys(VALUE_MAP).forEach(key => {
5130 if (options[key] !== undefined) {
5131 options[VALUE_MAP[key]] = options[key];
5132 delete options[key];
5133 }
5134 });
5135 ['V', 'DV'].forEach(key => {
5136 if (typeof options[key] === 'string') {
5137 options[key] = new String(options[key]);
5138 }
5139 });
5140 if (options.MK && options.MK.CA) {
5141 options.MK.CA = new String(options.MK.CA);
5142 }
5143 if (options.label) {
5144 options.MK = options.MK ? options.MK : {};
5145 options.MK.CA = new String(options.label);
5146 delete options.label;
5147 }
5148 return options;
5149 }
5150};
5151
5152var AttachmentsMixin = {
5153 /**
5154 * Embed contents of `src` in PDF
5155 * @param {Buffer | ArrayBuffer | string} src input Buffer, ArrayBuffer, base64 encoded string or path to file
5156 * @param {object} options
5157 * * options.name: filename to be shown in PDF, will use `src` if none set
5158 * * options.type: filetype to be shown in PDF
5159 * * options.description: description to be shown in PDF
5160 * * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
5161 * * options.creationDate: override creation date
5162 * * options.modifiedDate: override modified date
5163 * @returns filespec reference
5164 */
5165 file(src) {
5166 let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
5167 options.name = options.name || src;
5168 const refBody = {
5169 Type: 'EmbeddedFile',
5170 Params: {}
5171 };
5172 let data;
5173 if (!src) {
5174 throw new Error('No src specified');
5175 }
5176 if (Buffer.isBuffer(src)) {
5177 data = src;
5178 } else if (src instanceof ArrayBuffer) {
5179 data = Buffer.from(new Uint8Array(src));
5180 } else {
5181 let match;
5182 if (match = /^data:(.*?);base64,(.*)$/.exec(src)) {
5183 if (match[1]) {
5184 refBody.Subtype = match[1].replace('/', '#2F');
5185 }
5186 data = Buffer.from(match[2], 'base64');
5187 } else {
5188 data = fs.readFileSync(src);
5189 if (!data) {
5190 throw new Error(`Could not read contents of file at filepath ${src}`);
5191 }
5192
5193 // update CreationDate and ModDate
5194 const {
5195 birthtime,
5196 ctime
5197 } = fs.statSync(src);
5198 refBody.Params.CreationDate = birthtime;
5199 refBody.Params.ModDate = ctime;
5200 }
5201 }
5202
5203 // override creation date and modified date
5204 if (options.creationDate instanceof Date) {
5205 refBody.Params.CreationDate = options.creationDate;
5206 }
5207 if (options.modifiedDate instanceof Date) {
5208 refBody.Params.ModDate = options.modifiedDate;
5209 }
5210 // add optional subtype
5211 if (options.type) {
5212 refBody.Subtype = options.type.replace('/', '#2F');
5213 }
5214
5215 // add checksum and size information
5216 const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5217 refBody.Params.CheckSum = new String(checksum);
5218 refBody.Params.Size = data.byteLength;
5219
5220 // save some space when embedding the same file again
5221 // if a file with the same name and metadata exists, reuse its reference
5222 let ref;
5223 if (!this._fileRegistry) this._fileRegistry = {};
5224 let file = this._fileRegistry[options.name];
5225 if (file && isEqual(refBody, file)) {
5226 ref = file.ref;
5227 } else {
5228 ref = this.ref(refBody);
5229 ref.end(data);
5230 this._fileRegistry[options.name] = {
5231 ...refBody,
5232 ref
5233 };
5234 }
5235 // add filespec for embedded file
5236 const fileSpecBody = {
5237 Type: 'Filespec',
5238 F: new String(options.name),
5239 EF: {
5240 F: ref
5241 },
5242 UF: new String(options.name)
5243 };
5244 if (options.description) {
5245 fileSpecBody.Desc = new String(options.description);
5246 }
5247 const filespec = this.ref(fileSpecBody);
5248 filespec.end();
5249 if (!options.hidden) {
5250 this.addNamedEmbeddedFile(options.name, filespec);
5251 }
5252 return filespec;
5253 }
5254};
5255
5256/** check two embedded file metadata objects for equality */
5257function isEqual(a, b) {
5258 return a.Subtype === b.Subtype && a.Params.CheckSum.toString() === b.Params.CheckSum.toString() && a.Params.Size === b.Params.Size && a.Params.CreationDate === b.Params.CreationDate && a.Params.ModDate === b.Params.ModDate;
5259}
5260
5261var PDFA = {
5262 initPDFA(pSubset) {
5263 if (pSubset.charAt(pSubset.length - 3) === '-') {
5264 this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
5265 this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
5266 } else {
5267 // Default to Basic conformance when user doesn't specify
5268 this.subset_conformance = 'B';
5269 this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
5270 }
5271 },
5272 endSubset() {
5273 this._addPdfaMetadata();
5274 this._addColorOutputIntent(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
5275 },
5276 _addColorOutputIntent(pICCPath) {
5277 const iccProfile = fs.readFileSync(pICCPath);
5278 const colorProfileRef = this.ref({
5279 Length: iccProfile.length,
5280 N: 3
5281 });
5282 colorProfileRef.write(iccProfile);
5283 colorProfileRef.end();
5284 const intentRef = this.ref({
5285 Type: 'OutputIntent',
5286 S: 'GTS_PDFA1',
5287 Info: new String('sRGB IEC61966-2.1'),
5288 OutputConditionIdentifier: new String('sRGB IEC61966-2.1'),
5289 DestOutputProfile: colorProfileRef
5290 });
5291 intentRef.end();
5292 this._root.data.OutputIntents = [intentRef];
5293 },
5294 _getPdfaid() {
5295 return `
5296 <rdf:Description xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" rdf:about="">
5297 <pdfaid:part>${this.subset}</pdfaid:part>
5298 <pdfaid:conformance>${this.subset_conformance}</pdfaid:conformance>
5299 </rdf:Description>
5300 `;
5301 },
5302 _addPdfaMetadata() {
5303 this.appendXML(this._getPdfaid());
5304 }
5305};
5306
5307var PDFUA = {
5308 initPDFUA() {
5309 this.subset = 1;
5310 },
5311 endSubset() {
5312 this._addPdfuaMetadata();
5313 },
5314 _addPdfuaMetadata() {
5315 this.appendXML(this._getPdfuaid());
5316 },
5317 _getPdfuaid() {
5318 return `
5319 <rdf:Description xmlns:pdfuaid="http://www.aiim.org/pdfua/ns/id/" rdf:about="">
5320 <pdfuaid:part>${this.subset}</pdfuaid:part>
5321 </rdf:Description>
5322 `;
5323 }
5324};
5325
5326var SubsetMixin = {
5327 _importSubset(subset) {
5328 Object.assign(this, subset);
5329 },
5330 initSubset(options) {
5331 switch (options.subset) {
5332 case 'PDF/A-1':
5333 case 'PDF/A-1a':
5334 case 'PDF/A-1b':
5335 case 'PDF/A-2':
5336 case 'PDF/A-2a':
5337 case 'PDF/A-2b':
5338 case 'PDF/A-3':
5339 case 'PDF/A-3a':
5340 case 'PDF/A-3b':
5341 this._importSubset(PDFA);
5342 this.initPDFA(options.subset);
5343 break;
5344 case 'PDF/UA':
5345 this._importSubset(PDFUA);
5346 this.initPDFUA();
5347 break;
5348 }
5349 }
5350};
5351
5352class PDFMetadata {
5353 constructor() {
5354 this._metadata = `
5355 <?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
5356 <x:xmpmeta xmlns:x="adobe:ns:meta/">
5357 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
5358 `;
5359 }
5360 _closeTags() {
5361 this._metadata = this._metadata.concat(`
5362 </rdf:RDF>
5363 </x:xmpmeta>
5364 <?xpacket end="w"?>
5365 `);
5366 }
5367 append(xml) {
5368 let newline = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5369 this._metadata = this._metadata.concat(xml);
5370 if (newline) this._metadata = this._metadata.concat('\n');
5371 }
5372 getXML() {
5373 return this._metadata;
5374 }
5375 getLength() {
5376 return this._metadata.length;
5377 }
5378 end() {
5379 this._closeTags();
5380 this._metadata = this._metadata.trim();
5381 }
5382}
5383
5384var MetadataMixin = {
5385 initMetadata() {
5386 this.metadata = new PDFMetadata();
5387 },
5388 appendXML(xml) {
5389 let newline = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
5390 this.metadata.append(xml, newline);
5391 },
5392 _addInfo() {
5393 this.appendXML(`
5394 <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
5395 <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + "Z"}</xmp:CreateDate>
5396 <xmp:CreatorTool>${this.info.Creator}</xmp:CreatorTool>
5397 </rdf:Description>
5398 `);
5399 if (this.info.Title || this.info.Author || this.info.Subject) {
5400 this.appendXML(`
5401 <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
5402 `);
5403 if (this.info.Title) {
5404 this.appendXML(`
5405 <dc:title>
5406 <rdf:Alt>
5407 <rdf:li xml:lang="x-default">${this.info.Title}</rdf:li>
5408 </rdf:Alt>
5409 </dc:title>
5410 `);
5411 }
5412 if (this.info.Author) {
5413 this.appendXML(`
5414 <dc:creator>
5415 <rdf:Seq>
5416 <rdf:li>${this.info.Author}</rdf:li>
5417 </rdf:Seq>
5418 </dc:creator>
5419 `);
5420 }
5421 if (this.info.Subject) {
5422 this.appendXML(`
5423 <dc:description>
5424 <rdf:Alt>
5425 <rdf:li xml:lang="x-default">${this.info.Subject}</rdf:li>
5426 </rdf:Alt>
5427 </dc:description>
5428 `);
5429 }
5430 this.appendXML(`
5431 </rdf:Description>
5432 `);
5433 }
5434 this.appendXML(`
5435 <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
5436 <pdf:Producer>${this.info.Creator}</pdf:Producer>`, false);
5437 if (this.info.Keywords) {
5438 this.appendXML(`
5439 <pdf:Keywords>${this.info.Keywords}</pdf:Keywords>`, false);
5440 }
5441 this.appendXML(`
5442 </rdf:Description>
5443 `);
5444 },
5445 endMetadata() {
5446 this._addInfo();
5447 this.metadata.end();
5448
5449 /*
5450 Metadata was introduced in PDF 1.4, so adding it to 1.3
5451 will likely only take up more space.
5452 */
5453 if (this.version != 1.3) {
5454 this.metadataRef = this.ref({
5455 length: this.metadata.getLength(),
5456 Type: 'Metadata',
5457 Subtype: 'XML'
5458 });
5459 this.metadataRef.compress = false;
5460 this.metadataRef.write(Buffer.from(this.metadata.getXML(), 'utf-8'));
5461 this.metadataRef.end();
5462 this._root.data.Metadata = this.metadataRef;
5463 }
5464 }
5465};
5466
5467/*
5468PDFDocument - represents an entire PDF document
5469By Devon Govett
5470*/
5471class PDFDocument extends stream.Readable {
5472 constructor() {
5473 let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
5474 super(options);
5475 this.options = options;
5476
5477 // PDF version
5478 switch (options.pdfVersion) {
5479 case '1.4':
5480 this.version = 1.4;
5481 break;
5482 case '1.5':
5483 this.version = 1.5;
5484 break;
5485 case '1.6':
5486 this.version = 1.6;
5487 break;
5488 case '1.7':
5489 case '1.7ext3':
5490 this.version = 1.7;
5491 break;
5492 default:
5493 this.version = 1.3;
5494 break;
5495 }
5496
5497 // Whether streams should be compressed
5498 this.compress = this.options.compress != null ? this.options.compress : true;
5499 this._pageBuffer = [];
5500 this._pageBufferStart = 0;
5501
5502 // The PDF object store
5503 this._offsets = [];
5504 this._waiting = 0;
5505 this._ended = false;
5506 this._offset = 0;
5507 const Pages = this.ref({
5508 Type: 'Pages',
5509 Count: 0,
5510 Kids: []
5511 });
5512 const Names = this.ref({
5513 Dests: new PDFNameTree()
5514 });
5515 this._root = this.ref({
5516 Type: 'Catalog',
5517 Pages,
5518 Names
5519 });
5520 if (this.options.lang) {
5521 this._root.data.Lang = new String(this.options.lang);
5522 }
5523
5524 // The current page
5525 this.page = null;
5526
5527 // Initialize mixins
5528 this.initMetadata();
5529 this.initColor();
5530 this.initVector();
5531 this.initFonts(options.font);
5532 this.initText();
5533 this.initImages();
5534 this.initOutline();
5535 this.initMarkings(options);
5536 this.initSubset(options);
5537
5538 // Initialize the metadata
5539 this.info = {
5540 Producer: 'PDFKit',
5541 Creator: 'PDFKit',
5542 CreationDate: new Date()
5543 };
5544 if (this.options.info) {
5545 for (let key in this.options.info) {
5546 const val = this.options.info[key];
5547 this.info[key] = val;
5548 }
5549 }
5550 if (this.options.displayTitle) {
5551 this._root.data.ViewerPreferences = this.ref({
5552 DisplayDocTitle: true
5553 });
5554 }
5555
5556 // Generate file ID
5557 this._id = PDFSecurity.generateFileID(this.info);
5558
5559 // Initialize security settings
5560 this._security = PDFSecurity.create(this, options);
5561
5562 // Write the header
5563 // PDF version
5564 this._write(`%PDF-${this.version}`);
5565
5566 // 4 binary chars, as recommended by the spec
5567 this._write('%\xFF\xFF\xFF\xFF');
5568
5569 // Add the first page
5570 if (this.options.autoFirstPage !== false) {
5571 this.addPage();
5572 }
5573 }
5574 addPage(options) {
5575 if (options == null) {
5576 ({
5577 options
5578 } = this);
5579 }
5580
5581 // end the current page if needed
5582 if (!this.options.bufferPages) {
5583 this.flushPages();
5584 }
5585
5586 // create a page object
5587 this.page = new PDFPage(this, options);
5588 this._pageBuffer.push(this.page);
5589
5590 // add the page to the object store
5591 const pages = this._root.data.Pages.data;
5592 pages.Kids.push(this.page.dictionary);
5593 pages.Count++;
5594
5595 // reset x and y coordinates
5596 this.x = this.page.margins.left;
5597 this.y = this.page.margins.top;
5598
5599 // flip PDF coordinate system so that the origin is in
5600 // the top left rather than the bottom left
5601 this._ctm = [1, 0, 0, 1, 0, 0];
5602 this.transform(1, 0, 0, -1, 0, this.page.height);
5603 this.emit('pageAdded');
5604 return this;
5605 }
5606 continueOnNewPage(options) {
5607 const pageMarkings = this.endPageMarkings(this.page);
5608 this.addPage(options);
5609 this.initPageMarkings(pageMarkings);
5610 return this;
5611 }
5612 bufferedPageRange() {
5613 return {
5614 start: this._pageBufferStart,
5615 count: this._pageBuffer.length
5616 };
5617 }
5618 switchToPage(n) {
5619 let page;
5620 if (!(page = this._pageBuffer[n - this._pageBufferStart])) {
5621 throw new Error(`switchToPage(${n}) out of bounds, current buffer covers pages ${this._pageBufferStart} to ${this._pageBufferStart + this._pageBuffer.length - 1}`);
5622 }
5623 return this.page = page;
5624 }
5625 flushPages() {
5626 // this local variable exists so we're future-proof against
5627 // reentrant calls to flushPages.
5628 const pages = this._pageBuffer;
5629 this._pageBuffer = [];
5630 this._pageBufferStart += pages.length;
5631 for (let page of pages) {
5632 this.endPageMarkings(page);
5633 page.end();
5634 }
5635 }
5636 addNamedDestination(name) {
5637 for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
5638 args[_key - 1] = arguments[_key];
5639 }
5640 if (args.length === 0) {
5641 args = ['XYZ', null, null, null];
5642 }
5643 if (args[0] === 'XYZ' && args[2] !== null) {
5644 args[2] = this.page.height - args[2];
5645 }
5646 args.unshift(this.page.dictionary);
5647 this._root.data.Names.data.Dests.add(name, args);
5648 }
5649 addNamedEmbeddedFile(name, ref) {
5650 if (!this._root.data.Names.data.EmbeddedFiles) {
5651 // disabling /Limits for this tree fixes attachments not showing in Adobe Reader
5652 this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({
5653 limits: false
5654 });
5655 }
5656
5657 // add filespec to EmbeddedFiles
5658 this._root.data.Names.data.EmbeddedFiles.add(name, ref);
5659 }
5660 addNamedJavaScript(name, js) {
5661 if (!this._root.data.Names.data.JavaScript) {
5662 this._root.data.Names.data.JavaScript = new PDFNameTree();
5663 }
5664 let data = {
5665 JS: new String(js),
5666 S: 'JavaScript'
5667 };
5668 this._root.data.Names.data.JavaScript.add(name, data);
5669 }
5670 ref(data) {
5671 const ref = new PDFReference(this, this._offsets.length + 1, data);
5672 this._offsets.push(null); // placeholder for this object's offset once it is finalized
5673 this._waiting++;
5674 return ref;
5675 }
5676 _read() {}
5677 // do nothing, but this method is required by node
5678
5679 _write(data) {
5680 if (!Buffer.isBuffer(data)) {
5681 data = Buffer.from(data + '\n', 'binary');
5682 }
5683 this.push(data);
5684 return this._offset += data.length;
5685 }
5686 addContent(data) {
5687 this.page.write(data);
5688 return this;
5689 }
5690 _refEnd(ref) {
5691 this._offsets[ref.id - 1] = ref.offset;
5692 if (--this._waiting === 0 && this._ended) {
5693 this._finalize();
5694 return this._ended = false;
5695 }
5696 }
5697 end() {
5698 this.flushPages();
5699 this._info = this.ref();
5700 for (let key in this.info) {
5701 let val = this.info[key];
5702 if (typeof val === 'string') {
5703 val = new String(val);
5704 }
5705 let entry = this.ref(val);
5706 entry.end();
5707 this._info.data[key] = entry;
5708 }
5709 this._info.end();
5710 for (let name in this._fontFamilies) {
5711 const font = this._fontFamilies[name];
5712 font.finalize();
5713 }
5714 this.endOutline();
5715 this.endMarkings();
5716 if (this.subset) {
5717 this.endSubset();
5718 }
5719 this.endMetadata();
5720 this._root.end();
5721 this._root.data.Pages.end();
5722 this._root.data.Names.end();
5723 this.endAcroForm();
5724 if (this._root.data.ViewerPreferences) {
5725 this._root.data.ViewerPreferences.end();
5726 }
5727 if (this._security) {
5728 this._security.end();
5729 }
5730 if (this._waiting === 0) {
5731 return this._finalize();
5732 } else {
5733 return this._ended = true;
5734 }
5735 }
5736 _finalize() {
5737 // generate xref
5738 const xRefOffset = this._offset;
5739 this._write('xref');
5740 this._write(`0 ${this._offsets.length + 1}`);
5741 this._write('0000000000 65535 f ');
5742 for (let offset of this._offsets) {
5743 offset = `0000000000${offset}`.slice(-10);
5744 this._write(offset + ' 00000 n ');
5745 }
5746
5747 // trailer
5748 const trailer = {
5749 Size: this._offsets.length + 1,
5750 Root: this._root,
5751 Info: this._info,
5752 ID: [this._id, this._id]
5753 };
5754 if (this._security) {
5755 trailer.Encrypt = this._security.dictionary;
5756 }
5757 this._write('trailer');
5758 this._write(PDFObject.convert(trailer));
5759 this._write('startxref');
5760 this._write(`${xRefOffset}`);
5761 this._write('%%EOF');
5762
5763 // end the stream
5764 return this.push(null);
5765 }
5766 toString() {
5767 return '[object PDFDocument]';
5768 }
5769}
5770const mixin = methods => {
5771 Object.assign(PDFDocument.prototype, methods);
5772};
5773mixin(MetadataMixin);
5774mixin(ColorMixin);
5775mixin(VectorMixin);
5776mixin(FontsMixin);
5777mixin(TextMixin);
5778mixin(ImagesMixin);
5779mixin(AnnotationsMixin);
5780mixin(OutlineMixin);
5781mixin(MarkingsMixin);
5782mixin(AcroFormMixin);
5783mixin(AttachmentsMixin);
5784mixin(SubsetMixin);
5785PDFDocument.LineWrapper = LineWrapper;
5786
5787export default PDFDocument;
5788//# sourceMappingURL=pdfkit.es.js.map