diff --git a/src/core/chunked_stream.js b/src/core/chunked_stream.js index 561e70d57..940c419a0 100644 --- a/src/core/chunked_stream.js +++ b/src/core/chunked_stream.js @@ -13,12 +13,8 @@ * limitations under the License. */ -import { - arraysToBytes, - assert, - createPromiseCapability, -} from "../shared/util.js"; -import { MissingDataException } from "./core_utils.js"; +import { arrayBuffersToBytes, MissingDataException } from "./core_utils.js"; +import { assert, createPromiseCapability } from "../shared/util.js"; import { Stream } from "./stream.js"; class ChunkedStream extends Stream { @@ -294,7 +290,7 @@ class ChunkedStreamManager { const readChunk = ({ value, done }) => { try { if (done) { - const chunkData = arraysToBytes(chunks); + const chunkData = arrayBuffersToBytes(chunks); chunks = null; resolve(chunkData); return; diff --git a/src/core/core_utils.js b/src/core/core_utils.js index af1d01246..f4541dabe 100644 --- a/src/core/core_utils.js +++ b/src/core/core_utils.js @@ -80,6 +80,44 @@ class XRefParseException extends BaseException { } } +/** + * Combines multiple ArrayBuffers into a single Uint8Array. + * @param {Array} arr - An array of ArrayBuffers. + * @returns {Uint8Array} + */ +function arrayBuffersToBytes(arr) { + if ( + typeof PDFJSDev === "undefined" || + PDFJSDev.test("!PRODUCTION || TESTING") + ) { + for (const item of arr) { + assert( + item instanceof ArrayBuffer, + "arrayBuffersToBytes - expected an ArrayBuffer." + ); + } + } + const length = arr.length; + if (length === 0) { + return new Uint8Array(0); + } + if (length === 1) { + return new Uint8Array(arr[0]); + } + let dataLength = 0; + for (let i = 0; i < length; i++) { + dataLength += arr[i].byteLength; + } + const data = new Uint8Array(dataLength); + let pos = 0; + for (let i = 0; i < length; i++) { + const item = new Uint8Array(arr[i]); + data.set(item, pos); + pos += item.byteLength; + } + return data; +} + /** * Get the value of an inheritable property. * @@ -579,6 +617,7 @@ function getRotationMatrix(rotation, width, height) { } export { + arrayBuffersToBytes, collectActions, encodeToXmlString, escapePDFName, diff --git a/src/core/worker.js b/src/core/worker.js index dd68b5f3c..484759f4e 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -15,7 +15,6 @@ import { AbortException, - arraysToBytes, assert, createPromiseCapability, getVerbosityLevel, @@ -30,8 +29,12 @@ import { VerbosityLevel, warn, } from "../shared/util.js"; +import { + arrayBuffersToBytes, + getNewAnnotationsMap, + XRefParseException, +} from "./core_utils.js"; import { Dict, Ref } from "./primitives.js"; -import { getNewAnnotationsMap, XRefParseException } from "./core_utils.js"; import { LocalPdfManager, NetworkPdfManager } from "./pdf_manager.js"; import { clearGlobalCaches } from "./cleanup_helper.js"; import { incrementalUpdate } from "./writer.js"; @@ -93,7 +96,7 @@ class WorkerMessageHandler { let pdfManager; let terminated = false; let cancelXHRs = null; - const WorkerTasks = []; + const WorkerTasks = new Set(); const verbosity = getVerbosityLevel(); const { docId, apiVersion } = docParams; @@ -151,13 +154,12 @@ class WorkerMessageHandler { } function startWorkerTask(task) { - WorkerTasks.push(task); + WorkerTasks.add(task); } function finishWorkerTask(task) { task.finish(); - const i = WorkerTasks.indexOf(task); - WorkerTasks.splice(i, 1); + WorkerTasks.delete(task); } async function loadDocument(recoveryMode) { @@ -277,7 +279,7 @@ class WorkerMessageHandler { let loaded = 0; const flushChunks = function () { - const pdfFile = arraysToBytes(cachedChunks); + const pdfFile = arrayBuffersToBytes(cachedChunks); if (length && pdfFile.length !== length) { warn("reported HTTP length is different from actual"); } diff --git a/src/shared/util.js b/src/shared/util.js index 770b42eb0..8dca9842d 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -597,56 +597,6 @@ function stringToBytes(str) { return bytes; } -/** - * Gets length of the array (Array, Uint8Array, or string) in bytes. - * @param {Array|Uint8Array|string} arr - * @returns {number} - */ -// eslint-disable-next-line consistent-return -function arrayByteLength(arr) { - if (arr.length !== undefined) { - return arr.length; - } - if (arr.byteLength !== undefined) { - return arr.byteLength; - } - unreachable("Invalid argument for arrayByteLength"); -} - -/** - * Combines array items (arrays) into single Uint8Array object. - * @param {Array|Uint8Array|string>} arr - the array of the arrays - * (Array, Uint8Array, or string). - * @returns {Uint8Array} - */ -function arraysToBytes(arr) { - const length = arr.length; - // Shortcut: if first and only item is Uint8Array, return it. - if (length === 1 && arr[0] instanceof Uint8Array) { - return arr[0]; - } - let resultLength = 0; - for (let i = 0; i < length; i++) { - resultLength += arrayByteLength(arr[i]); - } - let pos = 0; - const data = new Uint8Array(resultLength); - for (let i = 0; i < length; i++) { - let item = arr[i]; - if (!(item instanceof Uint8Array)) { - if (typeof item === "string") { - item = stringToBytes(item); - } else { - item = new Uint8Array(item); - } - } - const itemLength = item.byteLength; - data.set(item, pos); - pos += itemLength; - } - return data; -} - function string32(value) { if ( typeof PDFJSDev === "undefined" || @@ -1115,7 +1065,6 @@ export { AnnotationReviewState, AnnotationStateModelType, AnnotationType, - arraysToBytes, assert, BaseException, BASELINE_FACTOR, diff --git a/test/unit/core_utils_spec.js b/test/unit/core_utils_spec.js index f8fb8da16..bc7266ba1 100644 --- a/test/unit/core_utils_spec.js +++ b/test/unit/core_utils_spec.js @@ -13,8 +13,8 @@ * limitations under the License. */ -import { Dict, Ref } from "../../src/core/primitives.js"; import { + arrayBuffersToBytes, encodeToXmlString, escapePDFName, escapeString, @@ -30,9 +30,36 @@ import { toRomanNumerals, validateCSSFont, } from "../../src/core/core_utils.js"; +import { Dict, Ref } from "../../src/core/primitives.js"; import { XRefMock } from "./test_utils.js"; describe("core_utils", function () { + describe("arrayBuffersToBytes", function () { + it("handles zero ArrayBuffers", function () { + const bytes = arrayBuffersToBytes([]); + + expect(bytes).toEqual(new Uint8Array(0)); + }); + + it("handles one ArrayBuffer", function () { + const buffer = new Uint8Array([1, 2, 3]).buffer; + const bytes = arrayBuffersToBytes([buffer]); + + expect(bytes).toEqual(new Uint8Array([1, 2, 3])); + // Ensure that the fast-path works correctly. + expect(bytes.buffer).toBe(buffer); + }); + + it("handles multiple ArrayBuffers", function () { + const buffer1 = new Uint8Array([1, 2, 3]).buffer, + buffer2 = new Uint8Array(0).buffer, + buffer3 = new Uint8Array([4, 5]).buffer; + const bytes = arrayBuffersToBytes([buffer1, buffer2, buffer3]); + + expect(bytes).toEqual(new Uint8Array([1, 2, 3, 4, 5])); + }); + }); + describe("getInheritableProperty", function () { it("handles non-dictionary arguments", function () { expect(getInheritableProperty({ dict: null, key: "foo" })).toEqual(