From de5297b9ea28c5a508304c7270bc708effd42892 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 26 Oct 2017 13:15:57 +0200 Subject: [PATCH] Fix the interface of `JpegStream`/`JpxStream`/`Jbig2Stream` to agree with the other `DecodeStream`s The interface of all of the "image" streams look kind of weird, and I'm actually a bit surprised that there hasn't been any errors because of it. For example: None of them actually implement `readBlock` methods, and it seems more luck that anything else that we're not calling `getBytes()` (without providing a length) for those streams, since that would trigger a code-path in `getBytes` that assumes `readBlock` to exist. To address this long-standing issue, the `ensureBuffer` methods are thus renamed to `readBlock`. Furthermore, the new `ensureBuffer` methods are now no-ops. Finally, this patch also replaces `var` with `let` in a number of places. --- src/core/jbig2_stream.js | 29 ++++++++++++---------- src/core/jpeg_stream.js | 43 ++++++++++++++++++--------------- src/core/jpx_stream.js | 52 +++++++++++++++++++++------------------- 3 files changed, 68 insertions(+), 56 deletions(-) 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 index e99a5ea82..75936ab8f 100644 --- a/src/core/jpeg_stream.js +++ b/src/core/jpeg_stream.js @@ -21,15 +21,15 @@ 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 + * 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() { +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. - var ch; + 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. @@ -48,27 +48,32 @@ var JpegStream = (function JpegStreamClosure() { Object.defineProperty(JpegStream.prototype, 'bytes', { get: function JpegStream_bytes() { - // 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, }); - JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) { - if (this.bufferLength) { + 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; } - var jpegImage = new JpegImage(); + let jpegImage = new JpegImage(); // Checking if values need to be transformed before conversion. - var decodeArr = this.dict.getArray('Decode', 'D'); + let 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) { + 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) { @@ -81,26 +86,26 @@ var JpegStream = (function JpegStreamClosure() { } // Fetching the 'ColorTransform' entry, if it exists. if (isDict(this.params)) { - var colorTransform = this.params.get('ColorTransform'); + let colorTransform = this.params.get('ColorTransform'); if (Number.isInteger(colorTransform)) { jpegImage.colorTransform = colorTransform; } } jpegImage.parse(this.bytes); - var data = jpegImage.getData(this.drawWidth, this.drawHeight, + let 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(); + JpegStream.prototype.getBytes = function(length) { + this.readBlock(); return this.buffer; }; - JpegStream.prototype.getIR = function JpegStream_getIR(forceDataSchema) { + JpegStream.prototype.getIR = function(forceDataSchema = false) { return createObjectURL(this.bytes, 'image/jpeg', forceDataSchema); }; diff --git a/src/core/jpx_stream.js b/src/core/jpx_stream.js index 556469c1c..7e564196f 100644 --- a/src/core/jpx_stream.js +++ b/src/core/jpx_stream.js @@ -21,7 +21,7 @@ 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() { +let JpxStream = (function JpxStreamClosure() { function JpxStream(stream, maybeLength, dict, params) { this.stream = stream; this.maybeLength = maybeLength; @@ -35,44 +35,48 @@ var JpxStream = (function JpxStreamClosure() { Object.defineProperty(JpxStream.prototype, 'bytes', { get: function JpxStream_bytes() { - // 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, }); - JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) { - if (this.bufferLength) { + 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; } - - var jpxImage = new JpxImage(); + let jpxImage = new JpxImage(); jpxImage.parse(this.bytes); - var width = jpxImage.width; - var height = jpxImage.height; - var componentsCount = jpxImage.componentsCount; - var tileCount = jpxImage.tiles.length; + 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 { - var data = new Uint8ClampedArray(width * height * componentsCount); + let 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; + 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; - var src = tileComponents.items; - var srcPosition = 0; - var dataPosition = (width * tileTop + tileLeft) * componentsCount; - var imgRowSize = width * componentsCount; - var tileRowSize = tileWidth * componentsCount; + let src = tileComponents.items; + let srcPosition = 0; + let dataPosition = (width * tileTop + tileLeft) * componentsCount; + let imgRowSize = width * componentsCount; + let tileRowSize = tileWidth * componentsCount; - for (var j = 0; j < tileHeight; j++) { - var rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize); + for (let j = 0; j < tileHeight; j++) { + let rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize); data.set(rowBytes, dataPosition); srcPosition += tileRowSize; dataPosition += imgRowSize;