[Editor] Add a color picker in the toolbar of Ink and Freetext annotations
This commit is contained in:
parent
e0783cd075
commit
dbd6f8cdd4
@ -306,9 +306,13 @@ pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fo
|
||||
|
||||
pdfjs-editor-free-text-button =
|
||||
.title = Text
|
||||
pdfjs-editor-color-picker-free-text-input =
|
||||
.title = Change text color
|
||||
pdfjs-editor-free-text-button-label = Text
|
||||
pdfjs-editor-ink-button =
|
||||
.title = Draw
|
||||
pdfjs-editor-color-picker-ink-input =
|
||||
.title = Change drawing color
|
||||
pdfjs-editor-ink-button-label = Draw
|
||||
pdfjs-editor-stamp-button =
|
||||
.title = Add or edit images
|
||||
|
||||
@ -17,6 +17,11 @@ import { AnnotationEditorParamsType, shadow } from "../../shared/util.js";
|
||||
import { KeyboardManager } from "./tools.js";
|
||||
import { noContextMenu } from "../display_utils.js";
|
||||
|
||||
/**
|
||||
* ColorPicker class provides a color picker for the annotation editor.
|
||||
* It displays a dropdown with some predefined colors and allows the user
|
||||
* to select a color for the annotation.
|
||||
*/
|
||||
class ColorPicker {
|
||||
#button = null;
|
||||
|
||||
@ -304,4 +309,64 @@ class ColorPicker {
|
||||
}
|
||||
}
|
||||
|
||||
export { ColorPicker };
|
||||
/**
|
||||
* BasicColorPicker class provides a simple color picker.
|
||||
* It displays an input element (with type="color") that allows the user
|
||||
* to select a color for the annotation.
|
||||
*/
|
||||
class BasicColorPicker {
|
||||
#input = null;
|
||||
|
||||
#editor = null;
|
||||
|
||||
#uiManager = null;
|
||||
|
||||
static #l10nColor = null;
|
||||
|
||||
constructor(editor) {
|
||||
this.#editor = editor;
|
||||
this.#uiManager = editor._uiManager;
|
||||
|
||||
BasicColorPicker.#l10nColor ||= Object.freeze({
|
||||
freetext: "pdfjs-editor-color-picker-free-text-input",
|
||||
ink: "pdfjs-editor-color-picker-ink-input",
|
||||
});
|
||||
}
|
||||
|
||||
renderButton() {
|
||||
if (this.#input) {
|
||||
return this.#input;
|
||||
}
|
||||
const { editorType, colorType, colorValue } = this.#editor;
|
||||
const input = (this.#input = document.createElement("input"));
|
||||
input.type = "color";
|
||||
input.value = colorValue || "#000000";
|
||||
input.className = "basicColorPicker";
|
||||
input.tabIndex = 0;
|
||||
input.setAttribute("data-l10n-id", BasicColorPicker.#l10nColor[editorType]);
|
||||
input.addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
this.#uiManager.updateParams(colorType, input.value);
|
||||
},
|
||||
{ signal: this.#uiManager._signal }
|
||||
);
|
||||
return input;
|
||||
}
|
||||
|
||||
update(value) {
|
||||
if (!this.#input) {
|
||||
return;
|
||||
}
|
||||
this.#input.value = value;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#input?.remove();
|
||||
this.#input = null;
|
||||
}
|
||||
|
||||
hideDropdown() {}
|
||||
}
|
||||
|
||||
export { BasicColorPicker, ColorPicker };
|
||||
|
||||
@ -67,6 +67,8 @@ class DrawingEditor extends AnnotationEditor {
|
||||
|
||||
#mustBeCommitted;
|
||||
|
||||
_colorPicker = null;
|
||||
|
||||
_drawId = null;
|
||||
|
||||
static _currentDrawId = -1;
|
||||
@ -240,6 +242,9 @@ class DrawingEditor extends AnnotationEditor {
|
||||
this._drawId,
|
||||
options.toSVGProperties()
|
||||
);
|
||||
if (type === this.colorType) {
|
||||
this._colorPicker?.update(val);
|
||||
}
|
||||
};
|
||||
this.addCommands({
|
||||
cmd: setter.bind(this, value),
|
||||
|
||||
@ -1059,7 +1059,7 @@ class AnnotationEditor {
|
||||
|
||||
/**
|
||||
* Get the toolbar buttons for this editor.
|
||||
* @returns {Array<Array<string|object>>|null}
|
||||
* @returns {Array<Array<string|object|null>>|null}
|
||||
*/
|
||||
get toolbarButtons() {
|
||||
return null;
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
} from "../../shared/util.js";
|
||||
import { AnnotationEditorUIManager, KeyboardManager } from "./tools.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { BasicColorPicker } from "./color_picker.js";
|
||||
import { FreeTextAnnotationElement } from "../annotation_layer.js";
|
||||
|
||||
const EOL_PATTERN = /\r\n?|\n/g;
|
||||
@ -44,6 +45,8 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
|
||||
#fontSize;
|
||||
|
||||
_colorPicker = null;
|
||||
|
||||
static _freeTextDefaultContent = "";
|
||||
|
||||
static _internalPadding = 0;
|
||||
@ -202,6 +205,20 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
get toolbarButtons() {
|
||||
this._colorPicker ||= new BasicColorPicker(this);
|
||||
return [["colorPicker", this._colorPicker]];
|
||||
}
|
||||
|
||||
get colorType() {
|
||||
return AnnotationEditorParamsType.FREETEXT_COLOR;
|
||||
}
|
||||
|
||||
get colorValue() {
|
||||
return this.#color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the font size and make this action as undoable.
|
||||
* @param {number} fontSize
|
||||
@ -232,6 +249,7 @@ class FreeTextEditor extends AnnotationEditor {
|
||||
#updateColor(color) {
|
||||
const setColor = col => {
|
||||
this.#color = this.editorDiv.style.color = col;
|
||||
this._colorPicker?.update(col);
|
||||
};
|
||||
const savedColor = this.#color;
|
||||
this.addCommands({
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
import { DrawingEditor, DrawingOptions } from "./draw.js";
|
||||
import { InkDrawOutline, InkDrawOutliner } from "./drawers/inkdraw.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { BasicColorPicker } from "./color_picker.js";
|
||||
import { InkAnnotationElement } from "../annotation_layer.js";
|
||||
|
||||
class InkDrawingOptions extends DrawingOptions {
|
||||
@ -177,6 +178,20 @@ class InkEditor extends DrawingEditor {
|
||||
return editor;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
get toolbarButtons() {
|
||||
this._colorPicker ||= new BasicColorPicker(this);
|
||||
return [["colorPicker", this._colorPicker]];
|
||||
}
|
||||
|
||||
get colorType() {
|
||||
return AnnotationEditorParamsType.INK_COLOR;
|
||||
}
|
||||
|
||||
get colorValue() {
|
||||
return this._drawingOptions.stroke;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
onScaleChanging() {
|
||||
if (!this.parent) {
|
||||
|
||||
@ -1805,12 +1805,14 @@ class AnnotationEditorUIManager {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const editor of this.#selectedEditors) {
|
||||
editor.updateParams(type, value);
|
||||
}
|
||||
|
||||
for (const editorType of this.#editorTypes) {
|
||||
editorType.updateDefaultParams(type, value);
|
||||
if (this.hasSelection) {
|
||||
for (const editor of this.#selectedEditors) {
|
||||
editor.updateParams(type, value);
|
||||
}
|
||||
} else {
|
||||
for (const editorType of this.#editorTypes) {
|
||||
editorType.updateDefaultParams(type, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3502,4 +3502,48 @@ describe("FreeText Editor", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FreeText must update its color", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the text color is the one chosen from the color picker", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToFreeText(page);
|
||||
|
||||
const rect = await getRect(page, ".annotationEditorLayer");
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const data = "Hello PDF.js World !!";
|
||||
await page.mouse.click(
|
||||
rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2
|
||||
);
|
||||
await page.waitForSelector(editorSelector, { visible: true });
|
||||
await page.type(`${editorSelector} .internal`, data);
|
||||
await commit(page);
|
||||
|
||||
const colorPickerSelector = `${editorSelector} input.basicColorPicker`;
|
||||
await page.waitForSelector(colorPickerSelector, { visible: true });
|
||||
await page.locator(colorPickerSelector).fill("#ff0000");
|
||||
|
||||
await page.waitForFunction(
|
||||
sel => {
|
||||
const el = document.querySelector(sel);
|
||||
return getComputedStyle(el).color === "rgb(255, 0, 0)";
|
||||
},
|
||||
{},
|
||||
`${editorSelector} .internal`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1294,3 +1294,45 @@ describe("Should switch from an editor and mode to others by double clicking", (
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ink must update its color", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the stroke color is the one chosen from the color picker", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToInk(page);
|
||||
|
||||
const rect = await getRect(page, ".annotationEditorLayer");
|
||||
|
||||
const x = rect.x + 20;
|
||||
const y = rect.y + 20;
|
||||
const clickHandle = await waitForPointerUp(page);
|
||||
await page.mouse.move(x, y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(x + 50, y + 50);
|
||||
await page.mouse.up();
|
||||
await awaitPromise(clickHandle);
|
||||
await commit(page);
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
const colorPickerSelector = `${editorSelector} input.basicColorPicker`;
|
||||
await page.waitForSelector(colorPickerSelector, { visible: true });
|
||||
await page.locator(colorPickerSelector).fill("#ff0000");
|
||||
|
||||
await page.waitForSelector(
|
||||
".canvasWrapper svg.draw[stroke='#ff0000']",
|
||||
{ visible: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1057,6 +1057,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.basicColorPicker {
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.annotationEditorLayer {
|
||||
&[data-main-rotation="0"] {
|
||||
.highlightEditor:not(.free) > .editToolbar {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user