mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 14:48:08 +02:00
Merge pull request #19201 from calixteman/fix_resize_stamp
[Editor] When resizing a stamp annotation, the opposite corner must stay fixed
This commit is contained in:
commit
44421d36f6
5 changed files with 139 additions and 125 deletions
|
@ -40,8 +40,6 @@ class StampEditor extends AnnotationEditor {
|
|||
|
||||
#canvas = null;
|
||||
|
||||
#observer = null;
|
||||
|
||||
#resizeTimeoutId = null;
|
||||
|
||||
#isSvg = false;
|
||||
|
@ -305,8 +303,6 @@ class StampEditor extends AnnotationEditor {
|
|||
this._uiManager.imageManager.deleteId(this.#bitmapId);
|
||||
this.#canvas?.remove();
|
||||
this.#canvas = null;
|
||||
this.#observer?.disconnect();
|
||||
this.#observer = null;
|
||||
if (this.#resizeTimeoutId) {
|
||||
clearTimeout(this.#resizeTimeoutId);
|
||||
this.#resizeTimeoutId = null;
|
||||
|
@ -398,9 +394,34 @@ class StampEditor extends AnnotationEditor {
|
|||
);
|
||||
}
|
||||
|
||||
this._uiManager.addShouldRescale(this);
|
||||
|
||||
return this.div;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_onResized() {
|
||||
// We used a CSS-zoom during the resizing, but now it's resized we can
|
||||
// rescale correctly the bitmap to fit the new dimensions.
|
||||
this.onScaleChanging();
|
||||
}
|
||||
|
||||
onScaleChanging() {
|
||||
if (!this.parent) {
|
||||
return;
|
||||
}
|
||||
if (this.#resizeTimeoutId !== null) {
|
||||
clearTimeout(this.#resizeTimeoutId);
|
||||
}
|
||||
// The user's zooming the page, there is no need to redraw the bitmap at
|
||||
// each step, hence we wait a bit before redrawing it.
|
||||
const TIME_TO_WAIT = 200;
|
||||
this.#resizeTimeoutId = setTimeout(() => {
|
||||
this.#resizeTimeoutId = null;
|
||||
this.#drawBitmap();
|
||||
}, TIME_TO_WAIT);
|
||||
}
|
||||
|
||||
#createCanvas() {
|
||||
const { div } = this;
|
||||
let { width, height } = this.#bitmap;
|
||||
|
@ -433,6 +454,15 @@ class StampEditor extends AnnotationEditor {
|
|||
canvas.setAttribute("role", "img");
|
||||
this.addContainer(canvas);
|
||||
|
||||
this.width = width / pageWidth;
|
||||
this.height = height / pageHeight;
|
||||
if (this._initialOptions?.isCentered) {
|
||||
this.center();
|
||||
} else {
|
||||
this.fixAndSetPosition();
|
||||
}
|
||||
this._initialOptions = null;
|
||||
|
||||
if (
|
||||
!this._uiManager.useNewAltTextWhenAddingImage ||
|
||||
!this._uiManager.useNewAltTextFlow ||
|
||||
|
@ -440,8 +470,7 @@ class StampEditor extends AnnotationEditor {
|
|||
) {
|
||||
div.hidden = false;
|
||||
}
|
||||
this.#drawBitmap(width, height);
|
||||
this.#createObserver();
|
||||
this.#drawBitmap();
|
||||
if (!this.#hasBeenAddedInUndoStack) {
|
||||
this.parent.addUndoableEditor(this);
|
||||
this.#hasBeenAddedInUndoStack = true;
|
||||
|
@ -584,37 +613,6 @@ class StampEditor extends AnnotationEditor {
|
|||
return { canvas, width, height, imageData };
|
||||
}
|
||||
|
||||
/**
|
||||
* When the dimensions of the div change the inner canvas must
|
||||
* renew its dimensions, hence it must redraw its own contents.
|
||||
* @param {number} width - the new width of the div
|
||||
* @param {number} height - the new height of the div
|
||||
* @returns
|
||||
*/
|
||||
#setDimensions(width, height) {
|
||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||
this.width = width / parentWidth;
|
||||
this.height = height / parentHeight;
|
||||
if (this._initialOptions?.isCentered) {
|
||||
this.center();
|
||||
} else {
|
||||
this.fixAndSetPosition();
|
||||
}
|
||||
this._initialOptions = null;
|
||||
if (this.#resizeTimeoutId !== null) {
|
||||
clearTimeout(this.#resizeTimeoutId);
|
||||
}
|
||||
// When the user is resizing the editor we just use CSS to scale the image
|
||||
// to avoid redrawing it too often.
|
||||
// And once the user stops resizing the editor we redraw the image in
|
||||
// rescaling it correctly (see this.#scaleBitmap).
|
||||
const TIME_TO_WAIT = 200;
|
||||
this.#resizeTimeoutId = setTimeout(() => {
|
||||
this.#resizeTimeoutId = null;
|
||||
this.#drawBitmap(width, height);
|
||||
}, TIME_TO_WAIT);
|
||||
}
|
||||
|
||||
#scaleBitmap(width, height) {
|
||||
const { width: bitmapWidth, height: bitmapHeight } = this.#bitmap;
|
||||
|
||||
|
@ -660,18 +658,21 @@ class StampEditor extends AnnotationEditor {
|
|||
return bitmap;
|
||||
}
|
||||
|
||||
#drawBitmap(width, height) {
|
||||
#drawBitmap() {
|
||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||
const { width, height } = this;
|
||||
const outputScale = new OutputScale();
|
||||
const scaledWidth = Math.ceil(width * outputScale.sx);
|
||||
const scaledHeight = Math.ceil(height * outputScale.sy);
|
||||
|
||||
const scaledWidth = Math.ceil(width * parentWidth * outputScale.sx);
|
||||
const scaledHeight = Math.ceil(height * parentHeight * outputScale.sy);
|
||||
const canvas = this.#canvas;
|
||||
|
||||
if (
|
||||
!canvas ||
|
||||
(canvas.width === scaledWidth && canvas.height === scaledHeight)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.width = scaledWidth;
|
||||
canvas.height = scaledHeight;
|
||||
|
||||
|
@ -746,32 +747,6 @@ class StampEditor extends AnnotationEditor {
|
|||
return structuredClone(this.#bitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the resize observer.
|
||||
*/
|
||||
#createObserver() {
|
||||
if (!this._uiManager._signal) {
|
||||
// This method is called after the canvas has been created but the canvas
|
||||
// creation is async, so it's possible that the viewer has been closed.
|
||||
return;
|
||||
}
|
||||
this.#observer = new ResizeObserver(entries => {
|
||||
const rect = entries[0].contentRect;
|
||||
if (rect.width && rect.height) {
|
||||
this.#setDimensions(rect.width, rect.height);
|
||||
}
|
||||
});
|
||||
this.#observer.observe(this.div);
|
||||
this._uiManager._signal.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
this.#observer?.disconnect();
|
||||
this.#observer = null;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static async deserialize(data, parent, uiManager) {
|
||||
let initialData = null;
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
copy,
|
||||
copyToClipboard,
|
||||
createPromise,
|
||||
dragAndDropAnnotation,
|
||||
dragAndDrop,
|
||||
firstPageOnTop,
|
||||
getEditors,
|
||||
getEditorSelector,
|
||||
|
@ -954,19 +954,14 @@ describe("FreeText Editor", () => {
|
|||
const serialized = await getSerialized(page);
|
||||
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
|
||||
|
||||
const editorRect = await getRect(page, getEditorSelector(0));
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const editorRect = await getRect(page, editorSelector);
|
||||
|
||||
// Select the annotation we want to move.
|
||||
await page.mouse.click(editorRect.x + 2, editorRect.y + 2);
|
||||
await waitForSelectedEditor(page, getEditorSelector(0));
|
||||
await waitForSelectedEditor(page, editorSelector);
|
||||
|
||||
await dragAndDropAnnotation(
|
||||
page,
|
||||
editorRect.x + editorRect.width / 2,
|
||||
editorRect.y + editorRect.height / 2,
|
||||
100,
|
||||
100
|
||||
);
|
||||
await dragAndDrop(page, editorSelector, [[100, 100]]);
|
||||
await waitForSerialized(page, 1);
|
||||
})
|
||||
);
|
||||
|
@ -2335,29 +2330,29 @@ describe("FreeText Editor", () => {
|
|||
const allPositions = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const editorSelector = getEditorSelector(i);
|
||||
await page.mouse.click(rect.x + 10 + 30 * i, rect.y + 100 + 5 * i);
|
||||
await page.waitForSelector(getEditorSelector(i), {
|
||||
await page.waitForSelector(editorSelector, {
|
||||
visible: true,
|
||||
});
|
||||
await page.type(
|
||||
`${getEditorSelector(i)} .internal`,
|
||||
`${editorSelector} .internal`,
|
||||
String.fromCharCode(65 + i)
|
||||
);
|
||||
|
||||
// Commit.
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForSelector(
|
||||
`${getEditorSelector(i)} .overlay.enabled`
|
||||
);
|
||||
await page.waitForSelector(`${editorSelector} .overlay.enabled`);
|
||||
|
||||
allPositions.push(await getRect(page, getEditorSelector(i)));
|
||||
allPositions.push(await getRect(page, editorSelector));
|
||||
}
|
||||
|
||||
await selectAll(page);
|
||||
await dragAndDropAnnotation(page, rect.x + 161, rect.y + 126, 39, 74);
|
||||
await dragAndDrop(page, getEditorSelector(4), [[39, 74]]);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const pos = await getRect(page, getEditorSelector(i));
|
||||
const editorSelector = getEditorSelector(i);
|
||||
const pos = await getRect(page, editorSelector);
|
||||
const oldPos = allPositions[i];
|
||||
expect(Math.abs(Math.round(pos.x - oldPos.x) - 39))
|
||||
.withContext(`In ${browserName}`)
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
awaitPromise,
|
||||
closePages,
|
||||
createPromise,
|
||||
dragAndDropAnnotation,
|
||||
dragAndDrop,
|
||||
getAnnotationSelector,
|
||||
getEditors,
|
||||
getEditorSelector,
|
||||
|
@ -822,13 +822,7 @@ describe("Ink Editor", () => {
|
|||
await page.mouse.click(editorRect.x + 2, editorRect.y + 2);
|
||||
await waitForSelectedEditor(page, edgeB);
|
||||
|
||||
await dragAndDropAnnotation(
|
||||
page,
|
||||
editorRect.x + editorRect.width / 2,
|
||||
editorRect.y + editorRect.height / 2,
|
||||
100,
|
||||
100
|
||||
);
|
||||
await dragAndDrop(page, edgeB, [[100, 100]]);
|
||||
await waitForSerialized(page, 1);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
import {
|
||||
applyFunctionToEditor,
|
||||
awaitPromise,
|
||||
cleanupEditing,
|
||||
clearInput,
|
||||
closePages,
|
||||
copy,
|
||||
copyToClipboard,
|
||||
dragAndDropAnnotation,
|
||||
dragAndDrop,
|
||||
getAnnotationSelector,
|
||||
getEditorDimensions,
|
||||
getEditors,
|
||||
|
@ -120,13 +121,7 @@ describe("Stamp Editor", () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
for (const [, page] of pages) {
|
||||
await page.evaluate(() => {
|
||||
window.uiManager.reset();
|
||||
});
|
||||
// Disable editing mode.
|
||||
await switchToStamp(page, /* disable */ true);
|
||||
}
|
||||
await cleanupEditing(pages, switchToStamp);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -216,13 +211,23 @@ describe("Stamp Editor", () => {
|
|||
let pages;
|
||||
|
||||
beforeAll(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer", 50);
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer", 50, {
|
||||
eventBusSetup: eventBus => {
|
||||
eventBus.on("annotationeditoruimanager", ({ uiManager }) => {
|
||||
window.uiManager = uiManager;
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupEditing(pages, switchToStamp);
|
||||
});
|
||||
|
||||
it("must check that an added image stay within the page", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
|
@ -239,14 +244,13 @@ describe("Stamp Editor", () => {
|
|||
await input.uploadFile(
|
||||
`${path.join(__dirname, "../images/firefox_logo.png")}`
|
||||
);
|
||||
await waitForImage(page, getEditorSelector(i));
|
||||
await page.waitForSelector(`${getEditorSelector(i)} .altText`);
|
||||
const editorSelector = getEditorSelector(i);
|
||||
await waitForImage(page, editorSelector);
|
||||
await page.waitForSelector(`${editorSelector} .altText`);
|
||||
|
||||
for (let j = 0; j < 4; j++) {
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForSelector(
|
||||
`${getEditorSelector(i)} .resizers.hidden`
|
||||
);
|
||||
await page.waitForSelector(`${editorSelector} .resizers.hidden`);
|
||||
|
||||
const handle = await waitForAnnotationEditorLayer(page);
|
||||
await page.evaluate(() => {
|
||||
|
@ -255,10 +259,10 @@ describe("Stamp Editor", () => {
|
|||
await awaitPromise(handle);
|
||||
|
||||
await page.focus(".stampEditor");
|
||||
await waitForSelectedEditor(page, getEditorSelector(i));
|
||||
await waitForSelectedEditor(page, editorSelector);
|
||||
|
||||
await page.waitForSelector(
|
||||
`${getEditorSelector(i)} .resizers:not(.hidden)`
|
||||
`${editorSelector} .resizers:not(.hidden)`
|
||||
);
|
||||
|
||||
const stampRect = await getRect(page, ".stampEditor");
|
||||
|
@ -285,6 +289,44 @@ describe("Stamp Editor", () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the opposite corner doesn't move", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToStamp(page);
|
||||
|
||||
await page.click("#editorStampAddImage");
|
||||
const input = await page.$("#stampEditorFileInput");
|
||||
await input.uploadFile(
|
||||
`${path.join(__dirname, "../images/firefox_logo.png")}`
|
||||
);
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await waitForImage(page, editorSelector);
|
||||
await page.waitForSelector(`${editorSelector} .resizer.topLeft`);
|
||||
const baseRect = await getRect(page, editorSelector);
|
||||
const bRX = baseRect.x + baseRect.width;
|
||||
const bRY = baseRect.y + baseRect.height;
|
||||
|
||||
await dragAndDrop(page, `${editorSelector} .resizer.topLeft`, [
|
||||
[-10, -10],
|
||||
[20, 20],
|
||||
[-10, -10],
|
||||
[20, 20],
|
||||
]);
|
||||
|
||||
const newRect = await getRect(page, editorSelector);
|
||||
const newBRX = newRect.x + newRect.width;
|
||||
const newBRY = newRect.y + newRect.height;
|
||||
|
||||
expect(Math.abs(bRX - newBRX) <= 1)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeTrue();
|
||||
expect(Math.abs(bRY - newBRY) <= 1)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeTrue();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Alt text dialog", () => {
|
||||
|
@ -1441,7 +1483,8 @@ describe("Stamp Editor", () => {
|
|||
const modeChangedHandle = await waitForAnnotationModeChanged(page);
|
||||
await page.click(getAnnotationSelector("25R"), { count: 2 });
|
||||
await awaitPromise(modeChangedHandle);
|
||||
await waitForSelectedEditor(page, getEditorSelector(0));
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await waitForSelectedEditor(page, editorSelector);
|
||||
|
||||
const editorIds = await getEditors(page, "stamp");
|
||||
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(5);
|
||||
|
@ -1451,22 +1494,13 @@ describe("Stamp Editor", () => {
|
|||
const serialized = await getSerialized(page);
|
||||
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
|
||||
|
||||
const editorRect = await page.$eval(getEditorSelector(0), el => {
|
||||
const { x, y, width, height } = el.getBoundingClientRect();
|
||||
return { x, y, width, height };
|
||||
});
|
||||
const editorRect = await getRect(page, editorSelector);
|
||||
|
||||
// Select the annotation we want to move.
|
||||
await page.mouse.click(editorRect.x + 2, editorRect.y + 2);
|
||||
await waitForSelectedEditor(page, getEditorSelector(0));
|
||||
await waitForSelectedEditor(page, editorSelector);
|
||||
|
||||
await dragAndDropAnnotation(
|
||||
page,
|
||||
editorRect.x + editorRect.width / 2,
|
||||
editorRect.y + editorRect.height / 2,
|
||||
100,
|
||||
100
|
||||
);
|
||||
await dragAndDrop(page, editorSelector, [[100, 100]]);
|
||||
await waitForSerialized(page, 1);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -522,10 +522,15 @@ async function serializeBitmapDimensions(page) {
|
|||
});
|
||||
}
|
||||
|
||||
async function dragAndDropAnnotation(page, startX, startY, tX, tY) {
|
||||
async function dragAndDrop(page, selector, translations) {
|
||||
const rect = await getRect(page, selector);
|
||||
const startX = rect.x + rect.width / 2;
|
||||
const startY = rect.y + rect.height / 2;
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(startX + tX, startY + tY);
|
||||
for (const [tX, tY] of translations) {
|
||||
await page.mouse.move(startX + tX, startY + tY);
|
||||
}
|
||||
await page.mouse.up();
|
||||
await page.waitForSelector("#viewer:not(.noUserSelect)");
|
||||
}
|
||||
|
@ -809,16 +814,27 @@ function isCanvasWhite(page, pageNumber, rectangle) {
|
|||
);
|
||||
}
|
||||
|
||||
async function cleanupEditing(pages, switcher) {
|
||||
for (const [, page] of pages) {
|
||||
await page.evaluate(() => {
|
||||
window.uiManager.reset();
|
||||
});
|
||||
// Disable editing mode.
|
||||
await switcher(page, /* disable */ true);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
applyFunctionToEditor,
|
||||
awaitPromise,
|
||||
cleanupEditing,
|
||||
clearInput,
|
||||
closePages,
|
||||
closeSinglePage,
|
||||
copy,
|
||||
copyToClipboard,
|
||||
createPromise,
|
||||
dragAndDropAnnotation,
|
||||
dragAndDrop,
|
||||
firstPageOnTop,
|
||||
getAnnotationSelector,
|
||||
getAnnotationStorage,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue