diff --git a/src/core/evaluator.js b/src/core/evaluator.js index ea1d2ae54..45de604b1 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -668,6 +668,51 @@ class PartialEvaluator { ); } + handleTransferFunction(tr) { + let transferArray; + if (Array.isArray(tr)) { + transferArray = tr; + } else if (isPDFFunction(tr)) { + transferArray = [tr]; + } else { + return null; // Not a valid transfer function entry. + } + + const transferMaps = []; + let numFns = 0, + numEffectfulFns = 0; + for (const entry of transferArray) { + const transferObj = this.xref.fetchIfRef(entry); + numFns++; + + if (isName(transferObj, "Identity")) { + transferMaps.push(null); + continue; + } else if (!isPDFFunction(transferObj)) { + return null; // Not a valid transfer function object. + } + + const transferFn = this._pdfFunctionFactory.create(transferObj); + const transferMap = new Uint8Array(256), + tmp = new Float32Array(1); + for (let j = 0; j < 256; j++) { + tmp[0] = j / 255; + transferFn(tmp, 0, tmp, 0); + transferMap[j] = (tmp[0] * 255) | 0; + } + transferMaps.push(transferMap); + numEffectfulFns++; + } + + if (!(numFns === 1 || numFns === 4)) { + return null; // Only 1 or 4 functions are supported, by the specification. + } + if (numEffectfulFns === 0) { + return null; // Only /Identity transfer functions found, which are no-ops. + } + return transferMaps; + } + handleTilingType( fn, args, @@ -846,6 +891,8 @@ class PartialEvaluator { gStateObj.push([key, value]); break; case "Font": + isSimpleGState = false; + promise = promise.then(() => { return this.handleSetFont( resources, @@ -885,7 +932,10 @@ class PartialEvaluator { } else { warn("Unsupported SMask type"); } - + break; + case "TR": + const transferMaps = this.handleTransferFunction(value); + gStateObj.push([key, transferMaps]); break; // Only generate info log messages for the following since // they are unlikely to have a big impact on the rendering. @@ -896,7 +946,6 @@ class PartialEvaluator { case "BG2": case "UCR": case "UCR2": - case "TR": case "TR2": case "HT": case "SM": diff --git a/src/display/canvas.js b/src/display/canvas.js index 83d95edfc..cca53d40d 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -416,6 +416,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() { this.lineWidth = 1; this.activeSMask = null; this.resumeSMaskCtx = null; // nonclonable field (see the save method below) + this.transferMaps = null; } CanvasExtraState.prototype = { @@ -484,7 +485,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this._cachedGetSinglePixelWidth = null; } - function putBinaryImageData(ctx, imgData) { + function putBinaryImageData(ctx, imgData, transferMaps = null) { if (typeof ImageData !== "undefined" && imgData instanceof ImageData) { ctx.putImageData(imgData, 0, 0); return; @@ -514,6 +515,24 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var dest = chunkImgData.data; var i, j, thisChunkHeight, elemsInThisChunk; + let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray; + if (transferMaps) { + switch (transferMaps.length) { + case 1: + transferMapRed = transferMaps[0]; + transferMapGreen = transferMaps[0]; + transferMapBlue = transferMaps[0]; + transferMapGray = transferMaps[0]; + break; + case 4: + transferMapRed = transferMaps[0]; + transferMapGreen = transferMaps[1]; + transferMapBlue = transferMaps[2]; + transferMapGray = transferMaps[3]; + break; + } + } + // There are multiple forms in which the pixel data can be passed, and // imgData.kind tells us which one this is. if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { @@ -524,13 +543,20 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var fullSrcDiff = (width + 7) >> 3; var white = 0xffffffff; var black = IsLittleEndianCached.value ? 0xff000000 : 0x000000ff; + + if (transferMapGray) { + if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) { + [white, black] = [black, white]; + } + } + for (i = 0; i < totalChunks; i++) { thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; destPos = 0; for (j = 0; j < thisChunkHeight; j++) { var srcDiff = srcLength - srcPos; - var k = 0; + let k = 0; var kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; var kEndUnrolled = kEnd & ~7; var mask = 0; @@ -565,6 +591,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } } else if (imgData.kind === ImageKind.RGBA_32BPP) { // RGBA, 32-bits per pixel. + const hasTransferMaps = !!( + transferMapRed || + transferMapGreen || + transferMapBlue + ); j = 0; elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; @@ -572,16 +603,51 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; + if (hasTransferMaps) { + for (let k = 0; k < elemsInThisChunk; k += 4) { + if (transferMapRed) { + dest[k + 0] = transferMapRed[dest[k + 0]]; + } + if (transferMapGreen) { + dest[k + 1] = transferMapGreen[dest[k + 1]]; + } + if (transferMapBlue) { + dest[k + 2] = transferMapBlue[dest[k + 2]]; + } + } + } + ctx.putImageData(chunkImgData, 0, j); j += FULL_CHUNK_HEIGHT; } if (i < totalChunks) { elemsInThisChunk = width * partialChunkHeight * 4; dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); + + if (hasTransferMaps) { + for (let k = 0; k < elemsInThisChunk; k += 4) { + if (transferMapRed) { + dest[k + 0] = transferMapRed[dest[k + 0]]; + } + if (transferMapGreen) { + dest[k + 1] = transferMapGreen[dest[k + 1]]; + } + if (transferMapBlue) { + dest[k + 2] = transferMapBlue[dest[k + 2]]; + } + } + } + ctx.putImageData(chunkImgData, 0, j); } } else if (imgData.kind === ImageKind.RGB_24BPP) { // RGB, 24-bits per pixel. + const hasTransferMaps = !!( + transferMapRed || + transferMapGreen || + transferMapBlue + ); + thisChunkHeight = FULL_CHUNK_HEIGHT; elemsInThisChunk = width * thisChunkHeight; for (i = 0; i < totalChunks; i++) { @@ -597,6 +663,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { dest[destPos++] = src[srcPos++]; dest[destPos++] = 255; } + + if (hasTransferMaps) { + for (let k = 0; k < destPos; k += 4) { + if (transferMapRed) { + dest[k + 0] = transferMapRed[dest[k + 0]]; + } + if (transferMapGreen) { + dest[k + 1] = transferMapGreen[dest[k + 1]]; + } + if (transferMapBlue) { + dest[k + 2] = transferMapBlue[dest[k + 2]]; + } + } + } + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else { @@ -1040,6 +1121,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } this.tempSMask = null; break; + case "TR": + this.current.transferMaps = value; } } }, @@ -2362,7 +2445,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else { tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); var tmpCtx = tmpCanvas.context; - putBinaryImageData(tmpCtx, imgData); + putBinaryImageData(tmpCtx, imgData, this.current.transferMaps); imgToPaint = tmpCanvas.canvas; } @@ -2447,7 +2530,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h); var tmpCtx = tmpCanvas.context; - putBinaryImageData(tmpCtx, imgData); + putBinaryImageData(tmpCtx, imgData, this.current.transferMaps); for (var i = 0, ii = map.length; i < ii; i++) { var entry = map[i]; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 377367262..3da0e6cab 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -45,6 +45,7 @@ !issue7492.pdf !issue7544.pdf !issue7507.pdf +!issue6931_reduced.pdf !issue7580.pdf !issue7598.pdf !issue7665.pdf diff --git a/test/pdfs/issue6931_reduced.pdf b/test/pdfs/issue6931_reduced.pdf new file mode 100644 index 000000000..9304e3e0e Binary files /dev/null and b/test/pdfs/issue6931_reduced.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index ee0ec466b..dac514ba5 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -4468,6 +4468,12 @@ "rounds": 1, "type": "eq" }, + { "id": "issue6931", + "file": "pdfs/issue6931_reduced.pdf", + "md5": "e61388913821a5e044bf85a5846d6d9a", + "rounds": 1, + "type": "eq" + }, { "id": "annotation-button-widget-annotations", "file": "pdfs/annotation-button-widget.pdf", "md5": "5cf23adfff84256d9cfe261bea96dade",