[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:
Calixte Denizet 2023-11-30 16:21:13 +01:00
parent c0436013a0
commit ff23d37fa2
22 changed files with 573 additions and 87 deletions

View File

@ -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).",

View File

@ -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([

View File

@ -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

View 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 };

View File

@ -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() {

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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,

View File

@ -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.

View File

@ -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,

View File

@ -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);
}
}
}
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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;
}

View File

@ -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);
}

View File

@ -32,6 +32,7 @@ const {
AnnotationMode,
build,
CMapCompressionType,
ColorPicker,
createValidAbsoluteUrl,
DOMSVGFactory,
DrawLayer,
@ -80,6 +81,7 @@ export {
AnnotationMode,
build,
CMapCompressionType,
ColorPicker,
createValidAbsoluteUrl,
DOMSVGFactory,
DrawLayer,

View File

@ -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;

View File

@ -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);
}

View File

@ -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>

View File

@ -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"),