diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 90615b22829040..fe407a7fa77972 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -589,6 +589,7 @@ class PartialEvaluator { } const imageMask = dict.get("ImageMask", "IM") || false; + const interpolate = dict.get("Interpolate", "I"); let imgData, args; if (imageMask) { // This depends on a tmpCanvas being filled with the @@ -612,6 +613,7 @@ class PartialEvaluator { height, imageIsFromDecodeStream: image instanceof DecodeStream, inverseDecode: !!decode && decode[0] > 0, + interpolate, }); imgData.cached = !!cacheKey; args = [imgData]; diff --git a/src/core/image.js b/src/core/image.js index 830f7a39f3c047..e1b55057af450a 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -139,7 +139,7 @@ class PDFImage { this.width = width; this.height = height; - this.interpolate = dict.get("Interpolate", "I") || false; + this.interpolate = dict.get("Interpolate", "I"); this.imageMask = dict.get("ImageMask", "IM") || false; this.matte = dict.get("Matte") || false; @@ -294,6 +294,7 @@ class PDFImage { height, imageIsFromDecodeStream, inverseDecode, + interpolate, }) { if ( typeof PDFJSDev === "undefined" || @@ -339,7 +340,7 @@ class PDFImage { } } - return { data, width, height }; + return { data, width, height, interpolate }; } get drawWidth() { @@ -593,6 +594,7 @@ class PDFImage { const imgData = { width: drawWidth, height: drawHeight, + interpolate: this.interpolate, kind: 0, data: null, // Other fields are filled in below. diff --git a/src/display/canvas.js b/src/display/canvas.js index 2938e0b9981044..e60e9d0ce80ffb 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -37,6 +37,9 @@ const MIN_FONT_SIZE = 16; const MAX_FONT_SIZE = 100; const MAX_GROUP_SIZE = 4096; +const CSS_PIXELS_PER_INCH = 96.0; +const PDF_PIXELS_PER_INCH = 72.0; + // This value comes from sampling a few PDFs that re-use patterns, there doesn't // seem to be any that benefit from caching more than 2 patterns. const MAX_CACHED_CANVAS_PATTERNS = 2; @@ -871,6 +874,21 @@ function composeSMask(ctx, smask, layerCtx) { ctx.drawImage(mask, 0, 0); } +function getImageSmoothingEnabled(transform, interpolate) { + const scale = Util.singularValueDecompose2dScale(transform); + const actualScale = + (window.devicePixelRatio * CSS_PIXELS_PER_INCH) / PDF_PIXELS_PER_INCH; + if (interpolate !== undefined) { + // If the value is explicitly set use it. + return interpolate; + } else if (scale[0] <= actualScale || scale[1] <= actualScale) { + // Smooth when downscaling. + return true; + } + // Don't smooth when upscaling. + return false; +} + const LINE_CAP_STYLES = ["butt", "round", "square"]; const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; const NORMAL_CLIP = {}; @@ -1183,6 +1201,10 @@ class CanvasGraphics { maskCanvas.canvas, fillCtx.mozCurrentTransformInverse ); + fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled( + fillCtx.mozCurrentTransform, + img.interpolate + ); fillCtx.drawImage( scaled.img, 0, @@ -2663,6 +2685,10 @@ class CanvasGraphics { } const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse); + ctx.imageSmoothingEnabled = getImageSmoothingEnabled( + ctx.mozCurrentTransform, + imgData.interpolate + ); ctx.drawImage( scaled.img, 0, diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index ee07ee273c90bd..c8845e4eeb0759 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -69,6 +69,7 @@ !issue8229.pdf !issue8276_reduced.pdf !issue8372.pdf +!issue9713.pdf !xfa_filled_imm1344e.pdf !issue8424.pdf !issue8480.pdf @@ -150,6 +151,7 @@ !complex_ttf_font.pdf !issue3694_reduced.pdf !extgstate.pdf +!issue4706.pdf !rotation.pdf !simpletype3font.pdf !sizes.pdf diff --git a/test/pdfs/issue4706.pdf b/test/pdfs/issue4706.pdf new file mode 100644 index 00000000000000..56f0881713cf39 Binary files /dev/null and b/test/pdfs/issue4706.pdf differ diff --git a/test/pdfs/issue9713.pdf b/test/pdfs/issue9713.pdf new file mode 100644 index 00000000000000..5b6e0a5def9a1a Binary files /dev/null and b/test/pdfs/issue9713.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 0f83502a2b9a0f..d59ed68acf32fb 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3110,6 +3110,12 @@ "rounds": 1, "type": "eq" }, + { "id": "issue4706", + "file": "pdfs/issue4706.pdf", + "md5": "f3e90a3cf52550583fa2a07a138b8660", + "rounds": 1, + "type": "eq" + }, { "id": "issue11242", "file": "pdfs/issue11242_reduced.pdf", "md5": "ba50b6ee537f3e815ccfe0c99e598e05", @@ -4421,6 +4427,12 @@ "link": true, "type": "eq" }, + { "id": "issue9713", + "file": "pdfs/issue9713.pdf", + "md5": "a62bd42d12271105b26a68c8eae5ea5f", + "rounds": 1, + "type": "eq" + }, { "id": "issue1936-text", "file": "pdfs/issue1936.pdf", "md5": "7302eb9b6a626308e2a933aaed9e1756",