diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 90615b228..fe407a7fa 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 830f7a39f..e1b55057a 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 2938e0b99..a165647c3 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { CSS_PIXELS_PER_INCH, PDF_PIXELS_PER_INCH } from "./display_utils.js"; import { FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, @@ -871,6 +871,27 @@ function composeSMask(ctx, smask, layerCtx) { ctx.drawImage(mask, 0, 0); } +function getImageSmoothingEnabled(transform, interpolate) { + const scale = Util.singularValueDecompose2dScale(transform); + // Round to a 32bit float so that `<=` check below will pass for numbers that + // are very close, but not exactly the same 64bit floats. + scale[0] = Math.fround(scale[0]); + scale[1] = Math.fround(scale[1]); + const actualScale = Math.fround( + ((globalThis.devicePixelRatio || 1) * 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 +1204,10 @@ class CanvasGraphics { maskCanvas.canvas, fillCtx.mozCurrentTransformInverse ); + fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled( + fillCtx.mozCurrentTransform, + img.interpolate + ); fillCtx.drawImage( scaled.img, 0, @@ -2663,6 +2688,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/src/display/display_utils.js b/src/display/display_utils.js index c94834a74..aabe7a24b 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -32,6 +32,9 @@ import { const DEFAULT_LINK_REL = "noopener noreferrer nofollow"; const SVG_NS = "http://www.w3.org/2000/svg"; +const CSS_PIXELS_PER_INCH = 96.0; +const PDF_PIXELS_PER_INCH = 72.0; + class DOMCanvasFactory extends BaseCanvasFactory { constructor({ ownerDocument = globalThis.document } = {}) { super(); @@ -622,6 +625,7 @@ function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) { export { addLinkAttributes, + CSS_PIXELS_PER_INCH, DEFAULT_LINK_REL, deprecated, DOMCanvasFactory, @@ -637,6 +641,7 @@ export { LinkTarget, loadScript, PageViewport, + PDF_PIXELS_PER_INCH, PDFDateString, RenderingCancelledException, StatTimer, diff --git a/src/pdf.js b/src/pdf.js index 8bbf9efa8..3b708cb23 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -16,6 +16,7 @@ import { addLinkAttributes, + CSS_PIXELS_PER_INCH, getFilenameFromUrl, getPdfFilenameFromUrl, getXfaPageViewport, @@ -23,6 +24,7 @@ import { isValidFetchUrl, LinkTarget, loadScript, + PDF_PIXELS_PER_INCH, PDFDateString, RenderingCancelledException, } from "./display/display_utils.js"; @@ -103,11 +105,13 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) { export { // From "./display/display_utils.js": addLinkAttributes, + CSS_PIXELS_PER_INCH, getFilenameFromUrl, getPdfFilenameFromUrl, isPdfFile, LinkTarget, loadScript, + PDF_PIXELS_PER_INCH, PDFDateString, RenderingCancelledException, getXfaPageViewport, diff --git a/test/driver.js b/test/driver.js index 7f0d651e3..6ed5143e7 100644 --- a/test/driver.js +++ b/test/driver.js @@ -20,15 +20,17 @@ const { AnnotationLayer, AnnotationMode, + CSS_PIXELS_PER_INCH, getDocument, GlobalWorkerOptions, + PDF_PIXELS_PER_INCH, renderTextLayer, XfaLayer, } = pdfjsLib; const { SimpleLinkService } = pdfjsViewer; const WAITING_TIME = 100; // ms -const PDF_TO_CSS_UNITS = 96.0 / 72.0; +const PDF_TO_CSS_UNITS = CSS_PIXELS_PER_INCH / PDF_PIXELS_PER_INCH; const CMAP_URL = "/build/generic/web/cmaps/"; const CMAP_PACKED = true; const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/"; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index ee07ee273..c8845e4ee 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 000000000..56f088171 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 000000000..5b6e0a5de Binary files /dev/null and b/test/pdfs/issue9713.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 56fff46c4..7a86fa714 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -3134,6 +3134,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", @@ -4443,6 +4449,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", diff --git a/web/firefox_print_service.js b/web/firefox_print_service.js index 227632447..9d938646f 100644 --- a/web/firefox_print_service.js +++ b/web/firefox_print_service.js @@ -13,7 +13,12 @@ * limitations under the License. */ -import { AnnotationMode, RenderingCancelledException, shadow } from "pdfjs-lib"; +import { + AnnotationMode, + PDF_PIXELS_PER_INCH, + RenderingCancelledException, + shadow, +} from "pdfjs-lib"; import { getXfaHtmlForPrinting } from "./print_utils.js"; import { PDFPrintServiceFactory } from "./app.js"; @@ -29,7 +34,7 @@ function composePage( const canvas = document.createElement("canvas"); // The size of the canvas in pixels for printing. - const PRINT_UNITS = printResolution / 72.0; + const PRINT_UNITS = printResolution / PDF_PIXELS_PER_INCH; canvas.width = Math.floor(size.width * PRINT_UNITS); canvas.height = Math.floor(size.height * PRINT_UNITS); diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js index 58acfeb61..aa678d34a 100644 --- a/web/pdf_print_service.js +++ b/web/pdf_print_service.js @@ -13,8 +13,8 @@ * limitations under the License. */ +import { AnnotationMode, PDF_PIXELS_PER_INCH } from "pdfjs-lib"; import { PDFPrintServiceFactory, PDFViewerApplication } from "./app.js"; -import { AnnotationMode } from "pdfjs-lib"; import { compatibilityParams } from "./app_options.js"; import { getXfaHtmlForPrinting } from "./print_utils.js"; @@ -34,7 +34,7 @@ function renderPage( const scratchCanvas = activeService.scratchCanvas; // The size of the canvas in pixels for printing. - const PRINT_UNITS = printResolution / 72.0; + const PRINT_UNITS = printResolution / PDF_PIXELS_PER_INCH; scratchCanvas.width = Math.floor(size.width * PRINT_UNITS); scratchCanvas.height = Math.floor(size.height * PRINT_UNITS); diff --git a/web/ui_utils.js b/web/ui_utils.js index 8f2f473f2..cd8e79d4d 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -13,7 +13,9 @@ * limitations under the License. */ -const CSS_UNITS = 96.0 / 72.0; +import { CSS_PIXELS_PER_INCH, PDF_PIXELS_PER_INCH } from "pdfjs-lib"; + +const CSS_UNITS = CSS_PIXELS_PER_INCH / PDF_PIXELS_PER_INCH; const DEFAULT_SCALE_VALUE = "auto"; const DEFAULT_SCALE = 1.0; const MIN_SCALE = 0.1;