From 0addf3a0d4bd77f0db5c865c2ad5c515f193a04d Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:27:53 +0200 Subject: [PATCH] Convert `src/core/jbig2.js` to use standard classes *Please note:* Ignoring whitespace-only changes is probably necessary in order to review this. --- src/core/jbig2.js | 4621 ++++++++++++++++++++++----------------------- 1 file changed, 2298 insertions(+), 2323 deletions(-) diff --git a/src/core/jbig2.js b/src/core/jbig2.js index 50375df4a..4bfeee70f 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -24,57 +24,54 @@ class Jbig2Error extends BaseException { } } -const Jbig2Image = (function Jbig2ImageClosure() { - // Utility data structures - function ContextCache() {} +// Utility data structures +class ContextCache { + getContexts(id) { + if (id in this) { + return this[id]; + } + return (this[id] = new Int8Array(1 << 16)); + } +} - ContextCache.prototype = { - getContexts(id) { - if (id in this) { - return this[id]; - } - return (this[id] = new Int8Array(1 << 16)); - }, - }; - - function DecodingContext(data, start, end) { +class DecodingContext { + constructor(data, start, end) { this.data = data; this.start = start; this.end = end; } - DecodingContext.prototype = { - get decoder() { - const decoder = new ArithmeticDecoder(this.data, this.start, this.end); - return shadow(this, "decoder", decoder); - }, - get contextCache() { - const cache = new ContextCache(); - return shadow(this, "contextCache", cache); - }, - }; + get decoder() { + const decoder = new ArithmeticDecoder(this.data, this.start, this.end); + return shadow(this, "decoder", decoder); + } - // Annex A. Arithmetic Integer Decoding Procedure - // A.2 Procedure for decoding values - function decodeInteger(contextCache, procedure, decoder) { - const contexts = contextCache.getContexts(procedure); - let prev = 1; + get contextCache() { + const cache = new ContextCache(); + return shadow(this, "contextCache", cache); + } +} - function readBits(length) { - let v = 0; - for (let i = 0; i < length; i++) { - const bit = decoder.readBit(contexts, prev); - prev = - prev < 256 ? (prev << 1) | bit : (((prev << 1) | bit) & 511) | 256; - v = (v << 1) | bit; - } - return v >>> 0; +// Annex A. Arithmetic Integer Decoding Procedure +// A.2 Procedure for decoding values +function decodeInteger(contextCache, procedure, decoder) { + const contexts = contextCache.getContexts(procedure); + let prev = 1; + + function readBits(length) { + let v = 0; + for (let i = 0; i < length; i++) { + const bit = decoder.readBit(contexts, prev); + prev = prev < 256 ? (prev << 1) | bit : (((prev << 1) | bit) & 511) | 256; + v = (v << 1) | bit; } + return v >>> 0; + } - const sign = readBits(1); - // prettier-ignore - /* eslint-disable no-nested-ternary */ - const value = readBits(1) ? + const sign = readBits(1); + // prettier-ignore + /* eslint-disable no-nested-ternary */ + const value = readBits(1) ? (readBits(1) ? (readBits(1) ? (readBits(1) ? @@ -85,1739 +82,1722 @@ const Jbig2Image = (function Jbig2ImageClosure() { readBits(6) + 20) : readBits(4) + 4) : readBits(2); - /* eslint-enable no-nested-ternary */ - if (sign === 0) { - return value; - } else if (value > 0) { - return -value; - } - return null; + /* eslint-enable no-nested-ternary */ + if (sign === 0) { + return value; + } else if (value > 0) { + return -value; } + return null; +} - // A.3 The IAID decoding procedure - function decodeIAID(contextCache, decoder, codeLength) { - const contexts = contextCache.getContexts("IAID"); +// A.3 The IAID decoding procedure +function decodeIAID(contextCache, decoder, codeLength) { + const contexts = contextCache.getContexts("IAID"); - let prev = 1; - for (let i = 0; i < codeLength; i++) { - const bit = decoder.readBit(contexts, prev); - prev = (prev << 1) | bit; - } - if (codeLength < 31) { - return prev & ((1 << codeLength) - 1); - } - return prev & 0x7fffffff; + let prev = 1; + for (let i = 0; i < codeLength; i++) { + const bit = decoder.readBit(contexts, prev); + prev = (prev << 1) | bit; } + if (codeLength < 31) { + return prev & ((1 << codeLength) - 1); + } + return prev & 0x7fffffff; +} - // 7.3 Segment types - const SegmentTypes = [ - "SymbolDictionary", - null, - null, - null, - "IntermediateTextRegion", - null, - "ImmediateTextRegion", - "ImmediateLosslessTextRegion", - null, - null, - null, - null, - null, - null, - null, - null, - "PatternDictionary", - null, - null, - null, - "IntermediateHalftoneRegion", - null, - "ImmediateHalftoneRegion", - "ImmediateLosslessHalftoneRegion", - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - "IntermediateGenericRegion", - null, - "ImmediateGenericRegion", - "ImmediateLosslessGenericRegion", - "IntermediateGenericRefinementRegion", - null, - "ImmediateGenericRefinementRegion", - "ImmediateLosslessGenericRefinementRegion", - null, - null, - null, - null, - "PageInformation", - "EndOfPage", - "EndOfStripe", - "EndOfFile", - "Profiles", - "Tables", - null, - null, - null, - null, - null, - null, - null, - null, - "Extension", - ]; +// 7.3 Segment types +const SegmentTypes = [ + "SymbolDictionary", + null, + null, + null, + "IntermediateTextRegion", + null, + "ImmediateTextRegion", + "ImmediateLosslessTextRegion", + null, + null, + null, + null, + null, + null, + null, + null, + "PatternDictionary", + null, + null, + null, + "IntermediateHalftoneRegion", + null, + "ImmediateHalftoneRegion", + "ImmediateLosslessHalftoneRegion", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "IntermediateGenericRegion", + null, + "ImmediateGenericRegion", + "ImmediateLosslessGenericRegion", + "IntermediateGenericRefinementRegion", + null, + "ImmediateGenericRefinementRegion", + "ImmediateLosslessGenericRefinementRegion", + null, + null, + null, + null, + "PageInformation", + "EndOfPage", + "EndOfStripe", + "EndOfFile", + "Profiles", + "Tables", + null, + null, + null, + null, + null, + null, + null, + null, + "Extension", +]; - const CodingTemplates = [ - [ - { x: -1, y: -2 }, - { x: 0, y: -2 }, - { x: 1, y: -2 }, - { x: -2, y: -1 }, +const CodingTemplates = [ + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: 2, y: -1 }, + { x: -4, y: 0 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: 2, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: 2, y: -1 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -3, y: -1 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -4, y: 0 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], +]; + +const RefinementTemplates = [ + { + coding: [ + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + ], + reference: [ + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: -1, y: 1 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ], + }, + { + coding: [ { x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, - { x: 2, y: -1 }, - { x: -4, y: 0 }, - { x: -3, y: 0 }, - { x: -2, y: 0 }, { x: -1, y: 0 }, ], - [ - { x: -1, y: -2 }, - { x: 0, y: -2 }, - { x: 1, y: -2 }, - { x: 2, y: -2 }, - { x: -2, y: -1 }, - { x: -1, y: -1 }, + reference: [ { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: 2, y: -1 }, - { x: -3, y: 0 }, - { x: -2, y: 0 }, { x: -1, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, ], - [ - { x: -1, y: -2 }, - { x: 0, y: -2 }, - { x: 1, y: -2 }, - { x: -2, y: -1 }, - { x: -1, y: -1 }, - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -2, y: 0 }, - { x: -1, y: 0 }, - ], - [ - { x: -3, y: -1 }, - { x: -2, y: -1 }, - { x: -1, y: -1 }, - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -4, y: 0 }, - { x: -3, y: 0 }, - { x: -2, y: 0 }, - { x: -1, y: 0 }, - ], - ]; + }, +]; - const RefinementTemplates = [ - { - coding: [ - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -1, y: 0 }, - ], - reference: [ - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -1, y: 0 }, - { x: 0, y: 0 }, - { x: 1, y: 0 }, - { x: -1, y: 1 }, - { x: 0, y: 1 }, - { x: 1, y: 1 }, - ], - }, - { - coding: [ - { x: -1, y: -1 }, - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -1, y: 0 }, - ], - reference: [ - { x: 0, y: -1 }, - { x: -1, y: 0 }, - { x: 0, y: 0 }, - { x: 1, y: 0 }, - { x: 0, y: 1 }, - { x: 1, y: 1 }, - ], - }, - ]; +// See 6.2.5.7 Decoding the bitmap. +const ReusedContexts = [ + 0x9b25, // 10011 0110010 0101 + 0x0795, // 0011 110010 101 + 0x00e5, // 001 11001 01 + 0x0195, // 011001 0101 +]; - // See 6.2.5.7 Decoding the bitmap. - const ReusedContexts = [ - 0x9b25, // 10011 0110010 0101 - 0x0795, // 0011 110010 101 - 0x00e5, // 001 11001 01 - 0x0195, // 011001 0101 - ]; +const RefinementReusedContexts = [ + 0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference) + 0x0008, // '0000' + '001000' +]; - const RefinementReusedContexts = [ - 0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference) - 0x0008, // '0000' + '001000' - ]; +function decodeBitmapTemplate0(width, height, decodingContext) { + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + const bitmap = []; + let contextLabel, i, j, pixel, row, row1, row2; - function decodeBitmapTemplate0(width, height, decodingContext) { - const decoder = decodingContext.decoder; - const contexts = decodingContext.contextCache.getContexts("GB"); - const bitmap = []; - let contextLabel, i, j, pixel, row, row1, row2; + // ...ooooo.... + // ..ooooooo... Context template for current pixel (X) + // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel) + const OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 - // ...ooooo.... - // ..ooooooo... Context template for current pixel (X) - // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel) - const OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 + for (i = 0; i < height; i++) { + row = bitmap[i] = new Uint8Array(width); + row1 = i < 1 ? row : bitmap[i - 1]; + row2 = i < 2 ? row : bitmap[i - 2]; - for (i = 0; i < height; i++) { - row = bitmap[i] = new Uint8Array(width); - row1 = i < 1 ? row : bitmap[i - 1]; - row2 = i < 2 ? row : bitmap[i - 2]; + // At the beginning of each row: + // Fill contextLabel with pixels that are above/right of (X) + contextLabel = + (row2[0] << 13) | + (row2[1] << 12) | + (row2[2] << 11) | + (row1[0] << 7) | + (row1[1] << 6) | + (row1[2] << 5) | + (row1[3] << 4); - // At the beginning of each row: - // Fill contextLabel with pixels that are above/right of (X) + for (j = 0; j < width; j++) { + row[j] = pixel = decoder.readBit(contexts, contextLabel); + + // At each pixel: Clear contextLabel pixels that are shifted + // out of the context, then add new ones. contextLabel = - (row2[0] << 13) | - (row2[1] << 12) | - (row2[2] << 11) | - (row1[0] << 7) | - (row1[1] << 6) | - (row1[2] << 5) | - (row1[3] << 4); - - for (j = 0; j < width; j++) { - row[j] = pixel = decoder.readBit(contexts, contextLabel); - - // At each pixel: Clear contextLabel pixels that are shifted - // out of the context, then add new ones. - contextLabel = - ((contextLabel & OLD_PIXEL_MASK) << 1) | - (j + 3 < width ? row2[j + 3] << 11 : 0) | - (j + 4 < width ? row1[j + 4] << 4 : 0) | - pixel; - } + ((contextLabel & OLD_PIXEL_MASK) << 1) | + (j + 3 < width ? row2[j + 3] << 11 : 0) | + (j + 4 < width ? row1[j + 4] << 4 : 0) | + pixel; } - - return bitmap; } - // 6.2 Generic Region Decoding Procedure - function decodeBitmap( - mmr, - width, - height, - templateIndex, - prediction, - skip, - at, - decodingContext + return bitmap; +} + +// 6.2 Generic Region Decoding Procedure +function decodeBitmap( + mmr, + width, + height, + templateIndex, + prediction, + skip, + at, + decodingContext +) { + if (mmr) { + const input = new Reader( + decodingContext.data, + decodingContext.start, + decodingContext.end + ); + return decodeMMRBitmap(input, width, height, false); + } + + // Use optimized version for the most common case + if ( + templateIndex === 0 && + !skip && + !prediction && + at.length === 4 && + at[0].x === 3 && + at[0].y === -1 && + at[1].x === -3 && + at[1].y === -1 && + at[2].x === 2 && + at[2].y === -2 && + at[3].x === -2 && + at[3].y === -2 ) { - if (mmr) { - const input = new Reader( - decodingContext.data, - decodingContext.start, - decodingContext.end - ); - return decodeMMRBitmap(input, width, height, false); - } + return decodeBitmapTemplate0(width, height, decodingContext); + } - // Use optimized version for the most common case + const useskip = !!skip; + const template = CodingTemplates[templateIndex].concat(at); + + // Sorting is non-standard, and it is not required. But sorting increases + // the number of template bits that can be reused from the previous + // contextLabel in the main loop. + template.sort(function (a, b) { + return a.y - b.y || a.x - b.x; + }); + + const templateLength = template.length; + const templateX = new Int8Array(templateLength); + const templateY = new Int8Array(templateLength); + const changingTemplateEntries = []; + let reuseMask = 0, + minX = 0, + maxX = 0, + minY = 0; + let c, k; + + for (k = 0; k < templateLength; k++) { + templateX[k] = template[k].x; + templateY[k] = template[k].y; + minX = Math.min(minX, template[k].x); + maxX = Math.max(maxX, template[k].x); + minY = Math.min(minY, template[k].y); + // Check if the template pixel appears in two consecutive context labels, + // so it can be reused. Otherwise, we add it to the list of changing + // template entries. if ( - templateIndex === 0 && - !skip && - !prediction && - at.length === 4 && - at[0].x === 3 && - at[0].y === -1 && - at[1].x === -3 && - at[1].y === -1 && - at[2].x === 2 && - at[2].y === -2 && - at[3].x === -2 && - at[3].y === -2 + k < templateLength - 1 && + template[k].y === template[k + 1].y && + template[k].x === template[k + 1].x - 1 ) { - return decodeBitmapTemplate0(width, height, decodingContext); + reuseMask |= 1 << (templateLength - 1 - k); + } else { + changingTemplateEntries.push(k); } + } + const changingEntriesLength = changingTemplateEntries.length; - const useskip = !!skip; - const template = CodingTemplates[templateIndex].concat(at); + const changingTemplateX = new Int8Array(changingEntriesLength); + const changingTemplateY = new Int8Array(changingEntriesLength); + const changingTemplateBit = new Uint16Array(changingEntriesLength); + for (c = 0; c < changingEntriesLength; c++) { + k = changingTemplateEntries[c]; + changingTemplateX[c] = template[k].x; + changingTemplateY[c] = template[k].y; + changingTemplateBit[c] = 1 << (templateLength - 1 - k); + } - // Sorting is non-standard, and it is not required. But sorting increases - // the number of template bits that can be reused from the previous - // contextLabel in the main loop. - template.sort(function (a, b) { - return a.y - b.y || a.x - b.x; - }); + // Get the safe bounding box edges from the width, height, minX, maxX, minY + const sbb_left = -minX; + const sbb_top = -minY; + const sbb_right = width - maxX; - const templateLength = template.length; - const templateX = new Int8Array(templateLength); - const templateY = new Int8Array(templateLength); - const changingTemplateEntries = []; - let reuseMask = 0, - minX = 0, - maxX = 0, - minY = 0; - let c, k; + const pseudoPixelContext = ReusedContexts[templateIndex]; + let row = new Uint8Array(width); + const bitmap = []; - for (k = 0; k < templateLength; k++) { - templateX[k] = template[k].x; - templateY[k] = template[k].y; - minX = Math.min(minX, template[k].x); - maxX = Math.max(maxX, template[k].x); - minY = Math.min(minY, template[k].y); - // Check if the template pixel appears in two consecutive context labels, - // so it can be reused. Otherwise, we add it to the list of changing - // template entries. - if ( - k < templateLength - 1 && - template[k].y === template[k + 1].y && - template[k].x === template[k + 1].x - 1 - ) { - reuseMask |= 1 << (templateLength - 1 - k); - } else { - changingTemplateEntries.push(k); + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + + let ltp = 0, + j, + i0, + j0, + contextLabel = 0, + bit, + shift; + for (let i = 0; i < height; i++) { + if (prediction) { + const sltp = decoder.readBit(contexts, pseudoPixelContext); + ltp ^= sltp; + if (ltp) { + bitmap.push(row); // duplicate previous row + continue; } } - const changingEntriesLength = changingTemplateEntries.length; - - const changingTemplateX = new Int8Array(changingEntriesLength); - const changingTemplateY = new Int8Array(changingEntriesLength); - const changingTemplateBit = new Uint16Array(changingEntriesLength); - for (c = 0; c < changingEntriesLength; c++) { - k = changingTemplateEntries[c]; - changingTemplateX[c] = template[k].x; - changingTemplateY[c] = template[k].y; - changingTemplateBit[c] = 1 << (templateLength - 1 - k); - } - - // Get the safe bounding box edges from the width, height, minX, maxX, minY - const sbb_left = -minX; - const sbb_top = -minY; - const sbb_right = width - maxX; - - const pseudoPixelContext = ReusedContexts[templateIndex]; - let row = new Uint8Array(width); - const bitmap = []; - - const decoder = decodingContext.decoder; - const contexts = decodingContext.contextCache.getContexts("GB"); - - let ltp = 0, - j, - i0, - j0, - contextLabel = 0, - bit, - shift; - for (let i = 0; i < height; i++) { - if (prediction) { - const sltp = decoder.readBit(contexts, pseudoPixelContext); - ltp ^= sltp; - if (ltp) { - bitmap.push(row); // duplicate previous row - continue; - } + row = new Uint8Array(row); + bitmap.push(row); + for (j = 0; j < width; j++) { + if (useskip && skip[i][j]) { + row[j] = 0; + continue; } - row = new Uint8Array(row); - bitmap.push(row); - for (j = 0; j < width; j++) { - if (useskip && skip[i][j]) { - row[j] = 0; - continue; - } - // Are we in the middle of a scanline, so we can reuse contextLabel - // bits? - if (j >= sbb_left && j < sbb_right && i >= sbb_top) { - // If yes, we can just shift the bits that are reusable and only - // fetch the remaining ones. - contextLabel = (contextLabel << 1) & reuseMask; - for (k = 0; k < changingEntriesLength; k++) { - i0 = i + changingTemplateY[k]; - j0 = j + changingTemplateX[k]; - bit = bitmap[i0][j0]; - if (bit) { - bit = changingTemplateBit[k]; - contextLabel |= bit; - } + // Are we in the middle of a scanline, so we can reuse contextLabel + // bits? + if (j >= sbb_left && j < sbb_right && i >= sbb_top) { + // If yes, we can just shift the bits that are reusable and only + // fetch the remaining ones. + contextLabel = (contextLabel << 1) & reuseMask; + for (k = 0; k < changingEntriesLength; k++) { + i0 = i + changingTemplateY[k]; + j0 = j + changingTemplateX[k]; + bit = bitmap[i0][j0]; + if (bit) { + bit = changingTemplateBit[k]; + contextLabel |= bit; } - } else { - // compute the contextLabel from scratch - contextLabel = 0; - shift = templateLength - 1; - for (k = 0; k < templateLength; k++, shift--) { - j0 = j + templateX[k]; - if (j0 >= 0 && j0 < width) { - i0 = i + templateY[k]; - if (i0 >= 0) { - bit = bitmap[i0][j0]; - if (bit) { - contextLabel |= bit << shift; - } + } + } else { + // compute the contextLabel from scratch + contextLabel = 0; + shift = templateLength - 1; + for (k = 0; k < templateLength; k++, shift--) { + j0 = j + templateX[k]; + if (j0 >= 0 && j0 < width) { + i0 = i + templateY[k]; + if (i0 >= 0) { + bit = bitmap[i0][j0]; + if (bit) { + contextLabel |= bit << shift; } } } } - const pixel = decoder.readBit(contexts, contextLabel); - row[j] = pixel; } + const pixel = decoder.readBit(contexts, contextLabel); + row[j] = pixel; } - return bitmap; + } + return bitmap; +} + +// 6.3.2 Generic Refinement Region Decoding Procedure +function decodeRefinement( + width, + height, + templateIndex, + referenceBitmap, + offsetX, + offsetY, + prediction, + at, + decodingContext +) { + let codingTemplate = RefinementTemplates[templateIndex].coding; + if (templateIndex === 0) { + codingTemplate = codingTemplate.concat([at[0]]); + } + const codingTemplateLength = codingTemplate.length; + const codingTemplateX = new Int32Array(codingTemplateLength); + const codingTemplateY = new Int32Array(codingTemplateLength); + let k; + for (k = 0; k < codingTemplateLength; k++) { + codingTemplateX[k] = codingTemplate[k].x; + codingTemplateY[k] = codingTemplate[k].y; } - // 6.3.2 Generic Refinement Region Decoding Procedure - function decodeRefinement( - width, - height, - templateIndex, - referenceBitmap, - offsetX, - offsetY, - prediction, - at, - decodingContext - ) { - let codingTemplate = RefinementTemplates[templateIndex].coding; - if (templateIndex === 0) { - codingTemplate = codingTemplate.concat([at[0]]); - } - const codingTemplateLength = codingTemplate.length; - const codingTemplateX = new Int32Array(codingTemplateLength); - const codingTemplateY = new Int32Array(codingTemplateLength); - let k; - for (k = 0; k < codingTemplateLength; k++) { - codingTemplateX[k] = codingTemplate[k].x; - codingTemplateY[k] = codingTemplate[k].y; - } - - let referenceTemplate = RefinementTemplates[templateIndex].reference; - if (templateIndex === 0) { - referenceTemplate = referenceTemplate.concat([at[1]]); - } - const referenceTemplateLength = referenceTemplate.length; - const referenceTemplateX = new Int32Array(referenceTemplateLength); - const referenceTemplateY = new Int32Array(referenceTemplateLength); - for (k = 0; k < referenceTemplateLength; k++) { - referenceTemplateX[k] = referenceTemplate[k].x; - referenceTemplateY[k] = referenceTemplate[k].y; - } - const referenceWidth = referenceBitmap[0].length; - const referenceHeight = referenceBitmap.length; - - const pseudoPixelContext = RefinementReusedContexts[templateIndex]; - const bitmap = []; - - const decoder = decodingContext.decoder; - const contexts = decodingContext.contextCache.getContexts("GR"); - - let ltp = 0; - for (let i = 0; i < height; i++) { - if (prediction) { - const sltp = decoder.readBit(contexts, pseudoPixelContext); - ltp ^= sltp; - if (ltp) { - throw new Jbig2Error("prediction is not supported"); - } - } - const row = new Uint8Array(width); - bitmap.push(row); - for (let j = 0; j < width; j++) { - let i0, j0; - let contextLabel = 0; - for (k = 0; k < codingTemplateLength; k++) { - i0 = i + codingTemplateY[k]; - j0 = j + codingTemplateX[k]; - if (i0 < 0 || j0 < 0 || j0 >= width) { - contextLabel <<= 1; // out of bound pixel - } else { - contextLabel = (contextLabel << 1) | bitmap[i0][j0]; - } - } - for (k = 0; k < referenceTemplateLength; k++) { - i0 = i + referenceTemplateY[k] - offsetY; - j0 = j + referenceTemplateX[k] - offsetX; - if ( - i0 < 0 || - i0 >= referenceHeight || - j0 < 0 || - j0 >= referenceWidth - ) { - contextLabel <<= 1; // out of bound pixel - } else { - contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; - } - } - const pixel = decoder.readBit(contexts, contextLabel); - row[j] = pixel; - } - } - - return bitmap; + let referenceTemplate = RefinementTemplates[templateIndex].reference; + if (templateIndex === 0) { + referenceTemplate = referenceTemplate.concat([at[1]]); } + const referenceTemplateLength = referenceTemplate.length; + const referenceTemplateX = new Int32Array(referenceTemplateLength); + const referenceTemplateY = new Int32Array(referenceTemplateLength); + for (k = 0; k < referenceTemplateLength; k++) { + referenceTemplateX[k] = referenceTemplate[k].x; + referenceTemplateY[k] = referenceTemplate[k].y; + } + const referenceWidth = referenceBitmap[0].length; + const referenceHeight = referenceBitmap.length; - // 6.5.5 Decoding the symbol dictionary - function decodeSymbolDictionary( - huffman, - refinement, - symbols, - numberOfNewSymbols, - numberOfExportedSymbols, - huffmanTables, - templateIndex, - at, - refinementTemplateIndex, - refinementAt, - decodingContext, - huffmanInput - ) { - if (huffman && refinement) { - throw new Jbig2Error("symbol refinement with Huffman is not supported"); + const pseudoPixelContext = RefinementReusedContexts[templateIndex]; + const bitmap = []; + + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GR"); + + let ltp = 0; + for (let i = 0; i < height; i++) { + if (prediction) { + const sltp = decoder.readBit(contexts, pseudoPixelContext); + ltp ^= sltp; + if (ltp) { + throw new Jbig2Error("prediction is not supported"); + } } - - const newSymbols = []; - let currentHeight = 0; - let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); - - const decoder = decodingContext.decoder; - const contextCache = decodingContext.contextCache; - let tableB1, symbolWidths; - if (huffman) { - tableB1 = getStandardTable(1); // standard table B.1 - symbolWidths = []; - symbolCodeLength = Math.max(symbolCodeLength, 1); // 6.5.8.2.3 - } - - while (newSymbols.length < numberOfNewSymbols) { - const deltaHeight = huffman - ? huffmanTables.tableDeltaHeight.decode(huffmanInput) - : decodeInteger(contextCache, "IADH", decoder); // 6.5.6 - currentHeight += deltaHeight; - let currentWidth = 0, - totalWidth = 0; - const firstSymbol = huffman ? symbolWidths.length : 0; - while (true) { - const deltaWidth = huffman - ? huffmanTables.tableDeltaWidth.decode(huffmanInput) - : decodeInteger(contextCache, "IADW", decoder); // 6.5.7 - if (deltaWidth === null) { - break; // OOB - } - currentWidth += deltaWidth; - totalWidth += currentWidth; - let bitmap; - if (refinement) { - // 6.5.8.2 Refinement/aggregate-coded symbol bitmap - const numberOfInstances = decodeInteger( - contextCache, - "IAAI", - decoder - ); - if (numberOfInstances > 1) { - bitmap = decodeTextRegion( - huffman, - refinement, - currentWidth, - currentHeight, - 0, - numberOfInstances, - 1, // strip size - symbols.concat(newSymbols), - symbolCodeLength, - 0, // transposed - 0, // ds offset - 1, // top left 7.4.3.1.1 - 0, // OR operator - huffmanTables, - refinementTemplateIndex, - refinementAt, - decodingContext, - 0, - huffmanInput - ); - } else { - const symbolId = decodeIAID( - contextCache, - decoder, - symbolCodeLength - ); - const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 - const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 - const symbol = - symbolId < symbols.length - ? symbols[symbolId] - : newSymbols[symbolId - symbols.length]; - bitmap = decodeRefinement( - currentWidth, - currentHeight, - refinementTemplateIndex, - symbol, - rdx, - rdy, - false, - refinementAt, - decodingContext - ); - } - newSymbols.push(bitmap); - } else if (huffman) { - // Store only symbol width and decode a collective bitmap when the - // height class is done. - symbolWidths.push(currentWidth); + const row = new Uint8Array(width); + bitmap.push(row); + for (let j = 0; j < width; j++) { + let i0, j0; + let contextLabel = 0; + for (k = 0; k < codingTemplateLength; k++) { + i0 = i + codingTemplateY[k]; + j0 = j + codingTemplateX[k]; + if (i0 < 0 || j0 < 0 || j0 >= width) { + contextLabel <<= 1; // out of bound pixel } else { - // 6.5.8.1 Direct-coded symbol bitmap - bitmap = decodeBitmap( - false, + contextLabel = (contextLabel << 1) | bitmap[i0][j0]; + } + } + for (k = 0; k < referenceTemplateLength; k++) { + i0 = i + referenceTemplateY[k] - offsetY; + j0 = j + referenceTemplateX[k] - offsetX; + if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) { + contextLabel <<= 1; // out of bound pixel + } else { + contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; + } + } + const pixel = decoder.readBit(contexts, contextLabel); + row[j] = pixel; + } + } + + return bitmap; +} + +// 6.5.5 Decoding the symbol dictionary +function decodeSymbolDictionary( + huffman, + refinement, + symbols, + numberOfNewSymbols, + numberOfExportedSymbols, + huffmanTables, + templateIndex, + at, + refinementTemplateIndex, + refinementAt, + decodingContext, + huffmanInput +) { + if (huffman && refinement) { + throw new Jbig2Error("symbol refinement with Huffman is not supported"); + } + + const newSymbols = []; + let currentHeight = 0; + let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); + + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; + let tableB1, symbolWidths; + if (huffman) { + tableB1 = getStandardTable(1); // standard table B.1 + symbolWidths = []; + symbolCodeLength = Math.max(symbolCodeLength, 1); // 6.5.8.2.3 + } + + while (newSymbols.length < numberOfNewSymbols) { + const deltaHeight = huffman + ? huffmanTables.tableDeltaHeight.decode(huffmanInput) + : decodeInteger(contextCache, "IADH", decoder); // 6.5.6 + currentHeight += deltaHeight; + let currentWidth = 0, + totalWidth = 0; + const firstSymbol = huffman ? symbolWidths.length : 0; + while (true) { + const deltaWidth = huffman + ? huffmanTables.tableDeltaWidth.decode(huffmanInput) + : decodeInteger(contextCache, "IADW", decoder); // 6.5.7 + if (deltaWidth === null) { + break; // OOB + } + currentWidth += deltaWidth; + totalWidth += currentWidth; + let bitmap; + if (refinement) { + // 6.5.8.2 Refinement/aggregate-coded symbol bitmap + const numberOfInstances = decodeInteger(contextCache, "IAAI", decoder); + if (numberOfInstances > 1) { + bitmap = decodeTextRegion( + huffman, + refinement, currentWidth, currentHeight, - templateIndex, - false, - null, - at, - decodingContext - ); - newSymbols.push(bitmap); - } - } - if (huffman && !refinement) { - // 6.5.9 Height class collective bitmap - const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput); - huffmanInput.byteAlign(); - let collectiveBitmap; - if (bitmapSize === 0) { - // Uncompressed collective bitmap - collectiveBitmap = readUncompressedBitmap( - huffmanInput, - totalWidth, - currentHeight + 0, + numberOfInstances, + 1, // strip size + symbols.concat(newSymbols), + symbolCodeLength, + 0, // transposed + 0, // ds offset + 1, // top left 7.4.3.1.1 + 0, // OR operator + huffmanTables, + refinementTemplateIndex, + refinementAt, + decodingContext, + 0, + huffmanInput ); } else { - // MMR collective bitmap - const originalEnd = huffmanInput.end; - const bitmapEnd = huffmanInput.position + bitmapSize; - huffmanInput.end = bitmapEnd; - collectiveBitmap = decodeMMRBitmap( - huffmanInput, - totalWidth, - currentHeight, - false - ); - huffmanInput.end = originalEnd; - huffmanInput.position = bitmapEnd; - } - const numberOfSymbolsDecoded = symbolWidths.length; - if (firstSymbol === numberOfSymbolsDecoded - 1) { - // collectiveBitmap is a single symbol. - newSymbols.push(collectiveBitmap); - } else { - // Divide collectiveBitmap into symbols. - let i, - y, - xMin = 0, - xMax, - bitmapWidth, - symbolBitmap; - for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) { - bitmapWidth = symbolWidths[i]; - xMax = xMin + bitmapWidth; - symbolBitmap = []; - for (y = 0; y < currentHeight; y++) { - symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); - } - newSymbols.push(symbolBitmap); - xMin = xMax; - } - } - } - } - - // 6.5.10 Exported symbols - const exportedSymbols = [], - flags = []; - let currentFlag = false, - i, - ii; - const totalSymbolsLength = symbols.length + numberOfNewSymbols; - while (flags.length < totalSymbolsLength) { - let runLength = huffman - ? tableB1.decode(huffmanInput) - : decodeInteger(contextCache, "IAEX", decoder); - while (runLength--) { - flags.push(currentFlag); - } - currentFlag = !currentFlag; - } - for (i = 0, ii = symbols.length; i < ii; i++) { - if (flags[i]) { - exportedSymbols.push(symbols[i]); - } - } - for (let j = 0; j < numberOfNewSymbols; i++, j++) { - if (flags[i]) { - exportedSymbols.push(newSymbols[j]); - } - } - return exportedSymbols; - } - - function decodeTextRegion( - huffman, - refinement, - width, - height, - defaultPixelValue, - numberOfSymbolInstances, - stripSize, - inputSymbols, - symbolCodeLength, - transposed, - dsOffset, - referenceCorner, - combinationOperator, - huffmanTables, - refinementTemplateIndex, - refinementAt, - decodingContext, - logStripSize, - huffmanInput - ) { - if (huffman && refinement) { - throw new Jbig2Error("refinement with Huffman is not supported"); - } - - // Prepare bitmap - const bitmap = []; - let i, row; - for (i = 0; i < height; i++) { - row = new Uint8Array(width); - if (defaultPixelValue) { - for (let j = 0; j < width; j++) { - row[j] = defaultPixelValue; - } - } - bitmap.push(row); - } - - const decoder = decodingContext.decoder; - const contextCache = decodingContext.contextCache; - - let stripT = huffman - ? -huffmanTables.tableDeltaT.decode(huffmanInput) - : -decodeInteger(contextCache, "IADT", decoder); // 6.4.6 - let firstS = 0; - i = 0; - while (i < numberOfSymbolInstances) { - const deltaT = huffman - ? huffmanTables.tableDeltaT.decode(huffmanInput) - : decodeInteger(contextCache, "IADT", decoder); // 6.4.6 - stripT += deltaT; - - const deltaFirstS = huffman - ? huffmanTables.tableFirstS.decode(huffmanInput) - : decodeInteger(contextCache, "IAFS", decoder); // 6.4.7 - firstS += deltaFirstS; - let currentS = firstS; - do { - let currentT = 0; // 6.4.9 - if (stripSize > 1) { - currentT = huffman - ? huffmanInput.readBits(logStripSize) - : decodeInteger(contextCache, "IAIT", decoder); - } - const t = stripSize * stripT + currentT; - const symbolId = huffman - ? huffmanTables.symbolIDTable.decode(huffmanInput) - : decodeIAID(contextCache, decoder, symbolCodeLength); - const applyRefinement = - refinement && - (huffman - ? huffmanInput.readBit() - : decodeInteger(contextCache, "IARI", decoder)); - let symbolBitmap = inputSymbols[symbolId]; - let symbolWidth = symbolBitmap[0].length; - let symbolHeight = symbolBitmap.length; - if (applyRefinement) { - const rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 - const rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 + const symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 - symbolWidth += rdw; - symbolHeight += rdh; - symbolBitmap = decodeRefinement( - symbolWidth, - symbolHeight, + const symbol = + symbolId < symbols.length + ? symbols[symbolId] + : newSymbols[symbolId - symbols.length]; + bitmap = decodeRefinement( + currentWidth, + currentHeight, refinementTemplateIndex, - symbolBitmap, - (rdw >> 1) + rdx, - (rdh >> 1) + rdy, + symbol, + rdx, + rdy, false, refinementAt, decodingContext ); } - const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); - const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); - let s2, t2, symbolRow; - if (transposed) { - // Place Symbol Bitmap from T1,S1 - for (s2 = 0; s2 < symbolHeight; s2++) { - row = bitmap[offsetS + s2]; - if (!row) { - continue; - } - symbolRow = symbolBitmap[s2]; - // To ignore Parts of Symbol bitmap which goes - // outside bitmap region - const maxWidth = Math.min(width - offsetT, symbolWidth); - switch (combinationOperator) { - case 0: // OR - for (t2 = 0; t2 < maxWidth; t2++) { - row[offsetT + t2] |= symbolRow[t2]; - } - break; - case 2: // XOR - for (t2 = 0; t2 < maxWidth; t2++) { - row[offsetT + t2] ^= symbolRow[t2]; - } - break; - default: - throw new Jbig2Error( - `operator ${combinationOperator} is not supported` - ); - } - } - currentS += symbolHeight - 1; - } else { - for (t2 = 0; t2 < symbolHeight; t2++) { - row = bitmap[offsetT + t2]; - if (!row) { - continue; - } - symbolRow = symbolBitmap[t2]; - switch (combinationOperator) { - case 0: // OR - for (s2 = 0; s2 < symbolWidth; s2++) { - row[offsetS + s2] |= symbolRow[s2]; - } - break; - case 2: // XOR - for (s2 = 0; s2 < symbolWidth; s2++) { - row[offsetS + s2] ^= symbolRow[s2]; - } - break; - default: - throw new Jbig2Error( - `operator ${combinationOperator} is not supported` - ); - } - } - currentS += symbolWidth - 1; - } - i++; - const deltaS = huffman - ? huffmanTables.tableDeltaS.decode(huffmanInput) - : decodeInteger(contextCache, "IADS", decoder); // 6.4.8 - if (deltaS === null) { - break; // OOB - } - currentS += deltaS + dsOffset; - } while (true); - } - return bitmap; - } - - function decodePatternDictionary( - mmr, - patternWidth, - patternHeight, - maxPatternIndex, - template, - decodingContext - ) { - const at = []; - if (!mmr) { - at.push({ - x: -patternWidth, - y: 0, - }); - if (template === 0) { - at.push({ - x: -3, - y: -1, - }); - at.push({ - x: 2, - y: -2, - }); - at.push({ - x: -2, - y: -2, - }); - } - } - const collectiveWidth = (maxPatternIndex + 1) * patternWidth; - const collectiveBitmap = decodeBitmap( - mmr, - collectiveWidth, - patternHeight, - template, - false, - null, - at, - decodingContext - ); - // Divide collective bitmap into patterns. - const patterns = []; - for (let i = 0; i <= maxPatternIndex; i++) { - const patternBitmap = []; - const xMin = patternWidth * i; - const xMax = xMin + patternWidth; - for (let y = 0; y < patternHeight; y++) { - patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); - } - patterns.push(patternBitmap); - } - return patterns; - } - - function decodeHalftoneRegion( - mmr, - patterns, - template, - regionWidth, - regionHeight, - defaultPixelValue, - enableSkip, - combinationOperator, - gridWidth, - gridHeight, - gridOffsetX, - gridOffsetY, - gridVectorX, - gridVectorY, - decodingContext - ) { - const skip = null; - if (enableSkip) { - throw new Jbig2Error("skip is not supported"); - } - if (combinationOperator !== 0) { - throw new Jbig2Error( - "operator " + - combinationOperator + - " is not supported in halftone region" - ); - } - - // Prepare bitmap. - const regionBitmap = []; - let i, j, row; - for (i = 0; i < regionHeight; i++) { - row = new Uint8Array(regionWidth); - if (defaultPixelValue) { - for (j = 0; j < regionWidth; j++) { - row[j] = defaultPixelValue; - } - } - regionBitmap.push(row); - } - - const numberOfPatterns = patterns.length; - const pattern0 = patterns[0]; - const patternWidth = pattern0[0].length, - patternHeight = pattern0.length; - const bitsPerValue = log2(numberOfPatterns); - const at = []; - if (!mmr) { - at.push({ - x: template <= 1 ? 3 : 2, - y: -1, - }); - if (template === 0) { - at.push({ - x: -3, - y: -1, - }); - at.push({ - x: 2, - y: -2, - }); - at.push({ - x: -2, - y: -2, - }); - } - } - // Annex C. Gray-scale Image Decoding Procedure. - const grayScaleBitPlanes = []; - let mmrInput, bitmap; - if (mmr) { - // MMR bit planes are in one continuous stream. Only EOFB codes indicate - // the end of each bitmap, so EOFBs must be decoded. - mmrInput = new Reader( - decodingContext.data, - decodingContext.start, - decodingContext.end - ); - } - for (i = bitsPerValue - 1; i >= 0; i--) { - if (mmr) { - bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true); + newSymbols.push(bitmap); + } else if (huffman) { + // Store only symbol width and decode a collective bitmap when the + // height class is done. + symbolWidths.push(currentWidth); } else { + // 6.5.8.1 Direct-coded symbol bitmap bitmap = decodeBitmap( false, - gridWidth, - gridHeight, - template, + currentWidth, + currentHeight, + templateIndex, false, - skip, + null, at, decodingContext ); + newSymbols.push(bitmap); } - grayScaleBitPlanes[i] = bitmap; } - // 6.6.5.2 Rendering the patterns. - let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow; - for (mg = 0; mg < gridHeight; mg++) { - for (ng = 0; ng < gridWidth; ng++) { - bit = 0; - patternIndex = 0; - for (j = bitsPerValue - 1; j >= 0; j--) { - bit = grayScaleBitPlanes[j][mg][ng] ^ bit; // Gray decoding - patternIndex |= bit << j; - } - patternBitmap = patterns[patternIndex]; - x = (gridOffsetX + mg * gridVectorY + ng * gridVectorX) >> 8; - y = (gridOffsetY + mg * gridVectorX - ng * gridVectorY) >> 8; - // Draw patternBitmap at (x, y). - if ( - x >= 0 && - x + patternWidth <= regionWidth && - y >= 0 && - y + patternHeight <= regionHeight - ) { - for (i = 0; i < patternHeight; i++) { - regionRow = regionBitmap[y + i]; - patternRow = patternBitmap[i]; - for (j = 0; j < patternWidth; j++) { - regionRow[x + j] |= patternRow[j]; - } + if (huffman && !refinement) { + // 6.5.9 Height class collective bitmap + const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput); + huffmanInput.byteAlign(); + let collectiveBitmap; + if (bitmapSize === 0) { + // Uncompressed collective bitmap + collectiveBitmap = readUncompressedBitmap( + huffmanInput, + totalWidth, + currentHeight + ); + } else { + // MMR collective bitmap + const originalEnd = huffmanInput.end; + const bitmapEnd = huffmanInput.position + bitmapSize; + huffmanInput.end = bitmapEnd; + collectiveBitmap = decodeMMRBitmap( + huffmanInput, + totalWidth, + currentHeight, + false + ); + huffmanInput.end = originalEnd; + huffmanInput.position = bitmapEnd; + } + const numberOfSymbolsDecoded = symbolWidths.length; + if (firstSymbol === numberOfSymbolsDecoded - 1) { + // collectiveBitmap is a single symbol. + newSymbols.push(collectiveBitmap); + } else { + // Divide collectiveBitmap into symbols. + let i, + y, + xMin = 0, + xMax, + bitmapWidth, + symbolBitmap; + for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) { + bitmapWidth = symbolWidths[i]; + xMax = xMin + bitmapWidth; + symbolBitmap = []; + for (y = 0; y < currentHeight; y++) { + symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); } - } else { - let regionX, regionY; - for (i = 0; i < patternHeight; i++) { - regionY = y + i; - if (regionY < 0 || regionY >= regionHeight) { - continue; - } - regionRow = regionBitmap[regionY]; - patternRow = patternBitmap[i]; - for (j = 0; j < patternWidth; j++) { - regionX = x + j; - if (regionX >= 0 && regionX < regionWidth) { - regionRow[regionX] |= patternRow[j]; + newSymbols.push(symbolBitmap); + xMin = xMax; + } + } + } + } + + // 6.5.10 Exported symbols + const exportedSymbols = [], + flags = []; + let currentFlag = false, + i, + ii; + const totalSymbolsLength = symbols.length + numberOfNewSymbols; + while (flags.length < totalSymbolsLength) { + let runLength = huffman + ? tableB1.decode(huffmanInput) + : decodeInteger(contextCache, "IAEX", decoder); + while (runLength--) { + flags.push(currentFlag); + } + currentFlag = !currentFlag; + } + for (i = 0, ii = symbols.length; i < ii; i++) { + if (flags[i]) { + exportedSymbols.push(symbols[i]); + } + } + for (let j = 0; j < numberOfNewSymbols; i++, j++) { + if (flags[i]) { + exportedSymbols.push(newSymbols[j]); + } + } + return exportedSymbols; +} + +function decodeTextRegion( + huffman, + refinement, + width, + height, + defaultPixelValue, + numberOfSymbolInstances, + stripSize, + inputSymbols, + symbolCodeLength, + transposed, + dsOffset, + referenceCorner, + combinationOperator, + huffmanTables, + refinementTemplateIndex, + refinementAt, + decodingContext, + logStripSize, + huffmanInput +) { + if (huffman && refinement) { + throw new Jbig2Error("refinement with Huffman is not supported"); + } + + // Prepare bitmap + const bitmap = []; + let i, row; + for (i = 0; i < height; i++) { + row = new Uint8Array(width); + if (defaultPixelValue) { + for (let j = 0; j < width; j++) { + row[j] = defaultPixelValue; + } + } + bitmap.push(row); + } + + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; + + let stripT = huffman + ? -huffmanTables.tableDeltaT.decode(huffmanInput) + : -decodeInteger(contextCache, "IADT", decoder); // 6.4.6 + let firstS = 0; + i = 0; + while (i < numberOfSymbolInstances) { + const deltaT = huffman + ? huffmanTables.tableDeltaT.decode(huffmanInput) + : decodeInteger(contextCache, "IADT", decoder); // 6.4.6 + stripT += deltaT; + + const deltaFirstS = huffman + ? huffmanTables.tableFirstS.decode(huffmanInput) + : decodeInteger(contextCache, "IAFS", decoder); // 6.4.7 + firstS += deltaFirstS; + let currentS = firstS; + do { + let currentT = 0; // 6.4.9 + if (stripSize > 1) { + currentT = huffman + ? huffmanInput.readBits(logStripSize) + : decodeInteger(contextCache, "IAIT", decoder); + } + const t = stripSize * stripT + currentT; + const symbolId = huffman + ? huffmanTables.symbolIDTable.decode(huffmanInput) + : decodeIAID(contextCache, decoder, symbolCodeLength); + const applyRefinement = + refinement && + (huffman + ? huffmanInput.readBit() + : decodeInteger(contextCache, "IARI", decoder)); + let symbolBitmap = inputSymbols[symbolId]; + let symbolWidth = symbolBitmap[0].length; + let symbolHeight = symbolBitmap.length; + if (applyRefinement) { + const rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 + const rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 + const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 + const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 + symbolWidth += rdw; + symbolHeight += rdh; + symbolBitmap = decodeRefinement( + symbolWidth, + symbolHeight, + refinementTemplateIndex, + symbolBitmap, + (rdw >> 1) + rdx, + (rdh >> 1) + rdy, + false, + refinementAt, + decodingContext + ); + } + const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); + const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); + let s2, t2, symbolRow; + if (transposed) { + // Place Symbol Bitmap from T1,S1 + for (s2 = 0; s2 < symbolHeight; s2++) { + row = bitmap[offsetS + s2]; + if (!row) { + continue; + } + symbolRow = symbolBitmap[s2]; + // To ignore Parts of Symbol bitmap which goes + // outside bitmap region + const maxWidth = Math.min(width - offsetT, symbolWidth); + switch (combinationOperator) { + case 0: // OR + for (t2 = 0; t2 < maxWidth; t2++) { + row[offsetT + t2] |= symbolRow[t2]; } + break; + case 2: // XOR + for (t2 = 0; t2 < maxWidth; t2++) { + row[offsetT + t2] ^= symbolRow[t2]; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + currentS += symbolHeight - 1; + } else { + for (t2 = 0; t2 < symbolHeight; t2++) { + row = bitmap[offsetT + t2]; + if (!row) { + continue; + } + symbolRow = symbolBitmap[t2]; + switch (combinationOperator) { + case 0: // OR + for (s2 = 0; s2 < symbolWidth; s2++) { + row[offsetS + s2] |= symbolRow[s2]; + } + break; + case 2: // XOR + for (s2 = 0; s2 < symbolWidth; s2++) { + row[offsetS + s2] ^= symbolRow[s2]; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + currentS += symbolWidth - 1; + } + i++; + const deltaS = huffman + ? huffmanTables.tableDeltaS.decode(huffmanInput) + : decodeInteger(contextCache, "IADS", decoder); // 6.4.8 + if (deltaS === null) { + break; // OOB + } + currentS += deltaS + dsOffset; + } while (true); + } + return bitmap; +} + +function decodePatternDictionary( + mmr, + patternWidth, + patternHeight, + maxPatternIndex, + template, + decodingContext +) { + const at = []; + if (!mmr) { + at.push({ + x: -patternWidth, + y: 0, + }); + if (template === 0) { + at.push({ + x: -3, + y: -1, + }); + at.push({ + x: 2, + y: -2, + }); + at.push({ + x: -2, + y: -2, + }); + } + } + const collectiveWidth = (maxPatternIndex + 1) * patternWidth; + const collectiveBitmap = decodeBitmap( + mmr, + collectiveWidth, + patternHeight, + template, + false, + null, + at, + decodingContext + ); + // Divide collective bitmap into patterns. + const patterns = []; + for (let i = 0; i <= maxPatternIndex; i++) { + const patternBitmap = []; + const xMin = patternWidth * i; + const xMax = xMin + patternWidth; + for (let y = 0; y < patternHeight; y++) { + patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); + } + patterns.push(patternBitmap); + } + return patterns; +} + +function decodeHalftoneRegion( + mmr, + patterns, + template, + regionWidth, + regionHeight, + defaultPixelValue, + enableSkip, + combinationOperator, + gridWidth, + gridHeight, + gridOffsetX, + gridOffsetY, + gridVectorX, + gridVectorY, + decodingContext +) { + const skip = null; + if (enableSkip) { + throw new Jbig2Error("skip is not supported"); + } + if (combinationOperator !== 0) { + throw new Jbig2Error( + `operator "${combinationOperator}" is not supported in halftone region` + ); + } + + // Prepare bitmap. + const regionBitmap = []; + let i, j, row; + for (i = 0; i < regionHeight; i++) { + row = new Uint8Array(regionWidth); + if (defaultPixelValue) { + for (j = 0; j < regionWidth; j++) { + row[j] = defaultPixelValue; + } + } + regionBitmap.push(row); + } + + const numberOfPatterns = patterns.length; + const pattern0 = patterns[0]; + const patternWidth = pattern0[0].length, + patternHeight = pattern0.length; + const bitsPerValue = log2(numberOfPatterns); + const at = []; + if (!mmr) { + at.push({ + x: template <= 1 ? 3 : 2, + y: -1, + }); + if (template === 0) { + at.push({ + x: -3, + y: -1, + }); + at.push({ + x: 2, + y: -2, + }); + at.push({ + x: -2, + y: -2, + }); + } + } + // Annex C. Gray-scale Image Decoding Procedure. + const grayScaleBitPlanes = []; + let mmrInput, bitmap; + if (mmr) { + // MMR bit planes are in one continuous stream. Only EOFB codes indicate + // the end of each bitmap, so EOFBs must be decoded. + mmrInput = new Reader( + decodingContext.data, + decodingContext.start, + decodingContext.end + ); + } + for (i = bitsPerValue - 1; i >= 0; i--) { + if (mmr) { + bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true); + } else { + bitmap = decodeBitmap( + false, + gridWidth, + gridHeight, + template, + false, + skip, + at, + decodingContext + ); + } + grayScaleBitPlanes[i] = bitmap; + } + // 6.6.5.2 Rendering the patterns. + let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow; + for (mg = 0; mg < gridHeight; mg++) { + for (ng = 0; ng < gridWidth; ng++) { + bit = 0; + patternIndex = 0; + for (j = bitsPerValue - 1; j >= 0; j--) { + bit = grayScaleBitPlanes[j][mg][ng] ^ bit; // Gray decoding + patternIndex |= bit << j; + } + patternBitmap = patterns[patternIndex]; + x = (gridOffsetX + mg * gridVectorY + ng * gridVectorX) >> 8; + y = (gridOffsetY + mg * gridVectorX - ng * gridVectorY) >> 8; + // Draw patternBitmap at (x, y). + if ( + x >= 0 && + x + patternWidth <= regionWidth && + y >= 0 && + y + patternHeight <= regionHeight + ) { + for (i = 0; i < patternHeight; i++) { + regionRow = regionBitmap[y + i]; + patternRow = patternBitmap[i]; + for (j = 0; j < patternWidth; j++) { + regionRow[x + j] |= patternRow[j]; + } + } + } else { + let regionX, regionY; + for (i = 0; i < patternHeight; i++) { + regionY = y + i; + if (regionY < 0 || regionY >= regionHeight) { + continue; + } + regionRow = regionBitmap[regionY]; + patternRow = patternBitmap[i]; + for (j = 0; j < patternWidth; j++) { + regionX = x + j; + if (regionX >= 0 && regionX < regionWidth) { + regionRow[regionX] |= patternRow[j]; } } } } } - return regionBitmap; + } + return regionBitmap; +} + +function readSegmentHeader(data, start) { + const segmentHeader = {}; + segmentHeader.number = readUint32(data, start); + const flags = data[start + 4]; + const segmentType = flags & 0x3f; + if (!SegmentTypes[segmentType]) { + throw new Jbig2Error("invalid segment type: " + segmentType); + } + segmentHeader.type = segmentType; + segmentHeader.typeName = SegmentTypes[segmentType]; + segmentHeader.deferredNonRetain = !!(flags & 0x80); + + const pageAssociationFieldSize = !!(flags & 0x40); + const referredFlags = data[start + 5]; + let referredToCount = (referredFlags >> 5) & 7; + const retainBits = [referredFlags & 31]; + let position = start + 6; + if (referredFlags === 7) { + referredToCount = readUint32(data, position - 1) & 0x1fffffff; + position += 3; + let bytes = (referredToCount + 7) >> 3; + retainBits[0] = data[position++]; + while (--bytes > 0) { + retainBits.push(data[position++]); + } + } else if (referredFlags === 5 || referredFlags === 6) { + throw new Jbig2Error("invalid referred-to flags"); } - function readSegmentHeader(data, start) { - const segmentHeader = {}; - segmentHeader.number = readUint32(data, start); - const flags = data[start + 4]; - const segmentType = flags & 0x3f; - if (!SegmentTypes[segmentType]) { - throw new Jbig2Error("invalid segment type: " + segmentType); - } - segmentHeader.type = segmentType; - segmentHeader.typeName = SegmentTypes[segmentType]; - segmentHeader.deferredNonRetain = !!(flags & 0x80); + segmentHeader.retainBits = retainBits; - const pageAssociationFieldSize = !!(flags & 0x40); - const referredFlags = data[start + 5]; - let referredToCount = (referredFlags >> 5) & 7; - const retainBits = [referredFlags & 31]; - let position = start + 6; - if (referredFlags === 7) { - referredToCount = readUint32(data, position - 1) & 0x1fffffff; - position += 3; - let bytes = (referredToCount + 7) >> 3; - retainBits[0] = data[position++]; - while (--bytes > 0) { - retainBits.push(data[position++]); - } - } else if (referredFlags === 5 || referredFlags === 6) { - throw new Jbig2Error("invalid referred-to flags"); - } - - segmentHeader.retainBits = retainBits; - - let referredToSegmentNumberSize = 4; - if (segmentHeader.number <= 256) { - referredToSegmentNumberSize = 1; - } else if (segmentHeader.number <= 65536) { - referredToSegmentNumberSize = 2; - } - const referredTo = []; - let i, ii; - for (i = 0; i < referredToCount; i++) { - let number; - if (referredToSegmentNumberSize === 1) { - number = data[position]; - } else if (referredToSegmentNumberSize === 2) { - number = readUint16(data, position); - } else { - number = readUint32(data, position); - } - referredTo.push(number); - position += referredToSegmentNumberSize; - } - segmentHeader.referredTo = referredTo; - if (!pageAssociationFieldSize) { - segmentHeader.pageAssociation = data[position++]; + let referredToSegmentNumberSize = 4; + if (segmentHeader.number <= 256) { + referredToSegmentNumberSize = 1; + } else if (segmentHeader.number <= 65536) { + referredToSegmentNumberSize = 2; + } + const referredTo = []; + let i, ii; + for (i = 0; i < referredToCount; i++) { + let number; + if (referredToSegmentNumberSize === 1) { + number = data[position]; + } else if (referredToSegmentNumberSize === 2) { + number = readUint16(data, position); } else { - segmentHeader.pageAssociation = readUint32(data, position); - position += 4; + number = readUint32(data, position); } - segmentHeader.length = readUint32(data, position); + referredTo.push(number); + position += referredToSegmentNumberSize; + } + segmentHeader.referredTo = referredTo; + if (!pageAssociationFieldSize) { + segmentHeader.pageAssociation = data[position++]; + } else { + segmentHeader.pageAssociation = readUint32(data, position); position += 4; - - if (segmentHeader.length === 0xffffffff) { - // 7.2.7 Segment data length, unknown segment length - if (segmentType === 38) { - // ImmediateGenericRegion - const genericRegionInfo = readRegionSegmentInformation(data, position); - const genericRegionSegmentFlags = - data[position + RegionSegmentInformationFieldLength]; - const genericRegionMmr = !!(genericRegionSegmentFlags & 1); - // searching for the segment end - const searchPatternLength = 6; - const searchPattern = new Uint8Array(searchPatternLength); - if (!genericRegionMmr) { - searchPattern[0] = 0xff; - searchPattern[1] = 0xac; - } - searchPattern[2] = (genericRegionInfo.height >>> 24) & 0xff; - searchPattern[3] = (genericRegionInfo.height >> 16) & 0xff; - searchPattern[4] = (genericRegionInfo.height >> 8) & 0xff; - searchPattern[5] = genericRegionInfo.height & 0xff; - for (i = position, ii = data.length; i < ii; i++) { - let j = 0; - while (j < searchPatternLength && searchPattern[j] === data[i + j]) { - j++; - } - if (j === searchPatternLength) { - segmentHeader.length = i + searchPatternLength; - break; - } - } - if (segmentHeader.length === 0xffffffff) { - throw new Jbig2Error("segment end was not found"); - } - } else { - throw new Jbig2Error("invalid unknown segment length"); - } - } - segmentHeader.headerEnd = position; - return segmentHeader; } + segmentHeader.length = readUint32(data, position); + position += 4; - function readSegments(header, data, start, end) { - const segments = []; - let position = start; - while (position < end) { - const segmentHeader = readSegmentHeader(data, position); - position = segmentHeader.headerEnd; - const segment = { - header: segmentHeader, - data, - }; - if (!header.randomAccess) { - segment.start = position; - position += segmentHeader.length; - segment.end = position; + if (segmentHeader.length === 0xffffffff) { + // 7.2.7 Segment data length, unknown segment length + if (segmentType === 38) { + // ImmediateGenericRegion + const genericRegionInfo = readRegionSegmentInformation(data, position); + const genericRegionSegmentFlags = + data[position + RegionSegmentInformationFieldLength]; + const genericRegionMmr = !!(genericRegionSegmentFlags & 1); + // searching for the segment end + const searchPatternLength = 6; + const searchPattern = new Uint8Array(searchPatternLength); + if (!genericRegionMmr) { + searchPattern[0] = 0xff; + searchPattern[1] = 0xac; } - segments.push(segment); - if (segmentHeader.type === 51) { - break; // end of file is found + searchPattern[2] = (genericRegionInfo.height >>> 24) & 0xff; + searchPattern[3] = (genericRegionInfo.height >> 16) & 0xff; + searchPattern[4] = (genericRegionInfo.height >> 8) & 0xff; + searchPattern[5] = genericRegionInfo.height & 0xff; + for (i = position, ii = data.length; i < ii; i++) { + let j = 0; + while (j < searchPatternLength && searchPattern[j] === data[i + j]) { + j++; + } + if (j === searchPatternLength) { + segmentHeader.length = i + searchPatternLength; + break; + } } + if (segmentHeader.length === 0xffffffff) { + throw new Jbig2Error("segment end was not found"); + } + } else { + throw new Jbig2Error("invalid unknown segment length"); } - if (header.randomAccess) { - for (let i = 0, ii = segments.length; i < ii; i++) { - segments[i].start = position; - position += segments[i].header.length; - segments[i].end = position; - } - } - return segments; } + segmentHeader.headerEnd = position; + return segmentHeader; +} - // 7.4.1 Region segment information field - function readRegionSegmentInformation(data, start) { - return { - width: readUint32(data, start), - height: readUint32(data, start + 4), - x: readUint32(data, start + 8), - y: readUint32(data, start + 12), - combinationOperator: data[start + 16] & 7, +function readSegments(header, data, start, end) { + const segments = []; + let position = start; + while (position < end) { + const segmentHeader = readSegmentHeader(data, position); + position = segmentHeader.headerEnd; + const segment = { + header: segmentHeader, + data, }; + if (!header.randomAccess) { + segment.start = position; + position += segmentHeader.length; + segment.end = position; + } + segments.push(segment); + if (segmentHeader.type === 51) { + break; // end of file is found + } } - const RegionSegmentInformationFieldLength = 17; + if (header.randomAccess) { + for (let i = 0, ii = segments.length; i < ii; i++) { + segments[i].start = position; + position += segments[i].header.length; + segments[i].end = position; + } + } + return segments; +} - function processSegment(segment, visitor) { - const header = segment.header; +// 7.4.1 Region segment information field +function readRegionSegmentInformation(data, start) { + return { + width: readUint32(data, start), + height: readUint32(data, start + 4), + x: readUint32(data, start + 8), + y: readUint32(data, start + 12), + combinationOperator: data[start + 16] & 7, + }; +} +const RegionSegmentInformationFieldLength = 17; - const data = segment.data, - end = segment.end; - let position = segment.start; - let args, at, i, atLength; - switch (header.type) { - case 0: // SymbolDictionary - // 7.4.2 Symbol dictionary segment syntax - const dictionary = {}; - const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 - dictionary.huffman = !!(dictionaryFlags & 1); - dictionary.refinement = !!(dictionaryFlags & 2); - dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; - dictionary.huffmanDWSelector = (dictionaryFlags >> 4) & 3; - dictionary.bitmapSizeSelector = (dictionaryFlags >> 6) & 1; - dictionary.aggregationInstancesSelector = (dictionaryFlags >> 7) & 1; - dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256); - dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512); - dictionary.template = (dictionaryFlags >> 10) & 3; - dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; - position += 2; - if (!dictionary.huffman) { - atLength = dictionary.template === 0 ? 4 : 1; - at = []; - for (i = 0; i < atLength; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; - } - dictionary.at = at; - } - if (dictionary.refinement && !dictionary.refinementTemplate) { - at = []; - for (i = 0; i < 2; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; - } - dictionary.refinementAt = at; - } - dictionary.numberOfExportedSymbols = readUint32(data, position); - position += 4; - dictionary.numberOfNewSymbols = readUint32(data, position); - position += 4; - args = [ - dictionary, - header.number, - header.referredTo, - data, - position, - end, - ]; - break; - case 6: // ImmediateTextRegion - case 7: // ImmediateLosslessTextRegion - const textRegion = {}; - textRegion.info = readRegionSegmentInformation(data, position); - position += RegionSegmentInformationFieldLength; - const textRegionSegmentFlags = readUint16(data, position); - position += 2; - textRegion.huffman = !!(textRegionSegmentFlags & 1); - textRegion.refinement = !!(textRegionSegmentFlags & 2); - textRegion.logStripSize = (textRegionSegmentFlags >> 2) & 3; - textRegion.stripSize = 1 << textRegion.logStripSize; - textRegion.referenceCorner = (textRegionSegmentFlags >> 4) & 3; - textRegion.transposed = !!(textRegionSegmentFlags & 64); - textRegion.combinationOperator = (textRegionSegmentFlags >> 7) & 3; - textRegion.defaultPixelValue = (textRegionSegmentFlags >> 9) & 1; - textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; - textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; - if (textRegion.huffman) { - const textRegionHuffmanFlags = readUint16(data, position); +function processSegment(segment, visitor) { + const header = segment.header; + + const data = segment.data, + end = segment.end; + let position = segment.start; + let args, at, i, atLength; + switch (header.type) { + case 0: // SymbolDictionary + // 7.4.2 Symbol dictionary segment syntax + const dictionary = {}; + const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 + dictionary.huffman = !!(dictionaryFlags & 1); + dictionary.refinement = !!(dictionaryFlags & 2); + dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; + dictionary.huffmanDWSelector = (dictionaryFlags >> 4) & 3; + dictionary.bitmapSizeSelector = (dictionaryFlags >> 6) & 1; + dictionary.aggregationInstancesSelector = (dictionaryFlags >> 7) & 1; + dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256); + dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512); + dictionary.template = (dictionaryFlags >> 10) & 3; + dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; + position += 2; + if (!dictionary.huffman) { + atLength = dictionary.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); position += 2; - textRegion.huffmanFS = textRegionHuffmanFlags & 3; - textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; - textRegion.huffmanDT = (textRegionHuffmanFlags >> 4) & 3; - textRegion.huffmanRefinementDW = (textRegionHuffmanFlags >> 6) & 3; - textRegion.huffmanRefinementDH = (textRegionHuffmanFlags >> 8) & 3; - textRegion.huffmanRefinementDX = (textRegionHuffmanFlags >> 10) & 3; - textRegion.huffmanRefinementDY = (textRegionHuffmanFlags >> 12) & 3; - textRegion.huffmanRefinementSizeSelector = !!( - textRegionHuffmanFlags & 0x4000 - ); } - if (textRegion.refinement && !textRegion.refinementTemplate) { - at = []; - for (i = 0; i < 2; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; - } - textRegion.refinementAt = at; + dictionary.at = at; + } + if (dictionary.refinement && !dictionary.refinementTemplate) { + at = []; + for (i = 0; i < 2; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; } - textRegion.numberOfSymbolInstances = readUint32(data, position); - position += 4; - args = [textRegion, header.referredTo, data, position, end]; - break; - case 16: // PatternDictionary - // 7.4.4. Pattern dictionary segment syntax - const patternDictionary = {}; - const patternDictionaryFlags = data[position++]; - patternDictionary.mmr = !!(patternDictionaryFlags & 1); - patternDictionary.template = (patternDictionaryFlags >> 1) & 3; - patternDictionary.patternWidth = data[position++]; - patternDictionary.patternHeight = data[position++]; - patternDictionary.maxPatternIndex = readUint32(data, position); - position += 4; - args = [patternDictionary, header.number, data, position, end]; - break; - case 22: // ImmediateHalftoneRegion - case 23: // ImmediateLosslessHalftoneRegion - // 7.4.5 Halftone region segment syntax - const halftoneRegion = {}; - halftoneRegion.info = readRegionSegmentInformation(data, position); - position += RegionSegmentInformationFieldLength; - const halftoneRegionFlags = data[position++]; - halftoneRegion.mmr = !!(halftoneRegionFlags & 1); - halftoneRegion.template = (halftoneRegionFlags >> 1) & 3; - halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); - halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7; - halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1; - halftoneRegion.gridWidth = readUint32(data, position); - position += 4; - halftoneRegion.gridHeight = readUint32(data, position); - position += 4; - halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; - position += 4; - halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; - position += 4; - halftoneRegion.gridVectorX = readUint16(data, position); + dictionary.refinementAt = at; + } + dictionary.numberOfExportedSymbols = readUint32(data, position); + position += 4; + dictionary.numberOfNewSymbols = readUint32(data, position); + position += 4; + args = [ + dictionary, + header.number, + header.referredTo, + data, + position, + end, + ]; + break; + case 6: // ImmediateTextRegion + case 7: // ImmediateLosslessTextRegion + const textRegion = {}; + textRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const textRegionSegmentFlags = readUint16(data, position); + position += 2; + textRegion.huffman = !!(textRegionSegmentFlags & 1); + textRegion.refinement = !!(textRegionSegmentFlags & 2); + textRegion.logStripSize = (textRegionSegmentFlags >> 2) & 3; + textRegion.stripSize = 1 << textRegion.logStripSize; + textRegion.referenceCorner = (textRegionSegmentFlags >> 4) & 3; + textRegion.transposed = !!(textRegionSegmentFlags & 64); + textRegion.combinationOperator = (textRegionSegmentFlags >> 7) & 3; + textRegion.defaultPixelValue = (textRegionSegmentFlags >> 9) & 1; + textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; + textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; + if (textRegion.huffman) { + const textRegionHuffmanFlags = readUint16(data, position); position += 2; - halftoneRegion.gridVectorY = readUint16(data, position); - position += 2; - args = [halftoneRegion, header.referredTo, data, position, end]; - break; - case 38: // ImmediateGenericRegion - case 39: // ImmediateLosslessGenericRegion - const genericRegion = {}; - genericRegion.info = readRegionSegmentInformation(data, position); - position += RegionSegmentInformationFieldLength; - const genericRegionSegmentFlags = data[position++]; - genericRegion.mmr = !!(genericRegionSegmentFlags & 1); - genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; - genericRegion.prediction = !!(genericRegionSegmentFlags & 8); - if (!genericRegion.mmr) { - atLength = genericRegion.template === 0 ? 4 : 1; - at = []; - for (i = 0; i < atLength; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; + textRegion.huffmanFS = textRegionHuffmanFlags & 3; + textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; + textRegion.huffmanDT = (textRegionHuffmanFlags >> 4) & 3; + textRegion.huffmanRefinementDW = (textRegionHuffmanFlags >> 6) & 3; + textRegion.huffmanRefinementDH = (textRegionHuffmanFlags >> 8) & 3; + textRegion.huffmanRefinementDX = (textRegionHuffmanFlags >> 10) & 3; + textRegion.huffmanRefinementDY = (textRegionHuffmanFlags >> 12) & 3; + textRegion.huffmanRefinementSizeSelector = !!( + textRegionHuffmanFlags & 0x4000 + ); + } + if (textRegion.refinement && !textRegion.refinementTemplate) { + at = []; + for (i = 0; i < 2; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + textRegion.refinementAt = at; + } + textRegion.numberOfSymbolInstances = readUint32(data, position); + position += 4; + args = [textRegion, header.referredTo, data, position, end]; + break; + case 16: // PatternDictionary + // 7.4.4. Pattern dictionary segment syntax + const patternDictionary = {}; + const patternDictionaryFlags = data[position++]; + patternDictionary.mmr = !!(patternDictionaryFlags & 1); + patternDictionary.template = (patternDictionaryFlags >> 1) & 3; + patternDictionary.patternWidth = data[position++]; + patternDictionary.patternHeight = data[position++]; + patternDictionary.maxPatternIndex = readUint32(data, position); + position += 4; + args = [patternDictionary, header.number, data, position, end]; + break; + case 22: // ImmediateHalftoneRegion + case 23: // ImmediateLosslessHalftoneRegion + // 7.4.5 Halftone region segment syntax + const halftoneRegion = {}; + halftoneRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const halftoneRegionFlags = data[position++]; + halftoneRegion.mmr = !!(halftoneRegionFlags & 1); + halftoneRegion.template = (halftoneRegionFlags >> 1) & 3; + halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); + halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7; + halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1; + halftoneRegion.gridWidth = readUint32(data, position); + position += 4; + halftoneRegion.gridHeight = readUint32(data, position); + position += 4; + halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; + position += 4; + halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; + position += 4; + halftoneRegion.gridVectorX = readUint16(data, position); + position += 2; + halftoneRegion.gridVectorY = readUint16(data, position); + position += 2; + args = [halftoneRegion, header.referredTo, data, position, end]; + break; + case 38: // ImmediateGenericRegion + case 39: // ImmediateLosslessGenericRegion + const genericRegion = {}; + genericRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const genericRegionSegmentFlags = data[position++]; + genericRegion.mmr = !!(genericRegionSegmentFlags & 1); + genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; + genericRegion.prediction = !!(genericRegionSegmentFlags & 8); + if (!genericRegion.mmr) { + atLength = genericRegion.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + genericRegion.at = at; + } + args = [genericRegion, data, position, end]; + break; + case 48: // PageInformation + const pageInfo = { + width: readUint32(data, position), + height: readUint32(data, position + 4), + resolutionX: readUint32(data, position + 8), + resolutionY: readUint32(data, position + 12), + }; + if (pageInfo.height === 0xffffffff) { + delete pageInfo.height; + } + const pageSegmentFlags = data[position + 16]; + readUint16(data, position + 17); // pageStripingInformation + pageInfo.lossless = !!(pageSegmentFlags & 1); + pageInfo.refinement = !!(pageSegmentFlags & 2); + pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1; + pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3; + pageInfo.requiresBuffer = !!(pageSegmentFlags & 32); + pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64); + args = [pageInfo]; + break; + case 49: // EndOfPage + break; + case 50: // EndOfStripe + break; + case 51: // EndOfFile + break; + case 53: // Tables + args = [header.number, data, position, end]; + break; + case 62: // 7.4.15 defines 2 extension types which + // are comments and can be ignored. + break; + default: + throw new Jbig2Error( + `segment type ${header.typeName}(${header.type})` + + " is not implemented" + ); + } + const callbackName = "on" + header.typeName; + if (callbackName in visitor) { + visitor[callbackName].apply(visitor, args); + } +} + +function processSegments(segments, visitor) { + for (let i = 0, ii = segments.length; i < ii; i++) { + processSegment(segments[i], visitor); + } +} + +function parseJbig2Chunks(chunks) { + const visitor = new SimpleSegmentVisitor(); + for (let i = 0, ii = chunks.length; i < ii; i++) { + const chunk = chunks[i]; + const segments = readSegments({}, chunk.data, chunk.start, chunk.end); + processSegments(segments, visitor); + } + return visitor.buffer; +} + +function parseJbig2(data) { + const end = data.length; + let position = 0; + + if ( + data[position] !== 0x97 || + data[position + 1] !== 0x4a || + data[position + 2] !== 0x42 || + data[position + 3] !== 0x32 || + data[position + 4] !== 0x0d || + data[position + 5] !== 0x0a || + data[position + 6] !== 0x1a || + data[position + 7] !== 0x0a + ) { + throw new Jbig2Error("parseJbig2 - invalid header."); + } + + const header = Object.create(null); + position += 8; + const flags = data[position++]; + header.randomAccess = !(flags & 1); + if (!(flags & 2)) { + header.numberOfPages = readUint32(data, position); + position += 4; + } + + const segments = readSegments(header, data, position, end); + const visitor = new SimpleSegmentVisitor(); + processSegments(segments, visitor); + + const { width, height } = visitor.currentPageInfo; + const bitPacked = visitor.buffer; + const imgData = new Uint8ClampedArray(width * height); + let q = 0, + k = 0; + for (let i = 0; i < height; i++) { + let mask = 0, + buffer; + for (let j = 0; j < width; j++) { + if (!mask) { + mask = 128; + buffer = bitPacked[k++]; + } + imgData[q++] = buffer & mask ? 0 : 255; + mask >>= 1; + } + } + + return { imgData, width, height }; +} + +class SimpleSegmentVisitor { + onPageInformation(info) { + this.currentPageInfo = info; + const rowSize = (info.width + 7) >> 3; + const buffer = new Uint8ClampedArray(rowSize * info.height); + // The contents of ArrayBuffers are initialized to 0. + // Fill the buffer with 0xFF only if info.defaultPixelValue is set + if (info.defaultPixelValue) { + for (let i = 0, ii = buffer.length; i < ii; i++) { + buffer[i] = 0xff; + } + } + this.buffer = buffer; + } + + drawBitmap(regionInfo, bitmap) { + const pageInfo = this.currentPageInfo; + const width = regionInfo.width, + height = regionInfo.height; + const rowSize = (pageInfo.width + 7) >> 3; + const combinationOperator = pageInfo.combinationOperatorOverride + ? regionInfo.combinationOperator + : pageInfo.combinationOperator; + const buffer = this.buffer; + const mask0 = 128 >> (regionInfo.x & 7); + let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); + let i, j, mask, offset; + switch (combinationOperator) { + case 0: // OR + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { + if (bitmap[i][j]) { + buffer[offset] |= mask; + } + mask >>= 1; + if (!mask) { + mask = 128; + offset++; + } } - genericRegion.at = at; + offset0 += rowSize; } - args = [genericRegion, data, position, end]; break; - case 48: // PageInformation - const pageInfo = { - width: readUint32(data, position), - height: readUint32(data, position + 4), - resolutionX: readUint32(data, position + 8), - resolutionY: readUint32(data, position + 12), - }; - if (pageInfo.height === 0xffffffff) { - delete pageInfo.height; + case 2: // XOR + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { + if (bitmap[i][j]) { + buffer[offset] ^= mask; + } + mask >>= 1; + if (!mask) { + mask = 128; + offset++; + } + } + offset0 += rowSize; } - const pageSegmentFlags = data[position + 16]; - readUint16(data, position + 17); // pageStripingInformation - pageInfo.lossless = !!(pageSegmentFlags & 1); - pageInfo.refinement = !!(pageSegmentFlags & 2); - pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1; - pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3; - pageInfo.requiresBuffer = !!(pageSegmentFlags & 32); - pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64); - args = [pageInfo]; - break; - case 49: // EndOfPage - break; - case 50: // EndOfStripe - break; - case 51: // EndOfFile - break; - case 53: // Tables - args = [header.number, data, position, end]; - break; - case 62: // 7.4.15 defines 2 extension types which - // are comments and can be ignored. break; default: throw new Jbig2Error( - `segment type ${header.typeName}(${header.type})` + - " is not implemented" + `operator ${combinationOperator} is not supported` ); } - const callbackName = "on" + header.typeName; - if (callbackName in visitor) { - visitor[callbackName].apply(visitor, args); - } } - function processSegments(segments, visitor) { - for (let i = 0, ii = segments.length; i < ii; i++) { - processSegment(segments[i], visitor); - } + onImmediateGenericRegion(region, data, start, end) { + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeBitmap( + region.mmr, + regionInfo.width, + regionInfo.height, + region.template, + region.prediction, + null, + region.at, + decodingContext + ); + this.drawBitmap(regionInfo, bitmap); } - function parseJbig2Chunks(chunks) { - const visitor = new SimpleSegmentVisitor(); - for (let i = 0, ii = chunks.length; i < ii; i++) { - const chunk = chunks[i]; - const segments = readSegments({}, chunk.data, chunk.start, chunk.end); - processSegments(segments, visitor); - } - return visitor.buffer; + onImmediateLosslessGenericRegion() { + this.onImmediateGenericRegion.apply(this, arguments); } - function parseJbig2(data) { - const end = data.length; - let position = 0; - - if ( - data[position] !== 0x97 || - data[position + 1] !== 0x4a || - data[position + 2] !== 0x42 || - data[position + 3] !== 0x32 || - data[position + 4] !== 0x0d || - data[position + 5] !== 0x0a || - data[position + 6] !== 0x1a || - data[position + 7] !== 0x0a - ) { - throw new Jbig2Error("parseJbig2 - invalid header."); - } - - const header = Object.create(null); - position += 8; - const flags = data[position++]; - header.randomAccess = !(flags & 1); - if (!(flags & 2)) { - header.numberOfPages = readUint32(data, position); - position += 4; - } - - const segments = readSegments(header, data, position, end); - const visitor = new SimpleSegmentVisitor(); - processSegments(segments, visitor); - - const { width, height } = visitor.currentPageInfo; - const bitPacked = visitor.buffer; - const imgData = new Uint8ClampedArray(width * height); - let q = 0, - k = 0; - for (let i = 0; i < height; i++) { - let mask = 0, - buffer; - for (let j = 0; j < width; j++) { - if (!mask) { - mask = 128; - buffer = bitPacked[k++]; - } - imgData[q++] = buffer & mask ? 0 : 255; - mask >>= 1; - } - } - - return { imgData, width, height }; - } - - function SimpleSegmentVisitor() {} - - SimpleSegmentVisitor.prototype = { - onPageInformation: function SimpleSegmentVisitor_onPageInformation(info) { - this.currentPageInfo = info; - const rowSize = (info.width + 7) >> 3; - const buffer = new Uint8ClampedArray(rowSize * info.height); - // The contents of ArrayBuffers are initialized to 0. - // Fill the buffer with 0xFF only if info.defaultPixelValue is set - if (info.defaultPixelValue) { - for (let i = 0, ii = buffer.length; i < ii; i++) { - buffer[i] = 0xff; - } - } - this.buffer = buffer; - }, - drawBitmap: function SimpleSegmentVisitor_drawBitmap(regionInfo, bitmap) { - const pageInfo = this.currentPageInfo; - const width = regionInfo.width, - height = regionInfo.height; - const rowSize = (pageInfo.width + 7) >> 3; - const combinationOperator = pageInfo.combinationOperatorOverride - ? regionInfo.combinationOperator - : pageInfo.combinationOperator; - const buffer = this.buffer; - const mask0 = 128 >> (regionInfo.x & 7); - let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); - let i, j, mask, offset; - switch (combinationOperator) { - case 0: // OR - for (i = 0; i < height; i++) { - mask = mask0; - offset = offset0; - for (j = 0; j < width; j++) { - if (bitmap[i][j]) { - buffer[offset] |= mask; - } - mask >>= 1; - if (!mask) { - mask = 128; - offset++; - } - } - offset0 += rowSize; - } - break; - case 2: // XOR - for (i = 0; i < height; i++) { - mask = mask0; - offset = offset0; - for (j = 0; j < width; j++) { - if (bitmap[i][j]) { - buffer[offset] ^= mask; - } - mask >>= 1; - if (!mask) { - mask = 128; - offset++; - } - } - offset0 += rowSize; - } - break; - default: - throw new Jbig2Error( - `operator ${combinationOperator} is not supported` - ); - } - }, - onImmediateGenericRegion: function SimpleSegmentVisitor_onImmediateGenericRegion( - region, - data, - start, - end - ) { - const regionInfo = region.info; - const decodingContext = new DecodingContext(data, start, end); - const bitmap = decodeBitmap( - region.mmr, - regionInfo.width, - regionInfo.height, - region.template, - region.prediction, - null, - region.at, - decodingContext + onSymbolDictionary( + dictionary, + currentSegment, + referredSegments, + data, + start, + end + ) { + let huffmanTables, huffmanInput; + if (dictionary.huffman) { + huffmanTables = getSymbolDictionaryHuffmanTables( + dictionary, + referredSegments, + this.customTables ); - this.drawBitmap(regionInfo, bitmap); - }, - onImmediateLosslessGenericRegion: function SimpleSegmentVisitor_onImmediateLosslessGenericRegion() { - this.onImmediateGenericRegion.apply(this, arguments); - }, - onSymbolDictionary: function SimpleSegmentVisitor_onSymbolDictionary( - dictionary, - currentSegment, - referredSegments, - data, - start, - end - ) { - let huffmanTables, huffmanInput; - if (dictionary.huffman) { - huffmanTables = getSymbolDictionaryHuffmanTables( - dictionary, - referredSegments, - this.customTables - ); - huffmanInput = new Reader(data, start, end); - } + huffmanInput = new Reader(data, start, end); + } - // Combines exported symbols from all referred segments - let symbols = this.symbols; - if (!symbols) { - this.symbols = symbols = {}; - } + // Combines exported symbols from all referred segments + let symbols = this.symbols; + if (!symbols) { + this.symbols = symbols = {}; + } - let inputSymbols = []; - for (let i = 0, ii = referredSegments.length; i < ii; i++) { - const referredSymbols = symbols[referredSegments[i]]; - // referredSymbols is undefined when we have a reference to a Tables - // segment instead of a SymbolDictionary. - if (referredSymbols) { - inputSymbols = inputSymbols.concat(referredSymbols); - } + let inputSymbols = []; + for (let i = 0, ii = referredSegments.length; i < ii; i++) { + const referredSymbols = symbols[referredSegments[i]]; + // referredSymbols is undefined when we have a reference to a Tables + // segment instead of a SymbolDictionary. + if (referredSymbols) { + inputSymbols = inputSymbols.concat(referredSymbols); } + } - const decodingContext = new DecodingContext(data, start, end); - symbols[currentSegment] = decodeSymbolDictionary( - dictionary.huffman, - dictionary.refinement, - inputSymbols, - dictionary.numberOfNewSymbols, - dictionary.numberOfExportedSymbols, - huffmanTables, - dictionary.template, - dictionary.at, - dictionary.refinementTemplate, - dictionary.refinementAt, - decodingContext, + const decodingContext = new DecodingContext(data, start, end); + symbols[currentSegment] = decodeSymbolDictionary( + dictionary.huffman, + dictionary.refinement, + inputSymbols, + dictionary.numberOfNewSymbols, + dictionary.numberOfExportedSymbols, + huffmanTables, + dictionary.template, + dictionary.at, + dictionary.refinementTemplate, + dictionary.refinementAt, + decodingContext, + huffmanInput + ); + } + + onImmediateTextRegion(region, referredSegments, data, start, end) { + const regionInfo = region.info; + let huffmanTables, huffmanInput; + + // Combines exported symbols from all referred segments + const symbols = this.symbols; + let inputSymbols = []; + for (let i = 0, ii = referredSegments.length; i < ii; i++) { + const referredSymbols = symbols[referredSegments[i]]; + // referredSymbols is undefined when we have a reference to a Tables + // segment instead of a SymbolDictionary. + if (referredSymbols) { + inputSymbols = inputSymbols.concat(referredSymbols); + } + } + const symbolCodeLength = log2(inputSymbols.length); + if (region.huffman) { + huffmanInput = new Reader(data, start, end); + huffmanTables = getTextRegionHuffmanTables( + region, + referredSegments, + this.customTables, + inputSymbols.length, huffmanInput ); - }, - onImmediateTextRegion: function SimpleSegmentVisitor_onImmediateTextRegion( - region, - referredSegments, - data, - start, - end - ) { - const regionInfo = region.info; - let huffmanTables, huffmanInput; + } - // Combines exported symbols from all referred segments - const symbols = this.symbols; - let inputSymbols = []; - for (let i = 0, ii = referredSegments.length; i < ii; i++) { - const referredSymbols = symbols[referredSegments[i]]; - // referredSymbols is undefined when we have a reference to a Tables - // segment instead of a SymbolDictionary. - if (referredSymbols) { - inputSymbols = inputSymbols.concat(referredSymbols); - } - } - const symbolCodeLength = log2(inputSymbols.length); - if (region.huffman) { - huffmanInput = new Reader(data, start, end); - huffmanTables = getTextRegionHuffmanTables( - region, - referredSegments, - this.customTables, - inputSymbols.length, - huffmanInput - ); - } + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeTextRegion( + region.huffman, + region.refinement, + regionInfo.width, + regionInfo.height, + region.defaultPixelValue, + region.numberOfSymbolInstances, + region.stripSize, + inputSymbols, + symbolCodeLength, + region.transposed, + region.dsOffset, + region.referenceCorner, + region.combinationOperator, + huffmanTables, + region.refinementTemplate, + region.refinementAt, + decodingContext, + region.logStripSize, + huffmanInput + ); + this.drawBitmap(regionInfo, bitmap); + } - const decodingContext = new DecodingContext(data, start, end); - const bitmap = decodeTextRegion( - region.huffman, - region.refinement, - regionInfo.width, - regionInfo.height, - region.defaultPixelValue, - region.numberOfSymbolInstances, - region.stripSize, - inputSymbols, - symbolCodeLength, - region.transposed, - region.dsOffset, - region.referenceCorner, - region.combinationOperator, - huffmanTables, - region.refinementTemplate, - region.refinementAt, - decodingContext, - region.logStripSize, - huffmanInput - ); - this.drawBitmap(regionInfo, bitmap); - }, - onImmediateLosslessTextRegion: function SimpleSegmentVisitor_onImmediateLosslessTextRegion() { - this.onImmediateTextRegion.apply(this, arguments); - }, - onPatternDictionary(dictionary, currentSegment, data, start, end) { - let patterns = this.patterns; - if (!patterns) { - this.patterns = patterns = {}; - } - const decodingContext = new DecodingContext(data, start, end); - patterns[currentSegment] = decodePatternDictionary( - dictionary.mmr, - dictionary.patternWidth, - dictionary.patternHeight, - dictionary.maxPatternIndex, - dictionary.template, - decodingContext - ); - }, - onImmediateHalftoneRegion(region, referredSegments, data, start, end) { - // HalftoneRegion refers to exactly one PatternDictionary. - const patterns = this.patterns[referredSegments[0]]; - const regionInfo = region.info; - const decodingContext = new DecodingContext(data, start, end); - const bitmap = decodeHalftoneRegion( - region.mmr, - patterns, - region.template, - regionInfo.width, - regionInfo.height, - region.defaultPixelValue, - region.enableSkip, - region.combinationOperator, - region.gridWidth, - region.gridHeight, - region.gridOffsetX, - region.gridOffsetY, - region.gridVectorX, - region.gridVectorY, - decodingContext - ); - this.drawBitmap(regionInfo, bitmap); - }, - onImmediateLosslessHalftoneRegion() { - this.onImmediateHalftoneRegion.apply(this, arguments); - }, - onTables(currentSegment, data, start, end) { - let customTables = this.customTables; - if (!customTables) { - this.customTables = customTables = {}; - } - customTables[currentSegment] = decodeTablesSegment(data, start, end); - }, - }; + onImmediateLosslessTextRegion() { + this.onImmediateTextRegion.apply(this, arguments); + } - function HuffmanLine(lineData) { + onPatternDictionary(dictionary, currentSegment, data, start, end) { + let patterns = this.patterns; + if (!patterns) { + this.patterns = patterns = {}; + } + const decodingContext = new DecodingContext(data, start, end); + patterns[currentSegment] = decodePatternDictionary( + dictionary.mmr, + dictionary.patternWidth, + dictionary.patternHeight, + dictionary.maxPatternIndex, + dictionary.template, + decodingContext + ); + } + + onImmediateHalftoneRegion(region, referredSegments, data, start, end) { + // HalftoneRegion refers to exactly one PatternDictionary. + const patterns = this.patterns[referredSegments[0]]; + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeHalftoneRegion( + region.mmr, + patterns, + region.template, + regionInfo.width, + regionInfo.height, + region.defaultPixelValue, + region.enableSkip, + region.combinationOperator, + region.gridWidth, + region.gridHeight, + region.gridOffsetX, + region.gridOffsetY, + region.gridVectorX, + region.gridVectorY, + decodingContext + ); + this.drawBitmap(regionInfo, bitmap); + } + + onImmediateLosslessHalftoneRegion() { + this.onImmediateHalftoneRegion.apply(this, arguments); + } + + onTables(currentSegment, data, start, end) { + let customTables = this.customTables; + if (!customTables) { + this.customTables = customTables = {}; + } + customTables[currentSegment] = decodeTablesSegment(data, start, end); + } +} + +class HuffmanLine { + constructor(lineData) { if (lineData.length === 2) { // OOB line. this.isOOB = true; @@ -1837,8 +1817,10 @@ const Jbig2Image = (function Jbig2ImageClosure() { this.isLowerRange = lineData[4] === "lower"; } } +} - function HuffmanTreeNode(line) { +class HuffmanTreeNode { + constructor(line) { this.children = []; if (line) { // Leaf node @@ -1853,38 +1835,39 @@ const Jbig2Image = (function Jbig2ImageClosure() { } } - HuffmanTreeNode.prototype = { - buildTree(line, shift) { - const bit = (line.prefixCode >> shift) & 1; - if (shift <= 0) { - // Create a leaf node. - this.children[bit] = new HuffmanTreeNode(line); - } else { - // Create an intermediate node and continue recursively. - let node = this.children[bit]; - if (!node) { - this.children[bit] = node = new HuffmanTreeNode(null); - } - node.buildTree(line, shift - 1); - } - }, - decodeNode(reader) { - if (this.isLeaf) { - if (this.isOOB) { - return null; - } - const htOffset = reader.readBits(this.rangeLength); - return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset); - } - const node = this.children[reader.readBit()]; + buildTree(line, shift) { + const bit = (line.prefixCode >> shift) & 1; + if (shift <= 0) { + // Create a leaf node. + this.children[bit] = new HuffmanTreeNode(line); + } else { + // Create an intermediate node and continue recursively. + let node = this.children[bit]; if (!node) { - throw new Jbig2Error("invalid Huffman data"); + this.children[bit] = node = new HuffmanTreeNode(null); } - return node.decodeNode(reader); - }, - }; + node.buildTree(line, shift - 1); + } + } - function HuffmanTable(lines, prefixCodesDone) { + decodeNode(reader) { + if (this.isLeaf) { + if (this.isOOB) { + return null; + } + const htOffset = reader.readBits(this.rangeLength); + return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset); + } + const node = this.children[reader.readBit()]; + if (!node) { + throw new Jbig2Error("invalid Huffman data"); + } + return node.decodeNode(reader); + } +} + +class HuffmanTable { + constructor(lines, prefixCodesDone) { if (!prefixCodesDone) { this.assignPrefixCodes(lines); } @@ -1898,357 +1881,356 @@ const Jbig2Image = (function Jbig2ImageClosure() { } } - HuffmanTable.prototype = { - decode(reader) { - return this.rootNode.decodeNode(reader); - }, - assignPrefixCodes(lines) { - // Annex B.3 Assigning the prefix codes. - const linesLength = lines.length; - let prefixLengthMax = 0; - for (let i = 0; i < linesLength; i++) { - prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength); - } - - const histogram = new Uint32Array(prefixLengthMax + 1); - for (let i = 0; i < linesLength; i++) { - histogram[lines[i].prefixLength]++; - } - let currentLength = 1, - firstCode = 0, - currentCode, - currentTemp, - line; - histogram[0] = 0; - - while (currentLength <= prefixLengthMax) { - firstCode = (firstCode + histogram[currentLength - 1]) << 1; - currentCode = firstCode; - currentTemp = 0; - while (currentTemp < linesLength) { - line = lines[currentTemp]; - if (line.prefixLength === currentLength) { - line.prefixCode = currentCode; - currentCode++; - } - currentTemp++; - } - currentLength++; - } - }, - }; - - function decodeTablesSegment(data, start, end) { - // Decodes a Tables segment, i.e., a custom Huffman table. - // Annex B.2 Code table structure. - const flags = data[start]; - const lowestValue = readUint32(data, start + 1) & 0xffffffff; - const highestValue = readUint32(data, start + 5) & 0xffffffff; - const reader = new Reader(data, start + 9, end); - - const prefixSizeBits = ((flags >> 1) & 7) + 1; - const rangeSizeBits = ((flags >> 4) & 7) + 1; - const lines = []; - let prefixLength, - rangeLength, - currentRangeLow = lowestValue; - - // Normal table lines - do { - prefixLength = reader.readBits(prefixSizeBits); - rangeLength = reader.readBits(rangeSizeBits); - lines.push( - new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]) - ); - currentRangeLow += 1 << rangeLength; - } while (currentRangeLow < highestValue); - - // Lower range table line - prefixLength = reader.readBits(prefixSizeBits); - lines.push( - new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"]) - ); - - // Upper range table line - prefixLength = reader.readBits(prefixSizeBits); - lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0])); - - if (flags & 1) { - // Out-of-band table line - prefixLength = reader.readBits(prefixSizeBits); - lines.push(new HuffmanLine([prefixLength, 0])); - } - - return new HuffmanTable(lines, false); + decode(reader) { + return this.rootNode.decodeNode(reader); } - const standardTablesCache = {}; - - function getStandardTable(number) { - // Annex B.5 Standard Huffman tables. - let table = standardTablesCache[number]; - if (table) { - return table; - } - let lines; - switch (number) { - case 1: - lines = [ - [0, 1, 4, 0x0], - [16, 2, 8, 0x2], - [272, 3, 16, 0x6], - [65808, 3, 32, 0x7], // upper - ]; - break; - case 2: - lines = [ - [0, 1, 0, 0x0], - [1, 2, 0, 0x2], - [2, 3, 0, 0x6], - [3, 4, 3, 0xe], - [11, 5, 6, 0x1e], - [75, 6, 32, 0x3e], // upper - [6, 0x3f], // OOB - ]; - break; - case 3: - lines = [ - [-256, 8, 8, 0xfe], - [0, 1, 0, 0x0], - [1, 2, 0, 0x2], - [2, 3, 0, 0x6], - [3, 4, 3, 0xe], - [11, 5, 6, 0x1e], - [-257, 8, 32, 0xff, "lower"], - [75, 7, 32, 0x7e], // upper - [6, 0x3e], // OOB - ]; - break; - case 4: - lines = [ - [1, 1, 0, 0x0], - [2, 2, 0, 0x2], - [3, 3, 0, 0x6], - [4, 4, 3, 0xe], - [12, 5, 6, 0x1e], - [76, 5, 32, 0x1f], // upper - ]; - break; - case 5: - lines = [ - [-255, 7, 8, 0x7e], - [1, 1, 0, 0x0], - [2, 2, 0, 0x2], - [3, 3, 0, 0x6], - [4, 4, 3, 0xe], - [12, 5, 6, 0x1e], - [-256, 7, 32, 0x7f, "lower"], - [76, 6, 32, 0x3e], // upper - ]; - break; - case 6: - lines = [ - [-2048, 5, 10, 0x1c], - [-1024, 4, 9, 0x8], - [-512, 4, 8, 0x9], - [-256, 4, 7, 0xa], - [-128, 5, 6, 0x1d], - [-64, 5, 5, 0x1e], - [-32, 4, 5, 0xb], - [0, 2, 7, 0x0], - [128, 3, 7, 0x2], - [256, 3, 8, 0x3], - [512, 4, 9, 0xc], - [1024, 4, 10, 0xd], - [-2049, 6, 32, 0x3e, "lower"], - [2048, 6, 32, 0x3f], // upper - ]; - break; - case 7: - lines = [ - [-1024, 4, 9, 0x8], - [-512, 3, 8, 0x0], - [-256, 4, 7, 0x9], - [-128, 5, 6, 0x1a], - [-64, 5, 5, 0x1b], - [-32, 4, 5, 0xa], - [0, 4, 5, 0xb], - [32, 5, 5, 0x1c], - [64, 5, 6, 0x1d], - [128, 4, 7, 0xc], - [256, 3, 8, 0x1], - [512, 3, 9, 0x2], - [1024, 3, 10, 0x3], - [-1025, 5, 32, 0x1e, "lower"], - [2048, 5, 32, 0x1f], // upper - ]; - break; - case 8: - lines = [ - [-15, 8, 3, 0xfc], - [-7, 9, 1, 0x1fc], - [-5, 8, 1, 0xfd], - [-3, 9, 0, 0x1fd], - [-2, 7, 0, 0x7c], - [-1, 4, 0, 0xa], - [0, 2, 1, 0x0], - [2, 5, 0, 0x1a], - [3, 6, 0, 0x3a], - [4, 3, 4, 0x4], - [20, 6, 1, 0x3b], - [22, 4, 4, 0xb], - [38, 4, 5, 0xc], - [70, 5, 6, 0x1b], - [134, 5, 7, 0x1c], - [262, 6, 7, 0x3c], - [390, 7, 8, 0x7d], - [646, 6, 10, 0x3d], - [-16, 9, 32, 0x1fe, "lower"], - [1670, 9, 32, 0x1ff], // upper - [2, 0x1], // OOB - ]; - break; - case 9: - lines = [ - [-31, 8, 4, 0xfc], - [-15, 9, 2, 0x1fc], - [-11, 8, 2, 0xfd], - [-7, 9, 1, 0x1fd], - [-5, 7, 1, 0x7c], - [-3, 4, 1, 0xa], - [-1, 3, 1, 0x2], - [1, 3, 1, 0x3], - [3, 5, 1, 0x1a], - [5, 6, 1, 0x3a], - [7, 3, 5, 0x4], - [39, 6, 2, 0x3b], - [43, 4, 5, 0xb], - [75, 4, 6, 0xc], - [139, 5, 7, 0x1b], - [267, 5, 8, 0x1c], - [523, 6, 8, 0x3c], - [779, 7, 9, 0x7d], - [1291, 6, 11, 0x3d], - [-32, 9, 32, 0x1fe, "lower"], - [3339, 9, 32, 0x1ff], // upper - [2, 0x0], // OOB - ]; - break; - case 10: - lines = [ - [-21, 7, 4, 0x7a], - [-5, 8, 0, 0xfc], - [-4, 7, 0, 0x7b], - [-3, 5, 0, 0x18], - [-2, 2, 2, 0x0], - [2, 5, 0, 0x19], - [3, 6, 0, 0x36], - [4, 7, 0, 0x7c], - [5, 8, 0, 0xfd], - [6, 2, 6, 0x1], - [70, 5, 5, 0x1a], - [102, 6, 5, 0x37], - [134, 6, 6, 0x38], - [198, 6, 7, 0x39], - [326, 6, 8, 0x3a], - [582, 6, 9, 0x3b], - [1094, 6, 10, 0x3c], - [2118, 7, 11, 0x7d], - [-22, 8, 32, 0xfe, "lower"], - [4166, 8, 32, 0xff], // upper - [2, 0x2], // OOB - ]; - break; - case 11: - lines = [ - [1, 1, 0, 0x0], - [2, 2, 1, 0x2], - [4, 4, 0, 0xc], - [5, 4, 1, 0xd], - [7, 5, 1, 0x1c], - [9, 5, 2, 0x1d], - [13, 6, 2, 0x3c], - [17, 7, 2, 0x7a], - [21, 7, 3, 0x7b], - [29, 7, 4, 0x7c], - [45, 7, 5, 0x7d], - [77, 7, 6, 0x7e], - [141, 7, 32, 0x7f], // upper - ]; - break; - case 12: - lines = [ - [1, 1, 0, 0x0], - [2, 2, 0, 0x2], - [3, 3, 1, 0x6], - [5, 5, 0, 0x1c], - [6, 5, 1, 0x1d], - [8, 6, 1, 0x3c], - [10, 7, 0, 0x7a], - [11, 7, 1, 0x7b], - [13, 7, 2, 0x7c], - [17, 7, 3, 0x7d], - [25, 7, 4, 0x7e], - [41, 8, 5, 0xfe], - [73, 8, 32, 0xff], // upper - ]; - break; - case 13: - lines = [ - [1, 1, 0, 0x0], - [2, 3, 0, 0x4], - [3, 4, 0, 0xc], - [4, 5, 0, 0x1c], - [5, 4, 1, 0xd], - [7, 3, 3, 0x5], - [15, 6, 1, 0x3a], - [17, 6, 2, 0x3b], - [21, 6, 3, 0x3c], - [29, 6, 4, 0x3d], - [45, 6, 5, 0x3e], - [77, 7, 6, 0x7e], - [141, 7, 32, 0x7f], // upper - ]; - break; - case 14: - lines = [ - [-2, 3, 0, 0x4], - [-1, 3, 0, 0x5], - [0, 1, 0, 0x0], - [1, 3, 0, 0x6], - [2, 3, 0, 0x7], - ]; - break; - case 15: - lines = [ - [-24, 7, 4, 0x7c], - [-8, 6, 2, 0x3c], - [-4, 5, 1, 0x1c], - [-2, 4, 0, 0xc], - [-1, 3, 0, 0x4], - [0, 1, 0, 0x0], - [1, 3, 0, 0x5], - [2, 4, 0, 0xd], - [3, 5, 1, 0x1d], - [5, 6, 2, 0x3d], - [9, 7, 4, 0x7d], - [-25, 7, 32, 0x7e, "lower"], - [25, 7, 32, 0x7f], // upper - ]; - break; - default: - throw new Jbig2Error(`standard table B.${number} does not exist`); + assignPrefixCodes(lines) { + // Annex B.3 Assigning the prefix codes. + const linesLength = lines.length; + let prefixLengthMax = 0; + for (let i = 0; i < linesLength; i++) { + prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength); } - for (let i = 0, ii = lines.length; i < ii; i++) { - lines[i] = new HuffmanLine(lines[i]); + const histogram = new Uint32Array(prefixLengthMax + 1); + for (let i = 0; i < linesLength; i++) { + histogram[lines[i].prefixLength]++; } - table = new HuffmanTable(lines, true); - standardTablesCache[number] = table; + let currentLength = 1, + firstCode = 0, + currentCode, + currentTemp, + line; + histogram[0] = 0; + + while (currentLength <= prefixLengthMax) { + firstCode = (firstCode + histogram[currentLength - 1]) << 1; + currentCode = firstCode; + currentTemp = 0; + while (currentTemp < linesLength) { + line = lines[currentTemp]; + if (line.prefixLength === currentLength) { + line.prefixCode = currentCode; + currentCode++; + } + currentTemp++; + } + currentLength++; + } + } +} + +function decodeTablesSegment(data, start, end) { + // Decodes a Tables segment, i.e., a custom Huffman table. + // Annex B.2 Code table structure. + const flags = data[start]; + const lowestValue = readUint32(data, start + 1) & 0xffffffff; + const highestValue = readUint32(data, start + 5) & 0xffffffff; + const reader = new Reader(data, start + 9, end); + + const prefixSizeBits = ((flags >> 1) & 7) + 1; + const rangeSizeBits = ((flags >> 4) & 7) + 1; + const lines = []; + let prefixLength, + rangeLength, + currentRangeLow = lowestValue; + + // Normal table lines + do { + prefixLength = reader.readBits(prefixSizeBits); + rangeLength = reader.readBits(rangeSizeBits); + lines.push( + new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]) + ); + currentRangeLow += 1 << rangeLength; + } while (currentRangeLow < highestValue); + + // Lower range table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"])); + + // Upper range table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0])); + + if (flags & 1) { + // Out-of-band table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([prefixLength, 0])); + } + + return new HuffmanTable(lines, false); +} + +const standardTablesCache = {}; + +function getStandardTable(number) { + // Annex B.5 Standard Huffman tables. + let table = standardTablesCache[number]; + if (table) { return table; } + let lines; + switch (number) { + case 1: + lines = [ + [0, 1, 4, 0x0], + [16, 2, 8, 0x2], + [272, 3, 16, 0x6], + [65808, 3, 32, 0x7], // upper + ]; + break; + case 2: + lines = [ + [0, 1, 0, 0x0], + [1, 2, 0, 0x2], + [2, 3, 0, 0x6], + [3, 4, 3, 0xe], + [11, 5, 6, 0x1e], + [75, 6, 32, 0x3e], // upper + [6, 0x3f], // OOB + ]; + break; + case 3: + lines = [ + [-256, 8, 8, 0xfe], + [0, 1, 0, 0x0], + [1, 2, 0, 0x2], + [2, 3, 0, 0x6], + [3, 4, 3, 0xe], + [11, 5, 6, 0x1e], + [-257, 8, 32, 0xff, "lower"], + [75, 7, 32, 0x7e], // upper + [6, 0x3e], // OOB + ]; + break; + case 4: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 0, 0x6], + [4, 4, 3, 0xe], + [12, 5, 6, 0x1e], + [76, 5, 32, 0x1f], // upper + ]; + break; + case 5: + lines = [ + [-255, 7, 8, 0x7e], + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 0, 0x6], + [4, 4, 3, 0xe], + [12, 5, 6, 0x1e], + [-256, 7, 32, 0x7f, "lower"], + [76, 6, 32, 0x3e], // upper + ]; + break; + case 6: + lines = [ + [-2048, 5, 10, 0x1c], + [-1024, 4, 9, 0x8], + [-512, 4, 8, 0x9], + [-256, 4, 7, 0xa], + [-128, 5, 6, 0x1d], + [-64, 5, 5, 0x1e], + [-32, 4, 5, 0xb], + [0, 2, 7, 0x0], + [128, 3, 7, 0x2], + [256, 3, 8, 0x3], + [512, 4, 9, 0xc], + [1024, 4, 10, 0xd], + [-2049, 6, 32, 0x3e, "lower"], + [2048, 6, 32, 0x3f], // upper + ]; + break; + case 7: + lines = [ + [-1024, 4, 9, 0x8], + [-512, 3, 8, 0x0], + [-256, 4, 7, 0x9], + [-128, 5, 6, 0x1a], + [-64, 5, 5, 0x1b], + [-32, 4, 5, 0xa], + [0, 4, 5, 0xb], + [32, 5, 5, 0x1c], + [64, 5, 6, 0x1d], + [128, 4, 7, 0xc], + [256, 3, 8, 0x1], + [512, 3, 9, 0x2], + [1024, 3, 10, 0x3], + [-1025, 5, 32, 0x1e, "lower"], + [2048, 5, 32, 0x1f], // upper + ]; + break; + case 8: + lines = [ + [-15, 8, 3, 0xfc], + [-7, 9, 1, 0x1fc], + [-5, 8, 1, 0xfd], + [-3, 9, 0, 0x1fd], + [-2, 7, 0, 0x7c], + [-1, 4, 0, 0xa], + [0, 2, 1, 0x0], + [2, 5, 0, 0x1a], + [3, 6, 0, 0x3a], + [4, 3, 4, 0x4], + [20, 6, 1, 0x3b], + [22, 4, 4, 0xb], + [38, 4, 5, 0xc], + [70, 5, 6, 0x1b], + [134, 5, 7, 0x1c], + [262, 6, 7, 0x3c], + [390, 7, 8, 0x7d], + [646, 6, 10, 0x3d], + [-16, 9, 32, 0x1fe, "lower"], + [1670, 9, 32, 0x1ff], // upper + [2, 0x1], // OOB + ]; + break; + case 9: + lines = [ + [-31, 8, 4, 0xfc], + [-15, 9, 2, 0x1fc], + [-11, 8, 2, 0xfd], + [-7, 9, 1, 0x1fd], + [-5, 7, 1, 0x7c], + [-3, 4, 1, 0xa], + [-1, 3, 1, 0x2], + [1, 3, 1, 0x3], + [3, 5, 1, 0x1a], + [5, 6, 1, 0x3a], + [7, 3, 5, 0x4], + [39, 6, 2, 0x3b], + [43, 4, 5, 0xb], + [75, 4, 6, 0xc], + [139, 5, 7, 0x1b], + [267, 5, 8, 0x1c], + [523, 6, 8, 0x3c], + [779, 7, 9, 0x7d], + [1291, 6, 11, 0x3d], + [-32, 9, 32, 0x1fe, "lower"], + [3339, 9, 32, 0x1ff], // upper + [2, 0x0], // OOB + ]; + break; + case 10: + lines = [ + [-21, 7, 4, 0x7a], + [-5, 8, 0, 0xfc], + [-4, 7, 0, 0x7b], + [-3, 5, 0, 0x18], + [-2, 2, 2, 0x0], + [2, 5, 0, 0x19], + [3, 6, 0, 0x36], + [4, 7, 0, 0x7c], + [5, 8, 0, 0xfd], + [6, 2, 6, 0x1], + [70, 5, 5, 0x1a], + [102, 6, 5, 0x37], + [134, 6, 6, 0x38], + [198, 6, 7, 0x39], + [326, 6, 8, 0x3a], + [582, 6, 9, 0x3b], + [1094, 6, 10, 0x3c], + [2118, 7, 11, 0x7d], + [-22, 8, 32, 0xfe, "lower"], + [4166, 8, 32, 0xff], // upper + [2, 0x2], // OOB + ]; + break; + case 11: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 1, 0x2], + [4, 4, 0, 0xc], + [5, 4, 1, 0xd], + [7, 5, 1, 0x1c], + [9, 5, 2, 0x1d], + [13, 6, 2, 0x3c], + [17, 7, 2, 0x7a], + [21, 7, 3, 0x7b], + [29, 7, 4, 0x7c], + [45, 7, 5, 0x7d], + [77, 7, 6, 0x7e], + [141, 7, 32, 0x7f], // upper + ]; + break; + case 12: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 1, 0x6], + [5, 5, 0, 0x1c], + [6, 5, 1, 0x1d], + [8, 6, 1, 0x3c], + [10, 7, 0, 0x7a], + [11, 7, 1, 0x7b], + [13, 7, 2, 0x7c], + [17, 7, 3, 0x7d], + [25, 7, 4, 0x7e], + [41, 8, 5, 0xfe], + [73, 8, 32, 0xff], // upper + ]; + break; + case 13: + lines = [ + [1, 1, 0, 0x0], + [2, 3, 0, 0x4], + [3, 4, 0, 0xc], + [4, 5, 0, 0x1c], + [5, 4, 1, 0xd], + [7, 3, 3, 0x5], + [15, 6, 1, 0x3a], + [17, 6, 2, 0x3b], + [21, 6, 3, 0x3c], + [29, 6, 4, 0x3d], + [45, 6, 5, 0x3e], + [77, 7, 6, 0x7e], + [141, 7, 32, 0x7f], // upper + ]; + break; + case 14: + lines = [ + [-2, 3, 0, 0x4], + [-1, 3, 0, 0x5], + [0, 1, 0, 0x0], + [1, 3, 0, 0x6], + [2, 3, 0, 0x7], + ]; + break; + case 15: + lines = [ + [-24, 7, 4, 0x7c], + [-8, 6, 2, 0x3c], + [-4, 5, 1, 0x1c], + [-2, 4, 0, 0xc], + [-1, 3, 0, 0x4], + [0, 1, 0, 0x0], + [1, 3, 0, 0x5], + [2, 4, 0, 0xd], + [3, 5, 1, 0x1d], + [5, 6, 2, 0x3d], + [9, 7, 4, 0x7d], + [-25, 7, 32, 0x7e, "lower"], + [25, 7, 32, 0x7f], // upper + ]; + break; + default: + throw new Jbig2Error(`standard table B.${number} does not exist`); + } - function Reader(data, start, end) { + for (let i = 0, ii = lines.length; i < ii; i++) { + lines[i] = new HuffmanLine(lines[i]); + } + table = new HuffmanTable(lines, true); + standardTablesCache[number] = table; + return table; +} + +class Reader { + constructor(data, start, end) { this.data = data; this.start = start; this.end = end; @@ -2257,337 +2239,330 @@ const Jbig2Image = (function Jbig2ImageClosure() { this.currentByte = 0; } - Reader.prototype = { - readBit() { - if (this.shift < 0) { - if (this.position >= this.end) { - throw new Jbig2Error("end of data while reading bit"); - } - this.currentByte = this.data[this.position++]; - this.shift = 7; - } - const bit = (this.currentByte >> this.shift) & 1; - this.shift--; - return bit; - }, - - readBits(numBits) { - let result = 0, - i; - for (i = numBits - 1; i >= 0; i--) { - result |= this.readBit() << i; - } - return result; - }, - - byteAlign() { - this.shift = -1; - }, - - next() { + readBit() { + if (this.shift < 0) { if (this.position >= this.end) { - return -1; - } - return this.data[this.position++]; - }, - }; - - function getCustomHuffmanTable(index, referredTo, customTables) { - // Returns a Tables segment that has been earlier decoded. - // See 7.4.2.1.6 (symbol dictionary) or 7.4.3.1.6 (text region). - let currentIndex = 0; - for (let i = 0, ii = referredTo.length; i < ii; i++) { - const table = customTables[referredTo[i]]; - if (table) { - if (index === currentIndex) { - return table; - } - currentIndex++; + throw new Jbig2Error("end of data while reading bit"); } + this.currentByte = this.data[this.position++]; + this.shift = 7; } - throw new Jbig2Error("can't find custom Huffman table"); + const bit = (this.currentByte >> this.shift) & 1; + this.shift--; + return bit; } - function getTextRegionHuffmanTables( - textRegion, - referredTo, - customTables, - numberOfSymbols, - reader - ) { - // 7.4.3.1.7 Symbol ID Huffman table decoding - - // Read code lengths for RUNCODEs 0...34. - const codes = []; - for (let i = 0; i <= 34; i++) { - const codeLength = reader.readBits(4); - codes.push(new HuffmanLine([i, codeLength, 0, 0])); + readBits(numBits) { + let result = 0, + i; + for (i = numBits - 1; i >= 0; i--) { + result |= this.readBit() << i; } - // Assign Huffman codes for RUNCODEs. - const runCodesTable = new HuffmanTable(codes, false); + return result; + } - // Read a Huffman code using the assignment above. - // Interpret the RUNCODE codes and the additional bits (if any). - codes.length = 0; - for (let i = 0; i < numberOfSymbols; ) { - const codeLength = runCodesTable.decode(reader); - if (codeLength >= 32) { - let repeatedLength, numberOfRepeats, j; - switch (codeLength) { - case 32: - if (i === 0) { - throw new Jbig2Error("no previous value in symbol ID table"); - } - numberOfRepeats = reader.readBits(2) + 3; - repeatedLength = codes[i - 1].prefixLength; - break; - case 33: - numberOfRepeats = reader.readBits(3) + 3; - repeatedLength = 0; - break; - case 34: - numberOfRepeats = reader.readBits(7) + 11; - repeatedLength = 0; - break; - default: - throw new Jbig2Error("invalid code length in symbol ID table"); - } - for (j = 0; j < numberOfRepeats; j++) { - codes.push(new HuffmanLine([i, repeatedLength, 0, 0])); - i++; - } - } else { - codes.push(new HuffmanLine([i, codeLength, 0, 0])); + byteAlign() { + this.shift = -1; + } + + next() { + if (this.position >= this.end) { + return -1; + } + return this.data[this.position++]; + } +} + +function getCustomHuffmanTable(index, referredTo, customTables) { + // Returns a Tables segment that has been earlier decoded. + // See 7.4.2.1.6 (symbol dictionary) or 7.4.3.1.6 (text region). + let currentIndex = 0; + for (let i = 0, ii = referredTo.length; i < ii; i++) { + const table = customTables[referredTo[i]]; + if (table) { + if (index === currentIndex) { + return table; + } + currentIndex++; + } + } + throw new Jbig2Error("can't find custom Huffman table"); +} + +function getTextRegionHuffmanTables( + textRegion, + referredTo, + customTables, + numberOfSymbols, + reader +) { + // 7.4.3.1.7 Symbol ID Huffman table decoding + + // Read code lengths for RUNCODEs 0...34. + const codes = []; + for (let i = 0; i <= 34; i++) { + const codeLength = reader.readBits(4); + codes.push(new HuffmanLine([i, codeLength, 0, 0])); + } + // Assign Huffman codes for RUNCODEs. + const runCodesTable = new HuffmanTable(codes, false); + + // Read a Huffman code using the assignment above. + // Interpret the RUNCODE codes and the additional bits (if any). + codes.length = 0; + for (let i = 0; i < numberOfSymbols; ) { + const codeLength = runCodesTable.decode(reader); + if (codeLength >= 32) { + let repeatedLength, numberOfRepeats, j; + switch (codeLength) { + case 32: + if (i === 0) { + throw new Jbig2Error("no previous value in symbol ID table"); + } + numberOfRepeats = reader.readBits(2) + 3; + repeatedLength = codes[i - 1].prefixLength; + break; + case 33: + numberOfRepeats = reader.readBits(3) + 3; + repeatedLength = 0; + break; + case 34: + numberOfRepeats = reader.readBits(7) + 11; + repeatedLength = 0; + break; + default: + throw new Jbig2Error("invalid code length in symbol ID table"); + } + for (j = 0; j < numberOfRepeats; j++) { + codes.push(new HuffmanLine([i, repeatedLength, 0, 0])); i++; } + } else { + codes.push(new HuffmanLine([i, codeLength, 0, 0])); + i++; } - reader.byteAlign(); - const symbolIDTable = new HuffmanTable(codes, false); - - // 7.4.3.1.6 Text region segment Huffman table selection - - let customIndex = 0, - tableFirstS, - tableDeltaS, - tableDeltaT; - - switch (textRegion.huffmanFS) { - case 0: - case 1: - tableFirstS = getStandardTable(textRegion.huffmanFS + 6); - break; - case 3: - tableFirstS = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman FS selector"); - } - - switch (textRegion.huffmanDS) { - case 0: - case 1: - case 2: - tableDeltaS = getStandardTable(textRegion.huffmanDS + 8); - break; - case 3: - tableDeltaS = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DS selector"); - } - - switch (textRegion.huffmanDT) { - case 0: - case 1: - case 2: - tableDeltaT = getStandardTable(textRegion.huffmanDT + 11); - break; - case 3: - tableDeltaT = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DT selector"); - } - - if (textRegion.refinement) { - // Load tables RDW, RDH, RDX and RDY. - throw new Jbig2Error("refinement with Huffman is not supported"); - } - - return { - symbolIDTable, - tableFirstS, - tableDeltaS, - tableDeltaT, - }; } + reader.byteAlign(); + const symbolIDTable = new HuffmanTable(codes, false); - function getSymbolDictionaryHuffmanTables( - dictionary, - referredTo, - customTables - ) { - // 7.4.2.1.6 Symbol dictionary segment Huffman table selection + // 7.4.3.1.6 Text region segment Huffman table selection - let customIndex = 0, - tableDeltaHeight, - tableDeltaWidth; - switch (dictionary.huffmanDHSelector) { - case 0: - case 1: - tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4); - break; - case 3: - tableDeltaHeight = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DH selector"); - } + let customIndex = 0, + tableFirstS, + tableDeltaS, + tableDeltaT; - switch (dictionary.huffmanDWSelector) { - case 0: - case 1: - tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2); - break; - case 3: - tableDeltaWidth = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DW selector"); - } - - let tableBitmapSize, tableAggregateInstances; - if (dictionary.bitmapSizeSelector) { - tableBitmapSize = getCustomHuffmanTable( + switch (textRegion.huffmanFS) { + case 0: + case 1: + tableFirstS = getStandardTable(textRegion.huffmanFS + 6); + break; + case 3: + tableFirstS = getCustomHuffmanTable( customIndex, referredTo, customTables ); customIndex++; - } else { - tableBitmapSize = getStandardTable(1); - } + break; + default: + throw new Jbig2Error("invalid Huffman FS selector"); + } - if (dictionary.aggregationInstancesSelector) { - tableAggregateInstances = getCustomHuffmanTable( + switch (textRegion.huffmanDS) { + case 0: + case 1: + case 2: + tableDeltaS = getStandardTable(textRegion.huffmanDS + 8); + break; + case 3: + tableDeltaS = getCustomHuffmanTable( customIndex, referredTo, customTables ); - } else { - tableAggregateInstances = getStandardTable(1); - } - - return { - tableDeltaHeight, - tableDeltaWidth, - tableBitmapSize, - tableAggregateInstances, - }; + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DS selector"); } - function readUncompressedBitmap(reader, width, height) { - const bitmap = []; - for (let y = 0; y < height; y++) { - const row = new Uint8Array(width); - bitmap.push(row); - for (let x = 0; x < width; x++) { - row[x] = reader.readBit(); - } - reader.byteAlign(); - } - return bitmap; + switch (textRegion.huffmanDT) { + case 0: + case 1: + case 2: + tableDeltaT = getStandardTable(textRegion.huffmanDT + 11); + break; + case 3: + tableDeltaT = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DT selector"); } - function decodeMMRBitmap(input, width, height, endOfBlock) { - // MMR is the same compression algorithm as the PDF filter - // CCITTFaxDecode with /K -1. - const params = { - K: -1, - Columns: width, - Rows: height, - BlackIs1: true, - EndOfBlock: endOfBlock, - }; - const decoder = new CCITTFaxDecoder(input, params); - const bitmap = []; - let currentByte, - eof = false; - - for (let y = 0; y < height; y++) { - const row = new Uint8Array(width); - bitmap.push(row); - let shift = -1; - for (let x = 0; x < width; x++) { - if (shift < 0) { - currentByte = decoder.readNextChar(); - if (currentByte === -1) { - // Set the rest of the bits to zero. - currentByte = 0; - eof = true; - } - shift = 7; - } - row[x] = (currentByte >> shift) & 1; - shift--; - } - } - - if (endOfBlock && !eof) { - // Read until EOFB has been consumed. - const lookForEOFLimit = 5; - for (let i = 0; i < lookForEOFLimit; i++) { - if (decoder.readNextChar() === -1) { - break; - } - } - } - - return bitmap; + if (textRegion.refinement) { + // Load tables RDW, RDH, RDX and RDY. + throw new Jbig2Error("refinement with Huffman is not supported"); } - // eslint-disable-next-line no-shadow - function Jbig2Image() {} - - Jbig2Image.prototype = { - parseChunks(chunks) { - return parseJbig2Chunks(chunks); - }, - - parse(data) { - const { imgData, width, height } = parseJbig2(data); - this.width = width; - this.height = height; - return imgData; - }, + return { + symbolIDTable, + tableFirstS, + tableDeltaS, + tableDeltaT, }; +} - return Jbig2Image; -})(); +function getSymbolDictionaryHuffmanTables( + dictionary, + referredTo, + customTables +) { + // 7.4.2.1.6 Symbol dictionary segment Huffman table selection + + let customIndex = 0, + tableDeltaHeight, + tableDeltaWidth; + switch (dictionary.huffmanDHSelector) { + case 0: + case 1: + tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4); + break; + case 3: + tableDeltaHeight = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DH selector"); + } + + switch (dictionary.huffmanDWSelector) { + case 0: + case 1: + tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2); + break; + case 3: + tableDeltaWidth = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DW selector"); + } + + let tableBitmapSize, tableAggregateInstances; + if (dictionary.bitmapSizeSelector) { + tableBitmapSize = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + } else { + tableBitmapSize = getStandardTable(1); + } + + if (dictionary.aggregationInstancesSelector) { + tableAggregateInstances = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + } else { + tableAggregateInstances = getStandardTable(1); + } + + return { + tableDeltaHeight, + tableDeltaWidth, + tableBitmapSize, + tableAggregateInstances, + }; +} + +function readUncompressedBitmap(reader, width, height) { + const bitmap = []; + for (let y = 0; y < height; y++) { + const row = new Uint8Array(width); + bitmap.push(row); + for (let x = 0; x < width; x++) { + row[x] = reader.readBit(); + } + reader.byteAlign(); + } + return bitmap; +} + +function decodeMMRBitmap(input, width, height, endOfBlock) { + // MMR is the same compression algorithm as the PDF filter + // CCITTFaxDecode with /K -1. + const params = { + K: -1, + Columns: width, + Rows: height, + BlackIs1: true, + EndOfBlock: endOfBlock, + }; + const decoder = new CCITTFaxDecoder(input, params); + const bitmap = []; + let currentByte, + eof = false; + + for (let y = 0; y < height; y++) { + const row = new Uint8Array(width); + bitmap.push(row); + let shift = -1; + for (let x = 0; x < width; x++) { + if (shift < 0) { + currentByte = decoder.readNextChar(); + if (currentByte === -1) { + // Set the rest of the bits to zero. + currentByte = 0; + eof = true; + } + shift = 7; + } + row[x] = (currentByte >> shift) & 1; + shift--; + } + } + + if (endOfBlock && !eof) { + // Read until EOFB has been consumed. + const lookForEOFLimit = 5; + for (let i = 0; i < lookForEOFLimit; i++) { + if (decoder.readNextChar() === -1) { + break; + } + } + } + + return bitmap; +} + +class Jbig2Image { + parseChunks(chunks) { + return parseJbig2Chunks(chunks); + } + + parse(data) { + const { imgData, width, height } = parseJbig2(data); + this.width = width; + this.height = height; + return imgData; + } +} export { Jbig2Image };