[Editor] Update the color and the position of the comment button in reading mode they've been modified

This commit is contained in:
Calixte Denizet 2025-09-23 21:01:21 +02:00
parent ae3c23e28a
commit 0b40bf1743
10 changed files with 209 additions and 68 deletions

View File

@ -202,7 +202,19 @@ class AnnotationElement {
} }
get hasPopupData() { get hasPopupData() {
return AnnotationElement._hasPopupData(this.data); return (
AnnotationElement._hasPopupData(this.data) ||
(this.enableComment && !!this.commentText)
);
}
get commentData() {
const { data } = this;
const editor = this.annotationStorage?.getEditor(data.id);
if (editor) {
return editor.getData();
}
return data;
} }
get hasCommentButton() { get hasCommentButton() {
@ -210,7 +222,11 @@ class AnnotationElement {
} }
get commentButtonPosition() { get commentButtonPosition() {
const { quadPoints, rect } = this.data; const editor = this.annotationStorage?.getEditor(this.data.id);
if (editor) {
return editor.commentButtonPositionInPage;
}
const { quadPoints, inkLists, rect } = this.data;
let maxX = -Infinity; let maxX = -Infinity;
let maxY = -Infinity; let maxY = -Infinity;
if (quadPoints?.length >= 8) { if (quadPoints?.length >= 8) {
@ -224,6 +240,21 @@ class AnnotationElement {
} }
return [maxX, maxY]; return [maxX, maxY];
} }
if (inkLists?.length >= 1) {
for (const inkList of inkLists) {
for (let i = 0, ii = inkList.length; i < ii; i += 2) {
if (inkList[i + 1] > maxY) {
maxY = inkList[i + 1];
maxX = inkList[i];
} else if (inkList[i + 1] === maxY) {
maxX = Math.max(maxX, inkList[i]);
}
}
}
if (maxX !== Infinity) {
return [maxX, maxY];
}
}
if (rect) { if (rect) {
return [rect[2], rect[3]]; return [rect[2], rect[3]];
} }
@ -2380,7 +2411,6 @@ class PopupElement {
this.#dateObj = PDFDateString.toDateObject(modificationDate); this.#dateObj = PDFDateString.toDateObject(modificationDate);
if (commentManager) { if (commentManager) {
this.#popupAbortController = new AbortController();
this.#renderCommentButton(); this.#renderCommentButton();
} else { } else {
this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup()); this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
@ -2457,7 +2487,7 @@ class PopupElement {
button.ariaHasPopup = "dialog"; button.ariaHasPopup = "dialog";
button.ariaControls = "commentPopup"; button.ariaControls = "commentPopup";
const { signal } = this.#popupAbortController; const { signal } = (this.#popupAbortController = new AbortController());
button.addEventListener("keydown", this.#boundKeyDown, { signal }); button.addEventListener("keydown", this.#boundKeyDown, { signal });
button.addEventListener( button.addEventListener(
"click", "click",
@ -2488,19 +2518,26 @@ class PopupElement {
}, },
{ signal } { signal }
); );
const { style } = button; this.#updateColor();
style.left = `calc(${this.#commentButtonPosition[0]}%)`; this.#updateCommentButtonPosition();
style.top = `calc(${this.#commentButtonPosition[1]}% - var(--comment-button-dim))`;
if (this.commentButtonColor) {
style.backgroundColor = this.commentButtonColor;
}
parentContainer.after(button); parentContainer.after(button);
} }
#updateCommentButtonPosition() {
this.#renderCommentButton();
const [x, y] = this.#commentButtonPosition;
const { style } = this.#commentButton;
style.left = `calc(${x}%)`;
style.top = `calc(${y}% - var(--comment-button-dim))`;
}
#updateColor() {
this.#renderCommentButton();
this.#commentButton.style.backgroundColor = this.commentButtonColor || "";
}
get commentButtonColor() { get commentButtonColor() {
const { const { color, opacity } = this.#firstElement.commentData;
data: { color, opacity },
} = this.#firstElement;
if (!color) { if (!color) {
return null; return null;
} }
@ -2509,7 +2546,7 @@ class PopupElement {
getData() { getData() {
const { richText, color, opacity, creationDate, modificationDate } = const { richText, color, opacity, creationDate, modificationDate } =
this.#firstElement.data; this.#firstElement.commentData;
return { return {
contentsObj: { str: this.comment }, contentsObj: { str: this.comment },
richText, richText,
@ -2743,7 +2780,22 @@ class PopupElement {
updateEdited({ rect, popup, deleted }) { updateEdited({ rect, popup, deleted }) {
if (this.#commentManager) { if (this.#commentManager) {
this.#commentText = deleted ? null : popup.text; if (deleted) {
this.remove();
this.#commentText = null;
} else if (popup) {
if (popup.deleted) {
this.remove();
} else {
this.#updateColor();
this.#commentText = popup.text;
}
}
if (rect) {
this.#commentButtonPosition = null;
this.#setCommentButtonPosition();
this.#updateCommentButtonPosition();
}
return; return;
} }
if (deleted || popup?.deleted) { if (deleted || popup?.deleted) {
@ -2758,7 +2810,7 @@ class PopupElement {
if (rect) { if (rect) {
this.#position = null; this.#position = null;
} }
if (popup) { if (popup && popup.text) {
this.#richText = this.#makePopupContent(popup.text); this.#richText = this.#makePopupContent(popup.text);
this.#dateObj = PDFDateString.toDateObject(popup.date); this.#dateObj = PDFDateString.toDateObject(popup.date);
this.#contentsObj = null; this.#contentsObj = null;
@ -3346,31 +3398,6 @@ class InkAnnotationElement extends AnnotationElement {
addHighlightArea() { addHighlightArea() {
this.container.classList.add("highlightArea"); this.container.classList.add("highlightArea");
} }
get commentButtonPosition() {
const { inkLists, rect } = this.data;
if (inkLists?.length >= 1) {
let maxX = -Infinity;
let maxY = -Infinity;
for (const inkList of inkLists) {
for (let i = 0, ii = inkList.length; i < ii; i += 2) {
if (inkList[i + 1] > maxY) {
maxY = inkList[i + 1];
maxX = inkList[i];
} else if (inkList[i + 1] === maxY) {
maxX = Math.max(maxX, inkList[i]);
}
}
}
if (maxX !== Infinity) {
return [maxX, maxY];
}
}
if (rect) {
return [rect[2], rect[3]];
}
return null;
}
} }
class HighlightAnnotationElement extends AnnotationElement { class HighlightAnnotationElement extends AnnotationElement {

View File

@ -268,6 +268,10 @@ class AnnotationStorage {
return false; return false;
} }
getEditor(annotationId) {
return this.#editorsMap?.get(annotationId) || null;
}
/** /**
* @returns {{ids: Set<string>, hash: string}} * @returns {{ids: Set<string>, hash: string}}
*/ */

View File

@ -1930,6 +1930,17 @@ class AnnotationEditor {
return this._uiManager.direction === "ltr" ? [1, 0] : [0, 0]; return this._uiManager.direction === "ltr" ? [1, 0] : [0, 0];
} }
get commentButtonPositionInPage() {
const {
commentButtonPosition: [posX, posY],
} = this;
const [blX, blY, trX, trY] = this.getPDFRect();
return [
AnnotationEditor._round(blX + (trX - blX) * posX),
AnnotationEditor._round(blY + (trY - blY) * (1 - posY)),
];
}
get commentButtonColor() { get commentButtonColor() {
return this._uiManager.makeCommentColor( return this._uiManager.makeCommentColor(
this.getNonHCMColor(), this.getNonHCMColor(),

View File

@ -904,13 +904,13 @@ class FreeTextEditor extends AnnotationEditor {
content.append(div); content.append(div);
} }
const params = { annotation.updateEdited({
rect: this.getPDFRect(), rect: this.getPDFRect(),
}; popup:
params.popup = this.hasEditedComment this._uiManager.hasCommentManager() || this.hasEditedComment
? this.comment ? this.comment
: { text: this.#content }; : { text: this.#content },
annotation.updateEdited(params); });
return content; return content;
} }

View File

@ -172,12 +172,13 @@ class HighlightEditor extends AnnotationEditor {
); );
this.#focusOutlines = outlinerForOutline.getOutlines(); this.#focusOutlines = outlinerForOutline.getOutlines();
// The last point is in the pages coordinate system. const { firstPoint } = this.#highlightOutlines;
const { firstPoint, lastPoint } = this.#focusOutlines;
this.#firstPoint = [ this.#firstPoint = [
(firstPoint[0] - this.x) / this.width, (firstPoint[0] - this.x) / this.width,
(firstPoint[1] - this.y) / this.height, (firstPoint[1] - this.y) / this.height,
]; ];
// The last point is in the pages coordinate system.
const { lastPoint } = this.#focusOutlines;
this.#lastPoint = [ this.#lastPoint = [
(lastPoint[0] - this.x) / this.width, (lastPoint[0] - this.x) / this.width,
(lastPoint[1] - this.y) / this.height, (lastPoint[1] - this.y) / this.height,
@ -268,11 +269,12 @@ class HighlightEditor extends AnnotationEditor {
} }
} }
const { firstPoint, lastPoint } = this.#focusOutlines; const { firstPoint } = highlightOutlines;
this.#firstPoint = [ this.#firstPoint = [
(firstPoint[0] - x) / width, (firstPoint[0] - x) / width,
(firstPoint[1] - y) / height, (firstPoint[1] - y) / height,
]; ];
const { lastPoint } = this.#focusOutlines;
this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height]; this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height];
} }
@ -1082,13 +1084,10 @@ class HighlightEditor extends AnnotationEditor {
annotation.hide(); annotation.hide();
return null; return null;
} }
const params = { annotation.updateEdited({
rect: this.getPDFRect(), rect: this.getPDFRect(),
}; popup: this.comment,
if (this.hasEditedComment) { });
params.popup = this.comment;
}
annotation.updateEdited(params);
return null; return null;
} }

View File

@ -305,15 +305,12 @@ class InkEditor extends DrawingEditor {
return null; return null;
} }
const { points, rect } = this.serializeDraw(/* isForCopying = */ false); const { points, rect } = this.serializeDraw(/* isForCopying = */ false);
const params = { annotation.updateEdited({
rect, rect,
thickness: this._drawingOptions["stroke-width"], thickness: this._drawingOptions["stroke-width"],
points, points,
}; popup: this.comment,
if (this.hasEditedComment) { });
params.popup = this.comment;
}
annotation.updateEdited(params);
return null; return null;
} }

View File

@ -942,13 +942,10 @@ class StampEditor extends AnnotationEditor {
annotation.hide(); annotation.hide();
return null; return null;
} }
const params = { annotation.updateEdited({
rect: this.getPDFRect(), rect: this.getPDFRect(),
}; popup: this.comment,
if (this.hasEditedComment) { });
params.popup = this.comment;
}
annotation.updateEdited(params);
return null; return null;
} }

View File

@ -15,16 +15,20 @@
import { import {
closePages, closePages,
dragAndDrop,
getEditorSelector, getEditorSelector,
getRect, getRect,
getSpanRectFromText, getSpanRectFromText,
loadAndWait, loadAndWait,
scrollIntoView, scrollIntoView,
selectEditor,
switchToEditor, switchToEditor,
waitAndClick, waitAndClick,
waitForSerialized,
} from "./test_utils.mjs"; } from "./test_utils.mjs";
const switchToHighlight = switchToEditor.bind(null, "Highlight"); const switchToHighlight = switchToEditor.bind(null, "Highlight");
const switchToStamp = switchToEditor.bind(null, "Stamp");
describe("Comment", () => { describe("Comment", () => {
describe("Comment edit dialog must be visible in ltr", () => { describe("Comment edit dialog must be visible in ltr", () => {
@ -131,4 +135,105 @@ describe("Comment", () => {
); );
}); });
}); });
describe("Update comment position and color in reading mode", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"comments.pdf",
".annotationEditorLayer",
"page-fit",
null,
{
enableComment: true,
highlightEditorColors:
"yellow=#FFFF00,green=#00FF00,blue=#0000FF,pink=#FF00FF,red=#FF0000",
}
);
});
afterEach(async () => {
await closePages(pages);
});
it("must set the comment button at the right place", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToStamp(page);
const stampSelector = getEditorSelector(8);
await selectEditor(page, stampSelector);
await dragAndDrop(page, stampSelector, [[100, 100]]);
await waitForSerialized(page, 1);
const rectCommentButton = await getRect(
page,
`${stampSelector} .annotationCommentButton`
);
await switchToStamp(page, /* disable = */ true);
const rectCommentButtonAfter = await getRect(
page,
`#pdfjs_internal_id_713R + .annotationCommentButton`
);
expect(Math.abs(rectCommentButtonAfter.x - rectCommentButton.x))
.withContext(`In ${browserName}`)
.toBeLessThanOrEqual(1);
expect(Math.abs(rectCommentButtonAfter.y - rectCommentButton.y))
.withContext(`In ${browserName}`)
.toBeLessThanOrEqual(1);
expect(
Math.abs(rectCommentButtonAfter.width - rectCommentButton.width)
)
.withContext(`In ${browserName}`)
.toBeLessThanOrEqual(1);
expect(
Math.abs(rectCommentButtonAfter.height - rectCommentButton.height)
)
.withContext(`In ${browserName}`)
.toBeLessThanOrEqual(1);
})
);
});
it("must set the right color to the comment button", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToHighlight(page);
const highlightSelector = getEditorSelector(0);
await selectEditor(page, highlightSelector);
const colorButtonSelector = `${highlightSelector} .editToolbar button`;
await page.waitForSelector(`${colorButtonSelector}.colorPicker`);
await page.click(`${colorButtonSelector}.colorPicker`);
await page.waitForSelector(`${colorButtonSelector}[title = "Red"]`);
await page.click(`${colorButtonSelector}[title = "Red"]`);
await page.waitForSelector(
`.page[data-page-number = "1"] svg.highlight[fill = "#FF0000"]`
);
const commentButtonColor = await page.evaluate(selector => {
const button = document.querySelector(
`${selector} .annotationCommentButton`
);
return window.getComputedStyle(button).backgroundColor;
}, highlightSelector);
await switchToHighlight(page, /* disable = */ true);
const commentButtonColorAfter = await page.evaluate(() => {
const button = document.querySelector(
"section[data-annotation-id='612R'] + .annotationCommentButton"
);
return window.getComputedStyle(button).backgroundColor;
});
expect(commentButtonColorAfter)
.withContext(`In ${browserName}`)
.toEqual(commentButtonColor);
})
);
});
});
}); });

View File

@ -745,3 +745,4 @@
!tracemonkey_annotation_on_page_8.pdf !tracemonkey_annotation_on_page_8.pdf
!issue20232.pdf !issue20232.pdf
!bug1989304.pdf !bug1989304.pdf
!comments.pdf

BIN
test/pdfs/comments.pdf Normal file

Binary file not shown.