From 24e5ecdf76458480e25876b82cf7a5d4d3c9027f Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 13 Apr 2021 18:26:02 +0200 Subject: [PATCH] Move `NameTree`/`NumberTree` from `src/core/obj.js` and into its own file The size of the `src/core/obj.js` file has increased slowly over the years, and it also contains a fair amount of *distinct* functionality. In order to improve readability and make it easier to navigate through the code, this patch moves `NameTree`/`NumberTree` into its own file. --- src/core/name_number_tree.js | 169 +++++++++++++++++++++++++++++++++++ src/core/obj.js | 154 +------------------------------ src/core/struct_tree.js | 2 +- 3 files changed, 172 insertions(+), 153 deletions(-) create mode 100644 src/core/name_number_tree.js diff --git a/src/core/name_number_tree.js b/src/core/name_number_tree.js new file mode 100644 index 000000000..19b90bf51 --- /dev/null +++ b/src/core/name_number_tree.js @@ -0,0 +1,169 @@ +/* Copyright 2021 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FormatError, info, unreachable, warn } from "../shared/util.js"; +import { isDict, RefSet } from "./primitives.js"; + +/** + * A NameTree/NumberTree is like a Dict but has some advantageous properties, + * see the specification (7.9.6 and 7.9.7) for additional details. + * TODO: implement all the Dict functions and make this more efficient. + */ +class NameOrNumberTree { + constructor(root, xref, type) { + if (this.constructor === NameOrNumberTree) { + unreachable("Cannot initialize NameOrNumberTree."); + } + this.root = root; + this.xref = xref; + this._type = type; + } + + getAll() { + const dict = Object.create(null); + if (!this.root) { + return dict; + } + const xref = this.xref; + // Reading Name/Number tree. + const processed = new RefSet(); + processed.put(this.root); + const queue = [this.root]; + while (queue.length > 0) { + const obj = xref.fetchIfRef(queue.shift()); + if (!isDict(obj)) { + continue; + } + if (obj.has("Kids")) { + const kids = obj.get("Kids"); + for (let i = 0, ii = kids.length; i < ii; i++) { + const kid = kids[i]; + if (processed.has(kid)) { + throw new FormatError(`Duplicate entry in "${this._type}" tree.`); + } + queue.push(kid); + processed.put(kid); + } + continue; + } + const entries = obj.get(this._type); + if (Array.isArray(entries)) { + for (let i = 0, ii = entries.length; i < ii; i += 2) { + dict[xref.fetchIfRef(entries[i])] = xref.fetchIfRef(entries[i + 1]); + } + } + } + return dict; + } + + get(key) { + if (!this.root) { + return null; + } + const xref = this.xref; + let kidsOrEntries = xref.fetchIfRef(this.root); + let loopCount = 0; + const MAX_LEVELS = 10; + + // Perform a binary search to quickly find the entry that + // contains the key we are looking for. + while (kidsOrEntries.has("Kids")) { + if (++loopCount > MAX_LEVELS) { + warn(`Search depth limit reached for "${this._type}" tree.`); + return null; + } + + const kids = kidsOrEntries.get("Kids"); + if (!Array.isArray(kids)) { + return null; + } + + let l = 0, + r = kids.length - 1; + while (l <= r) { + const m = (l + r) >> 1; + const kid = xref.fetchIfRef(kids[m]); + const limits = kid.get("Limits"); + + if (key < xref.fetchIfRef(limits[0])) { + r = m - 1; + } else if (key > xref.fetchIfRef(limits[1])) { + l = m + 1; + } else { + kidsOrEntries = xref.fetchIfRef(kids[m]); + break; + } + } + if (l > r) { + return null; + } + } + + // If we get here, then we have found the right entry. Now go through the + // entries in the dictionary until we find the key we're looking for. + const entries = kidsOrEntries.get(this._type); + if (Array.isArray(entries)) { + // Perform a binary search to reduce the lookup time. + let l = 0, + r = entries.length - 2; + while (l <= r) { + // Check only even indices (0, 2, 4, ...) because the + // odd indices contain the actual data. + const tmp = (l + r) >> 1, + m = tmp + (tmp & 1); + const currentKey = xref.fetchIfRef(entries[m]); + if (key < currentKey) { + r = m - 2; + } else if (key > currentKey) { + l = m + 2; + } else { + return xref.fetchIfRef(entries[m + 1]); + } + } + + // Fallback to an exhaustive search, in an attempt to handle corrupt + // PDF files where keys are not correctly ordered (fixes issue 10272). + info( + `Falling back to an exhaustive search, for key "${key}", ` + + `in "${this._type}" tree.` + ); + for (let m = 0, mm = entries.length; m < mm; m += 2) { + const currentKey = xref.fetchIfRef(entries[m]); + if (currentKey === key) { + warn( + `The "${key}" key was found at an incorrect, ` + + `i.e. out-of-order, position in "${this._type}" tree.` + ); + return xref.fetchIfRef(entries[m + 1]); + } + } + } + return null; + } +} + +class NameTree extends NameOrNumberTree { + constructor(root, xref) { + super(root, xref, "Names"); + } +} + +class NumberTree extends NameOrNumberTree { + constructor(root, xref) { + super(root, xref, "Nums"); + } +} + +export { NameTree, NumberTree }; diff --git a/src/core/obj.js b/src/core/obj.js index ea9e7d0e3..558f2c248 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -31,7 +31,6 @@ import { shadow, stringToPDFString, stringToUTF8String, - unreachable, warn, } from "../shared/util.js"; import { @@ -56,6 +55,7 @@ import { XRefParseException, } from "./core_utils.js"; import { Lexer, Parser } from "./parser.js"; +import { NameTree, NumberTree } from "./name_number_tree.js"; import { CipherTransformFactory } from "./crypto.js"; import { ColorSpace } from "./colorspace.js"; import { FileSpec } from "./file_spec.js"; @@ -2270,154 +2270,4 @@ var XRef = (function XRefClosure() { return XRef; })(); -/** - * A NameTree/NumberTree is like a Dict but has some advantageous properties, - * see the specification (7.9.6 and 7.9.7) for additional details. - * TODO: implement all the Dict functions and make this more efficient. - */ -class NameOrNumberTree { - constructor(root, xref, type) { - if (this.constructor === NameOrNumberTree) { - unreachable("Cannot initialize NameOrNumberTree."); - } - this.root = root; - this.xref = xref; - this._type = type; - } - - getAll() { - const dict = Object.create(null); - if (!this.root) { - return dict; - } - const xref = this.xref; - // Reading Name/Number tree. - const processed = new RefSet(); - processed.put(this.root); - const queue = [this.root]; - while (queue.length > 0) { - const obj = xref.fetchIfRef(queue.shift()); - if (!isDict(obj)) { - continue; - } - if (obj.has("Kids")) { - const kids = obj.get("Kids"); - for (let i = 0, ii = kids.length; i < ii; i++) { - const kid = kids[i]; - if (processed.has(kid)) { - throw new FormatError(`Duplicate entry in "${this._type}" tree.`); - } - queue.push(kid); - processed.put(kid); - } - continue; - } - const entries = obj.get(this._type); - if (Array.isArray(entries)) { - for (let i = 0, ii = entries.length; i < ii; i += 2) { - dict[xref.fetchIfRef(entries[i])] = xref.fetchIfRef(entries[i + 1]); - } - } - } - return dict; - } - - get(key) { - if (!this.root) { - return null; - } - const xref = this.xref; - let kidsOrEntries = xref.fetchIfRef(this.root); - let loopCount = 0; - const MAX_LEVELS = 10; - - // Perform a binary search to quickly find the entry that - // contains the key we are looking for. - while (kidsOrEntries.has("Kids")) { - if (++loopCount > MAX_LEVELS) { - warn(`Search depth limit reached for "${this._type}" tree.`); - return null; - } - - const kids = kidsOrEntries.get("Kids"); - if (!Array.isArray(kids)) { - return null; - } - - let l = 0, - r = kids.length - 1; - while (l <= r) { - const m = (l + r) >> 1; - const kid = xref.fetchIfRef(kids[m]); - const limits = kid.get("Limits"); - - if (key < xref.fetchIfRef(limits[0])) { - r = m - 1; - } else if (key > xref.fetchIfRef(limits[1])) { - l = m + 1; - } else { - kidsOrEntries = xref.fetchIfRef(kids[m]); - break; - } - } - if (l > r) { - return null; - } - } - - // If we get here, then we have found the right entry. Now go through the - // entries in the dictionary until we find the key we're looking for. - const entries = kidsOrEntries.get(this._type); - if (Array.isArray(entries)) { - // Perform a binary search to reduce the lookup time. - let l = 0, - r = entries.length - 2; - while (l <= r) { - // Check only even indices (0, 2, 4, ...) because the - // odd indices contain the actual data. - const tmp = (l + r) >> 1, - m = tmp + (tmp & 1); - const currentKey = xref.fetchIfRef(entries[m]); - if (key < currentKey) { - r = m - 2; - } else if (key > currentKey) { - l = m + 2; - } else { - return xref.fetchIfRef(entries[m + 1]); - } - } - - // Fallback to an exhaustive search, in an attempt to handle corrupt - // PDF files where keys are not correctly ordered (fixes issue 10272). - info( - `Falling back to an exhaustive search, for key "${key}", ` + - `in "${this._type}" tree.` - ); - for (let m = 0, mm = entries.length; m < mm; m += 2) { - const currentKey = xref.fetchIfRef(entries[m]); - if (currentKey === key) { - warn( - `The "${key}" key was found at an incorrect, ` + - `i.e. out-of-order, position in "${this._type}" tree.` - ); - return xref.fetchIfRef(entries[m + 1]); - } - } - } - return null; - } -} - -class NameTree extends NameOrNumberTree { - constructor(root, xref) { - super(root, xref, "Names"); - } -} - -class NumberTree extends NameOrNumberTree { - constructor(root, xref) { - super(root, xref, "Nums"); - } -} - -export { Catalog, NumberTree, XRef }; +export { Catalog, XRef }; diff --git a/src/core/struct_tree.js b/src/core/struct_tree.js index 41587d45c..1d2fabd27 100644 --- a/src/core/struct_tree.js +++ b/src/core/struct_tree.js @@ -15,7 +15,7 @@ import { isDict, isName, isRef } from "./primitives.js"; import { isString, stringToPDFString, warn } from "../shared/util.js"; -import { NumberTree } from "./obj.js"; +import { NumberTree } from "./name_number_tree.js"; const MAX_DEPTH = 40;