mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 06:38:07 +02:00
Merge pull request #17914 from calixteman/freetext_edit
[Editor] Provide an element to render in the annotation layer after a freetext has been edited (bug 1890535)
This commit is contained in:
commit
4866686749
11 changed files with 379 additions and 42 deletions
|
@ -20,6 +20,8 @@
|
|||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */
|
||||
/** @typedef {import("../../web/interfaces").IPDFLinkService} IPDFLinkService */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||
|
||||
import {
|
||||
AnnotationBorderStyleType,
|
||||
|
@ -157,6 +159,8 @@ class AnnotationElementFactory {
|
|||
}
|
||||
|
||||
class AnnotationElement {
|
||||
#updates = null;
|
||||
|
||||
#hasBorder = false;
|
||||
|
||||
constructor(
|
||||
|
@ -197,6 +201,52 @@ class AnnotationElement {
|
|||
return AnnotationElement._hasPopupData(this.data);
|
||||
}
|
||||
|
||||
updateEdited(params) {
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#updates ||= {
|
||||
rect: this.data.rect.slice(0),
|
||||
};
|
||||
|
||||
const { rect } = params;
|
||||
|
||||
if (rect) {
|
||||
this.#setRectEdited(rect);
|
||||
}
|
||||
}
|
||||
|
||||
resetEdited() {
|
||||
if (!this.#updates) {
|
||||
return;
|
||||
}
|
||||
this.#setRectEdited(this.#updates.rect);
|
||||
this.#updates = null;
|
||||
}
|
||||
|
||||
#setRectEdited(rect) {
|
||||
const {
|
||||
container: { style },
|
||||
data: { rect: currentRect, rotation },
|
||||
parent: {
|
||||
viewport: {
|
||||
rawDims: { pageWidth, pageHeight, pageX, pageY },
|
||||
},
|
||||
},
|
||||
} = this;
|
||||
currentRect?.splice(0, 4, ...rect);
|
||||
const { width, height } = getRectDims(rect);
|
||||
style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
|
||||
style.top = `${(100 * (pageHeight - rect[3] + pageY)) / pageHeight}%`;
|
||||
if (rotation === 0) {
|
||||
style.width = `${(100 * width) / pageWidth}%`;
|
||||
style.height = `${(100 * height) / pageHeight}%`;
|
||||
} else {
|
||||
this.setRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty container for the annotation's HTML element.
|
||||
*
|
||||
|
@ -216,13 +266,14 @@ class AnnotationElement {
|
|||
if (!(this instanceof WidgetAnnotationElement)) {
|
||||
container.tabIndex = DEFAULT_TAB_INDEX;
|
||||
}
|
||||
const { style } = container;
|
||||
|
||||
// The accessibility manager will move the annotation in the DOM in
|
||||
// order to match the visual ordering.
|
||||
// But if an annotation is above an other one, then we must draw it
|
||||
// after the other one whatever the order is in the DOM, hence the
|
||||
// use of the z-index.
|
||||
container.style.zIndex = this.parent.zIndex++;
|
||||
style.zIndex = this.parent.zIndex++;
|
||||
|
||||
if (data.popupRef) {
|
||||
container.setAttribute("aria-haspopup", "dialog");
|
||||
|
@ -236,8 +287,6 @@ class AnnotationElement {
|
|||
container.classList.add("norotate");
|
||||
}
|
||||
|
||||
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;
|
||||
|
||||
if (!data.rect || this instanceof PopupAnnotationElement) {
|
||||
const { rotation } = data;
|
||||
if (!data.hasOwnCanvas && rotation !== 0) {
|
||||
|
@ -248,35 +297,26 @@ class AnnotationElement {
|
|||
|
||||
const { width, height } = getRectDims(data.rect);
|
||||
|
||||
// Do *not* modify `data.rect`, since that will corrupt the annotation
|
||||
// position on subsequent calls to `_createContainer` (see issue 6804).
|
||||
const rect = Util.normalizeRect([
|
||||
data.rect[0],
|
||||
page.view[3] - data.rect[1] + page.view[1],
|
||||
data.rect[2],
|
||||
page.view[3] - data.rect[3] + page.view[1],
|
||||
]);
|
||||
|
||||
if (!ignoreBorder && data.borderStyle.width > 0) {
|
||||
container.style.borderWidth = `${data.borderStyle.width}px`;
|
||||
style.borderWidth = `${data.borderStyle.width}px`;
|
||||
|
||||
const horizontalRadius = data.borderStyle.horizontalCornerRadius;
|
||||
const verticalRadius = data.borderStyle.verticalCornerRadius;
|
||||
if (horizontalRadius > 0 || verticalRadius > 0) {
|
||||
const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`;
|
||||
container.style.borderRadius = radius;
|
||||
style.borderRadius = radius;
|
||||
} else if (this instanceof RadioButtonWidgetAnnotationElement) {
|
||||
const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`;
|
||||
container.style.borderRadius = radius;
|
||||
style.borderRadius = radius;
|
||||
}
|
||||
|
||||
switch (data.borderStyle.style) {
|
||||
case AnnotationBorderStyleType.SOLID:
|
||||
container.style.borderStyle = "solid";
|
||||
style.borderStyle = "solid";
|
||||
break;
|
||||
|
||||
case AnnotationBorderStyleType.DASHED:
|
||||
container.style.borderStyle = "dashed";
|
||||
style.borderStyle = "dashed";
|
||||
break;
|
||||
|
||||
case AnnotationBorderStyleType.BEVELED:
|
||||
|
@ -288,7 +328,7 @@ class AnnotationElement {
|
|||
break;
|
||||
|
||||
case AnnotationBorderStyleType.UNDERLINE:
|
||||
container.style.borderBottomStyle = "solid";
|
||||
style.borderBottomStyle = "solid";
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -298,24 +338,34 @@ class AnnotationElement {
|
|||
const borderColor = data.borderColor || null;
|
||||
if (borderColor) {
|
||||
this.#hasBorder = true;
|
||||
container.style.borderColor = Util.makeHexColor(
|
||||
style.borderColor = Util.makeHexColor(
|
||||
borderColor[0] | 0,
|
||||
borderColor[1] | 0,
|
||||
borderColor[2] | 0
|
||||
);
|
||||
} else {
|
||||
// Transparent (invisible) border, so do not draw it at all.
|
||||
container.style.borderWidth = 0;
|
||||
style.borderWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
container.style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
|
||||
container.style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`;
|
||||
// Do *not* modify `data.rect`, since that will corrupt the annotation
|
||||
// position on subsequent calls to `_createContainer` (see issue 6804).
|
||||
const rect = Util.normalizeRect([
|
||||
data.rect[0],
|
||||
page.view[3] - data.rect[1] + page.view[1],
|
||||
data.rect[2],
|
||||
page.view[3] - data.rect[3] + page.view[1],
|
||||
]);
|
||||
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;
|
||||
|
||||
style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
|
||||
style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`;
|
||||
|
||||
const { rotation } = data;
|
||||
if (data.hasOwnCanvas || rotation === 0) {
|
||||
container.style.width = `${(100 * width) / pageWidth}%`;
|
||||
container.style.height = `${(100 * height) / pageHeight}%`;
|
||||
style.width = `${(100 * width) / pageWidth}%`;
|
||||
style.height = `${(100 * height) / pageHeight}%`;
|
||||
} else {
|
||||
this.setRotation(rotation, container);
|
||||
}
|
||||
|
@ -2897,6 +2947,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
|
|||
* @property {Object<string, Array<Object>> | null} [fieldObjects]
|
||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -2913,6 +2964,7 @@ class AnnotationLayer {
|
|||
div,
|
||||
accessibilityManager,
|
||||
annotationCanvasMap,
|
||||
annotationEditorUIManager,
|
||||
page,
|
||||
viewport,
|
||||
}) {
|
||||
|
@ -2922,6 +2974,7 @@ class AnnotationLayer {
|
|||
this.page = page;
|
||||
this.viewport = viewport;
|
||||
this.zIndex = 0;
|
||||
this._annotationEditorUIManager = annotationEditorUIManager;
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
// For testing purposes.
|
||||
|
@ -3011,15 +3064,16 @@ class AnnotationLayer {
|
|||
}
|
||||
}
|
||||
|
||||
if (element.annotationEditorType > 0) {
|
||||
this.#editableAnnotations.set(element.data.id, element);
|
||||
}
|
||||
|
||||
const rendered = element.render();
|
||||
if (data.hidden) {
|
||||
rendered.style.visibility = "hidden";
|
||||
}
|
||||
this.#appendElement(rendered, data.id);
|
||||
|
||||
if (element.annotationEditorType > 0) {
|
||||
this.#editableAnnotations.set(element.data.id, element);
|
||||
this._annotationEditorUIManager?.renderAnnotationElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
this.#setAnnotationCanvasMap();
|
||||
|
@ -3051,13 +3105,16 @@ class AnnotationLayer {
|
|||
continue;
|
||||
}
|
||||
|
||||
canvas.className = "annotationContent";
|
||||
const { firstChild } = element;
|
||||
if (!firstChild) {
|
||||
element.append(canvas);
|
||||
} else if (firstChild.nodeName === "CANVAS") {
|
||||
firstChild.replaceWith(canvas);
|
||||
} else {
|
||||
} else if (!firstChild.classList.contains("annotationContent")) {
|
||||
firstChild.before(canvas);
|
||||
} else {
|
||||
firstChild.after(canvas);
|
||||
}
|
||||
}
|
||||
this.#annotationCanvasMap.clear();
|
||||
|
|
|
@ -248,7 +248,9 @@ class AnnotationEditorLayer {
|
|||
const annotationElementIds = new Set();
|
||||
for (const editor of this.#editors.values()) {
|
||||
editor.enableEditing();
|
||||
editor.show(true);
|
||||
if (editor.annotationElementId) {
|
||||
this.#uiManager.removeChangedExistingAnnotation(editor);
|
||||
annotationElementIds.add(editor.annotationElementId);
|
||||
}
|
||||
}
|
||||
|
@ -283,13 +285,19 @@ class AnnotationEditorLayer {
|
|||
this.#isDisabling = true;
|
||||
this.div.tabIndex = -1;
|
||||
this.togglePointerEvents(false);
|
||||
const hiddenAnnotationIds = new Set();
|
||||
const changedAnnotations = new Map();
|
||||
const resetAnnotations = new Map();
|
||||
for (const editor of this.#editors.values()) {
|
||||
editor.disableEditing();
|
||||
if (!editor.annotationElementId || editor.serialize() !== null) {
|
||||
hiddenAnnotationIds.add(editor.annotationElementId);
|
||||
if (!editor.annotationElementId) {
|
||||
continue;
|
||||
}
|
||||
if (editor.serialize() !== null) {
|
||||
changedAnnotations.set(editor.annotationElementId, editor);
|
||||
continue;
|
||||
} else {
|
||||
resetAnnotations.set(editor.annotationElementId, editor);
|
||||
}
|
||||
this.getEditableAnnotation(editor.annotationElementId)?.show();
|
||||
editor.remove();
|
||||
}
|
||||
|
@ -299,12 +307,23 @@ class AnnotationEditorLayer {
|
|||
const editables = this.#annotationLayer.getEditableAnnotations();
|
||||
for (const editable of editables) {
|
||||
const { id } = editable.data;
|
||||
if (
|
||||
hiddenAnnotationIds.has(id) ||
|
||||
this.#uiManager.isDeletedAnnotationElement(id)
|
||||
) {
|
||||
if (this.#uiManager.isDeletedAnnotationElement(id)) {
|
||||
continue;
|
||||
}
|
||||
let editor = resetAnnotations.get(id);
|
||||
if (editor) {
|
||||
editor.resetAnnotationElement(editable);
|
||||
editor.show(false);
|
||||
editable.show();
|
||||
continue;
|
||||
}
|
||||
|
||||
editor = changedAnnotations.get(id);
|
||||
if (editor) {
|
||||
this.#uiManager.addChangedExistingAnnotation(editor);
|
||||
editor.renderAnnotationElement(editable);
|
||||
editor.show(false);
|
||||
}
|
||||
editable.show();
|
||||
}
|
||||
}
|
||||
|
@ -461,7 +480,7 @@ class AnnotationEditorLayer {
|
|||
return;
|
||||
}
|
||||
|
||||
if (editor.annotationElementId) {
|
||||
if (editor.parent && editor.annotationElementId) {
|
||||
this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
|
||||
AnnotationEditor.deleteAnnotationElement(editor);
|
||||
editor.annotationElementId = null;
|
||||
|
|
|
@ -1336,6 +1336,17 @@ class AnnotationEditor {
|
|||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an existing annotation associated with this editor has been
|
||||
* modified.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get hasBeenModified() {
|
||||
return (
|
||||
!!this.annotationElementId && (this.deleted || this.serialize() !== null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this editor.
|
||||
* It's used on ctrl+backspace action.
|
||||
|
@ -1710,6 +1721,37 @@ class AnnotationEditor {
|
|||
}
|
||||
this.#disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an annotation in the annotation layer.
|
||||
* @param {Object} annotation
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
renderAnnotationElement(annotation) {
|
||||
let content = annotation.container.querySelector(".annotationContent");
|
||||
if (!content) {
|
||||
content = document.createElement("div");
|
||||
content.classList.add("annotationContent", this.editorType);
|
||||
annotation.container.prepend(content);
|
||||
} else if (content.nodeName === "CANVAS") {
|
||||
const canvas = content;
|
||||
content = document.createElement("div");
|
||||
content.classList.add("annotationContent", this.editorType);
|
||||
canvas.before(content);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
resetAnnotationElement(annotation) {
|
||||
const { firstChild } = annotation.container;
|
||||
if (
|
||||
firstChild.nodeName === "DIV" &&
|
||||
firstChild.classList.contains("annotationContent")
|
||||
) {
|
||||
firstChild.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This class is used to fake an editor which has been deleted.
|
||||
|
|
|
@ -408,11 +408,14 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
// we just insert it in the DOM, get its bounding box and then remove it.
|
||||
const { currentLayer, div } = this;
|
||||
const savedDisplay = div.style.display;
|
||||
const savedVisibility = div.classList.contains("hidden");
|
||||
div.classList.remove("hidden");
|
||||
div.style.display = "hidden";
|
||||
currentLayer.div.append(this.div);
|
||||
rect = div.getBoundingClientRect();
|
||||
div.remove();
|
||||
div.style.display = savedDisplay;
|
||||
div.classList.toggle("hidden", savedVisibility);
|
||||
}
|
||||
|
||||
// The dimensions are relative to the rotation of the page, hence we need to
|
||||
|
@ -778,7 +781,7 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
value: textContent.join("\n"),
|
||||
position: textPosition,
|
||||
pageIndex: pageNumber - 1,
|
||||
rect,
|
||||
rect: rect.slice(0),
|
||||
rotation,
|
||||
id,
|
||||
deleted: false,
|
||||
|
@ -853,6 +856,38 @@ class FreeTextEditor extends AnnotationEditor {
|
|||
serialized.pageIndex !== pageIndex
|
||||
);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
renderAnnotationElement(annotation) {
|
||||
const content = super.renderAnnotationElement(annotation);
|
||||
if (this.deleted) {
|
||||
return content;
|
||||
}
|
||||
const { style } = content;
|
||||
style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
|
||||
style.color = this.#color;
|
||||
|
||||
content.replaceChildren();
|
||||
for (const line of this.#content.split("\n")) {
|
||||
const div = document.createElement("div");
|
||||
div.append(
|
||||
line ? document.createTextNode(line) : document.createElement("br")
|
||||
);
|
||||
content.append(div);
|
||||
}
|
||||
|
||||
const padding = FreeTextEditor._internalPadding * this.parentScale;
|
||||
annotation.updateEdited({
|
||||
rect: this.getRect(padding, padding),
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
resetAnnotationElement(annotation) {
|
||||
super.resetAnnotationElement(annotation);
|
||||
annotation.resetEdited();
|
||||
}
|
||||
}
|
||||
|
||||
export { FreeTextEditor };
|
||||
|
|
|
@ -544,6 +544,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#annotationStorage = null;
|
||||
|
||||
#changedExistingAnnotations = null;
|
||||
|
||||
#commandManager = new CommandManager();
|
||||
|
||||
#currentPageIndex = 0;
|
||||
|
@ -1682,6 +1684,7 @@ class AnnotationEditorUIManager {
|
|||
*/
|
||||
addDeletedAnnotationElement(editor) {
|
||||
this.#deletedAnnotationsElementIds.add(editor.annotationElementId);
|
||||
this.addChangedExistingAnnotation(editor);
|
||||
editor.deleted = true;
|
||||
}
|
||||
|
||||
|
@ -1700,6 +1703,7 @@ class AnnotationEditorUIManager {
|
|||
*/
|
||||
removeDeletedAnnotationElement(editor) {
|
||||
this.#deletedAnnotationsElementIds.delete(editor.annotationElementId);
|
||||
this.removeChangedExistingAnnotation(editor);
|
||||
editor.deleted = false;
|
||||
}
|
||||
|
||||
|
@ -2243,6 +2247,32 @@ class AnnotationEditorUIManager {
|
|||
}
|
||||
return boxes.length === 0 ? null : boxes;
|
||||
}
|
||||
|
||||
addChangedExistingAnnotation({ annotationElementId, id }) {
|
||||
(this.#changedExistingAnnotations ||= new Map()).set(
|
||||
annotationElementId,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
removeChangedExistingAnnotation({ annotationElementId }) {
|
||||
this.#changedExistingAnnotations?.delete(annotationElementId);
|
||||
}
|
||||
|
||||
renderAnnotationElement(annotation) {
|
||||
const editorId = this.#changedExistingAnnotations?.get(annotation.data.id);
|
||||
if (!editorId) {
|
||||
return;
|
||||
}
|
||||
const editor = this.#annotationStorage.getRawValue(editorId);
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
if (this.#mode === AnnotationEditorType.NONE && !editor.hasBeenModified) {
|
||||
return;
|
||||
}
|
||||
editor.renderAnnotationElement(annotation);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
|
@ -1125,15 +1125,24 @@ describe("FreeText Editor", () => {
|
|||
);
|
||||
|
||||
// We want to check that the editor is displayed but not the original
|
||||
// annotation.
|
||||
// canvas.
|
||||
editorIds = await getEditors(page, "freeText");
|
||||
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
||||
const hidden = await page.$eval(
|
||||
"[data-annotation-id='26R']",
|
||||
el => el.hidden
|
||||
"[data-annotation-id='26R'] canvas",
|
||||
el => getComputedStyle(el).display === "none"
|
||||
);
|
||||
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
|
||||
|
||||
// Check we've now a div containing the text.
|
||||
const newDivText = await page.$eval(
|
||||
"[data-annotation-id='26R'] div.annotationContent",
|
||||
el => el.innerText.replaceAll("\xa0", " ")
|
||||
);
|
||||
expect(newDivText)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Hello World from Acrobat and edited in Firefox");
|
||||
|
||||
// Re-enable editing mode.
|
||||
await switchToFreeText(page);
|
||||
await page.focus(".annotationEditorLayer");
|
||||
|
@ -3715,4 +3724,123 @@ describe("FreeText Editor", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Update a freetext and scroll", () => {
|
||||
let pages;
|
||||
|
||||
beforeAll(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey_freetext.pdf",
|
||||
".annotationEditorLayer"
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that a freetext is still there after having updated it and scroll the doc", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToFreeText(page);
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const editorRect = await page.$eval(editorSelector, el => {
|
||||
const { x, y, width, height } = el.getBoundingClientRect();
|
||||
return { x, y, width, height };
|
||||
});
|
||||
await page.mouse.click(
|
||||
editorRect.x + editorRect.width / 2,
|
||||
editorRect.y + editorRect.height / 2,
|
||||
{ count: 2 }
|
||||
);
|
||||
await page.waitForSelector(
|
||||
`${editorSelector} .overlay:not(.enabled)`
|
||||
);
|
||||
|
||||
await kbGoToEnd(page);
|
||||
await page.waitForFunction(
|
||||
sel =>
|
||||
document.getSelection().anchorOffset ===
|
||||
document.querySelector(sel).innerText.length,
|
||||
{},
|
||||
`${editorSelector} .internal`
|
||||
);
|
||||
|
||||
await page.type(
|
||||
`${editorSelector} .internal`,
|
||||
" and edited in Firefox"
|
||||
);
|
||||
|
||||
// Disable editing mode.
|
||||
await page.click("#editorFreeText");
|
||||
await page.waitForSelector(
|
||||
`.annotationEditorLayer:not(.freetextEditing)`
|
||||
);
|
||||
|
||||
const oneToOne = Array.from(new Array(13).keys(), n => n + 2).concat(
|
||||
Array.from(new Array(13).keys(), n => 13 - n)
|
||||
);
|
||||
for (const pageNumber of oneToOne) {
|
||||
await scrollIntoView(
|
||||
page,
|
||||
`.page[data-page-number = "${pageNumber}"]`
|
||||
);
|
||||
}
|
||||
|
||||
await page.waitForSelector("[data-annotation-id='998R'] canvas");
|
||||
let hidden = await page.$eval(
|
||||
"[data-annotation-id='998R'] canvas",
|
||||
el => getComputedStyle(el).display === "none"
|
||||
);
|
||||
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
|
||||
|
||||
// Check we've now a div containing the text.
|
||||
await page.waitForSelector(
|
||||
"[data-annotation-id='998R'] div.annotationContent"
|
||||
);
|
||||
const newDivText = await page.$eval(
|
||||
"[data-annotation-id='998R'] div.annotationContent",
|
||||
el => el.innerText.replaceAll("\xa0", " ")
|
||||
);
|
||||
expect(newDivText)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Hello World and edited in Firefox");
|
||||
|
||||
const oneToThirteen = Array.from(new Array(13).keys(), n => n + 2);
|
||||
for (const pageNumber of oneToThirteen) {
|
||||
await scrollIntoView(
|
||||
page,
|
||||
`.page[data-page-number = "${pageNumber}"]`
|
||||
);
|
||||
}
|
||||
|
||||
await switchToFreeText(page);
|
||||
await kbUndo(page);
|
||||
await waitForSerialized(page, 0);
|
||||
|
||||
// Disable editing mode.
|
||||
await page.click("#editorFreeText");
|
||||
await page.waitForSelector(
|
||||
`.annotationEditorLayer:not(.freetextEditing)`
|
||||
);
|
||||
|
||||
const thirteenToOne = Array.from(new Array(13).keys(), n => 13 - n);
|
||||
for (const pageNumber of thirteenToOne) {
|
||||
await scrollIntoView(
|
||||
page,
|
||||
`.page[data-page-number = "${pageNumber}"]`
|
||||
);
|
||||
}
|
||||
|
||||
await page.waitForSelector("[data-annotation-id='998R'] canvas");
|
||||
hidden = await page.$eval(
|
||||
"[data-annotation-id='998R'] canvas",
|
||||
el => getComputedStyle(el).display === "none"
|
||||
);
|
||||
expect(hidden).withContext(`In ${browserName}`).toBeFalse();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
|
@ -643,3 +643,4 @@
|
|||
!bug1889122.pdf
|
||||
!issue17929.pdf
|
||||
!issue12213.pdf
|
||||
!tracemonkey_freetext.pdf
|
||||
|
|
BIN
test/pdfs/tracemonkey_freetext.pdf
Executable file
BIN
test/pdfs/tracemonkey_freetext.pdf
Executable file
Binary file not shown.
|
@ -94,11 +94,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
.annotationContent {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
|
||||
&.freetext {
|
||||
background: transparent;
|
||||
border: none;
|
||||
inset: 0;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
font: 10px sans-serif;
|
||||
line-height: 1.35;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
|
@ -107,6 +118,12 @@
|
|||
pointer-events: auto;
|
||||
box-sizing: border-box;
|
||||
transform-origin: 0 0;
|
||||
|
||||
&:has(div.annotationContent) {
|
||||
canvas.annotationContent {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a {
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||
|
||||
import { AnnotationLayer } from "pdfjs-lib";
|
||||
import { PresentationModeState } from "./ui_utils.js";
|
||||
|
@ -41,6 +43,7 @@ import { PresentationModeState } from "./ui_utils.js";
|
|||
* [fieldObjectsPromise]
|
||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
||||
* @property {function} [onAppend]
|
||||
*/
|
||||
|
||||
|
@ -64,6 +67,7 @@ class AnnotationLayerBuilder {
|
|||
fieldObjectsPromise = null,
|
||||
annotationCanvasMap = null,
|
||||
accessibilityManager = null,
|
||||
annotationEditorUIManager = null,
|
||||
onAppend = null,
|
||||
}) {
|
||||
this.pdfPage = pdfPage;
|
||||
|
@ -77,6 +81,7 @@ class AnnotationLayerBuilder {
|
|||
this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
|
||||
this._annotationCanvasMap = annotationCanvasMap;
|
||||
this._accessibilityManager = accessibilityManager;
|
||||
this._annotationEditorUIManager = annotationEditorUIManager;
|
||||
this.#onAppend = onAppend;
|
||||
|
||||
this.annotationLayer = null;
|
||||
|
@ -128,6 +133,7 @@ class AnnotationLayerBuilder {
|
|||
div,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
annotationEditorUIManager: this._annotationEditorUIManager,
|
||||
page: this.pdfPage,
|
||||
viewport: viewport.clone({ dontFlip: true }),
|
||||
});
|
||||
|
|
|
@ -938,6 +938,7 @@ class PDFPageView {
|
|||
) {
|
||||
const {
|
||||
annotationStorage,
|
||||
annotationEditorUIManager,
|
||||
downloadManager,
|
||||
enableScripting,
|
||||
fieldObjectsPromise,
|
||||
|
@ -958,6 +959,7 @@ class PDFPageView {
|
|||
fieldObjectsPromise,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
annotationEditorUIManager,
|
||||
onAppend: annotationLayerDiv => {
|
||||
this.#addLayer(annotationLayerDiv, "annotationLayer");
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue