UNPKG

@peculiar/pdf-core

Version:

Core functionality library for PDF document processing and manipulation

1,511 lines (1,483 loc) 344 kB
import { Convert, BufferSourceConverter } from 'pvtsutils'; import { __decorate } from 'tslib'; import * as pako from 'pako'; import { EventEmitter } from 'events'; import * as pkijs from 'pkijs'; import { Name } from '@peculiar/x509'; class CharSet { static { this.newLineChar = 0x0a; } static { this.whiteSpaceChars = new Uint8Array([ 0x00, 0x09, 0x0a, 0x0c, 0x0d, 0x20 ]); } static { this.lessThanChar = 0x3c; } static { this.greaterThanChar = 0x3e; } static { this.hexadecimalChars = new Uint8Array(Convert.FromUtf8String("01234567890abcdefABCDEF")); } static { this.percentChar = 0x25; } static { this.whiteSpaceChar = 0x20; } static { this.trailerChars = new Uint8Array([ 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72 ]); } static { this.xrefChars = new Uint8Array([0x78, 0x72, 0x65, 0x66]); } } class PDFError extends Error { static { this.NAME = "PDFError"; } constructor(message, cause) { super(message); this.name = this.constructor.NAME; if (cause) { this.inner = cause; } } } class ParsingError extends PDFError { static { this.NAME = "ParsingError"; } constructor(message, position = -1, cause) { message = `${message} at position ${position}`; super(message, cause); this.message = message; this.position = position; } } class BadCharError extends ParsingError { static { this.NAME = "BadCharError"; } constructor(messageOrPosition, positionOrCause, cause) { let message; let position; if (typeof messageOrPosition === "string") { message = `${messageOrPosition} at ${positionOrCause} position.`; position = positionOrCause; } else { message = `Wrong character at ${position} position.`; position = messageOrPosition; cause = positionOrCause; } super(message, position, cause); } } class UnregisteredObjectTypeError extends PDFError { static { this.NAME = "PDFUnregisteredObjectTypeError"; } constructor(name, cause) { super(`The PDF object type '${name}' has not been registered with the PDF library. Please register the object type before attempting to use it.`, cause); } } class ViewReader { get isEOF() { return this.position < 0 || this.position >= this.view.length; } constructor(view) { this.position = 0; this.backward = false; this.view = BufferSourceConverter.toUint8Array(view); } findIndex(param) { if (typeof param === "function") { return this.findIndexByCallback(param); } else if (typeof param === "string") { return this.findIndexByString(param); } else { return this.findIndexByView(param); } } findIndexByCallback(cb) { const step = this.backward ? -1 : 1; for (this.position; true; this.position += step) { const value = this.view[this.position]; if (value === undefined) { break; } if (cb(value, this.position, this.view, this)) { return this.position; } } return -1; } findIndexByString(text) { const buffer = Convert.FromUtf8String(text); const view = new Uint8Array(buffer); return this.findIndexByView(view); } findIndexByView(view) { const viewCopy = view.slice(); if (this.backward) { viewCopy.reverse(); } return this.findIndex((c, i, a) => { for (let j = 0; j < viewCopy.length; j++) { if (a[this.backward ? i-- : i++] !== viewCopy[j]) { return false; } } return true; }); } readByte() { const res = this.view[this.position]; this.position += this.backward ? -1 : 1; return res; } readUint8() { return this.readByte(); } readUint16(littleEndian = false) { const dataView = new DataView(this.read(2).slice().buffer); return dataView.getUint16(0, littleEndian); } readUint32(littleEndian = false) { const bytes = this.read(4).slice(); if (littleEndian) { bytes.reverse(); } const view = new Uint32Array(bytes.buffer); return view[0]; } readString(size) { const bytes = this.read(size); return Convert.ToBinary(bytes); } read(param) { const startPosition = this.position; if (typeof param === "number") { this.position += this.backward ? -param : param; } else if (!param) { this.position = this.backward ? -1 : this.view.length; } else if (param) { this.position = this.findIndex(param); if (this.position === -1 && !this.backward) { this.position = this.view.length; } } return this.backward ? this.view.subarray(this.position, startPosition) : this.view.subarray(startPosition, this.position); } get current() { return this.view[this.position]; } end() { this.position = this.view.length - 1; } start() { this.position = 0; } } class ViewWriter { constructor() { this.buffer = []; this.totalLength = 0; } get length() { return this.totalLength; } addToLength(length) { this.totalLength += length; } write(data, cb) { const uint8Data = BufferSourceConverter.toUint8Array(data); this.addToLength(uint8Data.length); this.buffer.push([uint8Data, cb]); } writeLine(data) { if (data) { this.write(data); } this.writeByte(CharSet.newLineChar); } writeByte(char) { this.addToLength(1); this.buffer.push([new Uint8Array([char])]); } writeByteLine(char) { this.addToLength(2); this.buffer.push([new Uint8Array([char, CharSet.newLineChar])]); } writeString(text) { const uint8Text = new Uint8Array(Convert.FromBinary(text)); this.addToLength(uint8Text.length); this.buffer.push([uint8Text]); } writeStringLine(text) { this.writeString(text); this.writeByte(CharSet.newLineChar); } toArrayBuffer() { return this.toUint8Array().buffer; } toUint8Array() { const res = new Uint8Array(this.totalLength); let offset = 0; for (const [item, cb] of this.buffer) { res.set(item, offset); if (cb) { cb(res.subarray(offset, offset + item.length)); } offset += item.length; } return res; } toString() { return Convert.ToBinary(this.toUint8Array()); } } function isPdfIndirect(data) { return (typeof data === "object" && !!data && "id" in data && typeof data.id === "number" && "generation" in data && typeof data.generation === "number"); } class PDFObject { constructor() { this.ownerElement = null; this.view = PDFObject.DEFAULT_VIEW; this.documentUpdate = null; this.padding = 0; } static { this.DEFAULT_VIEW = new Uint8Array(); } static create(target) { const obj = new this(); obj.documentUpdate = "update" in target ? target.update : target; obj.onCreate(); return obj; } onCreate() { } static getReader(data, offset = 0) { return data instanceof ViewReader ? data : typeof data === "string" ? new ViewReader(new Uint8Array(Convert.FromBinary(data))) : new ViewReader(BufferSourceConverter.toUint8Array(data).subarray(offset)); } static fromPDF(data, offset = 0) { const obj = new this(); obj.fromPDF(data, offset); return obj; } isIndirect() { return isPdfIndirect(this.ownerElement); } findIndirect(deep = false) { if (this.ownerElement) { if (isPdfIndirect(this.ownerElement)) { return this.ownerElement; } else if (deep) { return this.ownerElement.findIndirect(deep); } } return null; } getIndirect(deep = false) { const indirect = this.findIndirect(deep); if (!indirect) { throw new Error("Not found indirect"); } return indirect; } makeIndirect(compressed) { if (this.documentUpdate && !this.isIndirect()) { this.getDocument().append(this, compressed); } return this; } getDocument() { return this.getDocumentUpdate().document; } getDocumentUpdate() { if (!this.documentUpdate) { throw new Error("PDF object is not assigned to the PTF document"); } return this.documentUpdate; } toPDF(fromEarlySaved = false) { if (fromEarlySaved && this.view.length) { return this.view; } const writer = new ViewWriter(); this.writePDF(writer); return writer.toUint8Array(); } writePDF(writer, fromEarlySaved = false) { if (fromEarlySaved && this.view.length) { writer.write(this.view); return; } const offset = writer.length; this.onWritePDF(writer); let length = writer.length - offset; if (length < this.padding) { writer.writeString("".padEnd(this.padding - length, " ")); length = this.padding; } writer.write(new Uint8Array(), (subarray) => { this.view = new Uint8Array(subarray.buffer).subarray(offset, offset + length); }); } fromPDF(data, offset = 0) { const reader = PDFObject.getReader(data, offset); const startPosition = reader.position; try { this.onFromPDF(reader); this.view = reader.view.subarray(startPosition, reader.position); return reader.position; } catch (e) { if (e instanceof ParsingError) { if (e.position === -1) { e.position = startPosition; } throw e; } let message = `Cannot create ${this.constructor.name} from PDF stream at position ${reader.position}.`; if (e instanceof Error) { message += ` ${e.message}`; } throw new ParsingError(message, reader.position); } } adoptChild(obj) { if (this.documentUpdate) { obj.documentUpdate = this.documentUpdate; } if (this.ownerElement) { obj.ownerElement = this; } } copy() { const copy = new this.constructor(); this.onCopy(copy); return copy; } onCopy(copy) { copy.documentUpdate = this.documentUpdate; copy.ownerElement = this.ownerElement; } modify() { if (this.documentUpdate) { const lastUpdate = this.documentUpdate.document.update; if (this.documentUpdate === lastUpdate) { return this; } else { const indirect = this.findIndirect(true); if (indirect) { const obj = this.documentUpdate.document.getObject(indirect.id, indirect.generation); if (obj.value.documentUpdate === lastUpdate) { this.documentUpdate = lastUpdate; return this; } this.documentUpdate.document.append(obj); this.documentUpdate = lastUpdate; return obj.value; } } } return this; } equal(target) { if (this === target) { return true; } else if (this.isIndirect() && target.isIndirect()) { const thisRef = this.getIndirect(); const targetRef = target.getIndirect(); return (thisRef.id === targetRef.id && thisRef.generation === targetRef.generation); } return this.onEqual(target); } } const zeroChar = 0x30; const nineChar = 0x39; const plusChar = 0x2b; const minusChar = 0x2d; const pointChar = 0x2e; function isDigit(char) { return ((zeroChar <= char && char <= nineChar) || char === plusChar || char === minusChar || char === pointChar); } var ObjectTypeEnum; (function (ObjectTypeEnum) { ObjectTypeEnum["Null"] = "PDFNull"; ObjectTypeEnum["Boolean"] = "PDFBoolean"; ObjectTypeEnum["Numeric"] = "PDFNumeric"; ObjectTypeEnum["Name"] = "PDFName"; ObjectTypeEnum["LiteralString"] = "PDFLiteralString"; ObjectTypeEnum["HexString"] = "PDFHexString"; ObjectTypeEnum["IndirectReference"] = "PDFIndirectReference"; ObjectTypeEnum["Array"] = "PDFArray"; ObjectTypeEnum["Dictionary"] = "PDFDictionary"; ObjectTypeEnum["Stream"] = "PDFStream"; ObjectTypeEnum["Comment"] = "PDFComment"; })(ObjectTypeEnum || (ObjectTypeEnum = {})); function typeOf(data, name) { return (!!data && typeof data === "object" && "NAME" in data.constructor && data.constructor.NAME === name); } const falseChars = new Uint8Array([0x66, 0x61, 0x6c, 0x73, 0x65]); const trueChars = new Uint8Array([0x74, 0x72, 0x75, 0x65]); class PDFBoolean extends PDFObject { static { this.NAME = ObjectTypeEnum.Boolean; } constructor(value = false) { super(); this.value = value; } onWritePDF(writer) { if (this.value) { writer.write(trueChars); } else { writer.write(falseChars); } } onFromPDF(reader) { const firstChar = reader.view[reader.position]; switch (firstChar) { case 0x74: { if (!trueChars.every((c) => c === reader.readByte())) { throw new BadCharError(reader.position - 1); } this.value = true; break; } case 0x66: { if (!falseChars.every((c) => c === reader.readByte())) { throw new BadCharError(reader.position - 1); } this.value = false; break; } default: throw new BadCharError(reader.position); } } onCopy(copy) { super.onCopy(copy); copy.value = this.value; } toString() { return this.value ? "true" : "false"; } onEqual(target) { return target instanceof PDFBoolean && target.value === this.value; } } class PDFString extends PDFObject { static { this.DEFAULT_TEXT = ""; } constructor(param) { super(); this.text = PDFString.DEFAULT_TEXT; if (param instanceof PDFString) { this.view = param.view; this.text = param.text; } else if (typeof param === "string") { this.text = param; } else if (BufferSourceConverter.isBufferSource(param)) { this.text = Convert.ToBinary(param); } } onCopy(copy) { super.onCopy(copy); copy.text = this.text; } onEqual(target) { return target instanceof PDFString && target.text === this.text; } } class PDFComment extends PDFString { static { this.NAME = ObjectTypeEnum.Comment; } onWritePDF(writer) { writer.writeString(`% ${this.text}`); } onFromPDF(reader) { if (reader.readByte() !== CharSet.percentChar) { throw new BadCharError(reader.position - 1); } const textView = reader.read((c) => c === 0x0d || c === 0x0a); this.text = Convert.ToUtf8String(textView).trim(); } toString() { return `% ${this.text}`; } } let TextEncoder$1 = class TextEncoder { static { this.UTF8 = "\xEF\xBB\xBF"; } static { this.UTF16 = "\xFE\xFF"; } static from(text) { if (text.startsWith(this.UTF16)) { return Convert.ToUtf16String(Convert.FromBinary(text.substring(this.UTF16.length))); } else if (text.startsWith(this.UTF8)) { return text.substring(this.UTF8.length); } return text; } static to(text) { let ascii = true; for (const char of text) { if (char.charCodeAt(0) > 0xff) { ascii = false; break; } } if (ascii) { return text; } return `${this.UTF16}${Convert.ToBinary(Convert.FromUtf8String(text, "utf16be"))}`; } }; class PDFTextString extends PDFString { async encryptAsync() { const parent = this.findIndirect(true); const textView = Convert.FromBinary(TextEncoder$1.to(this.text)); if (!parent || !this.documentUpdate?.document.encryptHandler) { return textView; } return this.documentUpdate.document.encryptHandler.encrypt(textView, this); } async decryptAsync() { const parent = this.findIndirect(true); const textView = this.toUint8Array(); if (!parent || !this.documentUpdate?.document.encryptHandler) { return textView; } return this.documentUpdate.document.encryptHandler.decrypt(textView, this); } async encode() { if (!this.encrypted) { if (this.documentUpdate?.document.encryptHandler) { const decryptedText = await this.encryptAsync(); this.text = Convert.ToBinary(decryptedText); this.encrypted = true; } } return this.text; } async decode() { if (this.encrypted === undefined || this.encrypted) { if (this.documentUpdate?.document.encryptHandler) { const decryptedText = await this.decryptAsync(); this.text = TextEncoder$1.from(Convert.ToBinary(decryptedText)); } this.encrypted = false; } return this.text; } toArrayBuffer() { return Convert.FromBinary(this.text); } toUint8Array() { return BufferSourceConverter.toUint8Array(this.toArrayBuffer()); } onCreate() { this.encrypted = false; } onCopy(copy) { super.onCopy(copy); copy.encrypted = this.encrypted; } } class PDFHexString extends PDFTextString { static { this.NAME = ObjectTypeEnum.HexString; } onWritePDF(writer) { const encText = TextEncoder$1.to(this.text); writer.writeString(`<${Convert.ToHex(Convert.FromBinary(encText))}>`); } onFromPDF(reader) { if (reader.readByte() !== CharSet.lessThanChar) { throw new BadCharError(reader.position - 1); } const hexStrings = []; reader.read((c) => { if (c === CharSet.greaterThanChar) { return true; } if (CharSet.hexadecimalChars.includes(c)) { hexStrings.push(c); } else if (!CharSet.whiteSpaceChars.includes(c)) { throw new BadCharError(reader.position); } return false; }); const hexString = Convert.ToBinary(new Uint8Array(hexStrings)); const hexText = hexString + (hexString.length % 2 ? "0" : ""); const hexView = Convert.FromHex(hexText); this.text = TextEncoder$1.from(Convert.ToBinary(hexView)); reader.readByte(); } get data() { return new Uint8Array(Convert.FromBinary(this.text)); } toString() { return `<${Convert.ToHex(this.toUint8Array())}>`; } } class PDFNumeric extends PDFObject { static { this.NAME = ObjectTypeEnum.Numeric; } static assertPositiveInteger(number) { if (!(number.value >>> 0 === parseFloat(number.value.toString()))) { throw new Error("Number is not a positive integer"); } } constructor(value) { super(); this.value = value ?? 0; } onWritePDF(writer) { writer.writeString(this.toString()); } onFromPDF(reader) { const view = reader.read((c) => !isDigit(c)); if (!view.length) { throw new ParsingError(`Numeric sequence not found at position ${reader.position}`, reader.position); } const value = new Number(Convert.ToUtf8String(view)); if (Number.isNaN(value)) { throw new ParsingError("Parsed value is not a Number", reader.position); } this.value = value.valueOf(); } toString() { const value = new Number(this.value); const digits = /\.(.+)/.exec(this.value.toString())?.[1].length || 0; return value.toFixed(digits > 3 ? 3 : digits); } onCopy(copy) { super.onCopy(copy); copy.value = this.value; } onEqual(target) { return target instanceof PDFNumeric && target.value === this.value; } } class PDFIndirect extends PDFObject { constructor(id = 0, generation = 0) { super(); this.id = id; this.generation = generation; } onFromPDF(reader) { while (true) { reader.findIndex((c) => !CharSet.whiteSpaceChars.includes(c)); if (reader.isEOF) { break; } if (reader.view[reader.position] === CharSet.percentChar) { PDFComment.fromPDF(reader); continue; } break; } const objectNumber = PDFNumeric.fromPDF(reader); PDFNumeric.assertPositiveInteger(objectNumber); if (reader.readByte() !== 0x20) { throw new BadCharError(reader.position - 1); } const generationNumber = PDFNumeric.fromPDF(reader); PDFNumeric.assertPositiveInteger(objectNumber); if (reader.readByte() !== 0x20) { throw new BadCharError(reader.position - 1); } this.id = objectNumber.value; this.generation = generationNumber.value; } onCopy(copy) { super.onCopy(copy); copy.id = this.id; copy.generation = this.generation; } onEqual(target) { return (target instanceof PDFIndirect && target.id === this.id && target.generation === this.generation); } } const nullChars = new Uint8Array([0x6e, 0x75, 0x6c, 0x6c]); class PDFNull extends PDFObject { static { this.NAME = ObjectTypeEnum.Null; } onWritePDF(writer) { writer.write(nullChars); } onFromPDF(reader) { if (!nullChars.every((c) => c === reader.readByte())) { throw new BadCharError(reader.position - 1); } } toString() { return "null"; } onEqual(target) { return target instanceof PDFNull; } } class PDFObjectReader { static { this.items = {}; } static register(type) { this.items[type.NAME] = type; } static get(name) { const Constructor = this.items[name]; if (!Constructor) { throw new UnregisteredObjectTypeError(name); } return Constructor; } static read(reader, update = null, parent = null) { this.skip(reader); switch (true) { case reader.view[reader.position] === 0x6e: return this.get(ObjectTypeEnum.Null).fromPDF(reader); case reader.view[reader.position] === 0x74 || reader.view[reader.position] === 0x66: return this.get(ObjectTypeEnum.Boolean).fromPDF(reader); case reader.view[reader.position] === 0x2f: return this.get(ObjectTypeEnum.Name).fromPDF(reader); case reader.view[reader.position] === 0x28: return this.get(ObjectTypeEnum.LiteralString).fromPDF(reader); case reader.view[reader.position] === 0x5b: { const Constructor = this.get(ObjectTypeEnum.Array); const array = new Constructor(); array.documentUpdate = update; array.ownerElement = parent; array.fromPDF(reader); return array; } case isDigit(reader.view[reader.position]): { const startPosition = reader.position; try { const Constructor = this.get(ObjectTypeEnum.IndirectReference); const ref = new Constructor(); ref.documentUpdate = update; ref.ownerElement = parent; ref.fromPDF(reader); return ref; } catch { reader.position = startPosition; return this.get(ObjectTypeEnum.Numeric).fromPDF(reader); } } case reader.view[reader.position] === 0x3c: { if (reader.view[reader.position + 1] === 0x3c) { const dictionaryPosition = reader.position; const DictionaryConstructor = this.get(ObjectTypeEnum.Dictionary); const dictionary = new DictionaryConstructor(); dictionary.documentUpdate = update; dictionary.ownerElement = parent; dictionary.fromPDF(reader); this.skip(reader); if (reader.view[reader.position] !== 0x73) { return dictionary; } reader.position = dictionaryPosition; const StreamConstructor = this.get(ObjectTypeEnum.Stream); const stream = new StreamConstructor(); stream.documentUpdate = update; stream.ownerElement = parent; stream.fromPDF(reader); return stream; } else { return this.get(ObjectTypeEnum.HexString).fromPDF(reader); } } } throw new ParsingError(`Cannot read PDF object at position ${reader.position}`, reader.position); } static skip(reader) { while (true) { reader.findIndex((c) => !CharSet.whiteSpaceChars.includes(c)); if (reader.isEOF) { break; } if (reader.view[reader.position] === CharSet.percentChar) { this.get(ObjectTypeEnum.Comment).fromPDF(reader); continue; } break; } } } const objChars = new Uint8Array([0x6f, 0x62, 0x6a]); const endobjChars = new Uint8Array([0x65, 0x6e, 0x64, 0x6f, 0x62, 0x6a]); class PDFIndirectObject extends PDFIndirect { constructor(id, generation = 0, value = new PDFNull()) { if (id === undefined) { super(); } else { super(id, generation); } this.value = value || new PDFNull(); this.value.ownerElement = this; } onWritePDF(writer) { const objectNumber = new PDFNumeric(this.id).toString(); const generationNumber = new PDFNumeric(this.generation).toString(); writer.writeString(`${objectNumber} ${generationNumber} obj\n`); this.value.writePDF(writer); writer.writeString("\nendobj\n"); } onFromPDF(reader) { super.onFromPDF(reader); if (!objChars.every((c) => c === reader.readByte())) { throw new BadCharError(reader.position - 1); } PDFObjectReader.skip(reader); this.value = PDFObjectReader.read(reader, this.documentUpdate, this); this.adoptChild(this.value); PDFObjectReader.skip(reader); if (!endobjChars.every((c) => c === reader.readByte())) { throw new BadCharError(reader.position - 1); } } adoptChild(obj) { super.adoptChild(obj); obj.ownerElement = this; } onCopy(copy) { super.onCopy(copy); copy.value = this.value.copy(); } toString() { return `${this.id} ${this.generation} obj\n${this.value.toString()}\nendobj`; } } class PDFTypeConverter { static convert(target, type, replace = false) { if (type) { if (type.__proto__ === PDFObject || type.__proto__ === PDFString || type.__proto__ === PDFTextString) { if (!(target instanceof type)) { throw TypeError("Cannot convert target object to requested type"); } } else { if (!(target instanceof type)) { const res = new type(target); if (replace && target.ownerElement instanceof PDFIndirectObject) { target.ownerElement.value = res; } return res; } } } return target; } } class PDFIndirectReference extends PDFIndirect { static { this.NAME = ObjectTypeEnum.IndirectReference; } onWritePDF(writer) { const objectNumber = new PDFNumeric(this.id).toString(); const generationNumber = new PDFNumeric(this.generation).toString(); return writer.writeString(`${objectNumber} ${generationNumber} R`); } onFromPDF(reader) { super.onFromPDF(reader); if (reader.readByte() !== 0x52) { throw new BadCharError(reader.position - 1); } } getValue(type) { if (this.documentUpdate) { const value = this.documentUpdate.document.getObject(this).value; return PDFTypeConverter.convert(value, type); } throw new Error("IndirectReference is not assigned to DocumentUpdate"); } toString() { return `${this.id} ${this.generation} R`; } } const leftParenthesisChar = 0x28; const rightParenthesisChar = 0x29; class PDFLiteralString extends PDFTextString { static { this.NAME = ObjectTypeEnum.LiteralString; } static { this.ESCAPE_PATTERN = /[\n\r\t\f\x08\\()]/gm; } static { this.OCTAL_PATTERN = /[0-7]{1,3}/; } static { this.PARSE_PATTERN = /\\([0-7]{1,3}|\r\n|\n|\r|.)/gm; } static { this.WRITE_ESCAPE_MAP = new Map([ ["\n", "\\n"], ["\r", "\\r"], ["\t", "\\t"], ["\b", "\\b"], ["\f", "\\f"], ["(", "\\("], [")", "\\)"], ["\\", "\\\\"] ]); } static { this.READ_ESCAPE_MAP = new Map([ ["\\n", "\n"], ["\\r", "\r"], ["\\t", "\t"], ["\\b", "\b"], ["\\f", "\f"], ["\\(", "("], ["\\)", ")"], ["\\\\", "\\"], ["\\\r\n", ""], ["\\\n", ""], ["\\\r", ""] ]); } onWritePDF(writer) { const text = TextEncoder$1.to(this.text); const escapedValue = text.replace(PDFLiteralString.ESCAPE_PATTERN, (char) => PDFLiteralString.WRITE_ESCAPE_MAP.get(char) || char); return writer.writeString(`(${escapedValue})`); } static getMaxUnicode(text) { let maxUnicode = 0; for (const char of text) { const unicode = char.charCodeAt(0); if (unicode > maxUnicode) { maxUnicode = unicode; } } return maxUnicode; } onFromPDF(reader) { if (reader.readByte() !== leftParenthesisChar) { throw new BadCharError(reader.position - 1); } const { data, depth } = this.readStringData(reader); if (depth !== 0) { throw new Error("Missing closing parenthesis in literal string"); } const text = Convert.ToBinary(data).replace(PDFLiteralString.PARSE_PATTERN, (substring, group1) => { const escaped = PDFLiteralString.READ_ESCAPE_MAP.get(substring); if (escaped !== undefined) { return escaped; } if (PDFLiteralString.OCTAL_PATTERN.test(group1)) { return String.fromCharCode(parseInt(group1, 8)); } return group1; }); this.text = TextEncoder$1.from(text); reader.readByte(); } readStringData(reader) { let depth = 1; let reverseSolidus = 0; const data = reader.read((c) => { if (c === 0x5c) { reverseSolidus ^= 1; return false; } if (reverseSolidus && (c === leftParenthesisChar || c === rightParenthesisChar)) { reverseSolidus = 0; return false; } if (c === leftParenthesisChar) { depth++; } else if (c === rightParenthesisChar) { depth--; if (depth === 0) { return true; } } reverseSolidus = 0; return false; }); return { data, depth }; } toString() { return `(${this.text})`; } } const exclamationMarkChar = 0x21; const tildeChar = 0x7e; const solidusChar = 0x2f; const deprecatedChars = [ 0x2f, 0x28, 0x29, 0x5b, 0x5d, 0x3c, 0x3e, 0x7b, 0x7d, 0x25 ]; const ESCAPE_REGEX = /[^!-~]|[[\]<>(){}%#/]/gm; class PDFName extends PDFString { static { this.NAME = ObjectTypeEnum.Name; } static isNameChar(char) { return !(char < exclamationMarkChar || tildeChar < char || deprecatedChars.includes(char)); } onWritePDF(writer) { const escapedValue = this.text.replace(ESCAPE_REGEX, (substring) => `#${substring.charCodeAt(0).toString(16).padStart(2, "0")}`); return writer.writeString(`/${escapedValue}`); } onFromPDF(reader) { if (reader.readByte() !== solidusChar) { throw new BadCharError(reader.position - 1); } const textView = reader.read((v) => !PDFName.isNameChar(v)); this.text = Convert.ToUtf8String(textView).replace(/#([0-9A-F]{2})/gim, (substring, group1) => String.fromCharCode(parseInt(group1, 16))); } toString() { return `/${this.text}`; } } const leftSquareBracketChar = 0x5b; const rightSquareBracketChar = 0x5d; class PDFArray extends PDFObject { static { this.NAME = ObjectTypeEnum.Array; } [Symbol.iterator]() { let pointer = 0; const _this = this; return { next() { if (pointer < _this.items.length) { return { done: false, value: _this.get(pointer++) }; } else { return { done: true, value: null }; } } }; } constructor(...items) { super(); if (items.length === 1 && items[0] instanceof PDFArray) { this.items = items[0].items; this.ownerElement = items[0].ownerElement; this.documentUpdate = items[0].documentUpdate; this.view = items[0].view; } else { this.items = new Array(...items); } } get length() { return this.items.length; } find(index, type, replace = false) { const item = this.items[index]; if (item) { const res = typeOf(item, ObjectTypeEnum.IndirectReference) ? item.getValue() : item; const resType = type ? PDFTypeConverter.convert(res, type, replace) : PDFTypeConverter.convert(res); if (replace && !resType.isIndirect) { this.items[index] = resType; } return resType; } return null; } get(index, type, replace = false) { const item = type ? this.find(index, type, replace) : this.find(index); if (!item) { throw new RangeError("Array index is out of bounds"); } return item; } push(...items) { this.modify(); for (const item of items) { if (typeOf(item, ObjectTypeEnum.Stream)) { item.makeIndirect(); } if (!item.isIndirect()) { item.ownerElement = this; } if (this.documentUpdate && !item.documentUpdate) { item.documentUpdate = this.documentUpdate; } this.items.push(item); } } indexOf(item) { for (let index = 0; index < this.items.length; index++) { const element = this.items[index]; if (item.equal(element) || (typeOf(element, ObjectTypeEnum.IndirectReference) && item.ownerElement?.equal(element))) { return index; } } return -1; } includes(item) { return this.indexOf(item) !== -1; } splice(start, deleteCount) { const res = this.items.splice(start, deleteCount); if (res.length) { this.modify(); } return res; } onWritePDF(writer) { writer.writeString("[ "); for (const item of this.items) { if (item.isIndirect()) { const indirect = item.getIndirect(); const PDFIndirectReferenceConstructor = PDFObjectReader.get(ObjectTypeEnum.IndirectReference); const indirectRef = new PDFIndirectReferenceConstructor(indirect.id, indirect.generation); indirectRef.writePDF(writer); } else { item.writePDF(writer); } writer.writeString(" "); } writer.writeString("]"); } onFromPDF(reader) { if (this.items.length) { this.items = []; } if (reader.readByte() !== leftSquareBracketChar) { throw new BadCharError(reader.position - 1); } while (true) { PDFObjectReader.skip(reader); if (reader.view[reader.position] === rightSquareBracketChar) { reader.readByte(); break; } const value = PDFObjectReader.read(reader, this.documentUpdate, this); this.adoptChild(value); this.items.push(value); } } onCopy(copy) { super.onCopy(copy); const items = []; for (const item of this.items) { items.push(item.copy()); } copy.items = items; } toString() { return `[ ${this.items .map((o) => { if (o.isIndirect()) { const ref = o.getIndirect(); return `${ref.id} ${ref.generation} R`; } return o.toString(); }) .join(", ")} ]`; } onEqual(target) { if (target instanceof PDFArray && target.items.length === this.items.length) { for (let i = 0; i < target.length; i++) { const item = target.items[i]; if (this.items[i].equal(item)) { continue; } return false; } return true; } return false; } clear() { this.items = []; return this.modify(); } } const dictionaryLeftChars = new Uint8Array([ CharSet.lessThanChar, CharSet.lessThanChar ]); const dictionaryRightChars = new Uint8Array([ CharSet.greaterThanChar, CharSet.greaterThanChar ]); class PDFDictionary extends PDFObject { static { this.NAME = ObjectTypeEnum.Dictionary; } static { this.FORMAT_SPACE = " "; } static getName(name) { return typeof name === "string" ? name : name.text; } constructor(params) { super(); this.items = new Map(); if (params) { if (params instanceof PDFDictionary) { this.items = params.items; this.ownerElement = params.ownerElement; this.view = params.view; this.documentUpdate = params.documentUpdate; } else { for (const [key, value] of params) { this.items.set(PDFDictionary.getName(key), value); } } } } onWritePDF(writer) { writer.write(dictionaryLeftChars); writer.writeLine(); for (const [key, item] of this.items) { new PDFName(key).writePDF(writer); writer.writeByte(CharSet.whiteSpaceChar); if (item.isIndirect()) { const indirect = item.getIndirect(); const indirectRef = new PDFIndirectReference(indirect.id, indirect.generation); indirectRef.writePDF(writer); } else { item.writePDF(writer); } writer.writeLine(); } writer.write(dictionaryRightChars); } onFromPDF(reader) { if (!dictionaryLeftChars.every((c) => c === reader.readByte())) { throw new BadCharError(reader.position - 1); } this.items.clear(); while (true) { PDFObjectReader.skip(reader); if (dictionaryRightChars.every((c, i) => c === reader.view[reader.position + i])) { reader.read(2); break; } const key = PDFObjectReader.read(reader, this.documentUpdate, this); this.adoptChild(key); if (key instanceof PDFNull) { PDFObjectReader.skip(reader); if (!dictionaryRightChars.every((c, i) => c === reader.view[reader.position + i])) { throw new ParsingError("Must be '>>' after the 'null' value", reader.position); } reader.read(2); break; } if (!(key instanceof PDFName)) { throw new ParsingError(`Dictionary key at position ${reader.view.byteOffset} must be type of Name`, reader.view.byteOffset); } const value = PDFObjectReader.read(reader, this.documentUpdate, this); this.adoptChild(value); this.items.set(PDFDictionary.getName(key), value); } } get size() { return this.items.size; } get(name, type, replace = false) { const pdfName = PDFDictionary.getName(name); let res = this.items.get(PDFDictionary.getName(pdfName)); if (!res) { throw new Error(`Cannot get PDF Dictionary value by name '${pdfName.toString()}'`); } if (res instanceof PDFIndirectReference) { res = res.getValue(); } else if (!res.ownerElement) { res.ownerElement = this; } if (type && res instanceof PDFNull) { const newRes = type.create(this.documentUpdate); if (newRes.isIndirect()) { newRes.makeIndirect(); } res = newRes; this.set(name, res); } let resType; try { resType = PDFTypeConverter.convert(res, type, replace); } catch { throw new Error(`Cannot convert PDF Dictionary value by name '${pdfName.toString()}' to type '${type.name}'`); } if (replace && !resType.isIndirect) { this.set(name, resType); } return resType; } has(name) { return this.items.has(PDFDictionary.getName(name)); } set(name, value) { this.modify(); if (value instanceof PDFObject && value.constructor.NAME === ObjectTypeEnum.Stream) { value.makeIndirect(); } if (!value.isIndirect()) { value.ownerElement = this; } if (this.documentUpdate && !value.documentUpdate) { value.documentUpdate = this.documentUpdate; } this.items.set(PDFDictionary.getName(name), value); return this; } delete(name) { this.modify(); return this.items.delete(PDFDictionary.getName(name)); } clear() { this.items.clear(); this.onCreate(); } onCopy(copy) { super.onCopy(copy); const items = new Map(); for (const [key, value] of this.items) { items.set(key, value.copy()); } copy.items = items; } toString(depth = 0) { const records = []; const space = PDFDictionary.FORMAT_SPACE.repeat(depth); for (const [key, value] of this.items) { const padding = space + PDFDictionary.FORMAT_SPACE; const keyString = key.toString(); let valueString = ""; if (value.isIndirect()) { const ref = value.getIndirect(); valueString = `${ref.id} ${ref.generation} R`; } else { valueString = value instanceof PDFDictionary ? value.toString(depth + 1) : value.toString(); } records.push(`${padding}/${keyString} ${valueString}`); } return `<<\n${records.join("\n")}\n${space}>>`; } onEqual(target) { if (target instanceof PDFDictionary && target.items.size === this.items.size) { for (const [key, item] of target.items) { if (this.items.has(key) && this.items.get(key)?.equal(item)) { continue; } return false; } return true; } return false; } to(type, replace = false) { return PDFTypeConverter.convert(this, type, replace); } } class Maybe { static { this.DEFAULT_VIEW = new Uint8Array(0); } constructor(parent, name, indirect, _type) { this.parent = parent; this.name = name; this.indirect = indirect; this._type = _type; } get(required = false, compressed) { if (!this.parent.has(this.name) || this.parent.get(this.name) instanceof PDFNull) { if (required) { throw new Error(`Cannot get required field '${this.name}'. Field is empty.`); } if (!this.parent.documentUpdate) { throw new Error("Parent object doesn't assigned to PDF document update."); } const value = this._type.create(this.parent.documentUpdate); if (this.indirect) { value.makeIndirect(compressed); } this.parent .modify() .set(this.name, value).view = Maybe.DEFAULT_VIEW; return value; } return this.parent.get(this.name, this._type); } has() { return this.parent.has(this.name); } set(value) { const parent = this.parent.modify(); parent.view = Maybe.DEFAULT_VIEW; if (value === undefined || value === null) { parent.delete(this.name); } else { if (this.indirect) { value.makeIndirect(); } parent.set(this.name, value); } } } const cache = new WeakMap(); function PDFDictionaryField(parameters) { return (target, propertyKey) => { if ("type" in parameters && !parameters.type) { throw new Error(`Class not loaded for ${parameters.name}`); } if (parameters.maybe && !parameters.type) { throw new Error("Parameter 'maybe' shall be used with parameter 'type'."); } Object.defineProperty(target, propertyKey, { enumerable: false, get: function () { let cachedObject = cache.get(this); if (!cachedObject) { cachedObject = new Map(); cache.set(this, cachedObject); } if (parameters.cache && cachedObject.has(propertyKey)) { return cachedObject.get(propertyKey); } if (parameters.maybe) { const type = parameters.type; const maybe = new Maybe(this, parameters.name, !!parameters.indirect, type); return maybe; } else { if (this.has(parameters.name)) { const value = parameters.type ? this.get(parameters.name, parameters.type) : this.get(parameters.name); const res = parameters.get ? parameters.get.call(this, value) : value; if (parameters.cache) { cachedObject.set(propertyKey, res); } return res; } else if (!(parameters.optional || parameters.defaultValue !== undefined)) { throw new Error(`Cannot get required filed '${parameters.name}' from the PDF Dictionary`); } } return parameters.defaultValue ?? null; }, set: function (value) { if (value === undefined || value === null) { this.delete(parameters.name); } else { const result = parameters.set