diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 1200cba9f..6e47bda46 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -20,7 +20,7 @@ import { UNSUPPORTED_FEATURES, Util, warn } from '../shared/util'; import { CMapFactory, IdentityCMap } from './cmap'; -import { DecodeStream, JpegStream, Stream } from './stream'; +import { DecodeStream, Stream } from './stream'; import { Dict, isCmd, isDict, isEOF, isName, isRef, isStream, Name } from './primitives'; @@ -44,6 +44,7 @@ import { ColorSpace } from './colorspace'; import { getGlyphsUnicode } from './glyphlist'; import { getMetrics } from './metrics'; import { isPDFFunction } from './function'; +import { JpegStream } from './jpeg_stream'; import { MurmurHash3_64 } from './murmurhash3'; import { OperatorList } from './operator_list'; import { PDFImage } from './image'; diff --git a/src/core/image.js b/src/core/image.js index 9010bf970..cfc6555c8 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -14,9 +14,10 @@ */ import { assert, FormatError, ImageKind, info, warn } from '../shared/util'; -import { DecodeStream, JpegStream } from './stream'; import { isStream, Name } from './primitives'; import { ColorSpace } from './colorspace'; +import { DecodeStream } from './stream'; +import { JpegStream } from './jpeg_stream'; import { JpxImage } from './jpx'; var PDFImage = (function PDFImageClosure() { diff --git a/src/core/jbig2_stream.js b/src/core/jbig2_stream.js index ed7bec65a..1e3736b6a 100644 --- a/src/core/jbig2_stream.js +++ b/src/core/jbig2_stream.js @@ -22,7 +22,7 @@ import { shadow } from '../shared/util'; * For JBIG2's we use a library to decode these images and * the stream behaves like all the other DecodeStreams. */ -var Jbig2Stream = (function Jbig2StreamClosure() { +let Jbig2Stream = (function Jbig2StreamClosure() { function Jbig2Stream(stream, maybeLength, dict, params) { this.stream = stream; this.maybeLength = maybeLength; @@ -36,36 +36,39 @@ var Jbig2Stream = (function Jbig2StreamClosure() { Object.defineProperty(Jbig2Stream.prototype, 'bytes', { get() { - // If this.maybeLength is null, we'll get the entire stream. + // If `this.maybeLength` is null, we'll get the entire stream. return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength)); }, configurable: true, }); - Jbig2Stream.prototype.ensureBuffer = function(req) { - if (this.bufferLength) { + Jbig2Stream.prototype.ensureBuffer = function(requested) { + // No-op, since `this.readBlock` will always parse the entire image and + // directly insert all of its data into `this.buffer`. + }; + + Jbig2Stream.prototype.readBlock = function() { + if (this.eof) { return; } + let jbig2Image = new Jbig2Image(); - var jbig2Image = new Jbig2Image(); - - var chunks = []; + let chunks = []; if (isDict(this.params)) { - var globalsStream = this.params.get('JBIG2Globals'); + let globalsStream = this.params.get('JBIG2Globals'); if (isStream(globalsStream)) { - var globals = globalsStream.getBytes(); + let globals = globalsStream.getBytes(); chunks.push({ data: globals, start: 0, end: globals.length, }); } } chunks.push({ data: this.bytes, start: 0, end: this.bytes.length, }); - var data = jbig2Image.parseChunks(chunks); - var dataLength = data.length; + let data = jbig2Image.parseChunks(chunks); + let dataLength = data.length; // JBIG2 had black as 1 and white as 0, inverting the colors - for (var i = 0; i < dataLength; i++) { + for (let i = 0; i < dataLength; i++) { data[i] ^= 0xFF; } - this.buffer = data; this.bufferLength = dataLength; this.eof = true; diff --git a/src/core/jpeg_stream.js b/src/core/jpeg_stream.js new file mode 100644 index 000000000..75936ab8f --- /dev/null +++ b/src/core/jpeg_stream.js @@ -0,0 +1,117 @@ +/* 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 { createObjectURL, shadow } from '../shared/util'; +import { DecodeStream } from './stream'; +import { isDict } from './primitives'; +import { JpegImage } from './jpg'; + +/** + * Depending on the type of JPEG a JpegStream is handled in different ways. For + * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image + * data is stored and then loaded by the browser. For unsupported JPEG's we use + * a library to decode these images and the stream behaves like all the other + * DecodeStreams. + */ +let JpegStream = (function JpegStreamClosure() { + function JpegStream(stream, maybeLength, dict, params) { + // Some images may contain 'junk' before the SOI (start-of-image) marker. + // Note: this seems to mainly affect inline images. + let ch; + while ((ch = stream.getByte()) !== -1) { + if (ch === 0xFF) { // Find the first byte of the SOI marker (0xFFD8). + stream.skip(-1); // Reset the stream position to the SOI. + break; + } + } + this.stream = stream; + this.maybeLength = maybeLength; + this.dict = dict; + this.params = params; + + DecodeStream.call(this, maybeLength); + } + + JpegStream.prototype = Object.create(DecodeStream.prototype); + + Object.defineProperty(JpegStream.prototype, 'bytes', { + get: function JpegStream_bytes() { + // If `this.maybeLength` is null, we'll get the entire stream. + return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength)); + }, + configurable: true, + }); + + JpegStream.prototype.ensureBuffer = function(requested) { + // No-op, since `this.readBlock` will always parse the entire image and + // directly insert all of its data into `this.buffer`. + }; + + JpegStream.prototype.readBlock = function() { + if (this.eof) { + return; + } + let jpegImage = new JpegImage(); + + // Checking if values need to be transformed before conversion. + let decodeArr = this.dict.getArray('Decode', 'D'); + if (this.forceRGB && Array.isArray(decodeArr)) { + let bitsPerComponent = this.dict.get('BitsPerComponent') || 8; + let decodeArrLength = decodeArr.length; + let transform = new Int32Array(decodeArrLength); + let transformNeeded = false; + let maxValue = (1 << bitsPerComponent) - 1; + for (let i = 0; i < decodeArrLength; i += 2) { + transform[i] = ((decodeArr[i + 1] - decodeArr[i]) * 256) | 0; + transform[i + 1] = (decodeArr[i] * maxValue) | 0; + if (transform[i] !== 256 || transform[i + 1] !== 0) { + transformNeeded = true; + } + } + if (transformNeeded) { + jpegImage.decodeTransform = transform; + } + } + // Fetching the 'ColorTransform' entry, if it exists. + if (isDict(this.params)) { + let colorTransform = this.params.get('ColorTransform'); + if (Number.isInteger(colorTransform)) { + jpegImage.colorTransform = colorTransform; + } + } + + jpegImage.parse(this.bytes); + let data = jpegImage.getData(this.drawWidth, this.drawHeight, + this.forceRGB); + this.buffer = data; + this.bufferLength = data.length; + this.eof = true; + }; + + JpegStream.prototype.getBytes = function(length) { + this.readBlock(); + return this.buffer; + }; + + JpegStream.prototype.getIR = function(forceDataSchema = false) { + return createObjectURL(this.bytes, 'image/jpeg', forceDataSchema); + }; + + return JpegStream; +})(); + +export { + JpegStream, +}; diff --git a/src/core/jpx_stream.js b/src/core/jpx_stream.js new file mode 100644 index 000000000..7e564196f --- /dev/null +++ b/src/core/jpx_stream.js @@ -0,0 +1,96 @@ +/* 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 './stream'; +import { JpxImage } from './jpx'; +import { shadow } from '../shared/util'; + +/** + * For JPEG 2000's we use a library to decode these images and + * the stream behaves like all the other DecodeStreams. + */ +let JpxStream = (function JpxStreamClosure() { + function JpxStream(stream, maybeLength, dict, params) { + this.stream = stream; + this.maybeLength = maybeLength; + this.dict = dict; + this.params = params; + + DecodeStream.call(this, maybeLength); + } + + JpxStream.prototype = Object.create(DecodeStream.prototype); + + Object.defineProperty(JpxStream.prototype, 'bytes', { + get: function JpxStream_bytes() { + // If `this.maybeLength` is null, we'll get the entire stream. + return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength)); + }, + configurable: true, + }); + + JpxStream.prototype.ensureBuffer = function(requested) { + // No-op, since `this.readBlock` will always parse the entire image and + // directly insert all of its data into `this.buffer`. + }; + + JpxStream.prototype.readBlock = function() { + if (this.eof) { + return; + } + let jpxImage = new JpxImage(); + jpxImage.parse(this.bytes); + + let width = jpxImage.width; + let height = jpxImage.height; + let componentsCount = jpxImage.componentsCount; + let tileCount = jpxImage.tiles.length; + if (tileCount === 1) { + this.buffer = jpxImage.tiles[0].items; + } else { + let data = new Uint8ClampedArray(width * height * componentsCount); + + for (let k = 0; k < tileCount; k++) { + let tileComponents = jpxImage.tiles[k]; + let tileWidth = tileComponents.width; + let tileHeight = tileComponents.height; + let tileLeft = tileComponents.left; + let tileTop = tileComponents.top; + + let src = tileComponents.items; + let srcPosition = 0; + let dataPosition = (width * tileTop + tileLeft) * componentsCount; + let imgRowSize = width * componentsCount; + let tileRowSize = tileWidth * componentsCount; + + for (let j = 0; j < tileHeight; j++) { + let rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize); + data.set(rowBytes, dataPosition); + srcPosition += tileRowSize; + dataPosition += imgRowSize; + } + } + this.buffer = data; + } + this.bufferLength = this.buffer.length; + this.eof = true; + }; + + return JpxStream; +})(); + +export { + JpxStream, +}; diff --git a/src/core/parser.js b/src/core/parser.js index 54cfc3568..e6e60cd49 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -14,8 +14,8 @@ */ import { - Ascii85Stream, AsciiHexStream, FlateStream, JpegStream, JpxStream, LZWStream, - NullStream, PredictorStream, RunLengthStream + Ascii85Stream, AsciiHexStream, FlateStream, LZWStream, NullStream, + PredictorStream, RunLengthStream } from './stream'; import { assert, FormatError, info, isNum, isString, MissingDataException, StreamType, @@ -26,6 +26,8 @@ import { } from './primitives'; import { CCITTFaxStream } from './ccitt_stream'; import { Jbig2Stream } from './jbig2_stream'; +import { JpegStream } from './jpeg_stream'; +import { JpxStream } from './jpx_stream'; var MAX_LENGTH_TO_CACHE = 1000; diff --git a/src/core/stream.js b/src/core/stream.js index fbf957e8a..4bc5eb57e 100644 --- a/src/core/stream.js +++ b/src/core/stream.js @@ -13,12 +13,8 @@ * limitations under the License. */ -import { - createObjectURL, FormatError, isSpace, shadow, stringToBytes, Util -} from '../shared/util'; +import { FormatError, isSpace, stringToBytes, Util } from '../shared/util'; import { isDict } from './primitives'; -import { JpegImage } from './jpg'; -import { JpxImage } from './jpx'; var Stream = (function StreamClosure() { function Stream(arrayBuffer, start, length, dict) { @@ -871,165 +867,6 @@ var PredictorStream = (function PredictorStreamClosure() { return PredictorStream; })(); -/** - * Depending on the type of JPEG a JpegStream is handled in different ways. For - * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image - * data is stored and then loaded by the browser. For unsupported JPEG's we use - * a library to decode these images and the stream behaves like all the other - * DecodeStreams. - */ -var JpegStream = (function JpegStreamClosure() { - function JpegStream(stream, maybeLength, dict, params) { - // Some images may contain 'junk' before the SOI (start-of-image) marker. - // Note: this seems to mainly affect inline images. - var ch; - while ((ch = stream.getByte()) !== -1) { - if (ch === 0xFF) { // Find the first byte of the SOI marker (0xFFD8). - stream.skip(-1); // Reset the stream position to the SOI. - break; - } - } - this.stream = stream; - this.maybeLength = maybeLength; - this.dict = dict; - this.params = params; - - DecodeStream.call(this, maybeLength); - } - - JpegStream.prototype = Object.create(DecodeStream.prototype); - - Object.defineProperty(JpegStream.prototype, 'bytes', { - get: function JpegStream_bytes() { - // If this.maybeLength is null, we'll get the entire stream. - return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength)); - }, - configurable: true, - }); - - JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) { - if (this.bufferLength) { - return; - } - var jpegImage = new JpegImage(); - - // Checking if values need to be transformed before conversion. - var decodeArr = this.dict.getArray('Decode', 'D'); - if (this.forceRGB && Array.isArray(decodeArr)) { - var bitsPerComponent = this.dict.get('BitsPerComponent') || 8; - var decodeArrLength = decodeArr.length; - var transform = new Int32Array(decodeArrLength); - var transformNeeded = false; - var maxValue = (1 << bitsPerComponent) - 1; - for (var i = 0; i < decodeArrLength; i += 2) { - transform[i] = ((decodeArr[i + 1] - decodeArr[i]) * 256) | 0; - transform[i + 1] = (decodeArr[i] * maxValue) | 0; - if (transform[i] !== 256 || transform[i + 1] !== 0) { - transformNeeded = true; - } - } - if (transformNeeded) { - jpegImage.decodeTransform = transform; - } - } - // Fetching the 'ColorTransform' entry, if it exists. - if (isDict(this.params)) { - var colorTransform = this.params.get('ColorTransform'); - if (Number.isInteger(colorTransform)) { - jpegImage.colorTransform = colorTransform; - } - } - - jpegImage.parse(this.bytes); - var data = jpegImage.getData(this.drawWidth, this.drawHeight, - this.forceRGB); - this.buffer = data; - this.bufferLength = data.length; - this.eof = true; - }; - - JpegStream.prototype.getBytes = function JpegStream_getBytes(length) { - this.ensureBuffer(); - return this.buffer; - }; - - JpegStream.prototype.getIR = function JpegStream_getIR(forceDataSchema) { - return createObjectURL(this.bytes, 'image/jpeg', forceDataSchema); - }; - - return JpegStream; -})(); - -/** - * For JPEG 2000's we use a library to decode these images and - * the stream behaves like all the other DecodeStreams. - */ -var JpxStream = (function JpxStreamClosure() { - function JpxStream(stream, maybeLength, dict, params) { - this.stream = stream; - this.maybeLength = maybeLength; - this.dict = dict; - this.params = params; - - DecodeStream.call(this, maybeLength); - } - - JpxStream.prototype = Object.create(DecodeStream.prototype); - - Object.defineProperty(JpxStream.prototype, 'bytes', { - get: function JpxStream_bytes() { - // If this.maybeLength is null, we'll get the entire stream. - return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength)); - }, - configurable: true, - }); - - JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) { - if (this.bufferLength) { - return; - } - - var jpxImage = new JpxImage(); - jpxImage.parse(this.bytes); - - var width = jpxImage.width; - var height = jpxImage.height; - var componentsCount = jpxImage.componentsCount; - var tileCount = jpxImage.tiles.length; - if (tileCount === 1) { - this.buffer = jpxImage.tiles[0].items; - } else { - var data = new Uint8ClampedArray(width * height * componentsCount); - - for (var k = 0; k < tileCount; k++) { - var tileComponents = jpxImage.tiles[k]; - var tileWidth = tileComponents.width; - var tileHeight = tileComponents.height; - var tileLeft = tileComponents.left; - var tileTop = tileComponents.top; - - var src = tileComponents.items; - var srcPosition = 0; - var dataPosition = (width * tileTop + tileLeft) * componentsCount; - var imgRowSize = width * componentsCount; - var tileRowSize = tileWidth * componentsCount; - - for (var j = 0; j < tileHeight; j++) { - var rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize); - data.set(rowBytes, dataPosition); - srcPosition += tileRowSize; - dataPosition += imgRowSize; - } - } - this.buffer = data; - } - this.bufferLength = this.buffer.length; - this.eof = true; - }; - - return JpxStream; -})(); - var DecryptStream = (function DecryptStreamClosure() { function DecryptStream(str, maybeLength, decrypt) { this.str = str; @@ -1414,8 +1251,6 @@ export { DecryptStream, DecodeStream, FlateStream, - JpegStream, - JpxStream, NullStream, PredictorStream, RunLengthStream,