1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 22:58:07 +02:00

[Editor] Add a new editor to highlight some text in a pdf (bug 1866119)

This patch is first big step for the new highlight feature.
Few patches will follow in order to conform to the specs UX/UI gave us.
This commit is contained in:
Calixte Denizet 2023-11-22 19:02:42 +01:00
parent 4bf7ff2027
commit 1ea6293923
19 changed files with 897 additions and 56 deletions

View file

@ -13,6 +13,8 @@
* limitations under the License.
*/
@import url(draw_layer_builder.css);
:root {
--outline-width: 2px;
--outline-color: #0060df;
@ -188,7 +190,10 @@
border: var(--focus-outline-around);
}
}
}
.annotationEditorLayer
:is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor) {
.editToolbar {
--editor-toolbar-delete-image: url(images/editor-toolbar-delete.svg);
--editor-toolbar-bg-color: #f0f0f4;
@ -198,6 +203,7 @@
--editor-toolbar-active-bg-color: #cfcfd8;
--editor-toolbar-focus-outline-color: #0060df;
--editor-toolbar-shadow: 0 2px 6px 0 rgb(58 57 68 / 0.2);
--editor-toolbar-vert-offset: 6px;
@media (prefers-color-scheme: dark) {
--editor-toolbar-bg-color: #2b2a33;
@ -225,10 +231,11 @@
justify-content: center;
align-items: center;
cursor: default;
pointer-events: auto;
position: absolute;
inset-inline-end: 0;
inset-block-start: calc(100% + 6px);
inset-block-start: calc(100% + var(--editor-toolbar-vert-offset));
border-radius: 4px;
background-color: var(--editor-toolbar-bg-color);
@ -316,7 +323,7 @@
height: 100%;
}
.annotationEditorLayer .freeTextEditor .overlay.enabled {
.annotationEditorLayer freeTextEditor .overlay.enabled {
display: block;
}
@ -513,12 +520,12 @@
rotate: 270deg;
&:dir(ltr) {
inset-inline-start: calc(100% + 6px);
inset-inline-start: calc(100% + var(--editor-toolbar-vert-offset));
inset-block-start: 0;
}
&:dir(rtl) {
inset-inline-end: calc(100% + 6px);
inset-inline-end: calc(100% + var(--editor-toolbar-vert-offset));
inset-block-end: 0;
inset-block-start: unset;
}
@ -547,7 +554,7 @@
.editToolbar {
rotate: 180deg;
inset-inline-start: 0;
inset-block-end: calc(100% + 6px);
inset-block-end: calc(100% + var(--editor-toolbar-vert-offset));
inset-block-start: unset;
}
}
@ -585,13 +592,13 @@
rotate: 90deg;
&:dir(ltr) {
inset-inline-end: calc(100% + 6px);
inset-inline-end: calc(100% + var(--editor-toolbar-vert-offset));
inset-block-end: 0;
inset-block-start: unset;
}
&:dir(rtl) {
inset-inline-start: calc(100% + 6px);
inset-inline-start: calc(100% + var(--editor-toolbar-vert-offset));
inset-block-start: 0;
}
}
@ -1000,3 +1007,62 @@
}
}
}
.annotationEditorLayer {
&[data-main-rotation="0"] {
.highlightEditor > .editToolbar {
rotate: 0deg;
}
}
&[data-main-rotation="90"] {
.highlightEditor > .editToolbar {
rotate: 270deg;
}
}
&[data-main-rotation="180"] {
.highlightEditor > .editToolbar {
rotate: 180deg;
}
}
&[data-main-rotation="270"] {
.highlightEditor > .editToolbar {
rotate: 90deg;
}
}
.highlightEditor {
position: absolute;
background: transparent;
z-index: 1;
transform-origin: 0 0;
cursor: auto;
max-width: 100%;
max-height: 100%;
border: none;
outline: none;
pointer-events: none;
transform: none;
.internal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: auto;
}
&.selectedEditor {
.internal {
cursor: pointer;
}
}
.editToolbar {
transform-origin: center;
}
}
}

View file

@ -35,11 +35,17 @@ import { NullL10n } from "web-l10n_utils";
* @property {IL10n} [l10n]
* @property {TextAccessibilityManager} [accessibilityManager]
* @property {AnnotationLayer} [annotationLayer]
* @property {TextLayer} [textLayer]
* @property {DrawLayer} [drawLayer]
*/
class AnnotationEditorLayerBuilder {
#annotationLayer = null;
#drawLayer = null;
#textLayer = null;
#uiManager;
/**
@ -55,6 +61,8 @@ class AnnotationEditorLayerBuilder {
this._cancelled = false;
this.#uiManager = options.uiManager;
this.#annotationLayer = options.annotationLayer || null;
this.#textLayer = options.textLayer || null;
this.#drawLayer = options.drawLayer || null;
}
/**
@ -93,6 +101,8 @@ class AnnotationEditorLayerBuilder {
l10n: this.l10n,
viewport: clonedViewport,
annotationLayer: this.#annotationLayer,
textLayer: this.#textLayer,
drawLayer: this.#drawLayer,
});
const parameters = {

View file

@ -28,6 +28,8 @@ class AnnotationEditorParams {
#bindListeners({
editorFreeTextFontSize,
editorFreeTextColor,
editorHighlightColor,
editorHighlightOpacity,
editorInkColor,
editorInkThickness,
editorInkOpacity,
@ -46,6 +48,12 @@ 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);
});
@ -68,6 +76,12 @@ 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

@ -486,6 +486,11 @@ const PDFViewerApplication = {
appConfig.toolbar?.editorStampButton?.classList.add("hidden");
}
const editorHighlightButton = appConfig.toolbar?.editorHighlightButton;
if (editorHighlightButton && AppOptions.get("enableHighlightEditor")) {
editorHighlightButton.hidden = false;
}
this.annotationEditorParams = new AnnotationEditorParams(
appConfig.annotationEditorParams,
eventBus

View file

@ -125,6 +125,14 @@ const defaultOptions = {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
enableHighlightEditor: {
// We'll probably want to make some experiments before enabling this
// in Firefox release, but it has to be temporary.
// TODO: remove it when unnecessary.
/** @type {boolean} */
value: typeof PDFJSDev === "undefined",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
enablePermissions: {
/** @type {boolean} */
value: false,

View file

@ -13,13 +13,11 @@
* limitations under the License.
*/
/** @typedef {import("../src/display/draw_layer.js").DrawLayer} DrawLayer */
import { DrawLayer } from "pdfjs-lib";
/**
* @typedef {Object} DrawLayerBuilderOptions
* @property {DrawLayer} [drawLayer]
* @property {number} pageIndex
*/
class DrawLayerBuilder {
@ -53,6 +51,14 @@ class DrawLayerBuilder {
this.#drawLayer.destroy();
this.#drawLayer = null;
}
setParent(parent) {
this.#drawLayer?.setParent(parent);
}
getDrawLayer() {
return this.#drawLayer;
}
}
export { DrawLayerBuilder };

View file

@ -0,0 +1,6 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.10918 11.66C7.24918 11.8 7.43918 11.88 7.63918 11.88C7.83918 11.88 8.02918 11.8 8.16918 11.66L14.9192 4.91C15.2692 4.57 15.4592 4.11 15.4592 3.62C15.4592 3.13 15.2692 2.67 14.9192 2.33L13.1292 0.54C12.7892 0.19 12.3292 0 11.8392 0C11.3492 0 10.8892 0.2 10.5492 0.54L3.79918 7.29C3.50918 7.58 3.50918 8.06 3.79918 8.35L4.38988 8.9407L1.40918 11.93H5.64918L6.51419 11.065L7.10918 11.66ZM7.63918 10.07L5.38918 7.82V7.81L7.8648 5.33438L10.1198 7.58938L7.63918 10.07ZM11.1805 6.52872L13.8592 3.85C13.9892 3.72 13.9892 3.52 13.8592 3.39L12.0692 1.6C11.9892 1.52 11.8892 1.5 11.8392 1.5C11.8392 1.5 11.6892 1.51 11.6092 1.59L8.92546 4.27372L11.1805 6.52872Z" fill="#000"/>
<path d="M0.40918 14H15.4092V16H0.40918V14Z" fill="#000"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 910 B

View file

@ -42,6 +42,7 @@ import {
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
import { compatibilityParams } from "./app_options.js";
import { DrawLayerBuilder } from "./draw_layer_builder.js";
import { NullL10n } from "web-l10n_utils";
import { SimpleLinkService } from "./pdf_link_service.js";
import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
@ -177,6 +178,7 @@ class PDFPageView {
this.zoomLayer = null;
this.xfaLayer = null;
this.structTreeLayer = null;
this.drawLayer = null;
const div = document.createElement("div");
div.className = "page";
@ -354,6 +356,14 @@ class PDFPageView {
}
}
async #renderDrawLayer() {
try {
await this.drawLayer.render("display");
} catch (ex) {
console.error(`#renderDrawLayer: "${ex}".`);
}
}
async #renderXfaLayer() {
let error = null;
try {
@ -718,6 +728,10 @@ class PDFPageView {
this.annotationEditorLayer &&
(!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
) {
if (this.drawLayer) {
this.drawLayer.cancel();
this.drawLayer = null;
}
this.annotationEditorLayer.cancel();
this.annotationEditorLayer = null;
}
@ -770,6 +784,9 @@ class PDFPageView {
this.#renderAnnotationLayer();
}
if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
if (this.drawLayer) {
this.#renderDrawLayer();
}
this.#renderAnnotationEditorLayer();
}
if (redrawXfaLayer && this.xfaLayer) {
@ -1001,12 +1018,19 @@ class PDFPageView {
await this.#renderAnnotationLayer();
}
if (!this.annotationEditorLayer) {
const { annotationEditorUIManager } = this.#layerProperties;
const { annotationEditorUIManager } = this.#layerProperties;
if (!annotationEditorUIManager) {
return;
}
if (!annotationEditorUIManager) {
return;
}
this.drawLayer ||= new DrawLayerBuilder({
pageIndex: this.id,
});
await this.#renderDrawLayer();
this.drawLayer.setParent(canvasWrapper);
if (!this.annotationEditorLayer) {
this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
uiManager: annotationEditorUIManager,
pageDiv: div,
@ -1014,6 +1038,8 @@ class PDFPageView {
l10n,
accessibilityManager: this._accessibilityManager,
annotationLayer: this.annotationLayer?.annotationLayer,
textLayer: this.textLayer,
drawLayer: this.drawLayer.getDrawLayer(),
});
}
this.#renderAnnotationEditorLayer();

View file

@ -69,6 +69,18 @@ class Toolbar {
},
},
},
{
element: options.editorHighlightButton,
eventName: "switchannotationeditormode",
eventDetails: {
get mode() {
const { classList } = options.editorHighlightButton;
return classList.contains("toggled")
? AnnotationEditorType.NONE
: AnnotationEditorType.HIGHLIGHT;
},
},
},
{
element: options.editorInkButton,
eventName: "switchannotationeditormode",
@ -202,6 +214,8 @@ class Toolbar {
#bindEditorToolsListener({
editorFreeTextButton,
editorFreeTextParamsToolbar,
editorHighlightButton,
editorHighlightParamsToolbar,
editorInkButton,
editorInkParamsToolbar,
editorStampButton,
@ -213,6 +227,11 @@ class Toolbar {
mode === AnnotationEditorType.FREETEXT,
editorFreeTextParamsToolbar
);
toggleCheckedBtn(
editorHighlightButton,
mode === AnnotationEditorType.HIGHLIGHT,
editorHighlightParamsToolbar
);
toggleCheckedBtn(
editorInkButton,
mode === AnnotationEditorType.INK,
@ -226,6 +245,7 @@ class Toolbar {
const isDisable = mode === AnnotationEditorType.DISABLE;
editorFreeTextButton.disabled = isDisable;
editorHighlightButton.disabled = isDisable;
editorInkButton.disabled = isDisable;
editorStampButton.disabled = isDisable;
};

View file

@ -81,6 +81,7 @@
--treeitem-expanded-icon: url(images/treeitem-expanded.svg);
--treeitem-collapsed-icon: url(images/treeitem-collapsed.svg);
--toolbarButton-editorFreeText-icon: url(images/toolbarButton-editorFreeText.svg);
--toolbarButton-editorHighlight-icon: url(images/toolbarButton-editorHighlight.svg);
--toolbarButton-editorInk-icon: url(images/toolbarButton-editorInk.svg);
--toolbarButton-editorStamp-icon: url(images/toolbarButton-editorStamp.svg);
--toolbarButton-menuArrow-icon: url(images/toolbarButton-menuArrow.svg);
@ -584,6 +585,10 @@ body {
inset-inline-end: calc(var(--editor-toolbar-base-offset) + 56px);
}
#editorHighlightParamsToolbar {
inset-inline-end: calc(var(--editor-toolbar-base-offset) + 84px);
}
#editorStampAddImage::before {
mask-image: var(--editorParams-stampAddImage-icon);
}
@ -903,6 +908,10 @@ body {
mask-image: var(--toolbarButton-editorFreeText-icon);
}
#editorHighlight::before {
mask-image: var(--toolbarButton-editorHighlight-icon);
}
#editorInk::before {
mask-image: var(--toolbarButton-editorInk-icon);
}

View file

@ -171,15 +171,28 @@ See https://github.com/adobe-type-tools/cmap-resources
</div>
</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>
</div>
</div>
<div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
<div class="editorParamsToolbarContainer">
<div class="editorParamsSetter">
<label for="editorFreeTextColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
<input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="100">
<input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="102">
</div>
<div class="editorParamsSetter">
<label for="editorFreeTextFontSize" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
<input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="101">
<input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="103">
</div>
</div>
</div>
@ -188,22 +201,22 @@ See https://github.com/adobe-type-tools/cmap-resources
<div class="editorParamsToolbarContainer">
<div class="editorParamsSetter">
<label for="editorInkColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
<input type="color" id="editorInkColor" class="editorParamsColor" tabindex="102">
<input type="color" id="editorInkColor" class="editorParamsColor" tabindex="104">
</div>
<div class="editorParamsSetter">
<label for="editorInkThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
<input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="103">
<input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="105">
</div>
<div class="editorParamsSetter">
<label for="editorInkOpacity" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
<input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="104">
<input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="106">
</div>
</div>
</div>
<div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar">
<div class="editorParamsToolbarContainer">
<button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="105" 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>
</button>
</div>
@ -334,13 +347,15 @@ See https://github.com/adobe-type-tools/cmap-resources
</div>
<div id="toolbarViewerRight">
<div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup">
<button id="editorFreeText" class="toolbarButton" disabled="disabled" title="Text" role="radio" aria-checked="false" aria-controls="editorFreeTextParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-free-text-button">
<button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight">
<span data-l10n-id="pdfjs-editor-highlight-label">Highlight</span>
<button id="editorFreeText" class="toolbarButton" disabled="disabled" title="Text" role="radio" aria-checked="false" aria-controls="editorFreeTextParamsToolbar" tabindex="32" data-l10n-id="pdfjs-editor-free-text-button">
<span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
</button>
<button id="editorInk" class="toolbarButton" disabled="disabled" title="Draw" role="radio" aria-checked="false" aria-controls="editorInkParamsToolbar" tabindex="32" data-l10n-id="pdfjs-editor-ink-button">
<button id="editorInk" class="toolbarButton" disabled="disabled" title="Draw" role="radio" aria-checked="false" aria-controls="editorInkParamsToolbar" tabindex="33" data-l10n-id="pdfjs-editor-ink-button">
<span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
</button>
<button id="editorStamp" class="toolbarButton" disabled="disabled" title="Add or edit images" role="radio" aria-checked="false" aria-controls="editorStampParamsToolbar" tabindex="33" data-l10n-id="pdfjs-editor-stamp-button">
<button id="editorStamp" class="toolbarButton" disabled="disabled" title="Add or edit images" role="radio" aria-checked="false" aria-controls="editorStampParamsToolbar" tabindex="34" data-l10n-id="pdfjs-editor-stamp-button">
<span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
</button>
</div>

View file

@ -57,6 +57,10 @@ function getViewerConfiguration() {
editorFreeTextParamsToolbar: document.getElementById(
"editorFreeTextParamsToolbar"
),
editorHighlightButton: document.getElementById("editorHighlight"),
editorHighlightParamsToolbar: document.getElementById(
"editorHighlightParamsToolbar"
),
editorInkButton: document.getElementById("editorInk"),
editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
editorStampButton: document.getElementById("editorStamp"),
@ -164,6 +168,8 @@ 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"),