[Editor] Add a color picker with predefined colors for highlighting text (bug 1866434)
The doorhanger for highlighting has a basic color picker composed of 5 predefined colors to set the default color to use. These colors can be changed thanks to a preference for now but it's something which could be changed in the Firefox settings in the future. Each highlight has in its own toolbar a color picker to just change its color. The different color pickers are so similar (modulo few differences in their styles) that this patch introduces a new class ColorPicker which provides a color picker component which could be reused in future editors. All in all, a large part of this patch is dedicated to color picker itself and its style and the rest is almost a matter of wiring the component.
This commit is contained in:
parent
c0436013a0
commit
ff23d37fa2
@ -85,6 +85,10 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"highlightEditorColors": {
|
||||
"type": "string",
|
||||
"default": "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F"
|
||||
},
|
||||
"disableRange": {
|
||||
"title": "Disable range requests",
|
||||
"description": "Whether to disable range requests (not recommended).",
|
||||
|
@ -1079,6 +1079,7 @@ function buildComponents(defines, dir) {
|
||||
"web/images/loading-icon.gif",
|
||||
"web/images/altText_*.svg",
|
||||
"web/images/editor-toolbar-*.svg",
|
||||
"web/images/toolbarButton-menuArrow.svg",
|
||||
];
|
||||
|
||||
return merge([
|
||||
|
@ -332,6 +332,8 @@ pdfjs-editor-remove-freetext-button =
|
||||
.title = Remove text
|
||||
pdfjs-editor-remove-stamp-button =
|
||||
.title = Remove image
|
||||
pdfjs-editor-remove-highlight-button =
|
||||
.title = Remove highlight
|
||||
|
||||
##
|
||||
|
||||
@ -384,3 +386,23 @@ pdfjs-editor-resizer-label-bottom-right = Bottom right corner — resize
|
||||
pdfjs-editor-resizer-label-bottom-middle = Bottom middle — resize
|
||||
pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize
|
||||
pdfjs-editor-resizer-label-middle-left = Middle left — resize
|
||||
|
||||
## Color picker
|
||||
|
||||
# This means "Color used to highlight text"
|
||||
pdfjs-editor-highlight-colorpicker-label = Highlight color
|
||||
|
||||
pdfjs-editor-colorpicker-button =
|
||||
.title = Change color
|
||||
pdfjs-editor-colorpicker-dropdown =
|
||||
.aria-label = Color choices
|
||||
pdfjs-editor-colorpicker-yellow =
|
||||
.title = Yellow
|
||||
pdfjs-editor-colorpicker-green =
|
||||
.title = Green
|
||||
pdfjs-editor-colorpicker-blue =
|
||||
.title = Blue
|
||||
pdfjs-editor-colorpicker-pink =
|
||||
.title = Pink
|
||||
pdfjs-editor-colorpicker-red =
|
||||
.title = Red
|
||||
|
230
src/display/editor/color_picker.js
Normal file
230
src/display/editor/color_picker.js
Normal file
@ -0,0 +1,230 @@
|
||||
/* Copyright 2023 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AnnotationEditorParamsType, shadow } from "../../shared/util.js";
|
||||
import { KeyboardManager } from "./tools.js";
|
||||
import { noContextMenu } from "../display_utils.js";
|
||||
|
||||
class ColorPicker {
|
||||
#boundKeyDown = this.#keyDown.bind(this);
|
||||
|
||||
#button = null;
|
||||
|
||||
#buttonSwatch = null;
|
||||
|
||||
#defaultColor;
|
||||
|
||||
#dropdown = null;
|
||||
|
||||
#dropdownWasFromKeyboard = false;
|
||||
|
||||
#isMainColorPicker = false;
|
||||
|
||||
#eventBus;
|
||||
|
||||
#uiManager = null;
|
||||
|
||||
static get _keyboardManager() {
|
||||
return shadow(
|
||||
this,
|
||||
"_keyboardManager",
|
||||
new KeyboardManager([
|
||||
[
|
||||
["Escape", "mac+Escape"],
|
||||
ColorPicker.prototype._hideDropdownFromKeyboard,
|
||||
],
|
||||
[[" ", "mac+ "], ColorPicker.prototype._colorSelectFromKeyboard],
|
||||
[
|
||||
["ArrowDown", "ArrowRight", "mac+ArrowDown", "mac+ArrowRight"],
|
||||
ColorPicker.prototype._moveToNext,
|
||||
],
|
||||
[
|
||||
["ArrowUp", "ArrowLeft", "mac+ArrowUp", "mac+ArrowLeft"],
|
||||
ColorPicker.prototype._moveToPrevious,
|
||||
],
|
||||
[["Home", "mac+Home"], ColorPicker.prototype._moveToBeginning],
|
||||
[["End", "mac+End"], ColorPicker.prototype._moveToEnd],
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
constructor({ editor = null, uiManager = null }) {
|
||||
this.#isMainColorPicker = !editor;
|
||||
this.#uiManager = editor?._uiManager || uiManager;
|
||||
this.#eventBus = this.#uiManager._eventBus;
|
||||
this.#defaultColor =
|
||||
editor?.color ||
|
||||
this.#uiManager?.highlightColors.values().next().value ||
|
||||
"#FFFF98";
|
||||
}
|
||||
|
||||
renderButton() {
|
||||
const button = (this.#button = document.createElement("button"));
|
||||
button.className = "colorPicker";
|
||||
button.tabIndex = "0";
|
||||
button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button");
|
||||
button.setAttribute("aria-haspopup", true);
|
||||
button.addEventListener("click", this.#openDropdown.bind(this));
|
||||
const swatch = (this.#buttonSwatch = document.createElement("span"));
|
||||
swatch.className = "swatch";
|
||||
swatch.style.backgroundColor = this.#defaultColor;
|
||||
button.append(swatch);
|
||||
return button;
|
||||
}
|
||||
|
||||
renderMainDropdown() {
|
||||
const dropdown = (this.#dropdown = this.#getDropdownRoot(
|
||||
AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR
|
||||
));
|
||||
dropdown.setAttribute("aria-orientation", "horizontal");
|
||||
dropdown.setAttribute("aria-labelledby", "highlightColorPickerLabel");
|
||||
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
#getDropdownRoot(paramType) {
|
||||
const div = document.createElement("div");
|
||||
div.addEventListener("contextmenu", noContextMenu);
|
||||
div.className = "dropdown";
|
||||
div.role = "listbox";
|
||||
div.setAttribute("aria-multiselectable", false);
|
||||
div.setAttribute("aria-orientation", "vertical");
|
||||
div.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-dropdown");
|
||||
for (const [name, color] of this.#uiManager.highlightColors) {
|
||||
const button = document.createElement("button");
|
||||
button.tabIndex = "0";
|
||||
button.role = "option";
|
||||
button.setAttribute("data-color", color);
|
||||
button.title = name;
|
||||
button.setAttribute("data-l10n-id", `pdfjs-editor-colorpicker-${name}`);
|
||||
const swatch = document.createElement("span");
|
||||
button.append(swatch);
|
||||
swatch.className = "swatch";
|
||||
swatch.style.backgroundColor = color;
|
||||
button.setAttribute("aria-selected", color === this.#defaultColor);
|
||||
button.addEventListener(
|
||||
"click",
|
||||
this.#colorSelect.bind(this, paramType, color)
|
||||
);
|
||||
div.append(button);
|
||||
}
|
||||
|
||||
div.addEventListener("keydown", this.#boundKeyDown);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
#colorSelect(type, color, event) {
|
||||
event.stopPropagation();
|
||||
this.#eventBus.dispatch("switchannotationeditorparams", {
|
||||
source: this,
|
||||
type,
|
||||
value: color,
|
||||
});
|
||||
}
|
||||
|
||||
_colorSelectFromKeyboard(event) {
|
||||
const color = event.target.getAttribute("data-color");
|
||||
if (!color) {
|
||||
return;
|
||||
}
|
||||
this.#colorSelect(color, event);
|
||||
}
|
||||
|
||||
_moveToNext(event) {
|
||||
if (event.target === this.#button) {
|
||||
this.#dropdown.firstChild?.focus();
|
||||
return;
|
||||
}
|
||||
event.target.nextSibling?.focus();
|
||||
}
|
||||
|
||||
_moveToPrevious(event) {
|
||||
event.target.previousSibling?.focus();
|
||||
}
|
||||
|
||||
_moveToBeginning() {
|
||||
this.#dropdown.firstChild?.focus();
|
||||
}
|
||||
|
||||
_moveToEnd() {
|
||||
this.#dropdown.lastChild?.focus();
|
||||
}
|
||||
|
||||
#keyDown(event) {
|
||||
ColorPicker._keyboardManager.exec(this, event);
|
||||
}
|
||||
|
||||
#openDropdown(event) {
|
||||
if (this.#dropdown && !this.#dropdown.classList.contains("hidden")) {
|
||||
this.hideDropdown();
|
||||
return;
|
||||
}
|
||||
this.#button.addEventListener("keydown", this.#boundKeyDown);
|
||||
this.#dropdownWasFromKeyboard = event.detail === 0;
|
||||
if (this.#dropdown) {
|
||||
this.#dropdown.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
const root = (this.#dropdown = this.#getDropdownRoot(
|
||||
AnnotationEditorParamsType.HIGHLIGHT_COLOR
|
||||
));
|
||||
this.#button.append(root);
|
||||
}
|
||||
|
||||
hideDropdown() {
|
||||
this.#dropdown?.classList.add("hidden");
|
||||
}
|
||||
|
||||
_hideDropdownFromKeyboard() {
|
||||
if (
|
||||
this.#isMainColorPicker ||
|
||||
!this.#dropdown ||
|
||||
this.#dropdown.classList.contains("hidden")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.hideDropdown();
|
||||
this.#button.removeEventListener("keydown", this.#boundKeyDown);
|
||||
this.#button.focus({
|
||||
preventScroll: true,
|
||||
focusVisible: this.#dropdownWasFromKeyboard,
|
||||
});
|
||||
}
|
||||
|
||||
updateColor(color) {
|
||||
if (this.#buttonSwatch) {
|
||||
this.#buttonSwatch.style.backgroundColor = color;
|
||||
}
|
||||
if (!this.#dropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const i = this.#uiManager.highlightColors.values();
|
||||
for (const child of this.#dropdown.children) {
|
||||
child.setAttribute("aria-selected", i.next().value === color);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#button?.remove();
|
||||
this.#button = null;
|
||||
this.#buttonSwatch = null;
|
||||
this.#dropdown?.remove();
|
||||
this.#dropdown = null;
|
||||
}
|
||||
}
|
||||
|
||||
export { ColorPicker };
|
@ -903,15 +903,21 @@ class AnnotationEditor {
|
||||
this.#altText?.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a toolbar for this editor.
|
||||
* @returns {Promise<EditorToolbar|null>}
|
||||
*/
|
||||
async addEditToolbar() {
|
||||
if (this.#editToolbar || this.#isInEditMode) {
|
||||
return;
|
||||
return this.#editToolbar;
|
||||
}
|
||||
this.#editToolbar = new EditorToolbar(this);
|
||||
this.div.append(this.#editToolbar.render());
|
||||
if (this.#altText) {
|
||||
this.#editToolbar.addAltTextButton(await this.#altText.render());
|
||||
}
|
||||
|
||||
return this.#editToolbar;
|
||||
}
|
||||
|
||||
removeEditToolbar() {
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
} from "../../shared/util.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { bindEvents } from "./tools.js";
|
||||
import { ColorPicker } from "./color_picker.js";
|
||||
import { Outliner } from "./outliner.js";
|
||||
|
||||
/**
|
||||
@ -30,7 +31,7 @@ class HighlightEditor extends AnnotationEditor {
|
||||
|
||||
#clipPathId = null;
|
||||
|
||||
#color;
|
||||
#colorPicker = null;
|
||||
|
||||
#focusOutlines = null;
|
||||
|
||||
@ -46,9 +47,9 @@ class HighlightEditor extends AnnotationEditor {
|
||||
|
||||
#outlineId = null;
|
||||
|
||||
static _defaultColor = "#FFF066";
|
||||
static _defaultColor = null;
|
||||
|
||||
static _defaultOpacity = 0.4;
|
||||
static _defaultOpacity = 1;
|
||||
|
||||
static _l10nPromise;
|
||||
|
||||
@ -58,7 +59,9 @@ class HighlightEditor extends AnnotationEditor {
|
||||
|
||||
constructor(params) {
|
||||
super({ ...params, name: "highlightEditor" });
|
||||
this.#color = params.color || HighlightEditor._defaultColor;
|
||||
HighlightEditor._defaultColor ||=
|
||||
this._uiManager.highlightColors?.values().next().value || "#fff066";
|
||||
this.color = params.color || HighlightEditor._defaultColor;
|
||||
this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
|
||||
this.#boxes = params.boxes || null;
|
||||
this._isDraggable = false;
|
||||
@ -100,12 +103,9 @@ class HighlightEditor extends AnnotationEditor {
|
||||
|
||||
static updateDefaultParams(type, value) {
|
||||
switch (type) {
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
|
||||
HighlightEditor._defaultColor = value;
|
||||
break;
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_OPACITY:
|
||||
HighlightEditor._defaultOpacity = value / 100;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,22 +120,15 @@ class HighlightEditor extends AnnotationEditor {
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
|
||||
this.#updateColor(value);
|
||||
break;
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_OPACITY:
|
||||
this.#updateOpacity(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get defaultPropertiesToUpdate() {
|
||||
return [
|
||||
[
|
||||
AnnotationEditorParamsType.HIGHLIGHT_COLOR,
|
||||
AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR,
|
||||
HighlightEditor._defaultColor,
|
||||
],
|
||||
[
|
||||
AnnotationEditorParamsType.HIGHLIGHT_OPACITY,
|
||||
Math.round(HighlightEditor._defaultOpacity * 100),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -144,11 +137,7 @@ class HighlightEditor extends AnnotationEditor {
|
||||
return [
|
||||
[
|
||||
AnnotationEditorParamsType.HIGHLIGHT_COLOR,
|
||||
this.#color || HighlightEditor._defaultColor,
|
||||
],
|
||||
[
|
||||
AnnotationEditorParamsType.HIGHLIGHT_OPACITY,
|
||||
Math.round(100 * (this.#opacity ?? HighlightEditor._defaultOpacity)),
|
||||
this.color || HighlightEditor._defaultColor,
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -161,12 +150,14 @@ class HighlightEditor extends AnnotationEditor {
|
||||
const savedColor = this.color;
|
||||
this.addCommands({
|
||||
cmd: () => {
|
||||
this.#color = color;
|
||||
this.color = color;
|
||||
this.parent.drawLayer.changeColor(this.#id, color);
|
||||
this.#colorPicker?.updateColor(color);
|
||||
},
|
||||
undo: () => {
|
||||
this.#color = savedColor;
|
||||
this.color = savedColor;
|
||||
this.parent.drawLayer.changeColor(this.#id, savedColor);
|
||||
this.#colorPicker?.updateColor(savedColor);
|
||||
},
|
||||
mustExec: true,
|
||||
type: AnnotationEditorParamsType.HIGHLIGHT_COLOR,
|
||||
@ -175,27 +166,17 @@ class HighlightEditor extends AnnotationEditor {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the opacity and make this action undoable.
|
||||
* @param {number} opacity
|
||||
*/
|
||||
#updateOpacity(opacity) {
|
||||
opacity /= 100;
|
||||
const savedOpacity = this.#opacity;
|
||||
this.addCommands({
|
||||
cmd: () => {
|
||||
this.#opacity = opacity;
|
||||
this.parent.drawLayer.changeOpacity(this.#id, opacity);
|
||||
},
|
||||
undo: () => {
|
||||
this.#opacity = savedOpacity;
|
||||
this.parent.drawLayer.changeOpacity(this.#id, savedOpacity);
|
||||
},
|
||||
mustExec: true,
|
||||
type: AnnotationEditorParamsType.HIGHLIGHT_OPACITY,
|
||||
overwriteIfSameType: true,
|
||||
keepUndo: true,
|
||||
});
|
||||
/** @inheritdoc */
|
||||
async addEditToolbar() {
|
||||
const toolbar = await super.addEditToolbar();
|
||||
if (!toolbar) {
|
||||
return null;
|
||||
}
|
||||
if (this._uiManager.highlightColors) {
|
||||
this.#colorPicker = new ColorPicker({ editor: this });
|
||||
toolbar.addColorPicker(this.#colorPicker);
|
||||
}
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -277,7 +258,7 @@ class HighlightEditor extends AnnotationEditor {
|
||||
({ id: this.#id, clipPathId: this.#clipPathId } =
|
||||
parent.drawLayer.highlight(
|
||||
this.#highlightOutlines,
|
||||
this.#color,
|
||||
this.color,
|
||||
this.#opacity
|
||||
));
|
||||
if (this.#highlightDiv) {
|
||||
@ -415,7 +396,7 @@ class HighlightEditor extends AnnotationEditor {
|
||||
const editor = super.deserialize(data, parent, uiManager);
|
||||
|
||||
const { rect, color, quadPoints } = data;
|
||||
editor.#color = Util.makeHexColor(...color);
|
||||
editor.color = Util.makeHexColor(...color);
|
||||
editor.#opacity = data.opacity;
|
||||
|
||||
const [pageWidth, pageHeight] = editor.pageDimensions;
|
||||
@ -443,7 +424,7 @@ class HighlightEditor extends AnnotationEditor {
|
||||
}
|
||||
|
||||
const rect = this.getRect(0, 0);
|
||||
const color = AnnotationEditor._colorManager.convert(this.#color);
|
||||
const color = AnnotationEditor._colorManager.convert(this.color);
|
||||
|
||||
return {
|
||||
annotationType: AnnotationEditorType.HIGHLIGHT,
|
||||
|
@ -18,6 +18,8 @@ import { noContextMenu } from "../display_utils.js";
|
||||
class EditorToolbar {
|
||||
#toolbar = null;
|
||||
|
||||
#colorPicker = null;
|
||||
|
||||
#editor;
|
||||
|
||||
#buttons = null;
|
||||
@ -85,6 +87,7 @@ class EditorToolbar {
|
||||
|
||||
hide() {
|
||||
this.#toolbar.classList.add("hidden");
|
||||
this.#colorPicker?.hideDropdown();
|
||||
}
|
||||
|
||||
show() {
|
||||
@ -106,19 +109,28 @@ class EditorToolbar {
|
||||
this.#buttons.append(button);
|
||||
}
|
||||
|
||||
addAltTextButton(button) {
|
||||
this.#addListenersToElement(button);
|
||||
this.#buttons.prepend(button, this.#divider);
|
||||
}
|
||||
|
||||
get #divider() {
|
||||
const divider = document.createElement("div");
|
||||
divider.className = "divider";
|
||||
return divider;
|
||||
}
|
||||
|
||||
addAltTextButton(button) {
|
||||
this.#addListenersToElement(button);
|
||||
this.#buttons.prepend(button, this.#divider);
|
||||
}
|
||||
|
||||
addColorPicker(colorPicker) {
|
||||
this.#colorPicker = colorPicker;
|
||||
const button = colorPicker.renderButton();
|
||||
this.#addListenersToElement(button);
|
||||
this.#buttons.prepend(button, this.#divider);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.#toolbar.remove();
|
||||
this.#colorPicker?.destroy();
|
||||
this.#colorPicker = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,7 +438,7 @@ class KeyboardManager {
|
||||
if (checker && !checker(self, event)) {
|
||||
return;
|
||||
}
|
||||
callback.bind(self, ...args)();
|
||||
callback.bind(self, ...args, event)();
|
||||
|
||||
// For example, ctrl+s in a FreeText must be handled by the viewer, hence
|
||||
// the event must bubble.
|
||||
@ -545,6 +545,8 @@ class AnnotationEditorUIManager {
|
||||
|
||||
#focusMainContainerTimeoutId = null;
|
||||
|
||||
#highlightColors = null;
|
||||
|
||||
#idManager = new IdManager();
|
||||
|
||||
#isEnabled = false;
|
||||
@ -553,6 +555,8 @@ class AnnotationEditorUIManager {
|
||||
|
||||
#lastActiveElement = null;
|
||||
|
||||
#mainHighlightColorPicker = null;
|
||||
|
||||
#mode = AnnotationEditorType.NONE;
|
||||
|
||||
#selectedEditors = new Set();
|
||||
@ -607,6 +611,7 @@ class AnnotationEditorUIManager {
|
||||
// For example, sliders can be controlled with the arrow keys.
|
||||
return (
|
||||
self.#container.contains(document.activeElement) &&
|
||||
document.activeElement.tagName !== "BUTTON" &&
|
||||
self.hasSomethingToControl()
|
||||
);
|
||||
};
|
||||
@ -736,7 +741,8 @@ class AnnotationEditorUIManager {
|
||||
altTextManager,
|
||||
eventBus,
|
||||
pdfDocument,
|
||||
pageColors
|
||||
pageColors,
|
||||
highlightColors
|
||||
) {
|
||||
this.#container = container;
|
||||
this.#viewer = viewer;
|
||||
@ -749,6 +755,7 @@ class AnnotationEditorUIManager {
|
||||
this.#annotationStorage = pdfDocument.annotationStorage;
|
||||
this.#filterFactory = pdfDocument.filterFactory;
|
||||
this.#pageColors = pageColors;
|
||||
this.#highlightColors = highlightColors || null;
|
||||
this.viewParameters = {
|
||||
realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||
rotation: 0,
|
||||
@ -803,6 +810,24 @@ class AnnotationEditorUIManager {
|
||||
);
|
||||
}
|
||||
|
||||
get highlightColors() {
|
||||
return shadow(
|
||||
this,
|
||||
"highlightColors",
|
||||
this.#highlightColors
|
||||
? new Map(
|
||||
this.#highlightColors
|
||||
.split(",")
|
||||
.map(pair => pair.split("=").map(x => x.trim()))
|
||||
)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
setMainHighlightColorPicker(colorPicker) {
|
||||
this.#mainHighlightColorPicker = colorPicker;
|
||||
}
|
||||
|
||||
editAltText(editor) {
|
||||
this.#altTextManager?.editAltText(this, editor);
|
||||
}
|
||||
@ -1246,9 +1271,14 @@ class AnnotationEditorUIManager {
|
||||
if (!this.#editorTypes) {
|
||||
return;
|
||||
}
|
||||
if (type === AnnotationEditorParamsType.CREATE) {
|
||||
|
||||
switch (type) {
|
||||
case AnnotationEditorParamsType.CREATE:
|
||||
this.currentLayer.addNewEditor();
|
||||
return;
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
|
||||
this.#mainHighlightColorPicker?.updateColor(value);
|
||||
break;
|
||||
}
|
||||
|
||||
for (const editor of this.#selectedEditors) {
|
||||
|
@ -70,6 +70,7 @@ import { renderTextLayer, updateTextLayer } from "./display/text_layer.js";
|
||||
import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js";
|
||||
import { AnnotationEditorUIManager } from "./display/editor/tools.js";
|
||||
import { AnnotationLayer } from "./display/annotation_layer.js";
|
||||
import { ColorPicker } from "./display/editor/color_picker.js";
|
||||
import { DrawLayer } from "./display/draw_layer.js";
|
||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||
import { Outliner } from "./display/editor/outliner.js";
|
||||
@ -92,6 +93,7 @@ export {
|
||||
AnnotationMode,
|
||||
build,
|
||||
CMapCompressionType,
|
||||
ColorPicker,
|
||||
createValidAbsoluteUrl,
|
||||
DOMSVGFactory,
|
||||
DrawLayer,
|
||||
|
@ -87,7 +87,7 @@ const AnnotationEditorParamsType = {
|
||||
INK_THICKNESS: 22,
|
||||
INK_OPACITY: 23,
|
||||
HIGHLIGHT_COLOR: 31,
|
||||
HIGHLIGHT_OPACITY: 32,
|
||||
HIGHLIGHT_DEFAULT_COLOR: 32,
|
||||
};
|
||||
|
||||
// Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
|
||||
|
@ -63,6 +63,7 @@ import {
|
||||
import { AnnotationEditorLayer } from "../../src/display/editor/annotation_editor_layer.js";
|
||||
import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js";
|
||||
import { AnnotationLayer } from "../../src/display/annotation_layer.js";
|
||||
import { ColorPicker } from "../../src/display/editor/color_picker.js";
|
||||
import { DrawLayer } from "../../src/display/draw_layer.js";
|
||||
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
||||
import { Outliner } from "../../src/display/editor/outliner.js";
|
||||
@ -78,6 +79,7 @@ const expectedAPI = Object.freeze({
|
||||
AnnotationMode,
|
||||
build,
|
||||
CMapCompressionType,
|
||||
ColorPicker,
|
||||
createValidAbsoluteUrl,
|
||||
DOMSVGFactory,
|
||||
DrawLayer,
|
||||
|
@ -904,6 +904,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
.colorPicker {
|
||||
--hover-outline-color: #0250bb;
|
||||
--selected-outline-color: #0060df;
|
||||
--swatch-border-color: #cfcfd8;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--hover-outline-color: #80ebff;
|
||||
--selected-outline-color: #aaf2ff;
|
||||
--swatch-border-color: #52525e;
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--hover-outline-color: Highlight;
|
||||
--selected-outline-color: var(--hover-outline-color);
|
||||
--swatch-border-color: ButtonText;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid var(--swatch-border-color);
|
||||
border-radius: 100%;
|
||||
outline-offset: 2px;
|
||||
box-sizing: border-box;
|
||||
forced-color-adjust: none;
|
||||
}
|
||||
|
||||
button:is(:hover, .selected) > .swatch {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.annotationEditorLayer {
|
||||
&[data-main-rotation="0"] {
|
||||
.highlightEditor > .editToolbar {
|
||||
@ -962,7 +994,144 @@
|
||||
}
|
||||
|
||||
.editToolbar {
|
||||
--editor-toolbar-colorpicker-arrow-image: url(images/toolbarButton-menuArrow.svg);
|
||||
|
||||
transform-origin: center !important;
|
||||
|
||||
.buttons {
|
||||
.colorPicker {
|
||||
position: relative;
|
||||
width: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
mask-image: var(--editor-toolbar-colorpicker-arrow-image);
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
display: inline-block;
|
||||
background-color: var(--editor-toolbar-fg-color);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
background-color: var(--editor-toolbar-hover-fg-color);
|
||||
}
|
||||
|
||||
&:has(.dropdown:not(.hidden)) {
|
||||
background-color: var(--editor-toolbar-hover-bg-color);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 11px;
|
||||
padding-block: 8px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--editor-toolbar-bg-color);
|
||||
border: 1px solid var(--editor-toolbar-border-color);
|
||||
box-shadow: var(--editor-toolbar-shadow);
|
||||
inset-block-start: calc(100% + 4px);
|
||||
width: calc(100% + 2 * var(--editor-toolbar-padding));
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: none;
|
||||
|
||||
&:is(:active, :focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
> .swatch {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&[aria-selected="true"] > .swatch {
|
||||
outline: 2px solid var(--selected-outline-color);
|
||||
}
|
||||
|
||||
&:is(:hover, :active, :focus-visible) > .swatch {
|
||||
outline: 2px solid var(--hover-outline-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editorParamsToolbar:has(#highlightParamsToolbarContainer) {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
#highlightParamsToolbarContainer {
|
||||
height: auto;
|
||||
padding-inline: 10px;
|
||||
padding-block: 10px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
.colorPicker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
#highlightColorPickerLabel {
|
||||
width: fit-content;
|
||||
inset-inline-start: 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
height: auto;
|
||||
|
||||
button {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: none;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.swatch {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&:is(:active, :focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&[aria-selected="true"] > .swatch {
|
||||
outline: 2px solid var(--selected-outline-color);
|
||||
}
|
||||
|
||||
&:is(:hover, :active, :focus-visible) > .swatch {
|
||||
outline: 2px solid var(--hover-outline-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,6 @@ class AnnotationEditorParams {
|
||||
#bindListeners({
|
||||
editorFreeTextFontSize,
|
||||
editorFreeTextColor,
|
||||
editorHighlightColor,
|
||||
editorHighlightOpacity,
|
||||
editorInkColor,
|
||||
editorInkThickness,
|
||||
editorInkOpacity,
|
||||
@ -48,12 +46,6 @@ class AnnotationEditorParams {
|
||||
editorFreeTextColor.addEventListener("input", function () {
|
||||
dispatchEvent("FREETEXT_COLOR", this.value);
|
||||
});
|
||||
editorHighlightColor.addEventListener("input", function () {
|
||||
dispatchEvent("HIGHLIGHT_COLOR", this.value);
|
||||
});
|
||||
editorHighlightOpacity.addEventListener("input", function () {
|
||||
dispatchEvent("HIGHLIGHT_OPACITY", this.valueAsNumber);
|
||||
});
|
||||
editorInkColor.addEventListener("input", function () {
|
||||
dispatchEvent("INK_COLOR", this.value);
|
||||
});
|
||||
@ -76,12 +68,6 @@ class AnnotationEditorParams {
|
||||
case AnnotationEditorParamsType.FREETEXT_COLOR:
|
||||
editorFreeTextColor.value = value;
|
||||
break;
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
|
||||
editorHighlightColor.value = value;
|
||||
break;
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_OPACITY:
|
||||
editorHighlightOpacity.value = value;
|
||||
break;
|
||||
case AnnotationEditorParamsType.INK_COLOR:
|
||||
editorInkColor.value = value;
|
||||
break;
|
||||
|
@ -442,6 +442,7 @@ const PDFViewerApplication = {
|
||||
textLayerMode: AppOptions.get("textLayerMode"),
|
||||
annotationMode: AppOptions.get("annotationMode"),
|
||||
annotationEditorMode,
|
||||
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
|
||||
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
||||
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
||||
isOffscreenCanvasSupported,
|
||||
|
@ -158,6 +158,11 @@ const defaultOptions = {
|
||||
value: 0,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
highlightEditorColors: {
|
||||
/** @type {string} */
|
||||
value: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
historyUpdateUrl: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
|
@ -36,8 +36,14 @@
|
||||
}
|
||||
|
||||
&.highlight {
|
||||
--blend-mode: multiply;
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--blend-mode: difference;
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
mix-blend-mode: multiply;
|
||||
mix-blend-mode: var(--blend-mode);
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,8 @@ function isValidAnnotationEditorMode(mode) {
|
||||
* @property {number} [annotationEditorMode] - Enables the creation and editing
|
||||
* of new Annotations. The constants from {@link AnnotationEditorType} should
|
||||
* be used. The default value is `AnnotationEditorType.NONE`.
|
||||
* @property {string} [annotationEditorHighlightColors] - A comma separated list
|
||||
* of colors to propose to highlight some text in the pdf.
|
||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||
* mainly for annotation icons. Include trailing slash.
|
||||
* @property {boolean} [enablePrintAutoRotate] - Enables automatic rotation of
|
||||
@ -202,6 +204,8 @@ class PDFViewer {
|
||||
|
||||
#altTextManager = null;
|
||||
|
||||
#annotationEditorHighlightColors = null;
|
||||
|
||||
#annotationEditorMode = AnnotationEditorType.NONE;
|
||||
|
||||
#annotationEditorUIManager = null;
|
||||
@ -276,6 +280,8 @@ class PDFViewer {
|
||||
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
|
||||
this.#annotationEditorMode =
|
||||
options.annotationEditorMode ?? AnnotationEditorType.NONE;
|
||||
this.#annotationEditorHighlightColors =
|
||||
options.annotationEditorHighlightColors || null;
|
||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
@ -862,8 +868,13 @@ class PDFViewer {
|
||||
this.#altTextManager,
|
||||
this.eventBus,
|
||||
pdfDocument,
|
||||
this.pageColors
|
||||
this.pageColors,
|
||||
this.#annotationEditorHighlightColors
|
||||
);
|
||||
this.eventBus.dispatch("annotationeditoruimanager", {
|
||||
source: this,
|
||||
uiManager: this.#annotationEditorUIManager,
|
||||
});
|
||||
if (mode !== AnnotationEditorType.NONE) {
|
||||
this.#annotationEditorUIManager.updateMode(mode);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ const {
|
||||
AnnotationMode,
|
||||
build,
|
||||
CMapCompressionType,
|
||||
ColorPicker,
|
||||
createValidAbsoluteUrl,
|
||||
DOMSVGFactory,
|
||||
DrawLayer,
|
||||
@ -80,6 +81,7 @@ export {
|
||||
AnnotationMode,
|
||||
build,
|
||||
CMapCompressionType,
|
||||
ColorPicker,
|
||||
createValidAbsoluteUrl,
|
||||
DOMSVGFactory,
|
||||
DrawLayer,
|
||||
|
@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AnnotationEditorType, noContextMenu } from "pdfjs-lib";
|
||||
import { AnnotationEditorType, ColorPicker, noContextMenu } from "pdfjs-lib";
|
||||
import {
|
||||
DEFAULT_SCALE,
|
||||
DEFAULT_SCALE_VALUE,
|
||||
@ -120,9 +120,24 @@ class Toolbar {
|
||||
// Bind the event listeners for click and various other actions.
|
||||
this.#bindListeners(options);
|
||||
|
||||
if (options.editorHighlightColorPicker) {
|
||||
this.eventBus._on("annotationeditoruimanager", ({ uiManager }) => {
|
||||
this.#setAnnotationEditorUIManager(
|
||||
uiManager,
|
||||
options.editorHighlightColorPicker
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
#setAnnotationEditorUIManager(uiManager, parentContainer) {
|
||||
const colorPicker = new ColorPicker({ uiManager });
|
||||
uiManager.setMainHighlightColorPicker(colorPicker);
|
||||
parentContainer.append(colorPicker.renderMainDropdown());
|
||||
}
|
||||
|
||||
setPageNumber(pageNumber, pageLabel) {
|
||||
this.pageNumber = pageNumber;
|
||||
this.pageLabel = pageLabel;
|
||||
|
@ -532,6 +532,11 @@ body {
|
||||
.editorParamsToolbarContainer .editorParamsLabel {
|
||||
padding-inline-end: 10px;
|
||||
flex: none;
|
||||
font: menu;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
|
@ -172,14 +172,9 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
</div> <!-- findbar -->
|
||||
|
||||
<div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
|
||||
<div class="editorParamsToolbarContainer">
|
||||
<div class="editorParamsSetter">
|
||||
<label for="editorHighlightColor" class="editorParamsLabel" data-l10n-id="editor_highlight_color">Color</label>
|
||||
<input type="color" value="#FFFF00" id="editorHighlightColor" class="editorParamsColor" tabindex="100">
|
||||
</div>
|
||||
<div class="editorParamsSetter">
|
||||
<label for="editorHighlightOpacity" class="editorParamsLabel" data-l10n-id="editor_highlight_opacity">Opacity</label>
|
||||
<input type="range" id="editorHighlightOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="101">
|
||||
<div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
|
||||
<div id="editorHighlightColorPicker" class="colorPicker">
|
||||
<span id="highlightColorPickerLabel" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -217,7 +212,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar">
|
||||
<div class="editorParamsToolbarContainer">
|
||||
<button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="107" data-l10n-id="pdfjs-editor-stamp-add-image-button">
|
||||
<span data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span>
|
||||
<span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -61,6 +61,9 @@ function getViewerConfiguration() {
|
||||
editorHighlightParamsToolbar: document.getElementById(
|
||||
"editorHighlightParamsToolbar"
|
||||
),
|
||||
editorHighlightColorPicker: document.getElementById(
|
||||
"editorHighlightColorPicker"
|
||||
),
|
||||
editorInkButton: document.getElementById("editorInk"),
|
||||
editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
|
||||
editorStampButton: document.getElementById("editorStamp"),
|
||||
@ -168,8 +171,6 @@ function getViewerConfiguration() {
|
||||
annotationEditorParams: {
|
||||
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
|
||||
editorFreeTextColor: document.getElementById("editorFreeTextColor"),
|
||||
editorHighlightColor: document.getElementById("editorHighlightColor"),
|
||||
editorHighlightOpacity: document.getElementById("editorHighlightOpacity"),
|
||||
editorInkColor: document.getElementById("editorInkColor"),
|
||||
editorInkThickness: document.getElementById("editorInkThickness"),
|
||||
editorInkOpacity: document.getElementById("editorInkOpacity"),
|
||||
|
Loading…
Reference in New Issue
Block a user