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);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeSVG(svgElement, ctx) {
|
function writeSVG(svgElement, ctx, outputScale) {
|
||||||
// We need to have UTF-8 encoded XML.
|
// We need to have UTF-8 encoded XML.
|
||||||
const svg_xml = unescape(
|
const svg_xml = unescape(
|
||||||
encodeURIComponent(new XMLSerializer().serializeToString(svgElement))
|
encodeURIComponent(new XMLSerializer().serializeToString(svgElement))
|
||||||
@ -102,7 +102,7 @@ function inlineImages(images) {
|
|||||||
return Promise.all(imagePromises);
|
return Promise.all(imagePromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function convertCanvasesToImages(annotationCanvasMap) {
|
async function convertCanvasesToImages(annotationCanvasMap, outputScale) {
|
||||||
const results = new Map();
|
const results = new Map();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (const [key, canvas] of annotationCanvasMap) {
|
for (const [key, canvas] of annotationCanvasMap) {
|
||||||
@ -110,7 +110,10 @@ async function convertCanvasesToImages(annotationCanvasMap) {
|
|||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
canvas.toBlob(blob => {
|
canvas.toBlob(blob => {
|
||||||
const image = document.createElement("img");
|
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);
|
results.set(key, image);
|
||||||
image.src = URL.createObjectURL(blob);
|
image.src = URL.createObjectURL(blob);
|
||||||
});
|
});
|
||||||
@ -198,6 +201,7 @@ class Rasterize {
|
|||||||
static async annotationLayer(
|
static async annotationLayer(
|
||||||
ctx,
|
ctx,
|
||||||
viewport,
|
viewport,
|
||||||
|
outputScale,
|
||||||
annotations,
|
annotations,
|
||||||
annotationCanvasMap,
|
annotationCanvasMap,
|
||||||
page,
|
page,
|
||||||
@ -213,7 +217,8 @@ class Rasterize {
|
|||||||
|
|
||||||
const annotationViewport = viewport.clone({ dontFlip: true });
|
const annotationViewport = viewport.clone({ dontFlip: true });
|
||||||
const annotationImageMap = await convertCanvasesToImages(
|
const annotationImageMap = await convertCanvasesToImages(
|
||||||
annotationCanvasMap
|
annotationCanvasMap,
|
||||||
|
outputScale
|
||||||
);
|
);
|
||||||
|
|
||||||
// Rendering annotation layer as HTML.
|
// Rendering annotation layer as HTML.
|
||||||
@ -600,13 +605,38 @@ class Driver {
|
|||||||
ctx = this.canvas.getContext("2d", { alpha: false });
|
ctx = this.canvas.getContext("2d", { alpha: false });
|
||||||
task.pdfDoc.getPage(task.pageNum).then(
|
task.pdfDoc.getPage(task.pageNum).then(
|
||||||
page => {
|
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,
|
scale: PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||||
});
|
});
|
||||||
this.canvas.width = viewport.width;
|
// Restrict the test from creating a canvas that is too big.
|
||||||
this.canvas.height = viewport.height;
|
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();
|
this._clearCanvas();
|
||||||
|
|
||||||
|
const transform =
|
||||||
|
outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
|
||||||
|
|
||||||
// Initialize various `eq` test subtypes, see comment below.
|
// Initialize various `eq` test subtypes, see comment below.
|
||||||
let renderAnnotations = false,
|
let renderAnnotations = false,
|
||||||
renderForms = false,
|
renderForms = false,
|
||||||
@ -631,8 +661,8 @@ class Driver {
|
|||||||
textLayerCanvas = document.createElement("canvas");
|
textLayerCanvas = document.createElement("canvas");
|
||||||
this.textLayerCanvas = textLayerCanvas;
|
this.textLayerCanvas = textLayerCanvas;
|
||||||
}
|
}
|
||||||
textLayerCanvas.width = viewport.width;
|
textLayerCanvas.width = pixelWidth;
|
||||||
textLayerCanvas.height = viewport.height;
|
textLayerCanvas.height = pixelHeight;
|
||||||
const textLayerContext = textLayerCanvas.getContext("2d");
|
const textLayerContext = textLayerCanvas.getContext("2d");
|
||||||
textLayerContext.clearRect(
|
textLayerContext.clearRect(
|
||||||
0,
|
0,
|
||||||
@ -640,6 +670,7 @@ class Driver {
|
|||||||
textLayerCanvas.width,
|
textLayerCanvas.width,
|
||||||
textLayerCanvas.height
|
textLayerCanvas.height
|
||||||
);
|
);
|
||||||
|
textLayerContext.scale(outputScale, outputScale);
|
||||||
const enhanceText = !!task.enhance;
|
const enhanceText = !!task.enhance;
|
||||||
// The text builder will draw its content on the test canvas
|
// The text builder will draw its content on the test canvas
|
||||||
initPromise = page
|
initPromise = page
|
||||||
@ -672,8 +703,8 @@ class Driver {
|
|||||||
annotationLayerCanvas = document.createElement("canvas");
|
annotationLayerCanvas = document.createElement("canvas");
|
||||||
this.annotationLayerCanvas = annotationLayerCanvas;
|
this.annotationLayerCanvas = annotationLayerCanvas;
|
||||||
}
|
}
|
||||||
annotationLayerCanvas.width = viewport.width;
|
annotationLayerCanvas.width = pixelWidth;
|
||||||
annotationLayerCanvas.height = viewport.height;
|
annotationLayerCanvas.height = pixelHeight;
|
||||||
annotationLayerContext = annotationLayerCanvas.getContext("2d");
|
annotationLayerContext = annotationLayerCanvas.getContext("2d");
|
||||||
annotationLayerContext.clearRect(
|
annotationLayerContext.clearRect(
|
||||||
0,
|
0,
|
||||||
@ -681,6 +712,7 @@ class Driver {
|
|||||||
annotationLayerCanvas.width,
|
annotationLayerCanvas.width,
|
||||||
annotationLayerCanvas.height
|
annotationLayerCanvas.height
|
||||||
);
|
);
|
||||||
|
annotationLayerContext.scale(outputScale, outputScale);
|
||||||
|
|
||||||
if (!renderXfa) {
|
if (!renderXfa) {
|
||||||
// The annotation builder will draw its content
|
// The annotation builder will draw its content
|
||||||
@ -709,6 +741,7 @@ class Driver {
|
|||||||
viewport,
|
viewport,
|
||||||
optionalContentConfigPromise: task.optionalContentConfigPromise,
|
optionalContentConfigPromise: task.optionalContentConfigPromise,
|
||||||
annotationCanvasMap,
|
annotationCanvasMap,
|
||||||
|
transform,
|
||||||
};
|
};
|
||||||
if (renderForms) {
|
if (renderForms) {
|
||||||
renderContext.annotationMode = AnnotationMode.ENABLE_FORMS;
|
renderContext.annotationMode = AnnotationMode.ENABLE_FORMS;
|
||||||
@ -725,7 +758,7 @@ class Driver {
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
ctx.fillStyle = "rgb(128, 255, 128)"; // making it green
|
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.restore();
|
||||||
ctx.drawImage(textLayerCanvas, 0, 0);
|
ctx.drawImage(textLayerCanvas, 0, 0);
|
||||||
}
|
}
|
||||||
@ -755,6 +788,7 @@ class Driver {
|
|||||||
Rasterize.annotationLayer(
|
Rasterize.annotationLayer(
|
||||||
annotationLayerContext,
|
annotationLayerContext,
|
||||||
viewport,
|
viewport,
|
||||||
|
outputScale,
|
||||||
data,
|
data,
|
||||||
annotationCanvasMap,
|
annotationCanvasMap,
|
||||||
page,
|
page,
|
||||||
@ -864,6 +898,9 @@ class Driver {
|
|||||||
page: task.pageNum,
|
page: task.pageNum,
|
||||||
snapshot,
|
snapshot,
|
||||||
stats: task.stats.times,
|
stats: task.stats.times,
|
||||||
|
viewportWidth: task.viewportWidth,
|
||||||
|
viewportHeight: task.viewportHeight,
|
||||||
|
outputScale: task.outputScale,
|
||||||
});
|
});
|
||||||
this._send("/submit_task_results", result, callback);
|
this._send("/submit_task_results", result, callback);
|
||||||
}
|
}
|
||||||
|
@ -165,8 +165,8 @@ Original author: L. David Baron <dbaron@dbaron.org>
|
|||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
<g id="magnify">
|
<g id="magnify">
|
||||||
<image x="0" y="0" width="100%" height="100%" id="image1" />
|
<image x="0" y="0" id="image1" />
|
||||||
<image x="0" y="0" width="100%" height="100%" id="image2" />
|
<image x="0" y="0" id="image2" />
|
||||||
</g>
|
</g>
|
||||||
<rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
|
<rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -228,10 +228,15 @@ window.onload = function () {
|
|||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match = line.match(/^ {2}IMAGE[^:]*: (.*)$/);
|
match = line.match(/^ {2}IMAGE[^:]*\((\d+)x(\d+)x(\d+)\): (.*)$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const item = gTestItems[gTestItems.length - 1];
|
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();
|
buildViewer();
|
||||||
@ -335,16 +340,31 @@ window.onload = function () {
|
|||||||
const cell = ID("images");
|
const cell = ID("images");
|
||||||
|
|
||||||
ID("image1").style.display = "";
|
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";
|
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("diffrect").style.display = "none";
|
||||||
ID("imgcontrols").reset();
|
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
|
// Making the href be #image1 doesn't seem to work
|
||||||
ID("feimage1").setAttributeNS(
|
ID("feimage1").setAttributeNS(
|
||||||
XLINK_NS,
|
XLINK_NS,
|
||||||
"xlink:href",
|
"xlink:href",
|
||||||
gPath + item.images[0]
|
gPath + item.images[0].file
|
||||||
);
|
);
|
||||||
if (item.images.length === 1) {
|
if (item.images.length === 1) {
|
||||||
ID("imgcontrols").style.display = "none";
|
ID("imgcontrols").style.display = "none";
|
||||||
@ -353,30 +373,24 @@ window.onload = function () {
|
|||||||
ID("image2").setAttributeNS(
|
ID("image2").setAttributeNS(
|
||||||
XLINK_NS,
|
XLINK_NS,
|
||||||
"xlink:href",
|
"xlink:href",
|
||||||
gPath + item.images[1]
|
gPath + item.images[1].file
|
||||||
);
|
);
|
||||||
// Making the href be #image2 doesn't seem to work
|
// Making the href be #image2 doesn't seem to work
|
||||||
ID("feimage2").setAttributeNS(
|
ID("feimage2").setAttributeNS(
|
||||||
XLINK_NS,
|
XLINK_NS,
|
||||||
"xlink:href",
|
"xlink:href",
|
||||||
gPath + item.images[1]
|
gPath + item.images[1].file
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
cell.style.display = "";
|
cell.style.display = "";
|
||||||
getImageData(item.images[0], function (data) {
|
getImageData(item.images[0].file, function (data) {
|
||||||
gImage1Data = data;
|
gImage1Data = data;
|
||||||
syncSVGSize(gImage1Data);
|
|
||||||
});
|
});
|
||||||
getImageData(item.images[1], function (data) {
|
getImageData(item.images[1].file, function (data) {
|
||||||
gImage2Data = data;
|
gImage2Data = data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncSVGSize(imageData) {
|
|
||||||
ID("svg").setAttribute("width", imageData.width);
|
|
||||||
ID("svg").setAttribute("height", imageData.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showImage(i) {
|
function showImage(i) {
|
||||||
if (i === 1) {
|
if (i === 1) {
|
||||||
ID("image1").style.display = "";
|
ID("image1").style.display = "";
|
||||||
@ -414,7 +428,7 @@ window.onload = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function canvasPixelAsHex(data, x, y) {
|
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 r = data.data[offset];
|
||||||
const g = data.data[offset + 1];
|
const g = data.data[offset + 1];
|
||||||
const b = data.data[offset + 2];
|
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]) {
|
if (!pageResults[page]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var testSnapshot = pageResults[page].snapshot;
|
const pageResult = pageResults[page];
|
||||||
|
let testSnapshot = pageResult.snapshot;
|
||||||
if (testSnapshot && testSnapshot.startsWith("data:image/png;base64,")) {
|
if (testSnapshot && testSnapshot.startsWith("data:image/png;base64,")) {
|
||||||
testSnapshot = Buffer.from(testSnapshot.substring(22), "base64");
|
testSnapshot = Buffer.from(testSnapshot.substring(22), "base64");
|
||||||
} else {
|
} else {
|
||||||
@ -492,8 +493,8 @@ function checkEq(task, results, browser, masterMode) {
|
|||||||
refSnapshot
|
refSnapshot
|
||||||
);
|
);
|
||||||
|
|
||||||
// NB: this follows the format of Mozilla reftest output so that
|
// This no longer follows the format of Mozilla reftest output.
|
||||||
// we can reuse its reftest-analyzer script
|
const viewportString = `(${pageResult.viewportWidth}x${pageResult.viewportHeight}x${pageResult.outputScale})`;
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
eqLog,
|
eqLog,
|
||||||
"REFTEST TEST-UNEXPECTED-FAIL | " +
|
"REFTEST TEST-UNEXPECTED-FAIL | " +
|
||||||
@ -503,10 +504,10 @@ function checkEq(task, results, browser, masterMode) {
|
|||||||
"-page" +
|
"-page" +
|
||||||
(page + 1) +
|
(page + 1) +
|
||||||
" | image comparison (==)\n" +
|
" | image comparison (==)\n" +
|
||||||
"REFTEST IMAGE 1 (TEST): " +
|
`REFTEST IMAGE 1 (TEST)${viewportString}: ` +
|
||||||
path.join(testSnapshotDir, page + 1 + ".png") +
|
path.join(testSnapshotDir, page + 1 + ".png") +
|
||||||
"\n" +
|
"\n" +
|
||||||
"REFTEST IMAGE 2 (REFERENCE): " +
|
`REFTEST IMAGE 2 (REFERENCE)${viewportString}: ` +
|
||||||
path.join(testSnapshotDir, page + 1 + "_ref.png") +
|
path.join(testSnapshotDir, page + 1 + "_ref.png") +
|
||||||
"\n"
|
"\n"
|
||||||
);
|
);
|
||||||
@ -735,6 +736,9 @@ function refTestPostHandler(req, res) {
|
|||||||
taskResults[round][page] = {
|
taskResults[round][page] = {
|
||||||
failure,
|
failure,
|
||||||
snapshot,
|
snapshot,
|
||||||
|
viewportWidth: data.viewportWidth,
|
||||||
|
viewportHeight: data.viewportHeight,
|
||||||
|
outputScale: data.outputScale,
|
||||||
};
|
};
|
||||||
if (stats) {
|
if (stats) {
|
||||||
stats.push({
|
stats.push({
|
||||||
|
@ -6115,6 +6115,18 @@
|
|||||||
"forms": true,
|
"forms": true,
|
||||||
"lastPage": 1
|
"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",
|
{ "id": "xfa_issue13500",
|
||||||
"file": "pdfs/xfa_issue13500.pdf",
|
"file": "pdfs/xfa_issue13500.pdf",
|
||||||
"md5": "b81274a19f5a95c1466db3648f1be491",
|
"md5": "b81274a19f5a95c1466db3648f1be491",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user