: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
function fetchDest(dest) {
if (dest instanceof Dict) {
return isValidExplicitDest(dest) ? dest : null;
function fetchRemoteDest(action) {
let dest = action.get("D");
if (dest instanceof Name) {
if (typeof dest === "string") {
return stringToPDFString(dest);
} else if (isValidExplicitDest(dest)) {
return JSON.stringify(dest);
constructor(pdfManager, xref) {
this.pdfManager = pdfManager;
this._catDict = xref.getCatalogObj();
if (!(this._catDict instanceof Dict)) {
throw new FormatError("Catalog object is not a dictionary.");
this._actualNumPages = null;
this.fontCache = new RefSetCache();
this.builtInCMapCache = new Map();
this.standardFontDataCache = new Map();
this.globalImageCache = new GlobalImageCache();
this.pageKidsCountCache = new RefSetCache();
this.pageIndexCache = new RefSetCache();
this.nonBlendModesSet = new RefSet();
this.systemFontCache = new Map();
return this._catDict.clone();
const version = this._catDict.get("Version");
if (version instanceof Name) {
if (PDF_VERSION_REGEXP.test(version.name)) {
return shadow(this, "version", version.name);
warn(`Invalid PDF catalog version: ${version.name}`);
return shadow(this, "version", null);
const lang = this._catDict.get("Lang");
return shadow(this, "lang", lang && typeof lang === "string" ? stringToPDFString(lang) : null);
const needsRendering = this._catDict.get("NeedsRendering");
return shadow(this, "needsRendering", typeof needsRendering === "boolean" ? needsRendering : false);
const obj = this._catDict.get("Collection");
if (obj instanceof Dict && obj.size > 0) {
if (ex instanceof MissingDataException) {
info("Cannot fetch Collection entry; assuming no collection is present.");
return shadow(this, "collection", collection);
const obj = this._catDict.get("AcroForm");
if (obj instanceof Dict && obj.size > 0) {
if (ex instanceof MissingDataException) {
info("Cannot fetch AcroForm entry; assuming no forms are present.");
return shadow(this, "acroForm", acroForm);
const value = this._catDict.getRaw("AcroForm");
return shadow(this, "acroFormRef", value instanceof Ref ? value : null);
const streamRef = this._catDict.getRaw("Metadata");
if (!(streamRef instanceof Ref)) {
return shadow(this, "metadata", null);
const stream = this.xref.fetch(streamRef, !this.xref.encrypt?.encryptMetadata);
if (stream instanceof BaseStream && stream.dict instanceof Dict) {
const type = stream.dict.get("Type");
const subtype = stream.dict.get("Subtype");
if (isName(type, "Metadata") && isName(subtype, "XML")) {
const data = stringToUTF8String(stream.getString());
metadata = new MetadataParser(data).serializable;
if (ex instanceof MissingDataException) {
info(`Skipping invalid Metadata: "${ex}".`);
return shadow(this, "metadata", metadata);
markInfo = this._readMarkInfo();
if (ex instanceof MissingDataException) {
warn("Unable to read mark info.");
return shadow(this, "markInfo", markInfo);
const obj = this._catDict.get("MarkInfo");
if (!(obj instanceof Dict)) {
for (const key in markInfo) {
const value = obj.get(key);
if (typeof value === "boolean") {
structTree = this._readStructTreeRoot();
if (ex instanceof MissingDataException) {
warn("Unable read to structTreeRoot info.");
return shadow(this, "structTreeRoot", structTree);
const rawObj = this._catDict.getRaw("StructTreeRoot");
const obj = this.xref.fetchIfRef(rawObj);
if (!(obj instanceof Dict)) {
const root = new StructTreeRoot(obj, rawObj);
get toplevelPagesDict() {
const pagesObj = this._catDict.get("Pages");
if (!(pagesObj instanceof Dict)) {
throw new FormatError("Invalid top-level pages dictionary.");
return shadow(this, "toplevelPagesDict", pagesObj);
obj = this._readDocumentOutline();
if (ex instanceof MissingDataException) {
warn("Unable to read document outline.");
return shadow(this, "documentOutline", obj);
let obj = this._catDict.get("Outlines");
if (!(obj instanceof Dict)) {
obj = obj.getRaw("First");
if (!(obj instanceof Ref)) {
const processed = new RefSet();
blackColor = new Uint8ClampedArray(3);
while (queue.length > 0) {
const outlineDict = xref.fetchIfRef(i.obj);
if (outlineDict === null) {
if (!outlineDict.has("Title")) {
warn("Invalid outline item encountered.");
Catalog.parseDestDictionary({
docBaseUrl: this.baseUrl,
docAttachments: this.attachments
const title = outlineDict.get("Title");
const flags = outlineDict.get("F") || 0;
const color = outlineDict.getArray("C");
const count = outlineDict.get("Count");
let rgbColor = blackColor;
if (isNumberArray(color, 3) && (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) {
rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
attachment: data.attachment,
unsafeUrl: data.unsafeUrl,
newWindow: data.newWindow,
setOCGState: data.setOCGState,
title: typeof title === "string" ? stringToPDFString(title) : "",
count: Number.isInteger(count) ? count : undefined,
i.parent.items.push(outlineItem);
obj = outlineDict.getRaw("First");
if (obj instanceof Ref && !processed.has(obj)) {
obj = outlineDict.getRaw("Next");
if (obj instanceof Ref && !processed.has(obj)) {
return root.items.length > 0 ? root.items : null;
permissions = this._readPermissions();
if (ex instanceof MissingDataException) {
warn("Unable to read permissions.");
return shadow(this, "permissions", permissions);
const encrypt = this.xref.trailer.get("Encrypt");
if (!(encrypt instanceof Dict)) {
let flags = encrypt.get("P");
if (typeof flags !== "number") {
for (const key in PermissionFlag) {
const value = PermissionFlag[key];
get optionalContentConfig() {
const properties = this._catDict.get("OCProperties");
return shadow(this, "optionalContentConfig", null);
const defaultConfig = properties.get("D");
return shadow(this, "optionalContentConfig", null);
const groupsData = properties.get("OCGs");
if (!Array.isArray(groupsData)) {
return shadow(this, "optionalContentConfig", null);
const groupRefs = new RefSet();
for (const groupRef of groupsData) {
if (!(groupRef instanceof Ref) || groupRefs.has(groupRef)) {
groups.push(this.#readOptionalContentGroup(groupRef));
config = this.#readOptionalContentConfig(defaultConfig, groupRefs);
if (ex instanceof MissingDataException) {
warn(`Unable to read optional content config: ${ex}`);
return shadow(this, "optionalContentConfig", config);
#readOptionalContentGroup(groupRef) {
const group = this.xref.fetch(groupRef);
const name = group.get("Name");
if (typeof name === "string") {
obj.name = stringToPDFString(name);
let intent = group.getArray("Intent");
if (!Array.isArray(intent)) {
if (intent.every(i => i instanceof Name)) {
obj.intent = intent.map(i => i.name);
const usage = group.get("Usage");
if (!(usage instanceof Dict)) {
const usageObj = obj.usage;
const print = usage.get("Print");
if (print instanceof Dict) {
const printState = print.get("PrintState");
if (printState instanceof Name) {
switch (printState.name) {
printState: printState.name
const view = usage.get("View");
if (view instanceof Dict) {
const viewState = view.get("ViewState");
if (viewState instanceof Name) {
switch (viewState.name) {
viewState: viewState.name
#readOptionalContentConfig(config, contentGroupRefs) {
function parseOnOff(refs) {
if (Array.isArray(refs)) {
for (const value of refs) {
if (!(value instanceof Ref)) {
if (contentGroupRefs.has(value)) {
onParsed.push(value.toString());
function parseOrder(refs, nestedLevels = 0) {
if (!Array.isArray(refs)) {
for (const value of refs) {
if (value instanceof Ref && contentGroupRefs.has(value)) {
parsedOrderRefs.put(value);
order.push(value.toString());
const nestedOrder = parseNestedOrder(value, nestedLevels);
for (const groupRef of contentGroupRefs) {
if (parsedOrderRefs.has(groupRef)) {
hiddenGroups.push(groupRef.toString());
if (hiddenGroups.length) {
function parseNestedOrder(ref, nestedLevels) {
if (++nestedLevels > MAX_NESTED_LEVELS) {
warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");
const value = xref.fetchIfRef(ref);
if (!Array.isArray(value)) {
const nestedName = xref.fetchIfRef(value[0]);
if (typeof nestedName !== "string") {
const nestedOrder = parseOrder(value.slice(1), nestedLevels);
if (!nestedOrder || !nestedOrder.length) {
name: stringToPDFString(nestedName),
parsedOrderRefs = new RefSet(),
name: typeof config.get("Name") === "string" ? stringToPDFString(config.get("Name")) : null,
creator: typeof config.get("Creator") === "string" ? stringToPDFString(config.get("Creator")) : null,
baseState: config.get("BaseState") instanceof Name ? config.get("BaseState").name : null,
on: parseOnOff(config.get("ON")),
off: parseOnOff(config.get("OFF")),
order: parseOrder(config.get("Order")),
setActualNumPages(num = null) {
this._actualNumPages = num;
get hasActualNumPages() {
return this._actualNumPages !== null;
const obj = this.toplevelPagesDict.get("Count");
if (!Number.isInteger(obj)) {
throw new FormatError("Page count in top-level pages dictionary is not an integer.");
return shadow(this, "_pagesCount", obj);
return this.hasActualNumPages ? this._actualNumPages : this._pagesCount;
const obj = this._readDests(),
dests = Object.create(null);
if (obj instanceof NameTree) {