pdf.js.mirror/test/integration/text_layer_images_spec.mjs
Calixte Denizet dc3c07b3e3 Allow free-highlighting on top of image placeholders (bug 2034980)
The `textLayerImagePlaceholder` canvas added in #20626 covers scanned
pages and was not recognized as a valid pointerdown target by
`#textLayerPointerDown`, so free highlights couldn't start.
2026-04-27 20:13:39 +02:00

351 lines
11 KiB
JavaScript

/* Copyright 2026 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
awaitPromise,
closePages,
getEditorSelector,
getRect,
loadAndWait,
switchToEditor,
waitForPointerUp,
} from "./test_utils.mjs";
const switchToHighlight = switchToEditor.bind(null, "Highlight");
describe("Text layer images", () => {
describe("basic", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"images.pdf",
`.page[data-page-number = "1"] .endOfContent`,
undefined,
{
// When running Firefox with Puppeteer, setting the
// devicePixelRatio Puppeteer option does not properly set
// the `window.devicePixelRatio` value. Set it manually.
earlySetup: `() => { window.devicePixelRatio = 1 }`,
},
{ imagesRightClickMinSize: 16 },
{ width: 800, height: 600, devicePixelRatio: 1 }
);
});
afterEach(async () => {
await closePages(pages);
});
it("should render images in the text layer", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const images = await page.$$eval(
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`,
els => els.map(el => JSON.stringify(el.getBoundingClientRect()))
);
expect(images.length).withContext(`In ${browserName}`).toEqual(5);
})
);
});
it("when right-clicking an image it should get the contents", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const imageCanvas = await page.$(
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
);
expect(await page.evaluate(el => el.width, imageCanvas))
.withContext(`Initial width, in ${browserName}`)
.toBe(0);
expect(await page.evaluate(el => el.height, imageCanvas))
.withContext(`Initial height, in ${browserName}`)
.toBe(0);
await imageCanvas.click({ button: "right" });
expect(await page.evaluate(el => el.width, imageCanvas))
.withContext(`Final width, in ${browserName}`)
.toBeGreaterThan(0);
expect(await page.evaluate(el => el.height, imageCanvas))
.withContext(`Final height, in ${browserName}`)
.toBeGreaterThan(0);
expect(
await page.evaluate(el => {
const ctx = el.getContext("2d");
const imageData = ctx.getImageData(0, 0, el.width, el.height);
const pixels = new Uint32Array(imageData.data.buffer);
const firstPixel = pixels[0];
return pixels.some(pixel => pixel !== firstPixel);
}, imageCanvas)
)
.withContext(`Image is not all the same pixel, in ${browserName}`)
.toBe(true);
})
);
});
});
describe("transforms", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"images.pdf",
`.page[data-page-number = "1"] .endOfContent`,
undefined,
{
// When running Firefox with Puppeteer, setting the
// devicePixelRatio Puppeteer option does not properly set
// the `window.devicePixelRatio` value. Set it manually.
earlySetup: `() => { window.devicePixelRatio = 1 }`,
},
{ imagesRightClickMinSize: 16 },
{ width: 800, height: 600, devicePixelRatio: 1 }
);
});
afterEach(async () => {
await closePages(pages);
});
it("the three copies of the PDF.js logo have different rotations", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const getRotation = async nth =>
page.evaluate(n => {
const canvas = document.querySelectorAll(
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
)[n];
const cssTransform = getComputedStyle(canvas).transform;
if (cssTransform && cssTransform !== "none") {
const matrixValues = cssTransform
.slice(7, -1)
.split(", ")
.map(parseFloat);
return (
Math.atan2(matrixValues[1], matrixValues[0]) * (180 / Math.PI)
);
}
return 0;
}, nth);
const rotation1 = await getRotation(1);
const rotation2 = await getRotation(2);
const rotation4 = await getRotation(4);
expect(Math.abs(rotation1 - rotation2))
.withContext(`Rotation between 1 and 2, in ${browserName}`)
.toBeGreaterThan(10);
expect(Math.abs(rotation1 - rotation4))
.withContext(`Rotation between 1 and 4, in ${browserName}`)
.toBeGreaterThan(10);
expect(Math.abs(rotation2 - rotation4))
.withContext(`Rotation between 2 and 4, in ${browserName}`)
.toBeGreaterThan(10);
})
);
});
it("the three copies of the PDF.js logo have the same size", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const getSize = async nth =>
page.evaluate(n => {
const canvas = document.querySelectorAll(
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
)[n];
return { width: canvas.width, height: canvas.height };
}, nth);
const size1 = await getSize(1);
const size2 = await getSize(2);
const size4 = await getSize(4);
const EPSILON = 3;
expect(size1.width)
.withContext(`1-2 width, in ${browserName}`)
.toBeCloseTo(size2.width, EPSILON);
expect(size1.height)
.withContext(`1-2 height, in ${browserName}`)
.toBeCloseTo(size2.height, EPSILON);
expect(size1.width)
.withContext(`1-4 width, in ${browserName}`)
.toBeCloseTo(size4.width, EPSILON);
expect(size1.height)
.withContext(`1-4 height, in ${browserName}`)
.toBeCloseTo(size4.height, EPSILON);
})
);
});
});
describe("trimming", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"bug_jpx.pdf",
`.page[data-page-number = "1"] .endOfContent`,
undefined,
{
// When running Firefox with Puppeteer, setting the
// devicePixelRatio Puppeteer option does not properly set
// the `window.devicePixelRatio` value. Set it manually.
earlySetup: `() => { window.devicePixelRatio = 1 }`,
},
{ imagesRightClickMinSize: 16 },
{ width: 800, height: 600, devicePixelRatio: 1 }
);
});
afterEach(async () => {
await closePages(pages);
});
it("no white border around black image", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const canvasHandle = await page.$(
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
);
await canvasHandle.click({ button: "right" });
expect(
await page.evaluate(el => {
const ctx = el.getContext("2d");
const imageData = ctx.getImageData(0, 0, el.width, el.height);
const pixels = new Uint32Array(imageData.data.buffer);
return Array.from(pixels.filter(pixel => pixel !== 0xff000000));
}, canvasHandle)
)
.withContext(`Image is all black, in ${browserName}`)
.toEqual([]);
})
);
});
});
describe("trimming after rotation", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"image-rotated-black-white-ratio.pdf",
`.page[data-page-number = "1"] .endOfContent`,
undefined,
{
// When running Firefox with Puppeteer, setting the
// devicePixelRatio Puppeteer option does not properly set
// the `window.devicePixelRatio` value. Set it manually.
earlySetup: `() => { window.devicePixelRatio = 1 }`,
},
{ imagesRightClickMinSize: 16 },
{ width: 800, height: 600, devicePixelRatio: 1 }
);
});
afterEach(async () => {
await closePages(pages);
});
it("no white extra white around rotated image", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const canvasHandle = await page.$(
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
);
await canvasHandle.click({ button: "right" });
expect(
await page.evaluate(el => {
const ctx = el.getContext("2d");
const imageData = ctx.getImageData(0, 0, el.width, el.height);
const pixels = new Uint32Array(imageData.data.buffer);
const blackPixels = pixels.filter(
pixel => pixel === 0xff000000
).length;
const whitePixels = pixels.filter(
pixel => pixel === 0xffffffff
).length;
return blackPixels / (blackPixels + whitePixels);
}, canvasHandle)
)
.withContext(`Image is 75% black, in ${browserName}`)
.toBeCloseTo(0.75);
})
);
});
});
describe("free-highlighting on top of an image placeholder", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"images.pdf",
`.page[data-page-number = "1"] .endOfContent`,
undefined,
{
earlySetup: `() => { window.devicePixelRatio = 1 }`,
},
{
imagesRightClickMinSize: 16,
highlightEditorColors: "yellow=#FFFF00",
},
{ width: 800, height: 600, devicePixelRatio: 1 }
);
});
afterEach(async () => {
await closePages(pages);
});
it("must create a free highlight when dragging on an image placeholder (bug 2034980)", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToHighlight(page);
const rect = await getRect(
page,
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
);
const x1 = rect.x + rect.width / 4;
const y1 = rect.y + rect.height / 4;
const x2 = rect.x + (3 * rect.width) / 4;
const y2 = rect.y + (3 * rect.height) / 4;
const clickHandle = await waitForPointerUp(page);
await page.mouse.move(x1, y1);
await page.mouse.down();
await page.mouse.move(x2, y2);
await page.mouse.up();
await awaitPromise(clickHandle);
await page.waitForSelector(getEditorSelector(0));
})
);
});
});
});