Skip to content

Commit 2fcdc91

Browse files
committed
Generate test images at different output scales.
This will default to generating test images at the device pixel ratio of the machine the tests are created on unless the test explicitly defines and output scale using the `outputScale` setting. This makes the test look visually like they would on the machine they are running on. It also allows us to test different output scales.
1 parent 1d1e50e commit 2fcdc91

File tree

5 files changed

+101
-34
lines changed

5 files changed

+101
-34
lines changed

test/driver.js

+49-12
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function loadStyles(styles) {
5959
return Promise.all(promises);
6060
}
6161

62-
function writeSVG(svgElement, ctx) {
62+
function writeSVG(svgElement, ctx, outputScale) {
6363
// We need to have UTF-8 encoded XML.
6464
const svg_xml = unescape(
6565
encodeURIComponent(new XMLSerializer().serializeToString(svgElement))
@@ -102,15 +102,18 @@ function inlineImages(images) {
102102
return Promise.all(imagePromises);
103103
}
104104

105-
async function convertCanvasesToImages(annotationCanvasMap) {
105+
async function convertCanvasesToImages(annotationCanvasMap, outputScale) {
106106
const results = new Map();
107107
const promises = [];
108108
for (const [key, canvas] of annotationCanvasMap) {
109109
promises.push(
110110
new Promise(resolve => {
111111
canvas.toBlob(blob => {
112112
const image = document.createElement("img");
113-
image.onload = resolve;
113+
image.onload = function () {
114+
image.style.width = Math.floor(image.width / outputScale) + "px";
115+
resolve();
116+
};
114117
results.set(key, image);
115118
image.src = URL.createObjectURL(blob);
116119
});
@@ -198,6 +201,7 @@ class Rasterize {
198201
static async annotationLayer(
199202
ctx,
200203
viewport,
204+
outputScale,
201205
annotations,
202206
annotationCanvasMap,
203207
page,
@@ -213,7 +217,8 @@ class Rasterize {
213217

214218
const annotationViewport = viewport.clone({ dontFlip: true });
215219
const annotationImageMap = await convertCanvasesToImages(
216-
annotationCanvasMap
220+
annotationCanvasMap,
221+
outputScale
217222
);
218223

219224
// Rendering annotation layer as HTML.
@@ -600,13 +605,38 @@ class Driver {
600605
ctx = this.canvas.getContext("2d", { alpha: false });
601606
task.pdfDoc.getPage(task.pageNum).then(
602607
page => {
603-
const viewport = page.getViewport({
608+
// Default to creating the test images at the devices pixel ratio,
609+
// unless the test explicitly specifies an output scale.
610+
const outputScale = task.outputScale || window.devicePixelRatio;
611+
let viewport = page.getViewport({
604612
scale: PixelsPerInch.PDF_TO_CSS_UNITS,
605613
});
606-
this.canvas.width = viewport.width;
607-
this.canvas.height = viewport.height;
614+
// Restrict the test from creating a canvas that is too big.
615+
const MAX_CANVAS_PIXEL_DIMENSION = 4096;
616+
const largestDimension = Math.max(viewport.width, viewport.height);
617+
if (
618+
Math.floor(largestDimension * outputScale) >
619+
MAX_CANVAS_PIXEL_DIMENSION
620+
) {
621+
const rescale = MAX_CANVAS_PIXEL_DIMENSION / largestDimension;
622+
viewport = viewport.clone({
623+
scale: PixelsPerInch.PDF_TO_CSS_UNITS * rescale,
624+
});
625+
}
626+
const pixelWidth = Math.floor(viewport.width * outputScale);
627+
const pixelHeight = Math.floor(viewport.height * outputScale);
628+
task.viewportWidth = Math.floor(viewport.width);
629+
task.viewportHeight = Math.floor(viewport.height);
630+
task.outputScale = outputScale;
631+
this.canvas.width = pixelWidth;
632+
this.canvas.height = pixelHeight;
633+
this.canvas.style.width = Math.floor(viewport.width) + "px";
634+
this.canvas.style.height = Math.floor(viewport.height) + "px";
608635
this._clearCanvas();
609636

637+
const transform =
638+
outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
639+
610640
// Initialize various `eq` test subtypes, see comment below.
611641
let renderAnnotations = false,
612642
renderForms = false,
@@ -631,15 +661,16 @@ class Driver {
631661
textLayerCanvas = document.createElement("canvas");
632662
this.textLayerCanvas = textLayerCanvas;
633663
}
634-
textLayerCanvas.width = viewport.width;
635-
textLayerCanvas.height = viewport.height;
664+
textLayerCanvas.width = pixelWidth;
665+
textLayerCanvas.height = pixelHeight;
636666
const textLayerContext = textLayerCanvas.getContext("2d");
637667
textLayerContext.clearRect(
638668
0,
639669
0,
640670
textLayerCanvas.width,
641671
textLayerCanvas.height
642672
);
673+
textLayerContext.scale(outputScale, outputScale);
643674
const enhanceText = !!task.enhance;
644675
// The text builder will draw its content on the test canvas
645676
initPromise = page
@@ -672,15 +703,16 @@ class Driver {
672703
annotationLayerCanvas = document.createElement("canvas");
673704
this.annotationLayerCanvas = annotationLayerCanvas;
674705
}
675-
annotationLayerCanvas.width = viewport.width;
676-
annotationLayerCanvas.height = viewport.height;
706+
annotationLayerCanvas.width = pixelWidth;
707+
annotationLayerCanvas.height = pixelHeight;
677708
annotationLayerContext = annotationLayerCanvas.getContext("2d");
678709
annotationLayerContext.clearRect(
679710
0,
680711
0,
681712
annotationLayerCanvas.width,
682713
annotationLayerCanvas.height
683714
);
715+
annotationLayerContext.scale(outputScale, outputScale);
684716

685717
if (!renderXfa) {
686718
// The annotation builder will draw its content
@@ -709,6 +741,7 @@ class Driver {
709741
viewport,
710742
optionalContentConfigPromise: task.optionalContentConfigPromise,
711743
annotationCanvasMap,
744+
transform,
712745
};
713746
if (renderForms) {
714747
renderContext.annotationMode = AnnotationMode.ENABLE_FORMS;
@@ -725,7 +758,7 @@ class Driver {
725758
ctx.save();
726759
ctx.globalCompositeOperation = "screen";
727760
ctx.fillStyle = "rgb(128, 255, 128)"; // making it green
728-
ctx.fillRect(0, 0, viewport.width, viewport.height);
761+
ctx.fillRect(0, 0, pixelWidth, pixelHeight);
729762
ctx.restore();
730763
ctx.drawImage(textLayerCanvas, 0, 0);
731764
}
@@ -755,6 +788,7 @@ class Driver {
755788
Rasterize.annotationLayer(
756789
annotationLayerContext,
757790
viewport,
791+
outputScale,
758792
data,
759793
annotationCanvasMap,
760794
page,
@@ -864,6 +898,9 @@ class Driver {
864898
page: task.pageNum,
865899
snapshot,
866900
stats: task.stats.times,
901+
viewportWidth: task.viewportWidth,
902+
viewportHeight: task.viewportHeight,
903+
outputScale: task.outputScale,
867904
});
868905
this._send("/submit_task_results", result, callback);
869906
}

test/resources/reftest-analyzer.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ <h1>Reftest analyzer</h1>
165165
</filter>
166166
</defs>
167167
<g id="magnify">
168-
<image x="0" y="0" width="100%" height="100%" id="image1" />
169-
<image x="0" y="0" width="100%" height="100%" id="image2" />
168+
<image x="0" y="0" id="image1" />
169+
<image x="0" y="0" id="image2" />
170170
</g>
171171
<rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
172172
</svg>

test/resources/reftest-analyzer.js

+29-15
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,15 @@ window.onload = function () {
228228
});
229229
continue;
230230
}
231-
match = line.match(/^ {2}IMAGE[^:]*: (.*)$/);
231+
match = line.match(/^ {2}IMAGE[^:]*\((\d+)x(\d+)x(\d)+\): (.*)$/);
232232
if (match) {
233233
const item = gTestItems[gTestItems.length - 1];
234-
item.images.push(match[1]);
234+
item.images.push({
235+
width: parseFloat(match[1]),
236+
height: parseFloat(match[2]),
237+
outputScale: parseFloat(match[3]),
238+
file: match[4],
239+
});
235240
}
236241
}
237242
buildViewer();
@@ -335,16 +340,31 @@ window.onload = function () {
335340
const cell = ID("images");
336341

337342
ID("image1").style.display = "";
343+
const scale = item.images[0].outputScale / window.devicePixelRatio;
344+
ID("image1").setAttribute("width", item.images[0].width * scale);
345+
ID("image1").setAttribute("height", item.images[0].height * scale);
346+
347+
ID("svg").setAttribute("width", item.images[0].width * scale);
348+
ID("svg").setAttribute("height", item.images[0].height * scale);
349+
338350
ID("image2").style.display = "none";
351+
if (item.images[1]) {
352+
ID("image2").setAttribute("width", item.images[1].width * scale);
353+
ID("image2").setAttribute("height", item.images[1].height * scale);
354+
}
339355
ID("diffrect").style.display = "none";
340356
ID("imgcontrols").reset();
341357

342-
ID("image1").setAttributeNS(XLINK_NS, "xlink:href", gPath + item.images[0]);
358+
ID("image1").setAttributeNS(
359+
XLINK_NS,
360+
"xlink:href",
361+
gPath + item.images[0].file
362+
);
343363
// Making the href be #image1 doesn't seem to work
344364
ID("feimage1").setAttributeNS(
345365
XLINK_NS,
346366
"xlink:href",
347-
gPath + item.images[0]
367+
gPath + item.images[0].file
348368
);
349369
if (item.images.length === 1) {
350370
ID("imgcontrols").style.display = "none";
@@ -353,30 +373,24 @@ window.onload = function () {
353373
ID("image2").setAttributeNS(
354374
XLINK_NS,
355375
"xlink:href",
356-
gPath + item.images[1]
376+
gPath + item.images[1].file
357377
);
358378
// Making the href be #image2 doesn't seem to work
359379
ID("feimage2").setAttributeNS(
360380
XLINK_NS,
361381
"xlink:href",
362-
gPath + item.images[1]
382+
gPath + item.images[1].file
363383
);
364384
}
365385
cell.style.display = "";
366-
getImageData(item.images[0], function (data) {
386+
getImageData(item.images[0].file, function (data) {
367387
gImage1Data = data;
368-
syncSVGSize(gImage1Data);
369388
});
370-
getImageData(item.images[1], function (data) {
389+
getImageData(item.images[1].file, function (data) {
371390
gImage2Data = data;
372391
});
373392
}
374393

375-
function syncSVGSize(imageData) {
376-
ID("svg").setAttribute("width", imageData.width);
377-
ID("svg").setAttribute("height", imageData.height);
378-
}
379-
380394
function showImage(i) {
381395
if (i === 1) {
382396
ID("image1").style.display = "";
@@ -414,7 +428,7 @@ window.onload = function () {
414428
}
415429

416430
function canvasPixelAsHex(data, x, y) {
417-
const offset = (y * data.width + x) * 4;
431+
const offset = (y * data.width + x) * 4 * window.devicePixelRatio;
418432
const r = data.data[offset];
419433
const g = data.data[offset + 1];
420434
const b = data.data[offset + 2];

test/test.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,8 @@ function checkEq(task, results, browser, masterMode) {
451451
if (!pageResults[page]) {
452452
continue;
453453
}
454-
var testSnapshot = pageResults[page].snapshot;
454+
const pageResult = pageResults[page];
455+
let testSnapshot = pageResult.snapshot;
455456
if (testSnapshot && testSnapshot.startsWith("data:image/png;base64,")) {
456457
testSnapshot = Buffer.from(testSnapshot.substring(22), "base64");
457458
} else {
@@ -492,8 +493,8 @@ function checkEq(task, results, browser, masterMode) {
492493
refSnapshot
493494
);
494495

495-
// NB: this follows the format of Mozilla reftest output so that
496-
// we can reuse its reftest-analyzer script
496+
// This no longer follows the format of Mozilla reftest output.
497+
const viewportString = `(${pageResult.viewportWidth}x${pageResult.viewportHeight}x${pageResult.outputScale})`;
497498
fs.appendFileSync(
498499
eqLog,
499500
"REFTEST TEST-UNEXPECTED-FAIL | " +
@@ -503,10 +504,10 @@ function checkEq(task, results, browser, masterMode) {
503504
"-page" +
504505
(page + 1) +
505506
" | image comparison (==)\n" +
506-
"REFTEST IMAGE 1 (TEST): " +
507+
`REFTEST IMAGE 1 (TEST)${viewportString}: ` +
507508
path.join(testSnapshotDir, page + 1 + ".png") +
508509
"\n" +
509-
"REFTEST IMAGE 2 (REFERENCE): " +
510+
`REFTEST IMAGE 2 (REFERENCE)${viewportString}: ` +
510511
path.join(testSnapshotDir, page + 1 + "_ref.png") +
511512
"\n"
512513
);
@@ -735,6 +736,9 @@ function refTestPostHandler(req, res) {
735736
taskResults[round][page] = {
736737
failure,
737738
snapshot,
739+
viewportWidth: data.viewportWidth,
740+
viewportHeight: data.viewportHeight,
741+
outputScale: data.outputScale,
738742
};
739743
if (stats) {
740744
stats.push({

test/test_manifest.json

+12
Original file line numberDiff line numberDiff line change
@@ -6115,6 +6115,18 @@
61156115
"forms": true,
61166116
"lastPage": 1
61176117
},
6118+
{
6119+
"id": "issue12716-hidpi",
6120+
"file": "pdfs/issue12716.pdf",
6121+
"md5": "9bdc9c552bcfccd629f5f97385e79ca5",
6122+
"rounds": 1,
6123+
"link": true,
6124+
"type": "eq",
6125+
"forms": true,
6126+
"lastPage": 1,
6127+
"outputScale": 2,
6128+
"about": "This tests draws to another canvas for the button, so it's a good test to ensure output scale is working."
6129+
},
61186130
{ "id": "xfa_issue13500",
61196131
"file": "pdfs/xfa_issue13500.pdf",
61206132
"md5": "b81274a19f5a95c1466db3648f1be491",

0 commit comments

Comments
 (0)