diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 3389f7a30..6d0d3969e 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 { 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/jpeg_stream.js b/src/core/jpeg_stream.js new file mode 100644 index 000000000..e99a5ea82 --- /dev/null +++ b/src/core/jpeg_stream.js @@ -0,0 +1,112 @@ +/* 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. + */ +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; +})(); + +export { + JpegStream, +}; diff --git a/src/core/jpx_stream.js b/src/core/jpx_stream.js new file mode 100644 index 000000000..556469c1c --- /dev/null +++ b/src/core/jpx_stream.js @@ -0,0 +1,92 @@ +/* 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. + */ +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; +})(); + +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,