diff --git a/src/core/ascii_85_stream.js b/src/core/ascii_85_stream.js new file mode 100644 index 000000000..a006d40e2 --- /dev/null +++ b/src/core/ascii_85_stream.js @@ -0,0 +1,98 @@ +/* Copyright 2012 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 { DecodeStream } from "./decode_stream.js"; +import { isWhiteSpace } from "./core_utils.js"; + +class Ascii85Stream extends DecodeStream { + constructor(str, maybeLength) { + // Most streams increase in size when decoded, but Ascii85 streams + // typically shrink by ~20%. + if (maybeLength) { + maybeLength = 0.8 * maybeLength; + } + super(maybeLength); + + this.str = str; + this.dict = str.dict; + this.input = new Uint8Array(5); + } + + readBlock() { + const TILDA_CHAR = 0x7e; // '~' + const Z_LOWER_CHAR = 0x7a; // 'z' + const EOF = -1; + + const str = this.str; + + let c = str.getByte(); + while (isWhiteSpace(c)) { + c = str.getByte(); + } + + if (c === EOF || c === TILDA_CHAR) { + this.eof = true; + return; + } + + const bufferLength = this.bufferLength; + let buffer, i; + + // special code for z + if (c === Z_LOWER_CHAR) { + buffer = this.ensureBuffer(bufferLength + 4); + for (i = 0; i < 4; ++i) { + buffer[bufferLength + i] = 0; + } + this.bufferLength += 4; + } else { + const input = this.input; + input[0] = c; + for (i = 1; i < 5; ++i) { + c = str.getByte(); + while (isWhiteSpace(c)) { + c = str.getByte(); + } + + input[i] = c; + + if (c === EOF || c === TILDA_CHAR) { + break; + } + } + buffer = this.ensureBuffer(bufferLength + i - 1); + this.bufferLength += i - 1; + + // partial ending; + if (i < 5) { + for (; i < 5; ++i) { + input[i] = 0x21 + 84; + } + this.eof = true; + } + let t = 0; + for (i = 0; i < 5; ++i) { + t = t * 85 + (input[i] - 0x21); + } + + for (i = 3; i >= 0; --i) { + buffer[bufferLength + i] = t & 0xff; + t >>= 8; + } + } + } +} + +export { Ascii85Stream }; diff --git a/src/core/ascii_hex_stream.js b/src/core/ascii_hex_stream.js new file mode 100644 index 000000000..0bc1bd15a --- /dev/null +++ b/src/core/ascii_hex_stream.js @@ -0,0 +1,79 @@ +/* Copyright 2012 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 { DecodeStream } from "./decode_stream.js"; + +class AsciiHexStream extends DecodeStream { + constructor(str, maybeLength) { + // Most streams increase in size when decoded, but AsciiHex streams shrink + // by 50%. + if (maybeLength) { + maybeLength = 0.5 * maybeLength; + } + super(maybeLength); + + this.str = str; + this.dict = str.dict; + + this.firstDigit = -1; + } + + readBlock() { + const UPSTREAM_BLOCK_SIZE = 8000; + const bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE); + if (!bytes.length) { + this.eof = true; + return; + } + + const maxDecodeLength = (bytes.length + 1) >> 1; + const buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength); + let bufferLength = this.bufferLength; + + let firstDigit = this.firstDigit; + for (const ch of bytes) { + let digit; + if (ch >= /* '0' = */ 0x30 && ch <= /* '9' = */ 0x39) { + digit = ch & 0x0f; + } else if ( + (ch >= /* 'A' = */ 0x41 && ch <= /* 'Z' = */ 0x46) || + (ch >= /* 'a' = */ 0x61 && ch <= /* 'z' = */ 0x66) + ) { + digit = (ch & 0x0f) + 9; + } else if (ch === /* '>' = */ 0x3e) { + this.eof = true; + break; + } else { + // Probably whitespace, ignoring. + continue; + } + if (firstDigit < 0) { + firstDigit = digit; + } else { + buffer[bufferLength++] = (firstDigit << 4) | digit; + firstDigit = -1; + } + } + if (firstDigit >= 0 && this.eof) { + // incomplete byte + buffer[bufferLength++] = firstDigit << 4; + firstDigit = -1; + } + this.firstDigit = firstDigit; + this.bufferLength = bufferLength; + } +} + +export { AsciiHexStream }; diff --git a/src/core/base_stream.js b/src/core/base_stream.js new file mode 100644 index 000000000..86f9eb769 --- /dev/null +++ b/src/core/base_stream.js @@ -0,0 +1,106 @@ +/* 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 { shadow, unreachable } from "../shared/util.js"; + +class BaseStream { + constructor() { + if (this.constructor === BaseStream) { + unreachable("Cannot initialize BaseStream."); + } + } + + // eslint-disable-next-line getter-return + get length() { + unreachable("Abstract getter `length` accessed"); + } + + // eslint-disable-next-line getter-return + get isEmpty() { + unreachable("Abstract getter `isEmpty` accessed"); + } + + get isDataLoaded() { + return shadow(this, "isDataLoaded", true); + } + + getByte() { + unreachable("Abstract method `getByte` called"); + } + + getBytes(length, forceClamped = false) { + unreachable("Abstract method `getBytes` called"); + } + + peekByte() { + const peekedByte = this.getByte(); + if (peekedByte !== -1) { + this.pos--; + } + return peekedByte; + } + + peekBytes(length, forceClamped = false) { + const bytes = this.getBytes(length, forceClamped); + this.pos -= bytes.length; + return bytes; + } + + getUint16() { + const b0 = this.getByte(); + const b1 = this.getByte(); + if (b0 === -1 || b1 === -1) { + return -1; + } + return (b0 << 8) + b1; + } + + getInt32() { + const b0 = this.getByte(); + const b1 = this.getByte(); + const b2 = this.getByte(); + const b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + } + + getByteRange(begin, end) { + unreachable("Abstract method `getByteRange` called"); + } + + skip(n) { + this.pos += n || 1; + } + + reset() { + unreachable("Abstract method `reset` called"); + } + + moveStart() { + unreachable("Abstract method `moveStart` called"); + } + + makeSubStream(start, length, dict = null) { + unreachable("Abstract method `makeSubStream` called"); + } + + /** + * @returns {Array | null} + */ + getBaseStreams() { + return null; + } +} + +export { BaseStream }; diff --git a/src/core/ccitt_stream.js b/src/core/ccitt_stream.js index 6f597689a..d716b4841 100644 --- a/src/core/ccitt_stream.js +++ b/src/core/ccitt_stream.js @@ -15,7 +15,7 @@ import { Dict, isDict } from "./primitives.js"; import { CCITTFaxDecoder } from "./ccitt.js"; -import { DecodeStream } from "./stream.js"; +import { DecodeStream } from "./decode_stream.js"; class CCITTFaxStream extends DecodeStream { constructor(str, maybeLength, params) { diff --git a/src/core/chunked_stream.js b/src/core/chunked_stream.js index 451984874..5bfe512eb 100644 --- a/src/core/chunked_stream.js +++ b/src/core/chunked_stream.js @@ -19,13 +19,17 @@ import { createPromiseCapability, } from "../shared/util.js"; import { MissingDataException } from "./core_utils.js"; +import { Stream } from "./stream.js"; -class ChunkedStream { +class ChunkedStream extends Stream { constructor(length, chunkSize, manager) { - this.bytes = new Uint8Array(length); - this.start = 0; - this.pos = 0; - this.end = length; + super( + /* arrayBuffer = */ new Uint8Array(length), + /* start = */ 0, + /* length = */ length, + /* dict = */ null + ); + this.chunkSize = chunkSize; this._loadedChunks = new Set(); this.numChunks = Math.ceil(length / chunkSize); @@ -46,15 +50,11 @@ class ChunkedStream { return chunks; } - getBaseStreams() { - return [this]; - } - get numChunksLoaded() { return this._loadedChunks.size; } - allChunksLoaded() { + get isDataLoaded() { return this.numChunksLoaded === this.numChunks; } @@ -150,14 +150,6 @@ class ChunkedStream { return this._loadedChunks.has(chunk); } - get length() { - return this.end - this.start; - } - - get isEmpty() { - return this.length === 0; - } - getByte() { const pos = this.pos; if (pos >= this.end) { @@ -169,24 +161,6 @@ class ChunkedStream { return this.bytes[this.pos++]; } - getUint16() { - const b0 = this.getByte(); - const b1 = this.getByte(); - if (b0 === -1 || b1 === -1) { - return -1; - } - return (b0 << 8) + b1; - } - - getInt32() { - const b0 = this.getByte(); - const b1 = this.getByte(); - const b2 = this.getByte(); - const b3 = this.getByte(); - return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; - } - - // Returns subarray of original buffer, should only be read. getBytes(length, forceClamped = false) { const bytes = this.bytes; const pos = this.pos; @@ -215,20 +189,6 @@ class ChunkedStream { return forceClamped ? new Uint8ClampedArray(subarray) : subarray; } - peekByte() { - const peekedByte = this.getByte(); - if (peekedByte !== -1) { - this.pos--; - } - return peekedByte; - } - - peekBytes(length, forceClamped = false) { - const bytes = this.getBytes(length, forceClamped); - this.pos -= bytes.length; - return bytes; - } - getByteRange(begin, end) { if (begin < 0) { begin = 0; @@ -242,22 +202,7 @@ class ChunkedStream { return this.bytes.subarray(begin, end); } - skip(n) { - if (!n) { - n = 1; - } - this.pos += n; - } - - reset() { - this.pos = this.start; - } - - moveStart() { - this.start = this.pos; - } - - makeSubStream(start, length, dict) { + makeSubStream(start, length, dict = null) { if (length) { if (start + length > this.progressiveDataLength) { this.ensureRange(start, start + length); @@ -291,12 +236,15 @@ class ChunkedStream { } return missingChunks; }; - ChunkedStreamSubstream.prototype.allChunksLoaded = function () { - if (this.numChunksLoaded === this.numChunks) { - return true; - } - return this.getMissingChunks().length === 0; - }; + Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", { + get() { + if (this.numChunksLoaded === this.numChunks) { + return true; + } + return this.getMissingChunks().length === 0; + }, + configurable: true, + }); const subStream = new ChunkedStreamSubstream(); subStream.pos = subStream.start = start; @@ -304,6 +252,10 @@ class ChunkedStream { subStream.dict = dict; return subStream; } + + getBaseStreams() { + return [this]; + } } class ChunkedStreamManager { @@ -521,7 +473,7 @@ class ChunkedStreamManager { this.stream.onReceiveData(begin, chunk); } - if (this.stream.allChunksLoaded()) { + if (this.stream.isDataLoaded) { this._loadedStreamCapability.resolve(this.stream); } diff --git a/src/core/crypto.js b/src/core/crypto.js index 752e5e79d..dab3a1e52 100644 --- a/src/core/crypto.js +++ b/src/core/crypto.js @@ -26,7 +26,7 @@ import { warn, } from "../shared/util.js"; import { isDict, isName, Name } from "./primitives.js"; -import { DecryptStream } from "./stream.js"; +import { DecryptStream } from "./decrypt_stream.js"; class ARCFourCipher { constructor(key) { diff --git a/src/core/decode_stream.js b/src/core/decode_stream.js new file mode 100644 index 000000000..701d78dec --- /dev/null +++ b/src/core/decode_stream.js @@ -0,0 +1,170 @@ +/* Copyright 2012 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 { BaseStream } from "./base_stream.js"; +import { Stream } from "./stream.js"; + +// Lots of DecodeStreams are created whose buffers are never used. For these +// we share a single empty buffer. This is (a) space-efficient and (b) avoids +// having special cases that would be required if we used |null| for an empty +// buffer. +const emptyBuffer = new Uint8Array(0); + +// Super class for the decoding streams. +class DecodeStream extends BaseStream { + constructor(maybeMinBufferLength) { + super(); + this._rawMinBufferLength = maybeMinBufferLength || 0; + + this.pos = 0; + this.bufferLength = 0; + this.eof = false; + this.buffer = emptyBuffer; + this.minBufferLength = 512; + if (maybeMinBufferLength) { + // Compute the first power of two that is as big as maybeMinBufferLength. + while (this.minBufferLength < maybeMinBufferLength) { + this.minBufferLength *= 2; + } + } + } + + get isEmpty() { + while (!this.eof && this.bufferLength === 0) { + this.readBlock(); + } + return this.bufferLength === 0; + } + + ensureBuffer(requested) { + const buffer = this.buffer; + if (requested <= buffer.byteLength) { + return buffer; + } + let size = this.minBufferLength; + while (size < requested) { + size *= 2; + } + const buffer2 = new Uint8Array(size); + buffer2.set(buffer); + return (this.buffer = buffer2); + } + + getByte() { + const pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) { + return -1; + } + this.readBlock(); + } + return this.buffer[this.pos++]; + } + + getBytes(length, forceClamped = false) { + const pos = this.pos; + let end; + + if (length) { + this.ensureBuffer(pos + length); + end = pos + length; + + while (!this.eof && this.bufferLength < end) { + this.readBlock(); + } + const bufEnd = this.bufferLength; + if (end > bufEnd) { + end = bufEnd; + } + } else { + while (!this.eof) { + this.readBlock(); + } + end = this.bufferLength; + } + + this.pos = end; + const subarray = this.buffer.subarray(pos, end); + // `this.buffer` is either a `Uint8Array` or `Uint8ClampedArray` here. + return forceClamped && !(subarray instanceof Uint8ClampedArray) + ? new Uint8ClampedArray(subarray) + : subarray; + } + + reset() { + this.pos = 0; + } + + makeSubStream(start, length, dict = null) { + if (length === undefined) { + while (!this.eof) { + this.readBlock(); + } + } else { + const end = start + length; + while (this.bufferLength <= end && !this.eof) { + this.readBlock(); + } + } + return new Stream(this.buffer, start, length, dict); + } + + getBaseStreams() { + return this.str ? this.str.getBaseStreams() : null; + } +} + +class StreamsSequenceStream extends DecodeStream { + constructor(streams) { + let maybeLength = 0; + for (const stream of streams) { + maybeLength += + stream instanceof DecodeStream + ? stream._rawMinBufferLength + : stream.length; + } + super(maybeLength); + + this.streams = streams; + } + + readBlock() { + const streams = this.streams; + if (streams.length === 0) { + this.eof = true; + return; + } + const stream = streams.shift(); + const chunk = stream.getBytes(); + const bufferLength = this.bufferLength; + const newLength = bufferLength + chunk.length; + const buffer = this.ensureBuffer(newLength); + buffer.set(chunk, bufferLength); + this.bufferLength = newLength; + } + + getBaseStreams() { + const baseStreamsBuf = []; + for (const stream of this.streams) { + const baseStreams = stream.getBaseStreams(); + if (baseStreams) { + baseStreamsBuf.push(...baseStreams); + } + } + return baseStreamsBuf.length > 0 ? baseStreamsBuf : null; + } +} + +export { DecodeStream, StreamsSequenceStream }; diff --git a/src/core/decrypt_stream.js b/src/core/decrypt_stream.js new file mode 100644 index 000000000..6848ead7e --- /dev/null +++ b/src/core/decrypt_stream.js @@ -0,0 +1,59 @@ +/* Copyright 2012 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 { DecodeStream } from "./decode_stream.js"; + +const chunkSize = 512; + +class DecryptStream extends DecodeStream { + constructor(str, maybeLength, decrypt) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + this.decrypt = decrypt; + this.nextChunk = null; + this.initialized = false; + } + + readBlock() { + let chunk; + if (this.initialized) { + chunk = this.nextChunk; + } else { + chunk = this.str.getBytes(chunkSize); + this.initialized = true; + } + if (!chunk || chunk.length === 0) { + this.eof = true; + return; + } + this.nextChunk = this.str.getBytes(chunkSize); + const hasMoreData = this.nextChunk && this.nextChunk.length > 0; + + const decrypt = this.decrypt; + chunk = decrypt(chunk, !hasMoreData); + + let bufferLength = this.bufferLength; + const n = chunk.length, + buffer = this.ensureBuffer(bufferLength + n); + for (let i = 0; i < n; i++) { + buffer[bufferLength++] = chunk[i]; + } + this.bufferLength = bufferLength; + } +} + +export { DecryptStream }; diff --git a/src/core/document.js b/src/core/document.js index 0682d7813..3e0883331 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -53,7 +53,7 @@ import { XRefEntryException, XRefParseException, } from "./core_utils.js"; -import { NullStream, Stream, StreamsSequenceStream } from "./stream.js"; +import { NullStream, Stream } from "./stream.js"; import { AnnotationFactory } from "./annotation.js"; import { calculateMD5 } from "./crypto.js"; import { Catalog } from "./catalog.js"; @@ -61,6 +61,7 @@ import { Linearization } from "./parser.js"; import { ObjectLoader } from "./object_loader.js"; import { OperatorList } from "./operator_list.js"; import { PartialEvaluator } from "./evaluator.js"; +import { StreamsSequenceStream } from "./decode_stream.js"; import { StructTreePage } from "./struct_tree.js"; import { XFAFactory } from "./xfa/factory.js"; import { XRef } from "./xref.js"; diff --git a/src/core/evaluator.js b/src/core/evaluator.js index eb3ec46df..5c67ce3c6 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -47,7 +47,6 @@ import { Ref, RefSet, } from "./primitives.js"; -import { DecodeStream, NullStream } from "./stream.js"; import { ErrorFont, Font, @@ -85,10 +84,12 @@ import { } from "./image_utils.js"; import { bidi } from "./bidi.js"; import { ColorSpace } from "./colorspace.js"; +import { DecodeStream } from "./decode_stream.js"; import { getGlyphsUnicode } from "./glyphlist.js"; import { getLookupTableFactory } from "./core_utils.js"; import { getMetrics } from "./metrics.js"; import { MurmurHash3_64 } from "./murmurhash3.js"; +import { NullStream } from "./stream.js"; import { OperatorList } from "./operator_list.js"; import { PDFImage } from "./image.js"; diff --git a/src/core/flate_stream.js b/src/core/flate_stream.js new file mode 100644 index 000000000..6533bbd6b --- /dev/null +++ b/src/core/flate_stream.js @@ -0,0 +1,401 @@ +/* Copyright 2012 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. + */ +/* Copyright 1996-2003 Glyph & Cog, LLC + * + * The flate stream implementation contained in this file is a JavaScript port + * of XPDF's implementation, made available under the Apache 2.0 open source + * license. + */ + +import { DecodeStream } from "./decode_stream.js"; +import { FormatError } from "../shared/util.js"; + +// prettier-ignore +const codeLenCodeMap = new Int32Array([ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +]); + +// prettier-ignore +const lengthDecode = new Int32Array([ + 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, + 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, + 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, + 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 +]); + +// prettier-ignore +const distDecode = new Int32Array([ + 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, + 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, + 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, + 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 +]); + +// prettier-ignore +const fixedLitCodeTab = [new Int32Array([ + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, + 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, + 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, + 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, + 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, + 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, + 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, + 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, + 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, + 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, + 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, + 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, + 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, + 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, + 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, + 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, + 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, + 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, + 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, + 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, + 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, + 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, + 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, + 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, + 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, + 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, + 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, + 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, + 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, + 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, + 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, + 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, + 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, + 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, + 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, + 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, + 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, + 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, + 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, + 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, + 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, + 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, + 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, + 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, + 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, + 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, + 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, + 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, + 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff +]), 9]; + +// prettier-ignore +const fixedDistCodeTab = [new Int32Array([ + 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, + 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, + 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, + 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 +]), 5]; + +class FlateStream extends DecodeStream { + constructor(str, maybeLength) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + + const cmf = str.getByte(); + const flg = str.getByte(); + if (cmf === -1 || flg === -1) { + throw new FormatError(`Invalid header in flate stream: ${cmf}, ${flg}`); + } + if ((cmf & 0x0f) !== 0x08) { + throw new FormatError( + `Unknown compression method in flate stream: ${cmf}, ${flg}` + ); + } + if (((cmf << 8) + flg) % 31 !== 0) { + throw new FormatError(`Bad FCHECK in flate stream: ${cmf}, ${flg}`); + } + if (flg & 0x20) { + throw new FormatError(`FDICT bit set in flate stream: ${cmf}, ${flg}`); + } + + this.codeSize = 0; + this.codeBuf = 0; + } + + getBits(bits) { + const str = this.str; + let codeSize = this.codeSize; + let codeBuf = this.codeBuf; + + let b; + while (codeSize < bits) { + if ((b = str.getByte()) === -1) { + throw new FormatError("Bad encoding in flate stream"); + } + codeBuf |= b << codeSize; + codeSize += 8; + } + b = codeBuf & ((1 << bits) - 1); + this.codeBuf = codeBuf >> bits; + this.codeSize = codeSize -= bits; + + return b; + } + + getCode(table) { + const str = this.str; + const codes = table[0]; + const maxLen = table[1]; + let codeSize = this.codeSize; + let codeBuf = this.codeBuf; + + let b; + while (codeSize < maxLen) { + if ((b = str.getByte()) === -1) { + // premature end of stream. code might however still be valid. + // codeSize < codeLen check below guards against incomplete codeVal. + break; + } + codeBuf |= b << codeSize; + codeSize += 8; + } + const code = codes[codeBuf & ((1 << maxLen) - 1)]; + const codeLen = code >> 16; + const codeVal = code & 0xffff; + if (codeLen < 1 || codeSize < codeLen) { + throw new FormatError("Bad encoding in flate stream"); + } + this.codeBuf = codeBuf >> codeLen; + this.codeSize = codeSize - codeLen; + return codeVal; + } + + generateHuffmanTable(lengths) { + const n = lengths.length; + + // find max code length + let maxLen = 0; + let i; + for (i = 0; i < n; ++i) { + if (lengths[i] > maxLen) { + maxLen = lengths[i]; + } + } + + // build the table + const size = 1 << maxLen; + const codes = new Int32Array(size); + for ( + let len = 1, code = 0, skip = 2; + len <= maxLen; + ++len, code <<= 1, skip <<= 1 + ) { + for (let val = 0; val < n; ++val) { + if (lengths[val] === len) { + // bit-reverse the code + let code2 = 0; + let t = code; + for (i = 0; i < len; ++i) { + code2 = (code2 << 1) | (t & 1); + t >>= 1; + } + + // fill the table entries + for (i = code2; i < size; i += skip) { + codes[i] = (len << 16) | val; + } + ++code; + } + } + } + + return [codes, maxLen]; + } + + readBlock() { + let buffer, len; + const str = this.str; + // read block header + let hdr = this.getBits(3); + if (hdr & 1) { + this.eof = true; + } + hdr >>= 1; + + if (hdr === 0) { + // uncompressed block + let b; + + if ((b = str.getByte()) === -1) { + throw new FormatError("Bad block header in flate stream"); + } + let blockLen = b; + if ((b = str.getByte()) === -1) { + throw new FormatError("Bad block header in flate stream"); + } + blockLen |= b << 8; + if ((b = str.getByte()) === -1) { + throw new FormatError("Bad block header in flate stream"); + } + let check = b; + if ((b = str.getByte()) === -1) { + throw new FormatError("Bad block header in flate stream"); + } + check |= b << 8; + if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) { + // Ignoring error for bad "empty" block (see issue 1277) + throw new FormatError("Bad uncompressed block length in flate stream"); + } + + this.codeBuf = 0; + this.codeSize = 0; + + const bufferLength = this.bufferLength, + end = bufferLength + blockLen; + buffer = this.ensureBuffer(end); + this.bufferLength = end; + + if (blockLen === 0) { + if (str.peekByte() === -1) { + this.eof = true; + } + } else { + const block = str.getBytes(blockLen); + buffer.set(block, bufferLength); + if (block.length < blockLen) { + this.eof = true; + } + } + return; + } + + let litCodeTable; + let distCodeTable; + if (hdr === 1) { + // compressed block, fixed codes + litCodeTable = fixedLitCodeTab; + distCodeTable = fixedDistCodeTab; + } else if (hdr === 2) { + // compressed block, dynamic codes + const numLitCodes = this.getBits(5) + 257; + const numDistCodes = this.getBits(5) + 1; + const numCodeLenCodes = this.getBits(4) + 4; + + // build the code lengths code table + const codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length); + + let i; + for (i = 0; i < numCodeLenCodes; ++i) { + codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3); + } + const codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); + + // build the literal and distance code tables + len = 0; + i = 0; + const codes = numLitCodes + numDistCodes; + const codeLengths = new Uint8Array(codes); + let bitsLength, bitsOffset, what; + while (i < codes) { + const code = this.getCode(codeLenCodeTab); + if (code === 16) { + bitsLength = 2; + bitsOffset = 3; + what = len; + } else if (code === 17) { + bitsLength = 3; + bitsOffset = 3; + what = len = 0; + } else if (code === 18) { + bitsLength = 7; + bitsOffset = 11; + what = len = 0; + } else { + codeLengths[i++] = len = code; + continue; + } + + let repeatLength = this.getBits(bitsLength) + bitsOffset; + while (repeatLength-- > 0) { + codeLengths[i++] = what; + } + } + + litCodeTable = this.generateHuffmanTable( + codeLengths.subarray(0, numLitCodes) + ); + distCodeTable = this.generateHuffmanTable( + codeLengths.subarray(numLitCodes, codes) + ); + } else { + throw new FormatError("Unknown block type in flate stream"); + } + + buffer = this.buffer; + let limit = buffer ? buffer.length : 0; + let pos = this.bufferLength; + while (true) { + let code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; + } + buffer[pos++] = code1; + continue; + } + if (code1 === 256) { + this.bufferLength = pos; + return; + } + code1 -= 257; + code1 = lengthDecode[code1]; + let code2 = code1 >> 16; + if (code2 > 0) { + code2 = this.getBits(code2); + } + len = (code1 & 0xffff) + code2; + code1 = this.getCode(distCodeTable); + code1 = distDecode[code1]; + code2 = code1 >> 16; + if (code2 > 0) { + code2 = this.getBits(code2); + } + const dist = (code1 & 0xffff) + code2; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; + } + for (let k = 0; k < len; ++k, ++pos) { + buffer[pos] = buffer[pos - dist]; + } + } + } +} + +export { FlateStream }; diff --git a/src/core/image.js b/src/core/image.js index e718b9937..9de3c3216 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -16,7 +16,7 @@ import { assert, FormatError, ImageKind, info, warn } from "../shared/util.js"; import { isName, isStream, Name } from "./primitives.js"; import { ColorSpace } from "./colorspace.js"; -import { DecodeStream } from "./stream.js"; +import { DecodeStream } from "./decode_stream.js"; import { JpegStream } from "./jpeg_stream.js"; import { JpxImage } from "./jpx.js"; diff --git a/src/core/jbig2_stream.js b/src/core/jbig2_stream.js index c760e8d90..172ea6aa0 100644 --- a/src/core/jbig2_stream.js +++ b/src/core/jbig2_stream.js @@ -14,7 +14,7 @@ */ import { isDict, isStream } from "./primitives.js"; -import { DecodeStream } from "./stream.js"; +import { DecodeStream } from "./decode_stream.js"; import { Jbig2Image } from "./jbig2.js"; import { shadow } from "../shared/util.js"; @@ -23,12 +23,12 @@ import { shadow } from "../shared/util.js"; * the stream behaves like all the other DecodeStreams. */ class Jbig2Stream extends DecodeStream { - constructor(stream, maybeLength, dict, params) { + constructor(stream, maybeLength, params) { super(maybeLength); this.stream = stream; + this.dict = stream.dict; this.maybeLength = maybeLength; - this.dict = dict; this.params = params; } diff --git a/src/core/jpeg_stream.js b/src/core/jpeg_stream.js index 300f055a8..e8a0be588 100644 --- a/src/core/jpeg_stream.js +++ b/src/core/jpeg_stream.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { DecodeStream } from "./stream.js"; +import { DecodeStream } from "./decode_stream.js"; import { isDict } from "./primitives.js"; import { JpegImage } from "./jpg.js"; import { shadow } from "../shared/util.js"; @@ -23,7 +23,7 @@ import { shadow } from "../shared/util.js"; * like all the other DecodeStreams. */ class JpegStream extends DecodeStream { - constructor(stream, maybeLength, dict, params) { + constructor(stream, maybeLength, params) { // Some images may contain 'junk' before the SOI (start-of-image) marker. // Note: this seems to mainly affect inline images. let ch; @@ -37,8 +37,8 @@ class JpegStream extends DecodeStream { super(maybeLength); this.stream = stream; + this.dict = stream.dict; this.maybeLength = maybeLength; - this.dict = dict; this.params = params; } diff --git a/src/core/jpx_stream.js b/src/core/jpx_stream.js index 5808563b2..82f0e5286 100644 --- a/src/core/jpx_stream.js +++ b/src/core/jpx_stream.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { DecodeStream } from "./stream.js"; +import { DecodeStream } from "./decode_stream.js"; import { JpxImage } from "./jpx.js"; import { shadow } from "../shared/util.js"; @@ -22,12 +22,12 @@ import { shadow } from "../shared/util.js"; * the stream behaves like all the other DecodeStreams. */ class JpxStream extends DecodeStream { - constructor(stream, maybeLength, dict, params) { + constructor(stream, maybeLength, params) { super(maybeLength); this.stream = stream; + this.dict = stream.dict; this.maybeLength = maybeLength; - this.dict = dict; this.params = params; } diff --git a/src/core/lzw_stream.js b/src/core/lzw_stream.js new file mode 100644 index 000000000..9a0ae7448 --- /dev/null +++ b/src/core/lzw_stream.js @@ -0,0 +1,150 @@ +/* Copyright 2012 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 { DecodeStream } from "./decode_stream.js"; + +class LZWStream extends DecodeStream { + constructor(str, maybeLength, earlyChange) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + this.cachedData = 0; + this.bitsCached = 0; + + const maxLzwDictionarySize = 4096; + const lzwState = { + earlyChange, + codeLength: 9, + nextCode: 258, + dictionaryValues: new Uint8Array(maxLzwDictionarySize), + dictionaryLengths: new Uint16Array(maxLzwDictionarySize), + dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), + currentSequence: new Uint8Array(maxLzwDictionarySize), + currentSequenceLength: 0, + }; + for (let i = 0; i < 256; ++i) { + lzwState.dictionaryValues[i] = i; + lzwState.dictionaryLengths[i] = 1; + } + this.lzwState = lzwState; + } + + readBits(n) { + let bitsCached = this.bitsCached; + let cachedData = this.cachedData; + while (bitsCached < n) { + const c = this.str.getByte(); + if (c === -1) { + this.eof = true; + return null; + } + cachedData = (cachedData << 8) | c; + bitsCached += 8; + } + this.bitsCached = bitsCached -= n; + this.cachedData = cachedData; + this.lastCode = null; + return (cachedData >>> bitsCached) & ((1 << n) - 1); + } + + readBlock() { + const blockSize = 512, + decodedSizeDelta = blockSize; + let estimatedDecodedSize = blockSize * 2; + let i, j, q; + + const lzwState = this.lzwState; + if (!lzwState) { + return; // eof was found + } + + const earlyChange = lzwState.earlyChange; + let nextCode = lzwState.nextCode; + const dictionaryValues = lzwState.dictionaryValues; + const dictionaryLengths = lzwState.dictionaryLengths; + const dictionaryPrevCodes = lzwState.dictionaryPrevCodes; + let codeLength = lzwState.codeLength; + let prevCode = lzwState.prevCode; + const currentSequence = lzwState.currentSequence; + let currentSequenceLength = lzwState.currentSequenceLength; + + let decodedLength = 0; + let currentBufferLength = this.bufferLength; + let buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + + for (i = 0; i < blockSize; i++) { + const code = this.readBits(codeLength); + const hasPrev = currentSequenceLength > 0; + if (code < 256) { + currentSequence[0] = code; + currentSequenceLength = 1; + } else if (code >= 258) { + if (code < nextCode) { + currentSequenceLength = dictionaryLengths[code]; + for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { + currentSequence[j] = dictionaryValues[q]; + q = dictionaryPrevCodes[q]; + } + } else { + currentSequence[currentSequenceLength++] = currentSequence[0]; + } + } else if (code === 256) { + codeLength = 9; + nextCode = 258; + currentSequenceLength = 0; + continue; + } else { + this.eof = true; + delete this.lzwState; + break; + } + + if (hasPrev) { + dictionaryPrevCodes[nextCode] = prevCode; + dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; + dictionaryValues[nextCode] = currentSequence[0]; + nextCode++; + codeLength = + (nextCode + earlyChange) & (nextCode + earlyChange - 1) + ? codeLength + : Math.min( + Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, + 12 + ) | 0; + } + prevCode = code; + + decodedLength += currentSequenceLength; + if (estimatedDecodedSize < decodedLength) { + do { + estimatedDecodedSize += decodedSizeDelta; + } while (estimatedDecodedSize < decodedLength); + buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + } + for (j = 0; j < currentSequenceLength; j++) { + buffer[currentBufferLength++] = currentSequence[j]; + } + } + lzwState.nextCode = nextCode; + lzwState.codeLength = codeLength; + lzwState.prevCode = prevCode; + lzwState.currentSequenceLength = currentSequenceLength; + + this.bufferLength = currentBufferLength; + } +} + +export { LZWStream }; diff --git a/src/core/object_loader.js b/src/core/object_loader.js index f36016e6e..b79e90320 100644 --- a/src/core/object_loader.js +++ b/src/core/object_loader.js @@ -61,12 +61,8 @@ class ObjectLoader { } async load() { - // Don't walk the graph if all the data is already loaded; note that only - // `ChunkedStream` instances have a `allChunksLoaded` method. - if ( - !this.xref.stream.allChunksLoaded || - this.xref.stream.allChunksLoaded() - ) { + // Don't walk the graph if all the data is already loaded. + if (this.xref.stream.isDataLoaded) { return undefined; } @@ -112,18 +108,20 @@ class ObjectLoader { pendingRequests.push({ begin: ex.begin, end: ex.end }); } } - if (currentNode && currentNode.getBaseStreams) { + if (isStream(currentNode)) { const baseStreams = currentNode.getBaseStreams(); - let foundMissingData = false; - for (let i = 0, ii = baseStreams.length; i < ii; i++) { - const stream = baseStreams[i]; - if (stream.allChunksLoaded && !stream.allChunksLoaded()) { + if (baseStreams) { + let foundMissingData = false; + for (const stream of baseStreams) { + if (stream.isDataLoaded) { + continue; + } foundMissingData = true; pendingRequests.push({ begin: stream.start, end: stream.end }); } - } - if (foundMissingData) { - nodesToRevisit.push(currentNode); + if (foundMissingData) { + nodesToRevisit.push(currentNode); + } } } @@ -133,8 +131,7 @@ class ObjectLoader { if (pendingRequests.length) { await this.xref.stream.manager.requestRanges(pendingRequests); - for (let i = 0, ii = nodesToRevisit.length; i < ii; i++) { - const node = nodesToRevisit[i]; + for (const node of nodesToRevisit) { // Remove any reference nodes from the current `RefSet` so they // aren't skipped when we revist them. if (node instanceof Ref) { diff --git a/src/core/parser.js b/src/core/parser.js index 6692adbda..ca56a09f7 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -13,15 +13,6 @@ * limitations under the License. */ -import { - Ascii85Stream, - AsciiHexStream, - FlateStream, - LZWStream, - NullStream, - PredictorStream, - RunLengthStream, -} from "./stream.js"; import { assert, bytesToString, @@ -43,10 +34,17 @@ import { Ref, } from "./primitives.js"; import { isWhiteSpace, MissingDataException } from "./core_utils.js"; +import { Ascii85Stream } from "./ascii_85_stream.js"; +import { AsciiHexStream } from "./ascii_hex_stream.js"; import { CCITTFaxStream } from "./ccitt_stream.js"; +import { FlateStream } from "./flate_stream.js"; import { Jbig2Stream } from "./jbig2_stream.js"; import { JpegStream } from "./jpeg_stream.js"; import { JpxStream } from "./jpx_stream.js"; +import { LZWStream } from "./lzw_stream.js"; +import { NullStream } from "./stream.js"; +import { PredictorStream } from "./predictor_stream.js"; +import { RunLengthStream } from "./run_length_stream.js"; const MAX_LENGTH_TO_CACHE = 1000; const MAX_ADLER32_LENGTH = 5552; @@ -767,11 +765,11 @@ class Parser { } if (name === "DCTDecode" || name === "DCT") { xrefStreamStats[StreamType.DCT] = true; - return new JpegStream(stream, maybeLength, stream.dict, params); + return new JpegStream(stream, maybeLength, params); } if (name === "JPXDecode" || name === "JPX") { xrefStreamStats[StreamType.JPX] = true; - return new JpxStream(stream, maybeLength, stream.dict, params); + return new JpxStream(stream, maybeLength, params); } if (name === "ASCII85Decode" || name === "A85") { xrefStreamStats[StreamType.A85] = true; @@ -791,7 +789,7 @@ class Parser { } if (name === "JBIG2Decode") { xrefStreamStats[StreamType.JBIG] = true; - return new Jbig2Stream(stream, maybeLength, stream.dict, params); + return new Jbig2Stream(stream, maybeLength, params); } warn(`Filter "${name}" is not supported.`); return stream; diff --git a/src/core/predictor_stream.js b/src/core/predictor_stream.js new file mode 100644 index 000000000..9a682f7e0 --- /dev/null +++ b/src/core/predictor_stream.js @@ -0,0 +1,238 @@ +/* Copyright 2012 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 { DecodeStream } from "./decode_stream.js"; +import { FormatError } from "../shared/util.js"; +import { isDict } from "./primitives.js"; + +class PredictorStream extends DecodeStream { + constructor(str, maybeLength, params) { + if (!isDict(params)) { + return str; // no prediction + } + const predictor = params.get("Predictor") || 1; + + if (predictor <= 1) { + return str; // no prediction + } + if (predictor !== 2 && (predictor < 10 || predictor > 15)) { + throw new FormatError(`Unsupported predictor: ${predictor}`); + } + super(maybeLength); + this.predictor = predictor; + + if (predictor === 2) { + this.readBlock = this.readBlockTiff; + } else { + this.readBlock = this.readBlockPng; + } + + this.str = str; + this.dict = str.dict; + + const colors = (this.colors = params.get("Colors") || 1); + const bits = (this.bits = params.get("BitsPerComponent") || 8); + const columns = (this.columns = params.get("Columns") || 1); + + this.pixBytes = (colors * bits + 7) >> 3; + this.rowBytes = (columns * colors * bits + 7) >> 3; + + return this; + } + + readBlockTiff() { + const rowBytes = this.rowBytes; + + const bufferLength = this.bufferLength; + const buffer = this.ensureBuffer(bufferLength + rowBytes); + + const bits = this.bits; + const colors = this.colors; + + const rawBytes = this.str.getBytes(rowBytes); + this.eof = !rawBytes.length; + if (this.eof) { + return; + } + + let inbuf = 0, + outbuf = 0; + let inbits = 0, + outbits = 0; + let pos = bufferLength; + let i; + + if (bits === 1 && colors === 1) { + // Optimized version of the loop in the "else"-branch + // for 1 bit-per-component and 1 color TIFF images. + for (i = 0; i < rowBytes; ++i) { + let c = rawBytes[i] ^ inbuf; + c ^= c >> 1; + c ^= c >> 2; + c ^= c >> 4; + inbuf = (c & 1) << 7; + buffer[pos++] = c; + } + } else if (bits === 8) { + for (i = 0; i < colors; ++i) { + buffer[pos++] = rawBytes[i]; + } + for (; i < rowBytes; ++i) { + buffer[pos] = buffer[pos - colors] + rawBytes[i]; + pos++; + } + } else if (bits === 16) { + const bytesPerPixel = colors * 2; + for (i = 0; i < bytesPerPixel; ++i) { + buffer[pos++] = rawBytes[i]; + } + for (; i < rowBytes; i += 2) { + const sum = + ((rawBytes[i] & 0xff) << 8) + + (rawBytes[i + 1] & 0xff) + + ((buffer[pos - bytesPerPixel] & 0xff) << 8) + + (buffer[pos - bytesPerPixel + 1] & 0xff); + buffer[pos++] = (sum >> 8) & 0xff; + buffer[pos++] = sum & 0xff; + } + } else { + const compArray = new Uint8Array(colors + 1); + const bitMask = (1 << bits) - 1; + let j = 0, + k = bufferLength; + const columns = this.columns; + for (i = 0; i < columns; ++i) { + for (let kk = 0; kk < colors; ++kk) { + if (inbits < bits) { + inbuf = (inbuf << 8) | (rawBytes[j++] & 0xff); + inbits += 8; + } + compArray[kk] = + (compArray[kk] + (inbuf >> (inbits - bits))) & bitMask; + inbits -= bits; + outbuf = (outbuf << bits) | compArray[kk]; + outbits += bits; + if (outbits >= 8) { + buffer[k++] = (outbuf >> (outbits - 8)) & 0xff; + outbits -= 8; + } + } + } + if (outbits > 0) { + buffer[k++] = + (outbuf << (8 - outbits)) + (inbuf & ((1 << (8 - outbits)) - 1)); + } + } + this.bufferLength += rowBytes; + } + + readBlockPng() { + const rowBytes = this.rowBytes; + const pixBytes = this.pixBytes; + + const predictor = this.str.getByte(); + const rawBytes = this.str.getBytes(rowBytes); + this.eof = !rawBytes.length; + if (this.eof) { + return; + } + + const bufferLength = this.bufferLength; + const buffer = this.ensureBuffer(bufferLength + rowBytes); + + let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); + if (prevRow.length === 0) { + prevRow = new Uint8Array(rowBytes); + } + + let i, + j = bufferLength, + up, + c; + switch (predictor) { + case 0: + for (i = 0; i < rowBytes; ++i) { + buffer[j++] = rawBytes[i]; + } + break; + case 1: + for (i = 0; i < pixBytes; ++i) { + buffer[j++] = rawBytes[i]; + } + for (; i < rowBytes; ++i) { + buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xff; + j++; + } + break; + case 2: + for (i = 0; i < rowBytes; ++i) { + buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xff; + } + break; + case 3: + for (i = 0; i < pixBytes; ++i) { + buffer[j++] = (prevRow[i] >> 1) + rawBytes[i]; + } + for (; i < rowBytes; ++i) { + buffer[j] = + (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & 0xff; + j++; + } + break; + case 4: + // we need to save the up left pixels values. the simplest way + // is to create a new buffer + for (i = 0; i < pixBytes; ++i) { + up = prevRow[i]; + c = rawBytes[i]; + buffer[j++] = up + c; + } + for (; i < rowBytes; ++i) { + up = prevRow[i]; + const upLeft = prevRow[i - pixBytes]; + const left = buffer[j - pixBytes]; + const p = left + up - upLeft; + + let pa = p - left; + if (pa < 0) { + pa = -pa; + } + let pb = p - up; + if (pb < 0) { + pb = -pb; + } + let pc = p - upLeft; + if (pc < 0) { + pc = -pc; + } + + c = rawBytes[i]; + if (pa <= pb && pa <= pc) { + buffer[j++] = left + c; + } else if (pb <= pc) { + buffer[j++] = up + c; + } else { + buffer[j++] = upLeft + c; + } + } + break; + default: + throw new FormatError(`Unsupported predictor: ${predictor}`); + } + this.bufferLength += rowBytes; + } +} + +export { PredictorStream }; diff --git a/src/core/primitives.js b/src/core/primitives.js index 7e0d36ea2..e58e513f8 100644 --- a/src/core/primitives.js +++ b/src/core/primitives.js @@ -14,6 +14,7 @@ */ import { assert, unreachable } from "../shared/util.js"; +import { BaseStream } from "./base_stream.js"; const EOF = {}; @@ -383,7 +384,7 @@ function isRefsEqual(v1, v2) { } function isStream(v) { - return typeof v === "object" && v !== null && v.getBytes !== undefined; + return v instanceof BaseStream; } function clearPrimitiveCaches() { diff --git a/src/core/run_length_stream.js b/src/core/run_length_stream.js new file mode 100644 index 000000000..8df345fa7 --- /dev/null +++ b/src/core/run_length_stream.js @@ -0,0 +1,61 @@ +/* Copyright 2012 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 { DecodeStream } from "./decode_stream.js"; + +class RunLengthStream extends DecodeStream { + constructor(str, maybeLength) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + } + + readBlock() { + // The repeatHeader has following format. The first byte defines type of run + // and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes + // (in addition to the second byte from the header), n = 129 through 255 - + // duplicate the second byte from the header (257 - n) times, n = 128 - end. + const repeatHeader = this.str.getBytes(2); + if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) { + this.eof = true; + return; + } + + let buffer; + let bufferLength = this.bufferLength; + let n = repeatHeader[0]; + if (n < 128) { + // copy n bytes + buffer = this.ensureBuffer(bufferLength + n + 1); + buffer[bufferLength++] = repeatHeader[1]; + if (n > 0) { + const source = this.str.getBytes(n); + buffer.set(source, bufferLength); + bufferLength += n; + } + } else { + n = 257 - n; + const b = repeatHeader[1]; + buffer = this.ensureBuffer(bufferLength + n + 1); + for (let i = 0; i < n; i++) { + buffer[bufferLength++] = b; + } + } + this.bufferLength = bufferLength; + } +} + +export { RunLengthStream }; diff --git a/src/core/stream.js b/src/core/stream.js index 20717d3dc..6f24c4470 100644 --- a/src/core/stream.js +++ b/src/core/stream.js @@ -12,21 +12,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* Copyright 1996-2003 Glyph & Cog, LLC - * - * The flate stream implementation contained in this file is a JavaScript port - * of XPDF's implementation, made available under the Apache 2.0 open source - * license. - */ -/* eslint-disable no-var */ -import { FormatError, stringToBytes, unreachable } from "../shared/util.js"; -import { isDict } from "./primitives.js"; -import { isWhiteSpace } from "./core_utils.js"; +import { BaseStream } from "./base_stream.js"; +import { stringToBytes } from "../shared/util.js"; + +class Stream extends BaseStream { + constructor(arrayBuffer, start, length, dict) { + super(); -var Stream = (function StreamClosure() { - // eslint-disable-next-line no-shadow - function Stream(arrayBuffer, start, length, dict) { this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer @@ -37,1324 +30,74 @@ var Stream = (function StreamClosure() { this.dict = dict; } - // required methods for a stream. if a particular stream does not - // implement these, an error should be thrown - Stream.prototype = { - get length() { - return this.end - this.start; - }, - get isEmpty() { - return this.length === 0; - }, - getByte: function Stream_getByte() { - if (this.pos >= this.end) { - return -1; - } - return this.bytes[this.pos++]; - }, - getUint16: function Stream_getUint16() { - var b0 = this.getByte(); - var b1 = this.getByte(); - if (b0 === -1 || b1 === -1) { - return -1; - } - return (b0 << 8) + b1; - }, - getInt32: function Stream_getInt32() { - var b0 = this.getByte(); - var b1 = this.getByte(); - var b2 = this.getByte(); - var b3 = this.getByte(); - return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; - }, - // Returns subarray of original buffer, should only be read. - getBytes(length, forceClamped = false) { - var bytes = this.bytes; - var pos = this.pos; - var strEnd = this.end; + get length() { + return this.end - this.start; + } - if (!length) { - const subarray = bytes.subarray(pos, strEnd); - // `this.bytes` is always a `Uint8Array` here. - return forceClamped ? new Uint8ClampedArray(subarray) : subarray; - } - var end = pos + length; - if (end > strEnd) { - end = strEnd; - } - this.pos = end; - const subarray = bytes.subarray(pos, end); + get isEmpty() { + return this.length === 0; + } + + getByte() { + if (this.pos >= this.end) { + return -1; + } + return this.bytes[this.pos++]; + } + + getBytes(length, forceClamped = false) { + const bytes = this.bytes; + const pos = this.pos; + const strEnd = this.end; + + if (!length) { + const subarray = bytes.subarray(pos, strEnd); // `this.bytes` is always a `Uint8Array` here. return forceClamped ? new Uint8ClampedArray(subarray) : subarray; - }, - peekByte: function Stream_peekByte() { - var peekedByte = this.getByte(); - if (peekedByte !== -1) { - this.pos--; - } - return peekedByte; - }, - peekBytes(length, forceClamped = false) { - var bytes = this.getBytes(length, forceClamped); - this.pos -= bytes.length; - return bytes; - }, - - getByteRange(begin, end) { - if (begin < 0) { - begin = 0; - } - if (end > this.end) { - end = this.end; - } - return this.bytes.subarray(begin, end); - }, - - skip: function Stream_skip(n) { - if (!n) { - n = 1; - } - this.pos += n; - }, - reset: function Stream_reset() { - this.pos = this.start; - }, - moveStart: function Stream_moveStart() { - this.start = this.pos; - }, - makeSubStream: function Stream_makeSubStream(start, length, dict) { - return new Stream(this.bytes.buffer, start, length, dict); - }, - }; - - return Stream; -})(); - -var StringStream = (function StringStreamClosure() { - // eslint-disable-next-line no-shadow - function StringStream(str) { - const bytes = stringToBytes(str); - Stream.call(this, bytes); + } + let end = pos + length; + if (end > strEnd) { + end = strEnd; + } + this.pos = end; + const subarray = bytes.subarray(pos, end); + // `this.bytes` is always a `Uint8Array` here. + return forceClamped ? new Uint8ClampedArray(subarray) : subarray; } - StringStream.prototype = Stream.prototype; - - return StringStream; -})(); - -// super class for the decoding streams -var DecodeStream = (function DecodeStreamClosure() { - // Lots of DecodeStreams are created whose buffers are never used. For these - // we share a single empty buffer. This is (a) space-efficient and (b) avoids - // having special cases that would be required if we used |null| for an empty - // buffer. - var emptyBuffer = new Uint8Array(0); - - // eslint-disable-next-line no-shadow - function DecodeStream(maybeMinBufferLength) { - this._rawMinBufferLength = maybeMinBufferLength || 0; - - this.pos = 0; - this.bufferLength = 0; - this.eof = false; - this.buffer = emptyBuffer; - this.minBufferLength = 512; - if (maybeMinBufferLength) { - // Compute the first power of two that is as big as maybeMinBufferLength. - while (this.minBufferLength < maybeMinBufferLength) { - this.minBufferLength *= 2; - } + getByteRange(begin, end) { + if (begin < 0) { + begin = 0; } + if (end > this.end) { + end = this.end; + } + return this.bytes.subarray(begin, end); } - DecodeStream.prototype = { - // eslint-disable-next-line getter-return - get length() { - unreachable("Should not access DecodeStream.length"); - }, - - get isEmpty() { - while (!this.eof && this.bufferLength === 0) { - this.readBlock(); - } - return this.bufferLength === 0; - }, - ensureBuffer: function DecodeStream_ensureBuffer(requested) { - var buffer = this.buffer; - if (requested <= buffer.byteLength) { - return buffer; - } - var size = this.minBufferLength; - while (size < requested) { - size *= 2; - } - var buffer2 = new Uint8Array(size); - buffer2.set(buffer); - return (this.buffer = buffer2); - }, - getByte: function DecodeStream_getByte() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) { - return -1; - } - this.readBlock(); - } - return this.buffer[this.pos++]; - }, - getUint16: function DecodeStream_getUint16() { - var b0 = this.getByte(); - var b1 = this.getByte(); - if (b0 === -1 || b1 === -1) { - return -1; - } - return (b0 << 8) + b1; - }, - getInt32: function DecodeStream_getInt32() { - var b0 = this.getByte(); - var b1 = this.getByte(); - var b2 = this.getByte(); - var b3 = this.getByte(); - return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; - }, - getBytes(length, forceClamped = false) { - var end, - pos = this.pos; - - if (length) { - this.ensureBuffer(pos + length); - end = pos + length; - - while (!this.eof && this.bufferLength < end) { - this.readBlock(); - } - var bufEnd = this.bufferLength; - if (end > bufEnd) { - end = bufEnd; - } - } else { - while (!this.eof) { - this.readBlock(); - } - end = this.bufferLength; - } - - this.pos = end; - const subarray = this.buffer.subarray(pos, end); - // `this.buffer` is either a `Uint8Array` or `Uint8ClampedArray` here. - return forceClamped && !(subarray instanceof Uint8ClampedArray) - ? new Uint8ClampedArray(subarray) - : subarray; - }, - peekByte: function DecodeStream_peekByte() { - var peekedByte = this.getByte(); - if (peekedByte !== -1) { - this.pos--; - } - return peekedByte; - }, - peekBytes(length, forceClamped = false) { - var bytes = this.getBytes(length, forceClamped); - this.pos -= bytes.length; - return bytes; - }, - makeSubStream: function DecodeStream_makeSubStream(start, length, dict) { - if (length === undefined) { - while (!this.eof) { - this.readBlock(); - } - } else { - var end = start + length; - while (this.bufferLength <= end && !this.eof) { - this.readBlock(); - } - } - return new Stream(this.buffer, start, length, dict); - }, - - getByteRange(begin, end) { - unreachable("Should not call DecodeStream.getByteRange"); - }, - - skip: function DecodeStream_skip(n) { - if (!n) { - n = 1; - } - this.pos += n; - }, - reset: function DecodeStream_reset() { - this.pos = 0; - }, - getBaseStreams: function DecodeStream_getBaseStreams() { - if (this.str && this.str.getBaseStreams) { - return this.str.getBaseStreams(); - } - return []; - }, - }; - - return DecodeStream; -})(); - -var StreamsSequenceStream = (function StreamsSequenceStreamClosure() { - // eslint-disable-next-line no-shadow - function StreamsSequenceStream(streams) { - this.streams = streams; - - let maybeLength = 0; - for (let i = 0, ii = streams.length; i < ii; i++) { - const stream = streams[i]; - if (stream instanceof DecodeStream) { - maybeLength += stream._rawMinBufferLength; - } else { - maybeLength += stream.length; - } - } - DecodeStream.call(this, maybeLength); + reset() { + this.pos = this.start; } - StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype); - - StreamsSequenceStream.prototype.readBlock = function streamSequenceStreamReadBlock() { - var streams = this.streams; - if (streams.length === 0) { - this.eof = true; - return; - } - var stream = streams.shift(); - var chunk = stream.getBytes(); - var bufferLength = this.bufferLength; - var newLength = bufferLength + chunk.length; - var buffer = this.ensureBuffer(newLength); - buffer.set(chunk, bufferLength); - this.bufferLength = newLength; - }; - - StreamsSequenceStream.prototype.getBaseStreams = function StreamsSequenceStream_getBaseStreams() { - var baseStreams = []; - for (var i = 0, ii = this.streams.length; i < ii; i++) { - var stream = this.streams[i]; - if (stream.getBaseStreams) { - baseStreams.push(...stream.getBaseStreams()); - } - } - return baseStreams; - }; - - return StreamsSequenceStream; -})(); - -var FlateStream = (function FlateStreamClosure() { - // prettier-ignore - var codeLenCodeMap = new Int32Array([ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 - ]); - - // prettier-ignore - var lengthDecode = new Int32Array([ - 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, - 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, - 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, - 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 - ]); - - // prettier-ignore - var distDecode = new Int32Array([ - 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, - 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, - 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, - 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 - ]); - - // prettier-ignore - var fixedLitCodeTab = [new Int32Array([ - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, - 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, - 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, - 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, - 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, - 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, - 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, - 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, - 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, - 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, - 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, - 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, - 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, - 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, - 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, - 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, - 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, - 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, - 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, - 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, - 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, - 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, - 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, - 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, - 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, - 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, - 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, - 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, - 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, - 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, - 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, - 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, - 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, - 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, - 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, - 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, - 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, - 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, - 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, - 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, - 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, - 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, - 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, - 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, - 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, - 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, - 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, - 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, - 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, - 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, - 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, - 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, - 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, - 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, - 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, - 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, - 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, - 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, - 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff - ]), 9]; - - // prettier-ignore - var fixedDistCodeTab = [new Int32Array([ - 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, - 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, - 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, - 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 - ]), 5]; - - // eslint-disable-next-line no-shadow - function FlateStream(str, maybeLength) { - this.str = str; - this.dict = str.dict; - - var cmf = str.getByte(); - var flg = str.getByte(); - if (cmf === -1 || flg === -1) { - throw new FormatError(`Invalid header in flate stream: ${cmf}, ${flg}`); - } - if ((cmf & 0x0f) !== 0x08) { - throw new FormatError( - `Unknown compression method in flate stream: ${cmf}, ${flg}` - ); - } - if (((cmf << 8) + flg) % 31 !== 0) { - throw new FormatError(`Bad FCHECK in flate stream: ${cmf}, ${flg}`); - } - if (flg & 0x20) { - throw new FormatError(`FDICT bit set in flate stream: ${cmf}, ${flg}`); - } - - this.codeSize = 0; - this.codeBuf = 0; - - DecodeStream.call(this, maybeLength); + moveStart() { + this.start = this.pos; } - FlateStream.prototype = Object.create(DecodeStream.prototype); - - FlateStream.prototype.getBits = function FlateStream_getBits(bits) { - var str = this.str; - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - - var b; - while (codeSize < bits) { - if ((b = str.getByte()) === -1) { - throw new FormatError("Bad encoding in flate stream"); - } - codeBuf |= b << codeSize; - codeSize += 8; - } - b = codeBuf & ((1 << bits) - 1); - this.codeBuf = codeBuf >> bits; - this.codeSize = codeSize -= bits; - - return b; - }; - - FlateStream.prototype.getCode = function FlateStream_getCode(table) { - var str = this.str; - var codes = table[0]; - var maxLen = table[1]; - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - - var b; - while (codeSize < maxLen) { - if ((b = str.getByte()) === -1) { - // premature end of stream. code might however still be valid. - // codeSize < codeLen check below guards against incomplete codeVal. - break; - } - codeBuf |= b << codeSize; - codeSize += 8; - } - var code = codes[codeBuf & ((1 << maxLen) - 1)]; - var codeLen = code >> 16; - var codeVal = code & 0xffff; - if (codeLen < 1 || codeSize < codeLen) { - throw new FormatError("Bad encoding in flate stream"); - } - this.codeBuf = codeBuf >> codeLen; - this.codeSize = codeSize - codeLen; - return codeVal; - }; - - FlateStream.prototype.generateHuffmanTable = function flateStreamGenerateHuffmanTable( - lengths - ) { - var n = lengths.length; - - // find max code length - var maxLen = 0; - var i; - for (i = 0; i < n; ++i) { - if (lengths[i] > maxLen) { - maxLen = lengths[i]; - } - } - - // build the table - var size = 1 << maxLen; - var codes = new Int32Array(size); - for ( - var len = 1, code = 0, skip = 2; - len <= maxLen; - ++len, code <<= 1, skip <<= 1 - ) { - for (var val = 0; val < n; ++val) { - if (lengths[val] === len) { - // bit-reverse the code - var code2 = 0; - var t = code; - for (i = 0; i < len; ++i) { - code2 = (code2 << 1) | (t & 1); - t >>= 1; - } - - // fill the table entries - for (i = code2; i < size; i += skip) { - codes[i] = (len << 16) | val; - } - ++code; - } - } - } - - return [codes, maxLen]; - }; - - FlateStream.prototype.readBlock = function FlateStream_readBlock() { - var buffer, len; - var str = this.str; - // read block header - var hdr = this.getBits(3); - if (hdr & 1) { - this.eof = true; - } - hdr >>= 1; - - if (hdr === 0) { - // uncompressed block - var b; - - if ((b = str.getByte()) === -1) { - throw new FormatError("Bad block header in flate stream"); - } - var blockLen = b; - if ((b = str.getByte()) === -1) { - throw new FormatError("Bad block header in flate stream"); - } - blockLen |= b << 8; - if ((b = str.getByte()) === -1) { - throw new FormatError("Bad block header in flate stream"); - } - var check = b; - if ((b = str.getByte()) === -1) { - throw new FormatError("Bad block header in flate stream"); - } - check |= b << 8; - if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) { - // Ignoring error for bad "empty" block (see issue 1277) - throw new FormatError("Bad uncompressed block length in flate stream"); - } - - this.codeBuf = 0; - this.codeSize = 0; - - const bufferLength = this.bufferLength, - end = bufferLength + blockLen; - buffer = this.ensureBuffer(end); - this.bufferLength = end; - - if (blockLen === 0) { - if (str.peekByte() === -1) { - this.eof = true; - } - } else { - const block = str.getBytes(blockLen); - buffer.set(block, bufferLength); - if (block.length < blockLen) { - this.eof = true; - } - } - return; - } - - var litCodeTable; - var distCodeTable; - if (hdr === 1) { - // compressed block, fixed codes - litCodeTable = fixedLitCodeTab; - distCodeTable = fixedDistCodeTab; - } else if (hdr === 2) { - // compressed block, dynamic codes - var numLitCodes = this.getBits(5) + 257; - var numDistCodes = this.getBits(5) + 1; - var numCodeLenCodes = this.getBits(4) + 4; - - // build the code lengths code table - var codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length); - - var i; - for (i = 0; i < numCodeLenCodes; ++i) { - codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3); - } - var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); - - // build the literal and distance code tables - len = 0; - i = 0; - var codes = numLitCodes + numDistCodes; - var codeLengths = new Uint8Array(codes); - var bitsLength, bitsOffset, what; - while (i < codes) { - var code = this.getCode(codeLenCodeTab); - if (code === 16) { - bitsLength = 2; - bitsOffset = 3; - what = len; - } else if (code === 17) { - bitsLength = 3; - bitsOffset = 3; - what = len = 0; - } else if (code === 18) { - bitsLength = 7; - bitsOffset = 11; - what = len = 0; - } else { - codeLengths[i++] = len = code; - continue; - } - - var repeatLength = this.getBits(bitsLength) + bitsOffset; - while (repeatLength-- > 0) { - codeLengths[i++] = what; - } - } - - litCodeTable = this.generateHuffmanTable( - codeLengths.subarray(0, numLitCodes) - ); - distCodeTable = this.generateHuffmanTable( - codeLengths.subarray(numLitCodes, codes) - ); - } else { - throw new FormatError("Unknown block type in flate stream"); - } - - buffer = this.buffer; - var limit = buffer ? buffer.length : 0; - var pos = this.bufferLength; - while (true) { - var code1 = this.getCode(litCodeTable); - if (code1 < 256) { - if (pos + 1 >= limit) { - buffer = this.ensureBuffer(pos + 1); - limit = buffer.length; - } - buffer[pos++] = code1; - continue; - } - if (code1 === 256) { - this.bufferLength = pos; - return; - } - code1 -= 257; - code1 = lengthDecode[code1]; - var code2 = code1 >> 16; - if (code2 > 0) { - code2 = this.getBits(code2); - } - len = (code1 & 0xffff) + code2; - code1 = this.getCode(distCodeTable); - code1 = distDecode[code1]; - code2 = code1 >> 16; - if (code2 > 0) { - code2 = this.getBits(code2); - } - var dist = (code1 & 0xffff) + code2; - if (pos + len >= limit) { - buffer = this.ensureBuffer(pos + len); - limit = buffer.length; - } - for (var k = 0; k < len; ++k, ++pos) { - buffer[pos] = buffer[pos - dist]; - } - } - }; - - return FlateStream; -})(); - -var PredictorStream = (function PredictorStreamClosure() { - // eslint-disable-next-line no-shadow - function PredictorStream(str, maybeLength, params) { - if (!isDict(params)) { - return str; // no prediction - } - var predictor = (this.predictor = params.get("Predictor") || 1); - - if (predictor <= 1) { - return str; // no prediction - } - if (predictor !== 2 && (predictor < 10 || predictor > 15)) { - throw new FormatError(`Unsupported predictor: ${predictor}`); - } - - if (predictor === 2) { - this.readBlock = this.readBlockTiff; - } else { - this.readBlock = this.readBlockPng; - } - - this.str = str; - this.dict = str.dict; - - var colors = (this.colors = params.get("Colors") || 1); - var bits = (this.bits = params.get("BitsPerComponent") || 8); - var columns = (this.columns = params.get("Columns") || 1); - - this.pixBytes = (colors * bits + 7) >> 3; - this.rowBytes = (columns * colors * bits + 7) >> 3; - - DecodeStream.call(this, maybeLength); - return this; + makeSubStream(start, length, dict = null) { + return new Stream(this.bytes.buffer, start, length, dict); } +} - PredictorStream.prototype = Object.create(DecodeStream.prototype); - - PredictorStream.prototype.readBlockTiff = function predictorStreamReadBlockTiff() { - var rowBytes = this.rowBytes; - - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + rowBytes); - - var bits = this.bits; - var colors = this.colors; - - var rawBytes = this.str.getBytes(rowBytes); - this.eof = !rawBytes.length; - if (this.eof) { - return; - } - - var inbuf = 0, - outbuf = 0; - var inbits = 0, - outbits = 0; - var pos = bufferLength; - var i; - - if (bits === 1 && colors === 1) { - // Optimized version of the loop in the "else"-branch - // for 1 bit-per-component and 1 color TIFF images. - for (i = 0; i < rowBytes; ++i) { - var c = rawBytes[i] ^ inbuf; - c ^= c >> 1; - c ^= c >> 2; - c ^= c >> 4; - inbuf = (c & 1) << 7; - buffer[pos++] = c; - } - } else if (bits === 8) { - for (i = 0; i < colors; ++i) { - buffer[pos++] = rawBytes[i]; - } - for (; i < rowBytes; ++i) { - buffer[pos] = buffer[pos - colors] + rawBytes[i]; - pos++; - } - } else if (bits === 16) { - var bytesPerPixel = colors * 2; - for (i = 0; i < bytesPerPixel; ++i) { - buffer[pos++] = rawBytes[i]; - } - for (; i < rowBytes; i += 2) { - var sum = - ((rawBytes[i] & 0xff) << 8) + - (rawBytes[i + 1] & 0xff) + - ((buffer[pos - bytesPerPixel] & 0xff) << 8) + - (buffer[pos - bytesPerPixel + 1] & 0xff); - buffer[pos++] = (sum >> 8) & 0xff; - buffer[pos++] = sum & 0xff; - } - } else { - var compArray = new Uint8Array(colors + 1); - var bitMask = (1 << bits) - 1; - var j = 0, - k = bufferLength; - var columns = this.columns; - for (i = 0; i < columns; ++i) { - for (var kk = 0; kk < colors; ++kk) { - if (inbits < bits) { - inbuf = (inbuf << 8) | (rawBytes[j++] & 0xff); - inbits += 8; - } - compArray[kk] = - (compArray[kk] + (inbuf >> (inbits - bits))) & bitMask; - inbits -= bits; - outbuf = (outbuf << bits) | compArray[kk]; - outbits += bits; - if (outbits >= 8) { - buffer[k++] = (outbuf >> (outbits - 8)) & 0xff; - outbits -= 8; - } - } - } - if (outbits > 0) { - buffer[k++] = - (outbuf << (8 - outbits)) + (inbuf & ((1 << (8 - outbits)) - 1)); - } - } - this.bufferLength += rowBytes; - }; - - PredictorStream.prototype.readBlockPng = function predictorStreamReadBlockPng() { - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; - - var predictor = this.str.getByte(); - var rawBytes = this.str.getBytes(rowBytes); - this.eof = !rawBytes.length; - if (this.eof) { - return; - } - - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + rowBytes); - - var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); - if (prevRow.length === 0) { - prevRow = new Uint8Array(rowBytes); - } - - var i, - j = bufferLength, - up, - c; - switch (predictor) { - case 0: - for (i = 0; i < rowBytes; ++i) { - buffer[j++] = rawBytes[i]; - } - break; - case 1: - for (i = 0; i < pixBytes; ++i) { - buffer[j++] = rawBytes[i]; - } - for (; i < rowBytes; ++i) { - buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xff; - j++; - } - break; - case 2: - for (i = 0; i < rowBytes; ++i) { - buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xff; - } - break; - case 3: - for (i = 0; i < pixBytes; ++i) { - buffer[j++] = (prevRow[i] >> 1) + rawBytes[i]; - } - for (; i < rowBytes; ++i) { - buffer[j] = - (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & 0xff; - j++; - } - break; - case 4: - // we need to save the up left pixels values. the simplest way - // is to create a new buffer - for (i = 0; i < pixBytes; ++i) { - up = prevRow[i]; - c = rawBytes[i]; - buffer[j++] = up + c; - } - for (; i < rowBytes; ++i) { - up = prevRow[i]; - var upLeft = prevRow[i - pixBytes]; - var left = buffer[j - pixBytes]; - var p = left + up - upLeft; - - var pa = p - left; - if (pa < 0) { - pa = -pa; - } - var pb = p - up; - if (pb < 0) { - pb = -pb; - } - var pc = p - upLeft; - if (pc < 0) { - pc = -pc; - } - - c = rawBytes[i]; - if (pa <= pb && pa <= pc) { - buffer[j++] = left + c; - } else if (pb <= pc) { - buffer[j++] = up + c; - } else { - buffer[j++] = upLeft + c; - } - } - break; - default: - throw new FormatError(`Unsupported predictor: ${predictor}`); - } - this.bufferLength += rowBytes; - }; - - return PredictorStream; -})(); - -var DecryptStream = (function DecryptStreamClosure() { - // eslint-disable-next-line no-shadow - function DecryptStream(str, maybeLength, decrypt) { - this.str = str; - this.dict = str.dict; - this.decrypt = decrypt; - this.nextChunk = null; - this.initialized = false; - - DecodeStream.call(this, maybeLength); +class StringStream extends Stream { + constructor(str) { + super(stringToBytes(str)); } +} - var chunkSize = 512; - - DecryptStream.prototype = Object.create(DecodeStream.prototype); - - DecryptStream.prototype.readBlock = function DecryptStream_readBlock() { - var chunk; - if (this.initialized) { - chunk = this.nextChunk; - } else { - chunk = this.str.getBytes(chunkSize); - this.initialized = true; - } - if (!chunk || chunk.length === 0) { - this.eof = true; - return; - } - this.nextChunk = this.str.getBytes(chunkSize); - var hasMoreData = this.nextChunk && this.nextChunk.length > 0; - - var decrypt = this.decrypt; - chunk = decrypt(chunk, !hasMoreData); - - var bufferLength = this.bufferLength; - var i, - n = chunk.length; - var buffer = this.ensureBuffer(bufferLength + n); - for (i = 0; i < n; i++) { - buffer[bufferLength++] = chunk[i]; - } - this.bufferLength = bufferLength; - }; - - return DecryptStream; -})(); - -var Ascii85Stream = (function Ascii85StreamClosure() { - // eslint-disable-next-line no-shadow - function Ascii85Stream(str, maybeLength) { - this.str = str; - this.dict = str.dict; - this.input = new Uint8Array(5); - - // Most streams increase in size when decoded, but Ascii85 streams - // typically shrink by ~20%. - if (maybeLength) { - maybeLength = 0.8 * maybeLength; - } - DecodeStream.call(this, maybeLength); +class NullStream extends Stream { + constructor() { + super(new Uint8Array(0)); } +} - Ascii85Stream.prototype = Object.create(DecodeStream.prototype); - - Ascii85Stream.prototype.readBlock = function Ascii85Stream_readBlock() { - var TILDA_CHAR = 0x7e; // '~' - var Z_LOWER_CHAR = 0x7a; // 'z' - var EOF = -1; - - var str = this.str; - - var c = str.getByte(); - while (isWhiteSpace(c)) { - c = str.getByte(); - } - - if (c === EOF || c === TILDA_CHAR) { - this.eof = true; - return; - } - - var bufferLength = this.bufferLength, - buffer; - var i; - - // special code for z - if (c === Z_LOWER_CHAR) { - buffer = this.ensureBuffer(bufferLength + 4); - for (i = 0; i < 4; ++i) { - buffer[bufferLength + i] = 0; - } - this.bufferLength += 4; - } else { - var input = this.input; - input[0] = c; - for (i = 1; i < 5; ++i) { - c = str.getByte(); - while (isWhiteSpace(c)) { - c = str.getByte(); - } - - input[i] = c; - - if (c === EOF || c === TILDA_CHAR) { - break; - } - } - buffer = this.ensureBuffer(bufferLength + i - 1); - this.bufferLength += i - 1; - - // partial ending; - if (i < 5) { - for (; i < 5; ++i) { - input[i] = 0x21 + 84; - } - this.eof = true; - } - var t = 0; - for (i = 0; i < 5; ++i) { - t = t * 85 + (input[i] - 0x21); - } - - for (i = 3; i >= 0; --i) { - buffer[bufferLength + i] = t & 0xff; - t >>= 8; - } - } - }; - - return Ascii85Stream; -})(); - -var AsciiHexStream = (function AsciiHexStreamClosure() { - // eslint-disable-next-line no-shadow - function AsciiHexStream(str, maybeLength) { - this.str = str; - this.dict = str.dict; - - this.firstDigit = -1; - - // Most streams increase in size when decoded, but AsciiHex streams shrink - // by 50%. - if (maybeLength) { - maybeLength = 0.5 * maybeLength; - } - DecodeStream.call(this, maybeLength); - } - - AsciiHexStream.prototype = Object.create(DecodeStream.prototype); - - AsciiHexStream.prototype.readBlock = function AsciiHexStream_readBlock() { - var UPSTREAM_BLOCK_SIZE = 8000; - var bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE); - if (!bytes.length) { - this.eof = true; - return; - } - - var maxDecodeLength = (bytes.length + 1) >> 1; - var buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength); - var bufferLength = this.bufferLength; - - var firstDigit = this.firstDigit; - for (var i = 0, ii = bytes.length; i < ii; i++) { - var ch = bytes[i], - digit; - if (ch >= /* '0' = */ 0x30 && ch <= /* '9' = */ 0x39) { - digit = ch & 0x0f; - } else if ( - (ch >= /* 'A' = */ 0x41 && ch <= /* 'Z' = */ 0x46) || - (ch >= /* 'a' = */ 0x61 && ch <= /* 'z' = */ 0x66) - ) { - digit = (ch & 0x0f) + 9; - } else if (ch === /* '>' = */ 0x3e) { - this.eof = true; - break; - } else { - // Probably whitespace, ignoring. - continue; - } - if (firstDigit < 0) { - firstDigit = digit; - } else { - buffer[bufferLength++] = (firstDigit << 4) | digit; - firstDigit = -1; - } - } - if (firstDigit >= 0 && this.eof) { - // incomplete byte - buffer[bufferLength++] = firstDigit << 4; - firstDigit = -1; - } - this.firstDigit = firstDigit; - this.bufferLength = bufferLength; - }; - - return AsciiHexStream; -})(); - -var RunLengthStream = (function RunLengthStreamClosure() { - // eslint-disable-next-line no-shadow - function RunLengthStream(str, maybeLength) { - this.str = str; - this.dict = str.dict; - - DecodeStream.call(this, maybeLength); - } - - RunLengthStream.prototype = Object.create(DecodeStream.prototype); - - RunLengthStream.prototype.readBlock = function RunLengthStream_readBlock() { - // The repeatHeader has following format. The first byte defines type of run - // and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes - // (in addition to the second byte from the header), n = 129 through 255 - - // duplicate the second byte from the header (257 - n) times, n = 128 - end. - var repeatHeader = this.str.getBytes(2); - if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) { - this.eof = true; - return; - } - - var buffer; - var bufferLength = this.bufferLength; - var n = repeatHeader[0]; - if (n < 128) { - // copy n bytes - buffer = this.ensureBuffer(bufferLength + n + 1); - buffer[bufferLength++] = repeatHeader[1]; - if (n > 0) { - var source = this.str.getBytes(n); - buffer.set(source, bufferLength); - bufferLength += n; - } - } else { - n = 257 - n; - var b = repeatHeader[1]; - buffer = this.ensureBuffer(bufferLength + n + 1); - for (var i = 0; i < n; i++) { - buffer[bufferLength++] = b; - } - } - this.bufferLength = bufferLength; - }; - - return RunLengthStream; -})(); - -var LZWStream = (function LZWStreamClosure() { - // eslint-disable-next-line no-shadow - function LZWStream(str, maybeLength, earlyChange) { - this.str = str; - this.dict = str.dict; - this.cachedData = 0; - this.bitsCached = 0; - - var maxLzwDictionarySize = 4096; - var lzwState = { - earlyChange, - codeLength: 9, - nextCode: 258, - dictionaryValues: new Uint8Array(maxLzwDictionarySize), - dictionaryLengths: new Uint16Array(maxLzwDictionarySize), - dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), - currentSequence: new Uint8Array(maxLzwDictionarySize), - currentSequenceLength: 0, - }; - for (var i = 0; i < 256; ++i) { - lzwState.dictionaryValues[i] = i; - lzwState.dictionaryLengths[i] = 1; - } - this.lzwState = lzwState; - - DecodeStream.call(this, maybeLength); - } - - LZWStream.prototype = Object.create(DecodeStream.prototype); - - LZWStream.prototype.readBits = function LZWStream_readBits(n) { - var bitsCached = this.bitsCached; - var cachedData = this.cachedData; - while (bitsCached < n) { - var c = this.str.getByte(); - if (c === -1) { - this.eof = true; - return null; - } - cachedData = (cachedData << 8) | c; - bitsCached += 8; - } - this.bitsCached = bitsCached -= n; - this.cachedData = cachedData; - this.lastCode = null; - return (cachedData >>> bitsCached) & ((1 << n) - 1); - }; - - LZWStream.prototype.readBlock = function LZWStream_readBlock() { - var blockSize = 512; - var estimatedDecodedSize = blockSize * 2, - decodedSizeDelta = blockSize; - var i, j, q; - - var lzwState = this.lzwState; - if (!lzwState) { - return; // eof was found - } - - var earlyChange = lzwState.earlyChange; - var nextCode = lzwState.nextCode; - var dictionaryValues = lzwState.dictionaryValues; - var dictionaryLengths = lzwState.dictionaryLengths; - var dictionaryPrevCodes = lzwState.dictionaryPrevCodes; - var codeLength = lzwState.codeLength; - var prevCode = lzwState.prevCode; - var currentSequence = lzwState.currentSequence; - var currentSequenceLength = lzwState.currentSequenceLength; - - var decodedLength = 0; - var currentBufferLength = this.bufferLength; - var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); - - for (i = 0; i < blockSize; i++) { - var code = this.readBits(codeLength); - var hasPrev = currentSequenceLength > 0; - if (code < 256) { - currentSequence[0] = code; - currentSequenceLength = 1; - } else if (code >= 258) { - if (code < nextCode) { - currentSequenceLength = dictionaryLengths[code]; - for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { - currentSequence[j] = dictionaryValues[q]; - q = dictionaryPrevCodes[q]; - } - } else { - currentSequence[currentSequenceLength++] = currentSequence[0]; - } - } else if (code === 256) { - codeLength = 9; - nextCode = 258; - currentSequenceLength = 0; - continue; - } else { - this.eof = true; - delete this.lzwState; - break; - } - - if (hasPrev) { - dictionaryPrevCodes[nextCode] = prevCode; - dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; - dictionaryValues[nextCode] = currentSequence[0]; - nextCode++; - codeLength = - (nextCode + earlyChange) & (nextCode + earlyChange - 1) - ? codeLength - : Math.min( - Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, - 12 - ) | 0; - } - prevCode = code; - - decodedLength += currentSequenceLength; - if (estimatedDecodedSize < decodedLength) { - do { - estimatedDecodedSize += decodedSizeDelta; - } while (estimatedDecodedSize < decodedLength); - buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); - } - for (j = 0; j < currentSequenceLength; j++) { - buffer[currentBufferLength++] = currentSequence[j]; - } - } - lzwState.nextCode = nextCode; - lzwState.codeLength = codeLength; - lzwState.prevCode = prevCode; - lzwState.currentSequenceLength = currentSequenceLength; - - this.bufferLength = currentBufferLength; - }; - - return LZWStream; -})(); - -var NullStream = (function NullStreamClosure() { - // eslint-disable-next-line no-shadow - function NullStream() { - Stream.call(this, new Uint8Array(0)); - } - - NullStream.prototype = Stream.prototype; - - return NullStream; -})(); - -export { - Ascii85Stream, - AsciiHexStream, - DecodeStream, - DecryptStream, - FlateStream, - LZWStream, - NullStream, - PredictorStream, - RunLengthStream, - Stream, - StreamsSequenceStream, - StringStream, -}; +export { NullStream, Stream, StringStream }; diff --git a/test/unit/stream_spec.js b/test/unit/stream_spec.js index 0fcd6d492..6ed566654 100644 --- a/test/unit/stream_spec.js +++ b/test/unit/stream_spec.js @@ -13,8 +13,9 @@ * limitations under the License. */ -import { PredictorStream, Stream } from "../../src/core/stream.js"; import { Dict } from "../../src/core/primitives.js"; +import { PredictorStream } from "../../src/core/predictor_stream.js"; +import { Stream } from "../../src/core/stream.js"; describe("stream", function () { beforeEach(function () {