Merge pull request #17359 from calixteman/editor_highlight_color_picker
[Editor] Add a color picker with predefined colors for highlighting text (bug 1866434)
This commit is contained in:
		
						commit
						8702e1bbb2
					
				| @ -85,6 +85,10 @@ | |||||||
|       "type": "boolean", |       "type": "boolean", | ||||||
|       "default": false |       "default": false | ||||||
|     }, |     }, | ||||||
|  |     "highlightEditorColors": { | ||||||
|  |       "type": "string", | ||||||
|  |       "default": "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F" | ||||||
|  |     }, | ||||||
|     "disableRange": { |     "disableRange": { | ||||||
|       "title": "Disable range requests", |       "title": "Disable range requests", | ||||||
|       "description": "Whether to disable range requests (not recommended).", |       "description": "Whether to disable range requests (not recommended).", | ||||||
|  | |||||||
| @ -1079,6 +1079,7 @@ function buildComponents(defines, dir) { | |||||||
|     "web/images/loading-icon.gif", |     "web/images/loading-icon.gif", | ||||||
|     "web/images/altText_*.svg", |     "web/images/altText_*.svg", | ||||||
|     "web/images/editor-toolbar-*.svg", |     "web/images/editor-toolbar-*.svg", | ||||||
|  |     "web/images/toolbarButton-menuArrow.svg", | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   return merge([ |   return merge([ | ||||||
|  | |||||||
| @ -332,6 +332,8 @@ pdfjs-editor-remove-freetext-button = | |||||||
|     .title = Remove text |     .title = Remove text | ||||||
| pdfjs-editor-remove-stamp-button = | pdfjs-editor-remove-stamp-button = | ||||||
|     .title = Remove image |     .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-middle = Bottom middle — resize | ||||||
| pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize | pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize | ||||||
| pdfjs-editor-resizer-label-middle-left = Middle left — 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(); |     this.#altText?.finish(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Add a toolbar for this editor. | ||||||
|  |    * @returns {Promise<EditorToolbar|null>} | ||||||
|  |    */ | ||||||
|   async addEditToolbar() { |   async addEditToolbar() { | ||||||
|     if (this.#editToolbar || this.#isInEditMode) { |     if (this.#editToolbar || this.#isInEditMode) { | ||||||
|       return; |       return this.#editToolbar; | ||||||
|     } |     } | ||||||
|     this.#editToolbar = new EditorToolbar(this); |     this.#editToolbar = new EditorToolbar(this); | ||||||
|     this.div.append(this.#editToolbar.render()); |     this.div.append(this.#editToolbar.render()); | ||||||
|     if (this.#altText) { |     if (this.#altText) { | ||||||
|       this.#editToolbar.addAltTextButton(await this.#altText.render()); |       this.#editToolbar.addAltTextButton(await this.#altText.render()); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     return this.#editToolbar; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   removeEditToolbar() { |   removeEditToolbar() { | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ import { | |||||||
| } from "../../shared/util.js"; | } from "../../shared/util.js"; | ||||||
| import { AnnotationEditor } from "./editor.js"; | import { AnnotationEditor } from "./editor.js"; | ||||||
| import { bindEvents } from "./tools.js"; | import { bindEvents } from "./tools.js"; | ||||||
|  | import { ColorPicker } from "./color_picker.js"; | ||||||
| import { Outliner } from "./outliner.js"; | import { Outliner } from "./outliner.js"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -30,7 +31,7 @@ class HighlightEditor extends AnnotationEditor { | |||||||
| 
 | 
 | ||||||
|   #clipPathId = null; |   #clipPathId = null; | ||||||
| 
 | 
 | ||||||
|   #color; |   #colorPicker = null; | ||||||
| 
 | 
 | ||||||
|   #focusOutlines = null; |   #focusOutlines = null; | ||||||
| 
 | 
 | ||||||
| @ -46,9 +47,9 @@ class HighlightEditor extends AnnotationEditor { | |||||||
| 
 | 
 | ||||||
|   #outlineId = null; |   #outlineId = null; | ||||||
| 
 | 
 | ||||||
|   static _defaultColor = "#FFF066"; |   static _defaultColor = null; | ||||||
| 
 | 
 | ||||||
|   static _defaultOpacity = 0.4; |   static _defaultOpacity = 1; | ||||||
| 
 | 
 | ||||||
|   static _l10nPromise; |   static _l10nPromise; | ||||||
| 
 | 
 | ||||||
| @ -58,7 +59,9 @@ class HighlightEditor extends AnnotationEditor { | |||||||
| 
 | 
 | ||||||
|   constructor(params) { |   constructor(params) { | ||||||
|     super({ ...params, name: "highlightEditor" }); |     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.#opacity = params.opacity || HighlightEditor._defaultOpacity; | ||||||
|     this.#boxes = params.boxes || null; |     this.#boxes = params.boxes || null; | ||||||
|     this._isDraggable = false; |     this._isDraggable = false; | ||||||
| @ -100,12 +103,9 @@ class HighlightEditor extends AnnotationEditor { | |||||||
| 
 | 
 | ||||||
|   static updateDefaultParams(type, value) { |   static updateDefaultParams(type, value) { | ||||||
|     switch (type) { |     switch (type) { | ||||||
|       case AnnotationEditorParamsType.HIGHLIGHT_COLOR: |       case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR: | ||||||
|         HighlightEditor._defaultColor = value; |         HighlightEditor._defaultColor = value; | ||||||
|         break; |         break; | ||||||
|       case AnnotationEditorParamsType.HIGHLIGHT_OPACITY: |  | ||||||
|         HighlightEditor._defaultOpacity = value / 100; |  | ||||||
|         break; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -120,22 +120,15 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|       case AnnotationEditorParamsType.HIGHLIGHT_COLOR: |       case AnnotationEditorParamsType.HIGHLIGHT_COLOR: | ||||||
|         this.#updateColor(value); |         this.#updateColor(value); | ||||||
|         break; |         break; | ||||||
|       case AnnotationEditorParamsType.HIGHLIGHT_OPACITY: |  | ||||||
|         this.#updateOpacity(value); |  | ||||||
|         break; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static get defaultPropertiesToUpdate() { |   static get defaultPropertiesToUpdate() { | ||||||
|     return [ |     return [ | ||||||
|       [ |       [ | ||||||
|         AnnotationEditorParamsType.HIGHLIGHT_COLOR, |         AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, | ||||||
|         HighlightEditor._defaultColor, |         HighlightEditor._defaultColor, | ||||||
|       ], |       ], | ||||||
|       [ |  | ||||||
|         AnnotationEditorParamsType.HIGHLIGHT_OPACITY, |  | ||||||
|         Math.round(HighlightEditor._defaultOpacity * 100), |  | ||||||
|       ], |  | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -144,11 +137,7 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|     return [ |     return [ | ||||||
|       [ |       [ | ||||||
|         AnnotationEditorParamsType.HIGHLIGHT_COLOR, |         AnnotationEditorParamsType.HIGHLIGHT_COLOR, | ||||||
|         this.#color || HighlightEditor._defaultColor, |         this.color || HighlightEditor._defaultColor, | ||||||
|       ], |  | ||||||
|       [ |  | ||||||
|         AnnotationEditorParamsType.HIGHLIGHT_OPACITY, |  | ||||||
|         Math.round(100 * (this.#opacity ?? HighlightEditor._defaultOpacity)), |  | ||||||
|       ], |       ], | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| @ -161,12 +150,14 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|     const savedColor = this.color; |     const savedColor = this.color; | ||||||
|     this.addCommands({ |     this.addCommands({ | ||||||
|       cmd: () => { |       cmd: () => { | ||||||
|         this.#color = color; |         this.color = color; | ||||||
|         this.parent.drawLayer.changeColor(this.#id, color); |         this.parent.drawLayer.changeColor(this.#id, color); | ||||||
|  |         this.#colorPicker?.updateColor(color); | ||||||
|       }, |       }, | ||||||
|       undo: () => { |       undo: () => { | ||||||
|         this.#color = savedColor; |         this.color = savedColor; | ||||||
|         this.parent.drawLayer.changeColor(this.#id, savedColor); |         this.parent.drawLayer.changeColor(this.#id, savedColor); | ||||||
|  |         this.#colorPicker?.updateColor(savedColor); | ||||||
|       }, |       }, | ||||||
|       mustExec: true, |       mustExec: true, | ||||||
|       type: AnnotationEditorParamsType.HIGHLIGHT_COLOR, |       type: AnnotationEditorParamsType.HIGHLIGHT_COLOR, | ||||||
| @ -175,27 +166,17 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** @inheritdoc */ | ||||||
|    * Update the opacity and make this action undoable. |   async addEditToolbar() { | ||||||
|    * @param {number} opacity |     const toolbar = await super.addEditToolbar(); | ||||||
|    */ |     if (!toolbar) { | ||||||
|   #updateOpacity(opacity) { |       return null; | ||||||
|     opacity /= 100; |     } | ||||||
|     const savedOpacity = this.#opacity; |     if (this._uiManager.highlightColors) { | ||||||
|     this.addCommands({ |       this.#colorPicker = new ColorPicker({ editor: this }); | ||||||
|       cmd: () => { |       toolbar.addColorPicker(this.#colorPicker); | ||||||
|         this.#opacity = opacity; |     } | ||||||
|         this.parent.drawLayer.changeOpacity(this.#id, opacity); |     return toolbar; | ||||||
|       }, |  | ||||||
|       undo: () => { |  | ||||||
|         this.#opacity = savedOpacity; |  | ||||||
|         this.parent.drawLayer.changeOpacity(this.#id, savedOpacity); |  | ||||||
|       }, |  | ||||||
|       mustExec: true, |  | ||||||
|       type: AnnotationEditorParamsType.HIGHLIGHT_OPACITY, |  | ||||||
|       overwriteIfSameType: true, |  | ||||||
|       keepUndo: true, |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @inheritdoc */ |   /** @inheritdoc */ | ||||||
| @ -286,7 +267,7 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|     ({ id: this.#id, clipPathId: this.#clipPathId } = |     ({ id: this.#id, clipPathId: this.#clipPathId } = | ||||||
|       parent.drawLayer.highlight( |       parent.drawLayer.highlight( | ||||||
|         this.#highlightOutlines, |         this.#highlightOutlines, | ||||||
|         this.#color, |         this.color, | ||||||
|         this.#opacity |         this.#opacity | ||||||
|       )); |       )); | ||||||
|     if (this.#highlightDiv) { |     if (this.#highlightDiv) { | ||||||
| @ -424,7 +405,7 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|     const editor = super.deserialize(data, parent, uiManager); |     const editor = super.deserialize(data, parent, uiManager); | ||||||
| 
 | 
 | ||||||
|     const { rect, color, quadPoints } = data; |     const { rect, color, quadPoints } = data; | ||||||
|     editor.#color = Util.makeHexColor(...color); |     editor.color = Util.makeHexColor(...color); | ||||||
|     editor.#opacity = data.opacity; |     editor.#opacity = data.opacity; | ||||||
| 
 | 
 | ||||||
|     const [pageWidth, pageHeight] = editor.pageDimensions; |     const [pageWidth, pageHeight] = editor.pageDimensions; | ||||||
| @ -452,7 +433,7 @@ class HighlightEditor extends AnnotationEditor { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const rect = this.getRect(0, 0); |     const rect = this.getRect(0, 0); | ||||||
|     const color = AnnotationEditor._colorManager.convert(this.#color); |     const color = AnnotationEditor._colorManager.convert(this.color); | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       annotationType: AnnotationEditorType.HIGHLIGHT, |       annotationType: AnnotationEditorType.HIGHLIGHT, | ||||||
|  | |||||||
| @ -18,6 +18,8 @@ import { noContextMenu } from "../display_utils.js"; | |||||||
| class EditorToolbar { | class EditorToolbar { | ||||||
|   #toolbar = null; |   #toolbar = null; | ||||||
| 
 | 
 | ||||||
|  |   #colorPicker = null; | ||||||
|  | 
 | ||||||
|   #editor; |   #editor; | ||||||
| 
 | 
 | ||||||
|   #buttons = null; |   #buttons = null; | ||||||
| @ -85,6 +87,7 @@ class EditorToolbar { | |||||||
| 
 | 
 | ||||||
|   hide() { |   hide() { | ||||||
|     this.#toolbar.classList.add("hidden"); |     this.#toolbar.classList.add("hidden"); | ||||||
|  |     this.#colorPicker?.hideDropdown(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   show() { |   show() { | ||||||
| @ -106,19 +109,28 @@ class EditorToolbar { | |||||||
|     this.#buttons.append(button); |     this.#buttons.append(button); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   addAltTextButton(button) { |  | ||||||
|     this.#addListenersToElement(button); |  | ||||||
|     this.#buttons.prepend(button, this.#divider); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   get #divider() { |   get #divider() { | ||||||
|     const divider = document.createElement("div"); |     const divider = document.createElement("div"); | ||||||
|     divider.className = "divider"; |     divider.className = "divider"; | ||||||
|     return 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() { |   remove() { | ||||||
|     this.#toolbar.remove(); |     this.#toolbar.remove(); | ||||||
|  |     this.#colorPicker?.destroy(); | ||||||
|  |     this.#colorPicker = null; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -438,7 +438,7 @@ class KeyboardManager { | |||||||
|     if (checker && !checker(self, event)) { |     if (checker && !checker(self, event)) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     callback.bind(self, ...args)(); |     callback.bind(self, ...args, event)(); | ||||||
| 
 | 
 | ||||||
|     // For example, ctrl+s in a FreeText must be handled by the viewer, hence
 |     // For example, ctrl+s in a FreeText must be handled by the viewer, hence
 | ||||||
|     // the event must bubble.
 |     // the event must bubble.
 | ||||||
| @ -545,6 +545,8 @@ class AnnotationEditorUIManager { | |||||||
| 
 | 
 | ||||||
|   #focusMainContainerTimeoutId = null; |   #focusMainContainerTimeoutId = null; | ||||||
| 
 | 
 | ||||||
|  |   #highlightColors = null; | ||||||
|  | 
 | ||||||
|   #idManager = new IdManager(); |   #idManager = new IdManager(); | ||||||
| 
 | 
 | ||||||
|   #isEnabled = false; |   #isEnabled = false; | ||||||
| @ -553,6 +555,8 @@ class AnnotationEditorUIManager { | |||||||
| 
 | 
 | ||||||
|   #lastActiveElement = null; |   #lastActiveElement = null; | ||||||
| 
 | 
 | ||||||
|  |   #mainHighlightColorPicker = null; | ||||||
|  | 
 | ||||||
|   #mode = AnnotationEditorType.NONE; |   #mode = AnnotationEditorType.NONE; | ||||||
| 
 | 
 | ||||||
|   #selectedEditors = new Set(); |   #selectedEditors = new Set(); | ||||||
| @ -607,6 +611,7 @@ class AnnotationEditorUIManager { | |||||||
|       // For example, sliders can be controlled with the arrow keys.
 |       // For example, sliders can be controlled with the arrow keys.
 | ||||||
|       return ( |       return ( | ||||||
|         self.#container.contains(document.activeElement) && |         self.#container.contains(document.activeElement) && | ||||||
|  |         document.activeElement.tagName !== "BUTTON" && | ||||||
|         self.hasSomethingToControl() |         self.hasSomethingToControl() | ||||||
|       ); |       ); | ||||||
|     }; |     }; | ||||||
| @ -736,7 +741,8 @@ class AnnotationEditorUIManager { | |||||||
|     altTextManager, |     altTextManager, | ||||||
|     eventBus, |     eventBus, | ||||||
|     pdfDocument, |     pdfDocument, | ||||||
|     pageColors |     pageColors, | ||||||
|  |     highlightColors | ||||||
|   ) { |   ) { | ||||||
|     this.#container = container; |     this.#container = container; | ||||||
|     this.#viewer = viewer; |     this.#viewer = viewer; | ||||||
| @ -749,6 +755,7 @@ class AnnotationEditorUIManager { | |||||||
|     this.#annotationStorage = pdfDocument.annotationStorage; |     this.#annotationStorage = pdfDocument.annotationStorage; | ||||||
|     this.#filterFactory = pdfDocument.filterFactory; |     this.#filterFactory = pdfDocument.filterFactory; | ||||||
|     this.#pageColors = pageColors; |     this.#pageColors = pageColors; | ||||||
|  |     this.#highlightColors = highlightColors || null; | ||||||
|     this.viewParameters = { |     this.viewParameters = { | ||||||
|       realScale: PixelsPerInch.PDF_TO_CSS_UNITS, |       realScale: PixelsPerInch.PDF_TO_CSS_UNITS, | ||||||
|       rotation: 0, |       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) { |   editAltText(editor) { | ||||||
|     this.#altTextManager?.editAltText(this, editor); |     this.#altTextManager?.editAltText(this, editor); | ||||||
|   } |   } | ||||||
| @ -1246,9 +1271,14 @@ class AnnotationEditorUIManager { | |||||||
|     if (!this.#editorTypes) { |     if (!this.#editorTypes) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     if (type === AnnotationEditorParamsType.CREATE) { | 
 | ||||||
|       this.currentLayer.addNewEditor(); |     switch (type) { | ||||||
|       return; |       case AnnotationEditorParamsType.CREATE: | ||||||
|  |         this.currentLayer.addNewEditor(); | ||||||
|  |         return; | ||||||
|  |       case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR: | ||||||
|  |         this.#mainHighlightColorPicker?.updateColor(value); | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (const editor of this.#selectedEditors) { |     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 { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js"; | ||||||
| import { AnnotationEditorUIManager } from "./display/editor/tools.js"; | import { AnnotationEditorUIManager } from "./display/editor/tools.js"; | ||||||
| import { AnnotationLayer } from "./display/annotation_layer.js"; | import { AnnotationLayer } from "./display/annotation_layer.js"; | ||||||
|  | import { ColorPicker } from "./display/editor/color_picker.js"; | ||||||
| import { DrawLayer } from "./display/draw_layer.js"; | import { DrawLayer } from "./display/draw_layer.js"; | ||||||
| import { GlobalWorkerOptions } from "./display/worker_options.js"; | import { GlobalWorkerOptions } from "./display/worker_options.js"; | ||||||
| import { Outliner } from "./display/editor/outliner.js"; | import { Outliner } from "./display/editor/outliner.js"; | ||||||
| @ -92,6 +93,7 @@ export { | |||||||
|   AnnotationMode, |   AnnotationMode, | ||||||
|   build, |   build, | ||||||
|   CMapCompressionType, |   CMapCompressionType, | ||||||
|  |   ColorPicker, | ||||||
|   createValidAbsoluteUrl, |   createValidAbsoluteUrl, | ||||||
|   DOMSVGFactory, |   DOMSVGFactory, | ||||||
|   DrawLayer, |   DrawLayer, | ||||||
|  | |||||||
| @ -87,7 +87,7 @@ const AnnotationEditorParamsType = { | |||||||
|   INK_THICKNESS: 22, |   INK_THICKNESS: 22, | ||||||
|   INK_OPACITY: 23, |   INK_OPACITY: 23, | ||||||
|   HIGHLIGHT_COLOR: 31, |   HIGHLIGHT_COLOR: 31, | ||||||
|   HIGHLIGHT_OPACITY: 32, |   HIGHLIGHT_DEFAULT_COLOR: 32, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Permission flags from Table 22, Section 7.6.3.2 of the PDF specification.
 | // 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 { AnnotationEditorLayer } from "../../src/display/editor/annotation_editor_layer.js"; | ||||||
| import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js"; | import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js"; | ||||||
| import { AnnotationLayer } from "../../src/display/annotation_layer.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 { DrawLayer } from "../../src/display/draw_layer.js"; | ||||||
| import { GlobalWorkerOptions } from "../../src/display/worker_options.js"; | import { GlobalWorkerOptions } from "../../src/display/worker_options.js"; | ||||||
| import { Outliner } from "../../src/display/editor/outliner.js"; | import { Outliner } from "../../src/display/editor/outliner.js"; | ||||||
| @ -78,6 +79,7 @@ const expectedAPI = Object.freeze({ | |||||||
|   AnnotationMode, |   AnnotationMode, | ||||||
|   build, |   build, | ||||||
|   CMapCompressionType, |   CMapCompressionType, | ||||||
|  |   ColorPicker, | ||||||
|   createValidAbsoluteUrl, |   createValidAbsoluteUrl, | ||||||
|   DOMSVGFactory, |   DOMSVGFactory, | ||||||
|   DrawLayer, |   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 { | .annotationEditorLayer { | ||||||
|   &[data-main-rotation="0"] { |   &[data-main-rotation="0"] { | ||||||
|     .highlightEditor > .editToolbar { |     .highlightEditor > .editToolbar { | ||||||
| @ -962,7 +994,144 @@ | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .editToolbar { |     .editToolbar { | ||||||
|  |       --editor-toolbar-colorpicker-arrow-image: url(images/toolbarButton-menuArrow.svg); | ||||||
|  | 
 | ||||||
|       transform-origin: center !important; |       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({ |   #bindListeners({ | ||||||
|     editorFreeTextFontSize, |     editorFreeTextFontSize, | ||||||
|     editorFreeTextColor, |     editorFreeTextColor, | ||||||
|     editorHighlightColor, |  | ||||||
|     editorHighlightOpacity, |  | ||||||
|     editorInkColor, |     editorInkColor, | ||||||
|     editorInkThickness, |     editorInkThickness, | ||||||
|     editorInkOpacity, |     editorInkOpacity, | ||||||
| @ -48,12 +46,6 @@ class AnnotationEditorParams { | |||||||
|     editorFreeTextColor.addEventListener("input", function () { |     editorFreeTextColor.addEventListener("input", function () { | ||||||
|       dispatchEvent("FREETEXT_COLOR", this.value); |       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 () { |     editorInkColor.addEventListener("input", function () { | ||||||
|       dispatchEvent("INK_COLOR", this.value); |       dispatchEvent("INK_COLOR", this.value); | ||||||
|     }); |     }); | ||||||
| @ -76,12 +68,6 @@ class AnnotationEditorParams { | |||||||
|           case AnnotationEditorParamsType.FREETEXT_COLOR: |           case AnnotationEditorParamsType.FREETEXT_COLOR: | ||||||
|             editorFreeTextColor.value = value; |             editorFreeTextColor.value = value; | ||||||
|             break; |             break; | ||||||
|           case AnnotationEditorParamsType.HIGHLIGHT_COLOR: |  | ||||||
|             editorHighlightColor.value = value; |  | ||||||
|             break; |  | ||||||
|           case AnnotationEditorParamsType.HIGHLIGHT_OPACITY: |  | ||||||
|             editorHighlightOpacity.value = value; |  | ||||||
|             break; |  | ||||||
|           case AnnotationEditorParamsType.INK_COLOR: |           case AnnotationEditorParamsType.INK_COLOR: | ||||||
|             editorInkColor.value = value; |             editorInkColor.value = value; | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -442,6 +442,7 @@ const PDFViewerApplication = { | |||||||
|       textLayerMode: AppOptions.get("textLayerMode"), |       textLayerMode: AppOptions.get("textLayerMode"), | ||||||
|       annotationMode: AppOptions.get("annotationMode"), |       annotationMode: AppOptions.get("annotationMode"), | ||||||
|       annotationEditorMode, |       annotationEditorMode, | ||||||
|  |       annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"), | ||||||
|       imageResourcesPath: AppOptions.get("imageResourcesPath"), |       imageResourcesPath: AppOptions.get("imageResourcesPath"), | ||||||
|       enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), |       enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), | ||||||
|       isOffscreenCanvasSupported, |       isOffscreenCanvasSupported, | ||||||
|  | |||||||
| @ -158,6 +158,11 @@ const defaultOptions = { | |||||||
|     value: 0, |     value: 0, | ||||||
|     kind: OptionKind.VIEWER + OptionKind.PREFERENCE, |     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: { |   historyUpdateUrl: { | ||||||
|     /** @type {boolean} */ |     /** @type {boolean} */ | ||||||
|     value: false, |     value: false, | ||||||
|  | |||||||
| @ -36,8 +36,14 @@ | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     &.highlight { |     &.highlight { | ||||||
|  |       --blend-mode: multiply; | ||||||
|  | 
 | ||||||
|  |       @media screen and (forced-colors: active) { | ||||||
|  |         --blend-mode: difference; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       position: absolute; |       position: absolute; | ||||||
|       mix-blend-mode: multiply; |       mix-blend-mode: var(--blend-mode); | ||||||
|       fill-rule: evenodd; |       fill-rule: evenodd; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -109,6 +109,8 @@ function isValidAnnotationEditorMode(mode) { | |||||||
|  * @property {number} [annotationEditorMode] - Enables the creation and editing |  * @property {number} [annotationEditorMode] - Enables the creation and editing | ||||||
|  *   of new Annotations. The constants from {@link AnnotationEditorType} should |  *   of new Annotations. The constants from {@link AnnotationEditorType} should | ||||||
|  *   be used. The default value is `AnnotationEditorType.NONE`. |  *   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 |  * @property {string} [imageResourcesPath] - Path for image resources, mainly | ||||||
|  *   mainly for annotation icons. Include trailing slash. |  *   mainly for annotation icons. Include trailing slash. | ||||||
|  * @property {boolean} [enablePrintAutoRotate] - Enables automatic rotation of |  * @property {boolean} [enablePrintAutoRotate] - Enables automatic rotation of | ||||||
| @ -202,6 +204,8 @@ class PDFViewer { | |||||||
| 
 | 
 | ||||||
|   #altTextManager = null; |   #altTextManager = null; | ||||||
| 
 | 
 | ||||||
|  |   #annotationEditorHighlightColors = null; | ||||||
|  | 
 | ||||||
|   #annotationEditorMode = AnnotationEditorType.NONE; |   #annotationEditorMode = AnnotationEditorType.NONE; | ||||||
| 
 | 
 | ||||||
|   #annotationEditorUIManager = null; |   #annotationEditorUIManager = null; | ||||||
| @ -276,6 +280,8 @@ class PDFViewer { | |||||||
|       options.annotationMode ?? AnnotationMode.ENABLE_FORMS; |       options.annotationMode ?? AnnotationMode.ENABLE_FORMS; | ||||||
|     this.#annotationEditorMode = |     this.#annotationEditorMode = | ||||||
|       options.annotationEditorMode ?? AnnotationEditorType.NONE; |       options.annotationEditorMode ?? AnnotationEditorType.NONE; | ||||||
|  |     this.#annotationEditorHighlightColors = | ||||||
|  |       options.annotationEditorHighlightColors || null; | ||||||
|     this.imageResourcesPath = options.imageResourcesPath || ""; |     this.imageResourcesPath = options.imageResourcesPath || ""; | ||||||
|     this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; |     this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; | ||||||
|     if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { |     if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { | ||||||
| @ -862,8 +868,13 @@ class PDFViewer { | |||||||
|               this.#altTextManager, |               this.#altTextManager, | ||||||
|               this.eventBus, |               this.eventBus, | ||||||
|               pdfDocument, |               pdfDocument, | ||||||
|               this.pageColors |               this.pageColors, | ||||||
|  |               this.#annotationEditorHighlightColors | ||||||
|             ); |             ); | ||||||
|  |             this.eventBus.dispatch("annotationeditoruimanager", { | ||||||
|  |               source: this, | ||||||
|  |               uiManager: this.#annotationEditorUIManager, | ||||||
|  |             }); | ||||||
|             if (mode !== AnnotationEditorType.NONE) { |             if (mode !== AnnotationEditorType.NONE) { | ||||||
|               this.#annotationEditorUIManager.updateMode(mode); |               this.#annotationEditorUIManager.updateMode(mode); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ const { | |||||||
|   AnnotationMode, |   AnnotationMode, | ||||||
|   build, |   build, | ||||||
|   CMapCompressionType, |   CMapCompressionType, | ||||||
|  |   ColorPicker, | ||||||
|   createValidAbsoluteUrl, |   createValidAbsoluteUrl, | ||||||
|   DOMSVGFactory, |   DOMSVGFactory, | ||||||
|   DrawLayer, |   DrawLayer, | ||||||
| @ -80,6 +81,7 @@ export { | |||||||
|   AnnotationMode, |   AnnotationMode, | ||||||
|   build, |   build, | ||||||
|   CMapCompressionType, |   CMapCompressionType, | ||||||
|  |   ColorPicker, | ||||||
|   createValidAbsoluteUrl, |   createValidAbsoluteUrl, | ||||||
|   DOMSVGFactory, |   DOMSVGFactory, | ||||||
|   DrawLayer, |   DrawLayer, | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { AnnotationEditorType, noContextMenu } from "pdfjs-lib"; | import { AnnotationEditorType, ColorPicker, noContextMenu } from "pdfjs-lib"; | ||||||
| import { | import { | ||||||
|   DEFAULT_SCALE, |   DEFAULT_SCALE, | ||||||
|   DEFAULT_SCALE_VALUE, |   DEFAULT_SCALE_VALUE, | ||||||
| @ -120,9 +120,24 @@ class Toolbar { | |||||||
|     // Bind the event listeners for click and various other actions.
 |     // Bind the event listeners for click and various other actions.
 | ||||||
|     this.#bindListeners(options); |     this.#bindListeners(options); | ||||||
| 
 | 
 | ||||||
|  |     if (options.editorHighlightColorPicker) { | ||||||
|  |       this.eventBus._on("annotationeditoruimanager", ({ uiManager }) => { | ||||||
|  |         this.#setAnnotationEditorUIManager( | ||||||
|  |           uiManager, | ||||||
|  |           options.editorHighlightColorPicker | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     this.reset(); |     this.reset(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   #setAnnotationEditorUIManager(uiManager, parentContainer) { | ||||||
|  |     const colorPicker = new ColorPicker({ uiManager }); | ||||||
|  |     uiManager.setMainHighlightColorPicker(colorPicker); | ||||||
|  |     parentContainer.append(colorPicker.renderMainDropdown()); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setPageNumber(pageNumber, pageLabel) { |   setPageNumber(pageNumber, pageLabel) { | ||||||
|     this.pageNumber = pageNumber; |     this.pageNumber = pageNumber; | ||||||
|     this.pageLabel = pageLabel; |     this.pageLabel = pageLabel; | ||||||
|  | |||||||
| @ -532,6 +532,11 @@ body { | |||||||
| .editorParamsToolbarContainer .editorParamsLabel { | .editorParamsToolbarContainer .editorParamsLabel { | ||||||
|   padding-inline-end: 10px; |   padding-inline-end: 10px; | ||||||
|   flex: none; |   flex: none; | ||||||
|  |   font: menu; | ||||||
|  |   font-size: 13px; | ||||||
|  |   font-style: normal; | ||||||
|  |   font-weight: 400; | ||||||
|  |   line-height: 150%; | ||||||
|   color: var(--main-color); |   color: var(--main-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -172,14 +172,9 @@ See https://github.com/adobe-type-tools/cmap-resources | |||||||
|         </div>  <!-- findbar --> |         </div>  <!-- findbar --> | ||||||
| 
 | 
 | ||||||
|         <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar"> |         <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar"> | ||||||
|           <div class="editorParamsToolbarContainer"> |           <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer"> | ||||||
|             <div class="editorParamsSetter"> |             <div id="editorHighlightColorPicker" class="colorPicker"> | ||||||
|               <label for="editorHighlightColor" class="editorParamsLabel" data-l10n-id="editor_highlight_color">Color</label> |               <span id="highlightColorPickerLabel" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span> | ||||||
|               <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> |             </div> | ||||||
|           </div> |           </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="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar"> | ||||||
|           <div class="editorParamsToolbarContainer"> |           <div class="editorParamsToolbarContainer"> | ||||||
|             <button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="107" data-l10n-id="pdfjs-editor-stamp-add-image-button"> |             <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> |             </button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
| @ -61,6 +61,9 @@ function getViewerConfiguration() { | |||||||
|       editorHighlightParamsToolbar: document.getElementById( |       editorHighlightParamsToolbar: document.getElementById( | ||||||
|         "editorHighlightParamsToolbar" |         "editorHighlightParamsToolbar" | ||||||
|       ), |       ), | ||||||
|  |       editorHighlightColorPicker: document.getElementById( | ||||||
|  |         "editorHighlightColorPicker" | ||||||
|  |       ), | ||||||
|       editorInkButton: document.getElementById("editorInk"), |       editorInkButton: document.getElementById("editorInk"), | ||||||
|       editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"), |       editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"), | ||||||
|       editorStampButton: document.getElementById("editorStamp"), |       editorStampButton: document.getElementById("editorStamp"), | ||||||
| @ -168,8 +171,6 @@ function getViewerConfiguration() { | |||||||
|     annotationEditorParams: { |     annotationEditorParams: { | ||||||
|       editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"), |       editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"), | ||||||
|       editorFreeTextColor: document.getElementById("editorFreeTextColor"), |       editorFreeTextColor: document.getElementById("editorFreeTextColor"), | ||||||
|       editorHighlightColor: document.getElementById("editorHighlightColor"), |  | ||||||
|       editorHighlightOpacity: document.getElementById("editorHighlightOpacity"), |  | ||||||
|       editorInkColor: document.getElementById("editorInkColor"), |       editorInkColor: document.getElementById("editorInkColor"), | ||||||
|       editorInkThickness: document.getElementById("editorInkThickness"), |       editorInkThickness: document.getElementById("editorInkThickness"), | ||||||
|       editorInkOpacity: document.getElementById("editorInkOpacity"), |       editorInkOpacity: document.getElementById("editorInkOpacity"), | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user