diff --git a/test/driver.js b/test/driver.js index 52d9338893971..f6d5115ff3ae8 100644 --- a/test/driver.js +++ b/test/driver.js @@ -59,7 +59,7 @@ function loadStyles(styles) { return Promise.all(promises); } -function writeSVG(svgElement, ctx) { +function writeSVG(svgElement, ctx, outputScale) { // We need to have UTF-8 encoded XML. const svg_xml = unescape( encodeURIComponent(new XMLSerializer().serializeToString(svgElement)) @@ -102,7 +102,7 @@ function inlineImages(images) { return Promise.all(imagePromises); } -async function convertCanvasesToImages(annotationCanvasMap) { +async function convertCanvasesToImages(annotationCanvasMap, outputScale) { const results = new Map(); const promises = []; for (const [key, canvas] of annotationCanvasMap) { @@ -110,7 +110,10 @@ async function convertCanvasesToImages(annotationCanvasMap) { new Promise(resolve => { canvas.toBlob(blob => { const image = document.createElement("img"); - image.onload = resolve; + image.onload = function () { + image.style.width = Math.floor(image.width / outputScale) + "px"; + resolve(); + }; results.set(key, image); image.src = URL.createObjectURL(blob); }); @@ -198,6 +201,7 @@ class Rasterize { static async annotationLayer( ctx, viewport, + outputScale, annotations, annotationCanvasMap, page, @@ -213,7 +217,8 @@ class Rasterize { const annotationViewport = viewport.clone({ dontFlip: true }); const annotationImageMap = await convertCanvasesToImages( - annotationCanvasMap + annotationCanvasMap, + outputScale ); // Rendering annotation layer as HTML. @@ -600,13 +605,38 @@ class Driver { ctx = this.canvas.getContext("2d", { alpha: false }); task.pdfDoc.getPage(task.pageNum).then( page => { - const viewport = page.getViewport({ + // Default to creating the test images at the devices pixel ratio, + // unless the test explicitly specifies an output scale. + const outputScale = task.outputScale || window.devicePixelRatio; + let viewport = page.getViewport({ scale: PixelsPerInch.PDF_TO_CSS_UNITS, }); - this.canvas.width = viewport.width; - this.canvas.height = viewport.height; + // Restrict the test from creating a canvas that is too big. + const MAX_CANVAS_PIXEL_DIMENSION = 4096; + const largestDimension = Math.max(viewport.width, viewport.height); + if ( + Math.floor(largestDimension * outputScale) > + MAX_CANVAS_PIXEL_DIMENSION + ) { + const rescale = MAX_CANVAS_PIXEL_DIMENSION / largestDimension; + viewport = viewport.clone({ + scale: PixelsPerInch.PDF_TO_CSS_UNITS * rescale, + }); + } + const pixelWidth = Math.floor(viewport.width * outputScale); + const pixelHeight = Math.floor(viewport.height * outputScale); + task.viewportWidth = Math.floor(viewport.width); + task.viewportHeight = Math.floor(viewport.height); + task.outputScale = outputScale; + this.canvas.width = pixelWidth; + this.canvas.height = pixelHeight; + this.canvas.style.width = Math.floor(viewport.width) + "px"; + this.canvas.style.height = Math.floor(viewport.height) + "px"; this._clearCanvas(); + const transform = + outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null; + // Initialize various `eq` test subtypes, see comment below. let renderAnnotations = false, renderForms = false, @@ -631,8 +661,8 @@ class Driver { textLayerCanvas = document.createElement("canvas"); this.textLayerCanvas = textLayerCanvas; } - textLayerCanvas.width = viewport.width; - textLayerCanvas.height = viewport.height; + textLayerCanvas.width = pixelWidth; + textLayerCanvas.height = pixelHeight; const textLayerContext = textLayerCanvas.getContext("2d"); textLayerContext.clearRect( 0, @@ -640,6 +670,7 @@ class Driver { textLayerCanvas.width, textLayerCanvas.height ); + textLayerContext.scale(outputScale, outputScale); const enhanceText = !!task.enhance; // The text builder will draw its content on the test canvas initPromise = page @@ -672,8 +703,8 @@ class Driver { annotationLayerCanvas = document.createElement("canvas"); this.annotationLayerCanvas = annotationLayerCanvas; } - annotationLayerCanvas.width = viewport.width; - annotationLayerCanvas.height = viewport.height; + annotationLayerCanvas.width = pixelWidth; + annotationLayerCanvas.height = pixelHeight; annotationLayerContext = annotationLayerCanvas.getContext("2d"); annotationLayerContext.clearRect( 0, @@ -681,6 +712,7 @@ class Driver { annotationLayerCanvas.width, annotationLayerCanvas.height ); + annotationLayerContext.scale(outputScale, outputScale); if (!renderXfa) { // The annotation builder will draw its content @@ -709,6 +741,7 @@ class Driver { viewport, optionalContentConfigPromise: task.optionalContentConfigPromise, annotationCanvasMap, + transform, }; if (renderForms) { renderContext.annotationMode = AnnotationMode.ENABLE_FORMS; @@ -725,7 +758,7 @@ class Driver { ctx.save(); ctx.globalCompositeOperation = "screen"; ctx.fillStyle = "rgb(128, 255, 128)"; // making it green - ctx.fillRect(0, 0, viewport.width, viewport.height); + ctx.fillRect(0, 0, pixelWidth, pixelHeight); ctx.restore(); ctx.drawImage(textLayerCanvas, 0, 0); } @@ -755,6 +788,7 @@ class Driver { Rasterize.annotationLayer( annotationLayerContext, viewport, + outputScale, data, annotationCanvasMap, page, @@ -864,6 +898,9 @@ class Driver { page: task.pageNum, snapshot, stats: task.stats.times, + viewportWidth: task.viewportWidth, + viewportHeight: task.viewportHeight, + outputScale: task.outputScale, }); this._send("/submit_task_results", result, callback); } diff --git a/test/resources/reftest-analyzer.html b/test/resources/reftest-analyzer.html index 2836f686042db..249c2eb40b0d9 100644 --- a/test/resources/reftest-analyzer.html +++ b/test/resources/reftest-analyzer.html @@ -165,8 +165,8 @@

Reftest analyzer

- - + + diff --git a/test/resources/reftest-analyzer.js b/test/resources/reftest-analyzer.js index 2a192f4a0507a..913f2331eabe2 100644 --- a/test/resources/reftest-analyzer.js +++ b/test/resources/reftest-analyzer.js @@ -228,10 +228,15 @@ window.onload = function () { }); continue; } - match = line.match(/^ {2}IMAGE[^:]*: (.*)$/); + match = line.match(/^ {2}IMAGE[^:]*\((\d+)x(\d+)x(\d+)\): (.*)$/); if (match) { const item = gTestItems[gTestItems.length - 1]; - item.images.push(match[1]); + item.images.push({ + width: parseFloat(match[1]), + height: parseFloat(match[2]), + outputScale: parseFloat(match[3]), + file: match[4], + }); } } buildViewer(); @@ -335,16 +340,31 @@ window.onload = function () { const cell = ID("images"); ID("image1").style.display = ""; + const scale = item.images[0].outputScale / window.devicePixelRatio; + ID("image1").setAttribute("width", item.images[0].width * scale); + ID("image1").setAttribute("height", item.images[0].height * scale); + + ID("svg").setAttribute("width", item.images[0].width * scale); + ID("svg").setAttribute("height", item.images[0].height * scale); + ID("image2").style.display = "none"; + if (item.images[1]) { + ID("image2").setAttribute("width", item.images[1].width * scale); + ID("image2").setAttribute("height", item.images[1].height * scale); + } ID("diffrect").style.display = "none"; ID("imgcontrols").reset(); - ID("image1").setAttributeNS(XLINK_NS, "xlink:href", gPath + item.images[0]); + ID("image1").setAttributeNS( + XLINK_NS, + "xlink:href", + gPath + item.images[0].file + ); // Making the href be #image1 doesn't seem to work ID("feimage1").setAttributeNS( XLINK_NS, "xlink:href", - gPath + item.images[0] + gPath + item.images[0].file ); if (item.images.length === 1) { ID("imgcontrols").style.display = "none"; @@ -353,30 +373,24 @@ window.onload = function () { ID("image2").setAttributeNS( XLINK_NS, "xlink:href", - gPath + item.images[1] + gPath + item.images[1].file ); // Making the href be #image2 doesn't seem to work ID("feimage2").setAttributeNS( XLINK_NS, "xlink:href", - gPath + item.images[1] + gPath + item.images[1].file ); } cell.style.display = ""; - getImageData(item.images[0], function (data) { + getImageData(item.images[0].file, function (data) { gImage1Data = data; - syncSVGSize(gImage1Data); }); - getImageData(item.images[1], function (data) { + getImageData(item.images[1].file, function (data) { gImage2Data = data; }); } - function syncSVGSize(imageData) { - ID("svg").setAttribute("width", imageData.width); - ID("svg").setAttribute("height", imageData.height); - } - function showImage(i) { if (i === 1) { ID("image1").style.display = ""; @@ -414,7 +428,7 @@ window.onload = function () { } function canvasPixelAsHex(data, x, y) { - const offset = (y * data.width + x) * 4; + const offset = (y * data.width + x) * 4 * window.devicePixelRatio; const r = data.data[offset]; const g = data.data[offset + 1]; const b = data.data[offset + 2]; diff --git a/test/test.js b/test/test.js index 3ec9f518d4786..9cb619932d86b 100644 --- a/test/test.js +++ b/test/test.js @@ -451,7 +451,8 @@ function checkEq(task, results, browser, masterMode) { if (!pageResults[page]) { continue; } - var testSnapshot = pageResults[page].snapshot; + const pageResult = pageResults[page]; + let testSnapshot = pageResult.snapshot; if (testSnapshot && testSnapshot.startsWith("data:image/png;base64,")) { testSnapshot = Buffer.from(testSnapshot.substring(22), "base64"); } else { @@ -492,8 +493,8 @@ function checkEq(task, results, browser, masterMode) { refSnapshot ); - // NB: this follows the format of Mozilla reftest output so that - // we can reuse its reftest-analyzer script + // This no longer follows the format of Mozilla reftest output. + const viewportString = `(${pageResult.viewportWidth}x${pageResult.viewportHeight}x${pageResult.outputScale})`; fs.appendFileSync( eqLog, "REFTEST TEST-UNEXPECTED-FAIL | " + @@ -503,10 +504,10 @@ function checkEq(task, results, browser, masterMode) { "-page" + (page + 1) + " | image comparison (==)\n" + - "REFTEST IMAGE 1 (TEST): " + + `REFTEST IMAGE 1 (TEST)${viewportString}: ` + path.join(testSnapshotDir, page + 1 + ".png") + "\n" + - "REFTEST IMAGE 2 (REFERENCE): " + + `REFTEST IMAGE 2 (REFERENCE)${viewportString}: ` + path.join(testSnapshotDir, page + 1 + "_ref.png") + "\n" ); @@ -735,6 +736,9 @@ function refTestPostHandler(req, res) { taskResults[round][page] = { failure, snapshot, + viewportWidth: data.viewportWidth, + viewportHeight: data.viewportHeight, + outputScale: data.outputScale, }; if (stats) { stats.push({ diff --git a/test/test_manifest.json b/test/test_manifest.json index 38c3eee19f03b..3e5441f999f1e 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -6115,6 +6115,18 @@ "forms": true, "lastPage": 1 }, + { + "id": "issue12716-hidpi", + "file": "pdfs/issue12716.pdf", + "md5": "9bdc9c552bcfccd629f5f97385e79ca5", + "rounds": 1, + "link": true, + "type": "eq", + "forms": true, + "lastPage": 1, + "outputScale": 2, + "about": "This tests draws to another canvas for the button, so it's a good test to ensure output scale is working." + }, { "id": "xfa_issue13500", "file": "pdfs/xfa_issue13500.pdf", "md5": "b81274a19f5a95c1466db3648f1be491",