1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-22 16:18:08 +02:00

[Editor] Always have an ink editor (when in ink mode)

Previously it was created only on mouseover event but on a touch screen
there are no fingerover event...
The idea behind creating the ink editor on mouseover was to avoid to have
a canvas on each visible page.
So now, when the editor is created, the canvas has dimensions 1x1 and
only when the user starts drawing the dimensions are set to the page ones.
This commit is contained in:
Calixte Denizet 2022-07-12 17:32:14 +02:00
parent 220f980e12
commit 2df2defa02
6 changed files with 97 additions and 53 deletions

View file

@ -146,7 +146,11 @@ class AnnotationStorage {
const clone = new Map();
for (const [key, val] of this._storage) {
clone.set(key, val instanceof AnnotationEditor ? val.serialize() : val);
const serialized =
val instanceof AnnotationEditor ? val.serialize() : val;
if (serialized) {
clone.set(key, serialized);
}
}
return clone;
}

View file

@ -42,10 +42,10 @@ import { InkEditor } from "./ink.js";
class AnnotationEditorLayer {
#boundClick;
#boundMouseover;
#editors = new Map();
#isCleaningUp = false;
#uiManager;
static _initialized = false;
@ -92,7 +92,6 @@ class AnnotationEditorLayer {
this.pageIndex = options.pageIndex;
this.div = options.div;
this.#boundClick = this.click.bind(this);
this.#boundMouseover = this.mouseover.bind(this);
for (const editor of this.#uiManager.getEditors(options.pageIndex)) {
this.add(editor);
@ -114,24 +113,36 @@ class AnnotationEditorLayer {
* The mode has changed: it must be updated.
* @param {number} mode
*/
updateMode(mode) {
switch (mode) {
case AnnotationEditorType.INK:
// We want to have the ink editor covering all of the page without
// having to click to create it: it must be here when we start to draw.
this.div.addEventListener("mouseover", this.#boundMouseover);
this.div.removeEventListener("click", this.#boundClick);
break;
case AnnotationEditorType.FREETEXT:
this.div.removeEventListener("mouseover", this.#boundMouseover);
this.div.addEventListener("click", this.#boundClick);
break;
default:
this.div.removeEventListener("mouseover", this.#boundMouseover);
this.div.removeEventListener("click", this.#boundClick);
updateMode(mode = this.#uiManager.getMode()) {
this.#cleanup();
if (mode === AnnotationEditorType.INK) {
// We always want to an ink editor ready to draw in.
this.addInkEditorIfNeeded(false);
}
this.setActiveEditor(null);
}
addInkEditorIfNeeded(isCommitting) {
if (
!isCommitting &&
this.#uiManager.getMode() !== AnnotationEditorType.INK
) {
return;
}
this.setActiveEditor(null);
if (!isCommitting) {
// We're removing an editor but an empty one can already exist so in this
// case we don't need to create a new one.
for (const editor of this.#editors.values()) {
if (editor.isEmpty()) {
editor.setInBackground();
return;
}
}
}
const editor = this.#createAndAddNewEditor({ offsetX: 0, offsetY: 0 });
editor.setInBackground();
}
/**
@ -142,25 +153,6 @@ class AnnotationEditorLayer {
this.#uiManager.setEditingState(isEditing);
}
/**
* Mouseover callback.
* @param {MouseEvent} event
*/
mouseover(event) {
if (
event.target === this.div &&
event.buttons === 0 &&
!this.#uiManager.hasActive()
) {
// The div is the target so there is no ink editor, hence we can
// create a new one.
// event.buttons === 0 is here to avoid adding a new ink editor
// when we drop an editor.
const editor = this.#createAndAddNewEditor(event);
editor.setInBackground();
}
}
/**
* Add some commands into the CommandManager (undo/redo stuff).
* @param {Object} params
@ -258,14 +250,12 @@ class AnnotationEditorLayer {
currentActive.commitOrRemove();
}
this.#uiManager.allowClick =
this.#uiManager.getMode() === AnnotationEditorType.INK;
if (editor) {
this.unselectAll();
this.div.removeEventListener("click", this.#boundClick);
} else {
// When in Ink mode, setting the editor to null allows the
// user to have to make one click in order to start drawing.
this.#uiManager.allowClick =
this.#uiManager.getMode() === AnnotationEditorType.INK;
this.div.addEventListener("click", this.#boundClick);
}
}
@ -295,6 +285,10 @@ class AnnotationEditorLayer {
this.setActiveEditor(null);
this.#uiManager.allowClick = true;
}
if (!this.#isCleaningUp) {
this.addInkEditorIfNeeded(/* isCommitting = */ false);
}
}
/**
@ -496,6 +490,19 @@ class AnnotationEditorLayer {
this.#uiManager.removeLayer(this);
}
#cleanup() {
// When we're cleaning up, some editors are removed but we don't want
// to add a new one which will induce an addition in this.#editors, hence
// an infinite loop.
this.#isCleaningUp = true;
for (const editor of this.#editors.values()) {
if (editor.isEmpty()) {
editor.remove();
}
}
this.#isCleaningUp = false;
}
/**
* Render the main editor.
* @param {Object} parameters
@ -505,6 +512,7 @@ class AnnotationEditorLayer {
bindEvents(this, this.div, ["dragover", "drop", "keydown"]);
this.div.addEventListener("click", this.#boundClick);
this.setDimensions();
this.updateMode();
}
/**
@ -515,6 +523,7 @@ class AnnotationEditorLayer {
this.setActiveEditor(null);
this.viewport = parameters.viewport;
this.setDimensions();
this.updateMode();
}
/**

View file

@ -16,8 +16,12 @@
// eslint-disable-next-line max-len
/** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */
import {
AnnotationEditorPrefix,
shadow,
unreachable,
} from "../../shared/util.js";
import { bindEvents, ColorManager } from "./tools.js";
import { shadow, unreachable } from "../../shared/util.js";
/**
* @typedef {Object} AnnotationEditorParameters
@ -109,7 +113,10 @@ class AnnotationEditor {
event.preventDefault();
this.commitOrRemove();
this.parent.setActiveEditor(null);
if (!target?.id?.startsWith(AnnotationEditorPrefix)) {
this.parent.setActiveEditor(null);
}
}
commitOrRemove() {

View file

@ -372,6 +372,10 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */
serialize() {
if (this.isEmpty()) {
return null;
}
const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor;
const rect = this.getRect(padding, padding);

View file

@ -41,6 +41,8 @@ class InkEditor extends AnnotationEditor {
#disableEditing = false;
#isCanvasInitialized = false;
#observer = null;
#realWidth = 0;
@ -53,11 +55,8 @@ class InkEditor extends AnnotationEditor {
constructor(params) {
super({ ...params, name: "inkEditor" });
this.color =
params.color ||
InkEditor._defaultColor ||
AnnotationEditor._defaultLineColor;
this.thickness = params.thickness || InkEditor._defaultThickness;
this.color = params.color || null;
this.thickness = params.thickness || null;
this.paths = [];
this.bezierPath2D = [];
this.currentPath = [];
@ -255,7 +254,6 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */
onceAdded() {
this.div.draggable = !this.isEmpty();
this.div.focus();
}
/** @inheritdoc */
@ -298,6 +296,13 @@ class InkEditor extends AnnotationEditor {
* @param {number} y
*/
#startDrawing(x, y) {
if (!this.#isCanvasInitialized) {
this.#isCanvasInitialized = true;
this.#setCanvasDims();
this.thickness ||= InkEditor._defaultThickness;
this.color ||=
InkEditor._defaultColor || AnnotationEditor._defaultLineColor;
}
this.currentPath.push([x, y]);
this.#setStroke();
this.ctx.beginPath();
@ -406,6 +411,8 @@ class InkEditor extends AnnotationEditor {
this.div.classList.add("disabled");
this.#fitToContent();
this.parent.addInkEditorIfNeeded(/* isCommitting = */ true);
}
/** @inheritdoc */
@ -491,6 +498,7 @@ class InkEditor extends AnnotationEditor {
*/
#createCanvas() {
this.canvas = document.createElement("canvas");
this.canvas.width = this.canvas.height = 0;
this.canvas.className = "inkEditorCanvas";
this.div.append(this.canvas);
this.ctx = this.canvas.getContext("2d");
@ -522,7 +530,6 @@ class InkEditor extends AnnotationEditor {
}
super.render();
this.div.classList.add("editing");
const [x, y, w, h] = this.#getInitialBBox();
this.setAt(x, y, 0, 0);
this.setDims(w, h);
@ -531,6 +538,7 @@ class InkEditor extends AnnotationEditor {
if (this.width) {
// This editor was created in using copy (ctrl+c).
this.#isCanvasInitialized = true;
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
this.setAt(
baseX * parentWidth,
@ -542,6 +550,9 @@ class InkEditor extends AnnotationEditor {
this.#setCanvasDims();
this.#redraw();
this.div.classList.add("disabled");
} else {
this.div.classList.add("editing");
this.enableEditMode();
}
this.#createObserver();
@ -550,6 +561,9 @@ class InkEditor extends AnnotationEditor {
}
#setCanvasDims() {
if (!this.#isCanvasInitialized) {
return;
}
const [parentWidth, parentHeight] = this.parent.viewportBaseDimensions;
this.canvas.width = this.width * parentWidth;
this.canvas.height = this.height * parentHeight;
@ -861,6 +875,10 @@ class InkEditor extends AnnotationEditor {
/** @inheritdoc */
serialize() {
if (this.isEmpty()) {
return null;
}
const rect = this.getRect(0, 0);
const height =
this.rotation % 180 === 0 ? rect[3] - rect[1] : rect[2] - rect[0];

View file

@ -779,7 +779,9 @@ class AnnotationEditorUIManager {
const editors = Array.from(this.#allEditors.values());
cmd = () => {
for (const editor of editors) {
editor.remove();
if (!editor.isEmpty()) {
editor.remove();
}
}
};