From 1460a8128a1f5895d7e5ff86709b753d84097205 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 2 Dec 2024 09:17:07 +0100 Subject: [PATCH] Fix the clickable area for rotated ink annotations --- src/display/annotation_layer.js | 57 ++++++++++++++++----------- test/integration/annotation_spec.mjs | 35 ++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/rotated_ink.pdf | Bin 0 -> 17873 bytes 4 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 test/pdfs/rotated_ink.pdf diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index e2dc138a8ded4..fdfdf7e262cea 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -2815,37 +2815,48 @@ class InkAnnotationElement extends AnnotationElement { // Create an invisible polyline with the same points that acts as the // trigger for the popup. const { - data: { rect, inkLists, borderStyle, popupRef }, + data: { rect, rotation, inkLists, borderStyle, popupRef }, } = this; - const { width, height } = getRectDims(rect); + let { width, height } = getRectDims(rect); + let transform; + + // PDF coordinates are calculated from a bottom left origin, so + // transform the polyline coordinates to a top left origin for the + // SVG element. + switch (rotation) { + case 90: + transform = `rotate(90) translate(${-rect[0]},${rect[3] - height}) scale(1,-1)`; + [width, height] = [height, width]; + break; + case 180: + transform = `rotate(180) translate(${-rect[0] - width},${rect[3] - height}) scale(1,-1)`; + break; + case 270: + transform = `rotate(270) translate(${-rect[0] - width},${rect[3]}) scale(1,-1)`; + [width, height] = [height, width]; + break; + default: + transform = `translate(${-rect[0]},${rect[3]}) scale(1,-1)`; + break; + } + const svg = this.svgFactory.create( width, height, /* skipDimensions = */ true ); + const basePolyline = this.svgFactory.createElement(this.svgElementName); + // Ensure that the 'stroke-width' is always non-zero, since otherwise it + // won't be possible to open/close the popup (note e.g. issue 11122). + basePolyline.setAttribute("stroke-width", borderStyle.width || 1); + basePolyline.setAttribute("stroke", "transparent"); + basePolyline.setAttribute("fill", "transparent"); + basePolyline.setAttribute("transform", transform); - for (const inkList of inkLists) { - // Convert the ink list to a single points string that the SVG - // polyline element expects ("x1,y1 x2,y2 ..."). PDF coordinates are - // calculated from a bottom left origin, so transform the polyline - // coordinates to a top left origin for the SVG element. - let points = []; - for (let i = 0, ii = inkList.length; i < ii; i += 2) { - const x = inkList[i] - rect[0]; - const y = rect[3] - inkList[i + 1]; - points.push(`${x},${y}`); - } - points = points.join(" "); - - const polyline = this.svgFactory.createElement(this.svgElementName); + for (let i = 0, ii = inkLists.length; i < ii; i++) { + const polyline = i < ii - 1 ? basePolyline.cloneNode() : basePolyline; this.#polylines.push(polyline); - polyline.setAttribute("points", points); - // Ensure that the 'stroke-width' is always non-zero, since otherwise it - // won't be possible to open/close the popup (note e.g. issue 11122). - polyline.setAttribute("stroke-width", borderStyle.width || 1); - polyline.setAttribute("stroke", "transparent"); - polyline.setAttribute("fill", "transparent"); - + polyline.setAttribute("points", inkLists[i].join(",")); svg.append(polyline); } diff --git a/test/integration/annotation_spec.mjs b/test/integration/annotation_spec.mjs index 52e33698d9c99..7431a17d17e22 100644 --- a/test/integration/annotation_spec.mjs +++ b/test/integration/annotation_spec.mjs @@ -16,6 +16,7 @@ import { closePages, getQuerySelector, + getRect, getSelector, loadAndWait, } from "./test_utils.mjs"; @@ -654,4 +655,38 @@ describe("ResetForm action", () => { }); }); }); + + describe("Rotated annotation and its clickable area", () => { + describe("issue14438.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait( + "rotated_ink.pdf", + "[data-annotation-id='18R']" + ); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that the clickable area has been rotated", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + const rect = await getRect(page, "[data-annotation-id='18R']"); + const promisePopup = page.waitForSelector( + "[data-annotation-id='19R']", + { visible: true } + ); + await page.mouse.move( + rect.x + rect.width * 0.1, + rect.y + rect.height * 0.9 + ); + await promisePopup; + }) + ); + }); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 9419dafdc5390..45949cc358c30 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -684,3 +684,4 @@ !issue19083.pdf !issue19120.pdf !bug1934157.pdf +!rotated_ink.pdf diff --git a/test/pdfs/rotated_ink.pdf b/test/pdfs/rotated_ink.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8b69361eaf3f65bf506926b879a4eaff3e61bf1f GIT binary patch literal 17873 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 X19R6WHarM;<-6TTA#}0g