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.
This commit is contained in:
parent
1d1e50e8ee
commit
f5c3abb8f7
@ -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);
|
||||
}
|
||||
|
@ -165,8 +165,8 @@ Original author: L. David Baron <dbaron@dbaron.org>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="magnify">
|
||||
<image x="0" y="0" width="100%" height="100%" id="image1" />
|
||||
<image x="0" y="0" width="100%" height="100%" id="image2" />
|
||||
<image x="0" y="0" id="image1" />
|
||||
<image x="0" y="0" id="image2" />
|
||||
</g>
|
||||
<rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
|
||||
</svg>
|
||||
|
@ -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];
|
||||
|
14
test/test.js
14
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({
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user