@peculiar/pdf-core
Version:
Core functionality library for PDF document processing and manipulation
1,515 lines (1,483 loc) • 354 kB
JavaScript
'use strict';
var pvtsutils = require('pvtsutils');
var tslib = require('tslib');
var pako = require('pako');
var events = require('events');
var pkijs = require('pkijs');
var x509 = require('@peculiar/x509');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var pako__namespace = /*#__PURE__*/_interopNamespaceDefault(pako);
var pkijs__namespace = /*#__PURE__*/_interopNamespaceDefault(pkijs);
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(pvtsutils.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 = pvtsutils.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 = pvtsutils.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 pvtsutils.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 = pvtsutils.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(pvtsutils.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 pvtsutils.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(pvtsutils.Convert.FromBinary(data)))
: new ViewReader(pvtsutils.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 (pvtsutils.BufferSourceConverter.isBufferSource(param)) {
this.text = pvtsutils.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 = pvtsutils.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 pvtsutils.Convert.ToUtf16String(pvtsutils.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}${pvtsutils.Convert.ToBinary(pvtsutils.Convert.FromUtf8String(text, "utf16be"))}`;
}
};
class PDFTextString extends PDFString {
async encryptAsync() {
const parent = this.findIndirect(true);
const textView = pvtsutils.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 = pvtsutils.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(pvtsutils.Convert.ToBinary(decryptedText));
}
this.encrypted = false;
}
return this.text;
}
toArrayBuffer() {
return pvtsutils.Convert.FromBinary(this.text);
}
toUint8Array() {
return pvtsutils.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(`<${pvtsutils.Convert.ToHex(pvtsutils.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 = pvtsutils.Convert.ToBinary(new Uint8Array(hexStrings));
const hexText = hexString + (hexString.length % 2 ? "0" : "");
const hexView = pvtsutils.Convert.FromHex(hexText);
this.text = TextEncoder$1.from(pvtsutils.Convert.ToBinary(hexView));
reader.readByte();
}
get data() {
return new Uint8Array(pvtsutils.Convert.FromBinary(this.text));
}
toString() {
return `<${pvtsutils.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(pvtsutils.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 = pvtsutils.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 = pvtsutils.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