diff --git a/src/core/jbig2.js b/src/core/jbig2.js index d7cd398ff..94b101260 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -111,7 +111,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var SegmentTypes = [ 'SymbolDictionary', null, null, null, 'IntermediateTextRegion', null, 'ImmediateTextRegion', 'ImmediateLosslessTextRegion', null, null, null, - null, null, null, null, null, 'patternDictionary', 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, @@ -619,6 +619,148 @@ var Jbig2Image = (function Jbig2ImageClosure() { return bitmap; } + function decodePatternDictionary(mmr, patternWidth, patternHeight, + maxPatternIndex, template, decodingContext) { + let at = []; + 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, + }); + } + let collectiveWidth = (maxPatternIndex + 1) * patternWidth; + let collectiveBitmap = decodeBitmap(mmr, collectiveWidth, patternHeight, + template, false, null, at, + decodingContext); + // Divide collective bitmap into patterns. + let patterns = [], i = 0, patternBitmap, xMin, xMax, y; + while (i <= maxPatternIndex) { + patternBitmap = []; + xMin = patternWidth * i; + xMax = xMin + patternWidth; + for (y = 0; y < patternHeight; y++) { + patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); + } + patterns.push(patternBitmap); + i++; + } + return patterns; + } + + function decodeHalftoneRegion(mmr, patterns, template, regionWidth, + regionHeight, defaultPixelValue, enableSkip, + combinationOperator, gridWidth, gridHeight, + gridOffsetX, gridOffsetY, gridVectorX, + gridVectorY, decodingContext) { + let 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. + let 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); + } + + let numberOfPatterns = patterns.length; + let pattern0 = patterns[0]; + let patternWidth = pattern0[0].length, patternHeight = pattern0.length; + let bitsPerValue = log2(numberOfPatterns); + let at = []; + 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. + let grayScaleBitPlanes = []; + for (i = bitsPerValue - 1; i >= 0; i--) { + grayScaleBitPlanes[i] = decodeBitmap(mmr, gridWidth, gridHeight, + template, false, skip, at, + decodingContext); + } + // 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; + } + function readSegmentHeader(data, start) { var segmentHeader = {}; segmentHeader.number = readUint32(data, start); @@ -850,6 +992,44 @@ var Jbig2Image = (function Jbig2ImageClosure() { } args = [textRegion, header.referredTo, data, position, end]; break; + case 16: // PatternDictionary + // 7.4.4. Pattern dictionary segment syntax + let patternDictionary = {}; + let 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 + let halftoneRegion = {}; + halftoneRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + let 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 var genericRegion = {}; @@ -1086,6 +1266,32 @@ var Jbig2Image = (function Jbig2ImageClosure() { function SimpleSegmentVisitor_onImmediateLosslessTextRegion() { this.onImmediateTextRegion.apply(this, arguments); }, + onPatternDictionary(dictionary, currentSegment, data, start, end) { + let patterns = this.patterns; + if (!patterns) { + this.patterns = patterns = {}; + } + let 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. + let patterns = this.patterns[referredSegments[0]]; + let regionInfo = region.info; + let decodingContext = new DecodingContext(data, start, end); + let 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); + }, }; function Jbig2Image() {} diff --git a/test/pdfs/pr8491.pdf.link b/test/pdfs/pr8491.pdf.link new file mode 100644 index 000000000..5ea4b02f2 --- /dev/null +++ b/test/pdfs/pr8491.pdf.link @@ -0,0 +1 @@ +https://github.com/mozilla/pdf.js/files/1055278/castle_halftone.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index d4d6aec6f..5e63f5b89 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3618,5 +3618,12 @@ "md5": "ced0e2d88cfd5b4d3a55d937ea288af1", "rounds": 1, "type": "eq" + }, + { "id": "pr8491", + "file": "pdfs/pr8491.pdf", + "md5": "36ea2e28cd77e9e70731f574ab27cbe0", + "rounds": 1, + "link": true, + "type": "eq" } ]