mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[Editor] Add the possibility to create an highlight from the context menu when some text is selected (bug 1867739)
This commit is contained in:
parent
72b8b29147
commit
e1f6f5179f
7 changed files with 266 additions and 72 deletions
|
@ -184,6 +184,10 @@ class AnnotationEditorLayer {
|
|||
this.div.hidden = false;
|
||||
}
|
||||
|
||||
hasTextLayer(textLayer) {
|
||||
return textLayer === this.#textLayer?.div;
|
||||
}
|
||||
|
||||
addInkEditorIfNeeded(isCommitting) {
|
||||
if (this.#uiManager.getMode() !== AnnotationEditorType.INK) {
|
||||
// We don't want to add an ink editor if we're not in ink mode!
|
||||
|
@ -721,78 +725,13 @@ class AnnotationEditorLayer {
|
|||
* @param {PointerEvent} event
|
||||
*/
|
||||
pointerUpAfterSelection(event) {
|
||||
const selection = document.getSelection();
|
||||
if (selection.rangeCount === 0) {
|
||||
return;
|
||||
}
|
||||
const range = selection.getRangeAt(0);
|
||||
if (range.collapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#textLayer?.div.contains(range.commonAncestorContainer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
x: layerX,
|
||||
y: layerY,
|
||||
width: parentWidth,
|
||||
height: parentHeight,
|
||||
} = this.#textLayer.div.getBoundingClientRect();
|
||||
const bboxes = range.getClientRects();
|
||||
|
||||
// We must rotate the boxes because we want to have them in the non-rotated
|
||||
// page coordinates.
|
||||
let rotator;
|
||||
switch (this.viewport.rotation) {
|
||||
case 90:
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: (y - layerY) / parentHeight,
|
||||
y: 1 - (x + w - layerX) / parentWidth,
|
||||
width: h / parentHeight,
|
||||
height: w / parentWidth,
|
||||
});
|
||||
break;
|
||||
case 180:
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: 1 - (x + w - layerX) / parentWidth,
|
||||
y: 1 - (y + h - layerY) / parentHeight,
|
||||
width: w / parentWidth,
|
||||
height: h / parentHeight,
|
||||
});
|
||||
break;
|
||||
case 270:
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: 1 - (y + h - layerY) / parentHeight,
|
||||
y: (x - layerX) / parentWidth,
|
||||
width: h / parentHeight,
|
||||
height: w / parentWidth,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: (x - layerX) / parentWidth,
|
||||
y: (y - layerY) / parentHeight,
|
||||
width: w / parentWidth,
|
||||
height: h / parentHeight,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const boxes = [];
|
||||
for (const { x, y, width, height } of bboxes) {
|
||||
if (width === 0 || height === 0) {
|
||||
continue;
|
||||
}
|
||||
boxes.push(rotator(x, y, width, height));
|
||||
}
|
||||
if (boxes.length !== 0) {
|
||||
const boxes = this.#uiManager.getSelectionBoxes(this.#textLayer?.div);
|
||||
if (boxes) {
|
||||
this.createAndAddNewEditor(event, false, {
|
||||
boxes,
|
||||
});
|
||||
document.getSelection().empty();
|
||||
}
|
||||
selection.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -551,6 +551,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#focusMainContainerTimeoutId = null;
|
||||
|
||||
#hasSelection = false;
|
||||
|
||||
#highlightColors = null;
|
||||
|
||||
#idManager = new IdManager();
|
||||
|
@ -569,6 +571,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#selectedEditors = new Set();
|
||||
|
||||
#selectedTextNode = null;
|
||||
|
||||
#pageColors = null;
|
||||
|
||||
#boundBlur = this.blur.bind(this);
|
||||
|
@ -591,6 +595,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#boundOnScaleChanging = this.onScaleChanging.bind(this);
|
||||
|
||||
#boundSelectionChange = this.#selectionChange.bind(this);
|
||||
|
||||
#boundOnRotationChanging = this.onRotationChanging.bind(this);
|
||||
|
||||
#previousStates = {
|
||||
|
@ -599,6 +605,7 @@ class AnnotationEditorUIManager {
|
|||
hasSomethingToUndo: false,
|
||||
hasSomethingToRedo: false,
|
||||
hasSelectedEditor: false,
|
||||
hasSelectedText: false,
|
||||
};
|
||||
|
||||
#translation = [0, 0];
|
||||
|
@ -762,6 +769,7 @@ class AnnotationEditorUIManager {
|
|||
this._eventBus._on("pagechanging", this.#boundOnPageChanging);
|
||||
this._eventBus._on("scalechanging", this.#boundOnScaleChanging);
|
||||
this._eventBus._on("rotationchanging", this.#boundOnRotationChanging);
|
||||
this.#addSelectionListener();
|
||||
this.#annotationStorage = pdfDocument.annotationStorage;
|
||||
this.#filterFactory = pdfDocument.filterFactory;
|
||||
this.#pageColors = pageColors;
|
||||
|
@ -799,6 +807,7 @@ class AnnotationEditorUIManager {
|
|||
clearTimeout(this.#translationTimeoutId);
|
||||
this.#translationTimeoutId = null;
|
||||
}
|
||||
this.#removeSelectionListener();
|
||||
}
|
||||
|
||||
async mlGuess(data) {
|
||||
|
@ -905,6 +914,33 @@ class AnnotationEditorUIManager {
|
|||
this.viewParameters.rotation = pagesRotation;
|
||||
}
|
||||
|
||||
highlightSelection() {
|
||||
const selection = document.getSelection();
|
||||
if (!selection || selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
const { anchorNode } = selection;
|
||||
const anchorElement =
|
||||
anchorNode.nodeType === Node.TEXT_NODE
|
||||
? anchorNode.parentElement
|
||||
: anchorNode;
|
||||
const textLayer = anchorElement.closest(".textLayer");
|
||||
const boxes = this.getSelectionBoxes(textLayer);
|
||||
selection.empty();
|
||||
if (this.#mode === AnnotationEditorType.NONE) {
|
||||
this._eventBus.dispatch("showannotationeditorui", {
|
||||
source: this,
|
||||
mode: AnnotationEditorType.HIGHLIGHT,
|
||||
});
|
||||
}
|
||||
for (const layer of this.#allLayers.values()) {
|
||||
if (layer.hasTextLayer(textLayer)) {
|
||||
layer.createAndAddNewEditor({ x: 0, y: 0 }, false, { boxes });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an editor in the annotation storage.
|
||||
* @param {AnnotationEditor} editor
|
||||
|
@ -919,6 +955,52 @@ class AnnotationEditorUIManager {
|
|||
}
|
||||
}
|
||||
|
||||
#selectionChange() {
|
||||
const selection = document.getSelection();
|
||||
if (!selection || selection.isCollapsed) {
|
||||
if (this.#hasSelection) {
|
||||
this.#hasSelection = false;
|
||||
this.#selectedTextNode = null;
|
||||
this.#dispatchUpdateStates({
|
||||
hasSelectedText: false,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const { anchorNode } = selection;
|
||||
if (anchorNode === this.#selectedTextNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const anchorElement =
|
||||
anchorNode.nodeType === Node.TEXT_NODE
|
||||
? anchorNode.parentElement
|
||||
: anchorNode;
|
||||
if (!anchorElement.closest(".textLayer")) {
|
||||
if (this.#hasSelection) {
|
||||
this.#hasSelection = false;
|
||||
this.#selectedTextNode = null;
|
||||
this.#dispatchUpdateStates({
|
||||
hasSelectedText: false,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.#hasSelection = true;
|
||||
this.#selectedTextNode = anchorNode;
|
||||
this.#dispatchUpdateStates({
|
||||
hasSelectedText: true,
|
||||
});
|
||||
}
|
||||
|
||||
#addSelectionListener() {
|
||||
document.addEventListener("selectionchange", this.#boundSelectionChange);
|
||||
}
|
||||
|
||||
#removeSelectionListener() {
|
||||
document.removeEventListener("selectionchange", this.#boundSelectionChange);
|
||||
}
|
||||
|
||||
#addFocusManager() {
|
||||
window.addEventListener("focus", this.#boundFocus);
|
||||
window.addEventListener("blur", this.#boundBlur);
|
||||
|
@ -1127,7 +1209,11 @@ class AnnotationEditorUIManager {
|
|||
* @param {Object} details
|
||||
*/
|
||||
onEditingAction(details) {
|
||||
if (["undo", "redo", "delete", "selectAll"].includes(details.name)) {
|
||||
if (
|
||||
["undo", "redo", "delete", "selectAll", "highlightSelection"].includes(
|
||||
details.name
|
||||
)
|
||||
) {
|
||||
this[details.name]();
|
||||
}
|
||||
}
|
||||
|
@ -1916,6 +2002,80 @@ class AnnotationEditorUIManager {
|
|||
get imageManager() {
|
||||
return shadow(this, "imageManager", new ImageManager());
|
||||
}
|
||||
|
||||
getSelectionBoxes(textLayer) {
|
||||
if (!textLayer) {
|
||||
return null;
|
||||
}
|
||||
const selection = document.getSelection();
|
||||
for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
|
||||
if (
|
||||
!textLayer.contains(selection.getRangeAt(i).commonAncestorContainer)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
x: layerX,
|
||||
y: layerY,
|
||||
width: parentWidth,
|
||||
height: parentHeight,
|
||||
} = textLayer.getBoundingClientRect();
|
||||
|
||||
// We must rotate the boxes because we want to have them in the non-rotated
|
||||
// page coordinates.
|
||||
let rotator;
|
||||
switch (textLayer.getAttribute("data-main-rotation")) {
|
||||
case "90":
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: (y - layerY) / parentHeight,
|
||||
y: 1 - (x + w - layerX) / parentWidth,
|
||||
width: h / parentHeight,
|
||||
height: w / parentWidth,
|
||||
});
|
||||
break;
|
||||
case "180":
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: 1 - (x + w - layerX) / parentWidth,
|
||||
y: 1 - (y + h - layerY) / parentHeight,
|
||||
width: w / parentWidth,
|
||||
height: h / parentHeight,
|
||||
});
|
||||
break;
|
||||
case "270":
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: 1 - (y + h - layerY) / parentHeight,
|
||||
y: (x - layerX) / parentWidth,
|
||||
width: h / parentHeight,
|
||||
height: w / parentWidth,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
rotator = (x, y, w, h) => ({
|
||||
x: (x - layerX) / parentWidth,
|
||||
y: (y - layerY) / parentHeight,
|
||||
width: w / parentWidth,
|
||||
height: h / parentHeight,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const boxes = [];
|
||||
for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
|
||||
const range = selection.getRangeAt(i);
|
||||
if (range.collapsed) {
|
||||
continue;
|
||||
}
|
||||
for (const { x, y, width, height } of range.getClientRects()) {
|
||||
if (width === 0 || height === 0) {
|
||||
continue;
|
||||
}
|
||||
boxes.push(rotator(x, y, width, height));
|
||||
}
|
||||
}
|
||||
return boxes.length === 0 ? null : boxes;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue