mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 06:38:07 +02:00
Merge pull request #19414 from calixteman/signature_dialog2
[Editor] Add a new dialog for the signature editor (bug 1945574)
This commit is contained in:
commit
08663f715b
16 changed files with 1596 additions and 66 deletions
|
@ -217,6 +217,7 @@ function createWebpackAlias(defines) {
|
|||
"web-preferences": "",
|
||||
"web-print_service": "",
|
||||
"web-secondary_toolbar": "web/secondary_toolbar.js",
|
||||
"web-signature_manager": "web/signature_manager.js",
|
||||
"web-toolbar": "web/toolbar.js",
|
||||
};
|
||||
|
||||
|
|
|
@ -331,6 +331,8 @@ pdfjs-editor-remove-stamp-button =
|
|||
.title = Remove image
|
||||
pdfjs-editor-remove-highlight-button =
|
||||
.title = Remove highlight
|
||||
pdfjs-editor-remove-signature-button =
|
||||
.title = Remove signature
|
||||
|
||||
##
|
||||
|
||||
|
@ -510,6 +512,7 @@ pdfjs-editor-undo-bar-message-highlight = Highlight removed
|
|||
pdfjs-editor-undo-bar-message-freetext = Text removed
|
||||
pdfjs-editor-undo-bar-message-ink = Drawing removed
|
||||
pdfjs-editor-undo-bar-message-stamp = Image removed
|
||||
pdfjs-editor-undo-bar-message-signature = Signature removed
|
||||
# Variables:
|
||||
# $count (Number) - the number of removed annotations.
|
||||
pdfjs-editor-undo-bar-message-multiple =
|
||||
|
@ -524,3 +527,61 @@ pdfjs-editor-undo-bar-undo-button-label = Undo
|
|||
pdfjs-editor-undo-bar-close-button =
|
||||
.title = Close
|
||||
pdfjs-editor-undo-bar-close-button-label = Close
|
||||
|
||||
## Add a signature dialog
|
||||
|
||||
pdfjs-editor-add-signature-dialog-label = This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.
|
||||
pdfjs-editor-add-signature-dialog-title = Add a signature
|
||||
|
||||
## Tab names
|
||||
|
||||
# Type is a verb (you can type your name as signature)
|
||||
pdfjs-editor-add-signature-type-button = Type
|
||||
.title = Type
|
||||
# Draw is a verb (you can draw your signature)
|
||||
pdfjs-editor-add-signature-draw-button = Draw
|
||||
.title = Draw
|
||||
pdfjs-editor-add-signature-image-button = Image
|
||||
.title = Image
|
||||
|
||||
## Tab panels
|
||||
|
||||
pdfjs-editor-add-signature-type-input =
|
||||
.aria-label = Type your signature
|
||||
.placeholder = Type your signature
|
||||
pdfjs-editor-add-signature-draw-placeholder = Draw your signature
|
||||
pdfjs-editor-add-signature-draw-thickness-range-label = Thickness
|
||||
|
||||
# Variables:
|
||||
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
|
||||
pdfjs-editor-add-signature-draw-thickness-range =
|
||||
.title = Drawing thickness: { $thickness }
|
||||
|
||||
pdfjs-editor-add-signature-image-placeholder = Drag a file here to upload
|
||||
pdfjs-editor-add-signature-image-browse-link =
|
||||
{ PLATFORM() ->
|
||||
[macos] Or choose image files
|
||||
*[other] Or browse image files
|
||||
}
|
||||
|
||||
## Controls
|
||||
|
||||
pdfjs-editor-add-signature-description-label = Description (alt text)
|
||||
pdfjs-editor-add-signature-description-input =
|
||||
.title = Description (alt text)
|
||||
pdfjs-editor-add-signature-description-default-when-drawing = Signature
|
||||
|
||||
|
||||
pdfjs-editor-add-signature-clear-button-label = Clear signature
|
||||
pdfjs-editor-add-signature-clear-button =
|
||||
.title = Clear signature
|
||||
pdfjs-editor-add-signature-save-checkbox = Save signature
|
||||
pdfjs-editor-add-signature-save-warning-message = You’ve reached the limit of 5 saved signatures. Remove one to save more.
|
||||
pdfjs-editor-add-signature-image-upload-error-title = Couldn’t upload image
|
||||
pdfjs-editor-add-signature-image-upload-error-description = Check your network connection or try another image.
|
||||
pdfjs-editor-add-signature-error-close-button = Close
|
||||
|
||||
## Dialog buttons
|
||||
|
||||
pdfjs-editor-add-signature-cancel-button = Cancel
|
||||
pdfjs-editor-add-signature-add-button = Add
|
||||
|
|
|
@ -318,6 +318,10 @@ class InkDrawOutline extends Outline {
|
|||
this.#computeBbox();
|
||||
}
|
||||
|
||||
get thickness() {
|
||||
return this.#thickness;
|
||||
}
|
||||
|
||||
setLastElement(element) {
|
||||
this.#lines.push(element);
|
||||
return {
|
||||
|
|
|
@ -25,11 +25,9 @@ import { AnnotationEditor } from "./editor.js";
|
|||
import { InkAnnotationElement } from "../annotation_layer.js";
|
||||
|
||||
class InkDrawingOptions extends DrawingOptions {
|
||||
#viewParameters;
|
||||
|
||||
constructor(viewerParameters) {
|
||||
super();
|
||||
this.#viewParameters = viewerParameters;
|
||||
this._viewParameters = viewerParameters;
|
||||
|
||||
super.updateProperties({
|
||||
fill: "none",
|
||||
|
@ -45,13 +43,13 @@ class InkDrawingOptions extends DrawingOptions {
|
|||
updateSVGProperty(name, value) {
|
||||
if (name === "stroke-width") {
|
||||
value ??= this["stroke-width"];
|
||||
value *= this.#viewParameters.realScale;
|
||||
value *= this._viewParameters.realScale;
|
||||
}
|
||||
super.updateSVGProperty(name, value);
|
||||
}
|
||||
|
||||
clone() {
|
||||
const clone = new InkDrawingOptions(this.#viewParameters);
|
||||
const clone = new InkDrawingOptions(this._viewParameters);
|
||||
clone.updateAll(this);
|
||||
return clone;
|
||||
}
|
||||
|
@ -284,4 +282,4 @@ class InkEditor extends DrawingEditor {
|
|||
}
|
||||
}
|
||||
|
||||
export { InkEditor };
|
||||
export { InkDrawingOptions, InkEditor };
|
||||
|
|
|
@ -16,15 +16,13 @@
|
|||
import { AnnotationEditorType, shadow } from "../../shared/util.js";
|
||||
import { DrawingEditor, DrawingOptions } from "./draw.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { ContourDrawOutline } from "./drawers/contour.js";
|
||||
import { InkDrawingOptions } from "./ink.js";
|
||||
import { SignatureExtractor } from "./drawers/signaturedraw.js";
|
||||
import { SupportedImageMimeTypes } from "../display_utils.js";
|
||||
|
||||
class SignatureOptions extends DrawingOptions {
|
||||
#viewParameters;
|
||||
|
||||
constructor(viewerParameters) {
|
||||
constructor() {
|
||||
super();
|
||||
this.#viewParameters = viewerParameters;
|
||||
|
||||
super.updateProperties({
|
||||
fill: "black",
|
||||
|
@ -33,7 +31,24 @@ class SignatureOptions extends DrawingOptions {
|
|||
}
|
||||
|
||||
clone() {
|
||||
const clone = new SignatureOptions(this.#viewParameters);
|
||||
const clone = new SignatureOptions();
|
||||
clone.updateAll(this);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
class DrawnSignatureOptions extends InkDrawingOptions {
|
||||
constructor(viewerParameters) {
|
||||
super(viewerParameters);
|
||||
|
||||
super.updateProperties({
|
||||
stroke: "black",
|
||||
"stroke-width": 1,
|
||||
});
|
||||
}
|
||||
|
||||
clone() {
|
||||
const clone = new DrawnSignatureOptions(this._viewParameters);
|
||||
clone.updateAll(this);
|
||||
return clone;
|
||||
}
|
||||
|
@ -44,6 +59,8 @@ class SignatureOptions extends DrawingOptions {
|
|||
* a signature drawing.
|
||||
*/
|
||||
class SignatureEditor extends DrawingEditor {
|
||||
#isExtracted = false;
|
||||
|
||||
static _type = "signature";
|
||||
|
||||
static _editorType = AnnotationEditorType.SIGNATURE;
|
||||
|
@ -52,13 +69,15 @@ class SignatureEditor extends DrawingEditor {
|
|||
|
||||
constructor(params) {
|
||||
super({ ...params, mustBeCommitted: true, name: "signatureEditor" });
|
||||
this._willKeepAspectRatio = false;
|
||||
this._willKeepAspectRatio = true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static initialize(l10n, uiManager) {
|
||||
AnnotationEditor.initialize(l10n, uiManager);
|
||||
this._defaultDrawingOptions = new SignatureOptions(
|
||||
|
||||
this._defaultDrawingOptions = new SignatureOptions();
|
||||
this._defaultDrawnSignatureOptions = new DrawnSignatureOptions(
|
||||
uiManager.viewParameters
|
||||
);
|
||||
}
|
||||
|
@ -88,6 +107,14 @@ class SignatureEditor extends DrawingEditor {
|
|||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
onScaleChanging() {
|
||||
if (this._drawId === null) {
|
||||
return;
|
||||
}
|
||||
super.onScaleChanging();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
render() {
|
||||
if (this.div) {
|
||||
|
@ -98,69 +125,90 @@ class SignatureEditor extends DrawingEditor {
|
|||
this.div.hidden = true;
|
||||
this.div.setAttribute("role", "figure");
|
||||
|
||||
this.#extractSignature();
|
||||
this._uiManager.getSignature(this);
|
||||
|
||||
return this.div;
|
||||
}
|
||||
|
||||
async #extractSignature() {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = SupportedImageMimeTypes.join(",");
|
||||
const signal = this._uiManager._signal;
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
addSignature(outline, heightInPage) {
|
||||
const { x: savedX, y: savedY } = this;
|
||||
this.#isExtracted = outline instanceof ContourDrawOutline;
|
||||
let drawingOptions;
|
||||
if (this.#isExtracted) {
|
||||
drawingOptions = SignatureEditor.getDefaultDrawingOptions();
|
||||
} else {
|
||||
drawingOptions = SignatureEditor._defaultDrawnSignatureOptions.clone();
|
||||
drawingOptions.updateProperties({ "stroke-width": outline.thickness });
|
||||
}
|
||||
this._addOutlines({
|
||||
drawOutlines: outline,
|
||||
drawingOptions,
|
||||
});
|
||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||
const [, pageHeight] = this.pageDimensions;
|
||||
let newHeight = heightInPage / pageHeight;
|
||||
// Ensure the signature doesn't exceed the page height.
|
||||
// If the signature is too big, we scale it down to 50% of the page height.
|
||||
newHeight = newHeight >= 1 ? 0.5 : newHeight;
|
||||
|
||||
input.addEventListener(
|
||||
"change",
|
||||
async () => {
|
||||
if (!input.files || input.files.length === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
this._uiManager.enableWaiting(true);
|
||||
const data = await this._uiManager.imageManager.getFromFile(
|
||||
input.files[0]
|
||||
);
|
||||
this._uiManager.enableWaiting(false);
|
||||
resolve(data);
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
{ signal }
|
||||
);
|
||||
input.addEventListener("cancel", resolve, { signal });
|
||||
input.click();
|
||||
this.width *= newHeight / this.height;
|
||||
this.height = newHeight;
|
||||
this.setDims(parentWidth * this.width, parentHeight * this.height);
|
||||
this.x = savedX;
|
||||
this.y = savedY;
|
||||
this.center();
|
||||
|
||||
const bitmap = await promise;
|
||||
this._onResized();
|
||||
this.onScaleChanging();
|
||||
this.rotate();
|
||||
this._uiManager.addToAnnotationStorage(this);
|
||||
|
||||
this.div.hidden = false;
|
||||
}
|
||||
|
||||
getFromImage(bitmap) {
|
||||
const {
|
||||
rawDims: { pageWidth, pageHeight },
|
||||
rotation,
|
||||
} = this.parent.viewport;
|
||||
let drawOutlines;
|
||||
if (bitmap?.bitmap) {
|
||||
drawOutlines = SignatureExtractor.process(
|
||||
bitmap.bitmap,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
rotation,
|
||||
SignatureEditor._INNER_MARGIN
|
||||
);
|
||||
} else {
|
||||
drawOutlines = SignatureExtractor.extractContoursFromText(
|
||||
"Hello PDF.js' World !!",
|
||||
{ fontStyle: "italic", fontWeight: "400", fontFamily: "cursive" },
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
rotation,
|
||||
SignatureEditor._INNER_MARGIN
|
||||
);
|
||||
}
|
||||
this._addOutlines({
|
||||
drawOutlines,
|
||||
drawingOptions: SignatureEditor.getDefaultDrawingOptions(),
|
||||
return SignatureExtractor.process(
|
||||
bitmap,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
rotation,
|
||||
SignatureEditor._INNER_MARGIN
|
||||
);
|
||||
}
|
||||
|
||||
getFromText(text, fontInfo) {
|
||||
const {
|
||||
rawDims: { pageWidth, pageHeight },
|
||||
rotation,
|
||||
} = this.parent.viewport;
|
||||
return SignatureExtractor.extractContoursFromText(
|
||||
text,
|
||||
fontInfo,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
rotation,
|
||||
SignatureEditor._INNER_MARGIN
|
||||
);
|
||||
}
|
||||
|
||||
getDrawnSignature(curves) {
|
||||
const {
|
||||
rawDims: { pageWidth, pageHeight },
|
||||
rotation,
|
||||
} = this.parent.viewport;
|
||||
return SignatureExtractor.processDrawnLines({
|
||||
lines: curves,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
rotation,
|
||||
innerMargin: SignatureEditor._INNER_MARGIN,
|
||||
mustSmooth: false,
|
||||
areContours: false,
|
||||
});
|
||||
this.onScaleChanging();
|
||||
this.rotate();
|
||||
this.div.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -664,6 +664,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#selectedTextNode = null;
|
||||
|
||||
#signatureManager = null;
|
||||
|
||||
#pageColors = null;
|
||||
|
||||
#showAllStates = null;
|
||||
|
@ -828,6 +830,7 @@ class AnnotationEditorUIManager {
|
|||
container,
|
||||
viewer,
|
||||
altTextManager,
|
||||
signatureManager,
|
||||
eventBus,
|
||||
pdfDocument,
|
||||
pageColors,
|
||||
|
@ -843,6 +846,7 @@ class AnnotationEditorUIManager {
|
|||
this.#container = container;
|
||||
this.#viewer = viewer;
|
||||
this.#altTextManager = altTextManager;
|
||||
this.#signatureManager = signatureManager;
|
||||
this._eventBus = eventBus;
|
||||
eventBus._on("editingaction", this.onEditingAction.bind(this), { signal });
|
||||
eventBus._on("pagechanging", this.onPageChanging.bind(this), { signal });
|
||||
|
@ -905,6 +909,7 @@ class AnnotationEditorUIManager {
|
|||
this.#selectedEditors.clear();
|
||||
this.#commandManager.destroy();
|
||||
this.#altTextManager?.destroy();
|
||||
this.#signatureManager?.destroy();
|
||||
this.#highlightToolbar?.hide();
|
||||
this.#highlightToolbar = null;
|
||||
this.#mainHighlightColorPicker?.destroy();
|
||||
|
@ -1003,6 +1008,10 @@ class AnnotationEditorUIManager {
|
|||
this.#altTextManager?.editAltText(this, editor, firstTime);
|
||||
}
|
||||
|
||||
getSignature(editor) {
|
||||
this.#signatureManager?.getSignature({ uiManager: this, editor });
|
||||
}
|
||||
|
||||
switchToMode(mode, callback) {
|
||||
// Switching to a mode can be asynchronous.
|
||||
this._eventBus.on("annotationeditormodechanged", callback, {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
@import url(draw_layer_builder.css);
|
||||
@import url(toggle_button.css);
|
||||
@import url(signature_manager.css);
|
||||
|
||||
:root {
|
||||
--outline-width: 2px;
|
||||
|
|
10
web/app.js
10
web/app.js
|
@ -89,6 +89,7 @@ import { PDFThumbnailViewer } from "web-pdf_thumbnail_viewer";
|
|||
import { PDFViewer } from "./pdf_viewer.js";
|
||||
import { Preferences } from "web-preferences";
|
||||
import { SecondaryToolbar } from "web-secondary_toolbar";
|
||||
import { SignatureManager } from "web-signature_manager";
|
||||
import { Toolbar } from "web-toolbar";
|
||||
import { ViewHistory } from "./view_history.js";
|
||||
|
||||
|
@ -458,6 +459,14 @@ const PDFViewerApplication = {
|
|||
this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus);
|
||||
}
|
||||
|
||||
const signatureManager = appConfig.addSignatureDialog
|
||||
? new SignatureManager(
|
||||
appConfig.addSignatureDialog,
|
||||
this.overlayManager,
|
||||
this.l10n
|
||||
)
|
||||
: null;
|
||||
|
||||
const enableHWA = AppOptions.get("enableHWA");
|
||||
const pdfViewer = new PDFViewer({
|
||||
container,
|
||||
|
@ -467,6 +476,7 @@ const PDFViewerApplication = {
|
|||
linkService: pdfLinkService,
|
||||
downloadManager,
|
||||
altTextManager,
|
||||
signatureManager,
|
||||
editorUndoBar: this.editorUndoBar,
|
||||
findController,
|
||||
scriptingManager:
|
||||
|
|
|
@ -49,6 +49,9 @@
|
|||
--button-primary-hover-fg-color: var(--button-primary-fg-color);
|
||||
--button-primary-hover-border-color: var(--button-primary-hover-bg-color);
|
||||
|
||||
--button-disabled-bg-color: color-mix(in srgb, currentcolor, transparent 60%);
|
||||
--button-disabled-fg-color: var(--button-disabled-bg-color);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--dialog-bg-color: #1c1b22;
|
||||
--dialog-border-color: #1c1b22;
|
||||
|
@ -103,6 +106,9 @@
|
|||
--button-primary-fg-color: ButtonFace;
|
||||
--button-primary-hover-bg-color: AccentColor;
|
||||
--button-primary-hover-fg-color: AccentColorText;
|
||||
|
||||
--button-disabled-bg-color: GrayText;
|
||||
--button-disabled-fg-color: ButtonFace;
|
||||
}
|
||||
|
||||
font: message-box;
|
||||
|
@ -213,6 +219,10 @@
|
|||
filter: var(--hover-filter);
|
||||
}
|
||||
|
||||
> span {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.secondaryButton {
|
||||
color: var(--button-secondary-fg-color);
|
||||
background-color: var(--button-secondary-bg-color);
|
||||
|
@ -237,6 +247,13 @@
|
|||
border-color: var(--button-primary-hover-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--button-disabled-fg-color) !important;
|
||||
background-color: var(--button-disabled-bg-color);
|
||||
border-color: var(--button-disabled-bg-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -250,6 +250,8 @@ class PDFViewer {
|
|||
|
||||
#scaleTimeoutId = null;
|
||||
|
||||
#signatureManager = null;
|
||||
|
||||
#supportsPinchToZoom = true;
|
||||
|
||||
#textLayerMode = TextLayerMode.ENABLE;
|
||||
|
@ -287,6 +289,7 @@ class PDFViewer {
|
|||
this.downloadManager = options.downloadManager || null;
|
||||
this.findController = options.findController || null;
|
||||
this.#altTextManager = options.altTextManager || null;
|
||||
this.#signatureManager = options.signatureManager || null;
|
||||
this.#editorUndoBar = options.editorUndoBar || null;
|
||||
|
||||
if (this.findController) {
|
||||
|
@ -908,6 +911,7 @@ class PDFViewer {
|
|||
this.container,
|
||||
viewer,
|
||||
this.#altTextManager,
|
||||
this.#signatureManager,
|
||||
eventBus,
|
||||
pdfDocument,
|
||||
pageColors,
|
||||
|
|
586
web/signature_manager.css
Normal file
586
web/signature_manager.css
Normal file
|
@ -0,0 +1,586 @@
|
|||
/* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
#addSignatureDialog {
|
||||
--border-color: #8f8f9d;
|
||||
--primary-color: var(--text-primary-color);
|
||||
--secondary-color: var(--text-secondary-color);
|
||||
--bg-hover: #e0e0e6;
|
||||
--tab-top-line-active-color: #0060df;
|
||||
--tab-top-line-active-hover-color: var(--tab-text-hover-color);
|
||||
--tab-top-line-hover-color: #8f8f9d;
|
||||
--tab-top-line-inactive-color: #cfcfd8;
|
||||
--tab-bottom-line-active-color: var(--tab-top-line-inactive-color);
|
||||
--tab-bottom-line-hover-color: var(--tab-top-line-inactive-color);
|
||||
--tab-bottom-line-inactive-color: var(--tab-top-line-inactive-color);
|
||||
--tab-bg: var(--dialog-bg-color);
|
||||
--tab-bg-active-color: var(--tab-bg);
|
||||
--tab-bg-active-hover-color: var(--bg-hover);
|
||||
--tab-bg-hover: var(--bg-hover);
|
||||
--tab-text-color: var(--primary-color);
|
||||
--tab-text-active-color: var(--tab-top-line-active-color);
|
||||
--tab-text-hover-color: var(--tab-text-color);
|
||||
--signature-bg: #f9f9fb;
|
||||
--signature-placeholder-color: var(--secondary-color);
|
||||
--signature-draw-placeholder-color: var(--primary-color);
|
||||
--signature-color: var(--primary-color);
|
||||
--closing-button-icon: url(images/messageBar_closingButton.svg);
|
||||
--closing-button-color: var(--primary-color);
|
||||
--description-input-color: var(--primary-color);
|
||||
--clear-signature-button-border-width: 0;
|
||||
--clear-signature-button-border-style: solid;
|
||||
--clear-signature-button-border-color: transparent;
|
||||
--clear-signature-button-border-disabled-color: transparent;
|
||||
--clear-signature-button-icon: url(images/editor-toolbar-delete.svg);
|
||||
--clear-signature-button-color: var(--primary-color);
|
||||
--clear-signature-button-hover-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-active-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-disabled-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-focus-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-bg: var(--dialog-bg-color);
|
||||
--clear-signature-button-bg-hover: var(--bg-hover);
|
||||
--clear-signature-button-bg-active: #cfcfd8;
|
||||
--clear-signature-button-bg-focus: #f0f0f4;
|
||||
--clear-signature-button-bg-disabled: color-mix(
|
||||
in srgb,
|
||||
#f0f0f4,
|
||||
transparent 40%
|
||||
);
|
||||
--save-warning-color: --var(--secondary-color);
|
||||
--thickness-bg: var(--dialog-bg-color);
|
||||
--thickness-label-color: var(--primary-color);
|
||||
--thickness-slider-color: var(--primary-color);
|
||||
--draw-cursor: url(images/cursor-editorInk.svg) 0 16, pointer;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* TODO: Update the dialog colors for dark mode but in dialog.css */
|
||||
--dialog-bg-color: #42414d;
|
||||
--bg-hover: #52525e;
|
||||
--primary-color: #fbfbfe;
|
||||
--secondary-color: #cfcfd8;
|
||||
--tab-top-line-active-color: #0df;
|
||||
--tab-top-line-inactive-color: #8f8f9d;
|
||||
--signature-bg: #2b2a33;
|
||||
--clear-signature-button-bg-active: #5b5b66;
|
||||
--clear-signature-button-bg-focus: #2b2a33;
|
||||
--clear-signature-button-bg-disabled: color-mix(
|
||||
in srgb,
|
||||
#2b2a33,
|
||||
transparent 40%
|
||||
);
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--primary-color: ButtonText;
|
||||
--secondary-color: ButtonText;
|
||||
--bg: HighlightText;
|
||||
--bg-hover: var(--bg);
|
||||
--border-color: ButtonText;
|
||||
--tab-top-line-active-color: ButtonText;
|
||||
--tab-top-line-active-hover-color: HighlightText;
|
||||
--tab-top-line-hover-color: SelectedItem;
|
||||
--tab-top-line-inactive-color: ButtonText;
|
||||
--tab-bottom-line-active-color: var(--tab-top-line-active-color);
|
||||
--tab-bottom-line-hover-color: var(--tab-top-line-hover-color);
|
||||
--tab-bg: var(--bg);
|
||||
--tab-bg-active-color: SelectedItem;
|
||||
--tab-bg-active-hover-color: SelectedItem;
|
||||
--tab-text-color: ButtonText;
|
||||
--tab-text-active-color: HighlightText;
|
||||
--tab-text-hover-color: SelectedItem;
|
||||
--signature-bg: var(--bg);
|
||||
--signature-color: ButtonText;
|
||||
--clear-signature-button-border-width: 1px;
|
||||
--clear-signature-button-border-style: solid;
|
||||
--clear-signature-button-border-color: ButtonText;
|
||||
--clear-signature-button-border-disabled-color: GrayText;
|
||||
--clear-signature-button-color: ButtonText;
|
||||
--clear-signature-button-hover-color: HighlightText;
|
||||
--clear-signature-button-active-color: SelectedItem;
|
||||
--clear-signature-button-focus-color: CanvasText;
|
||||
--clear-signature-button-disabled-color: GrayText;
|
||||
--clear-signature-button-bg: var(--bg);
|
||||
--clear-signature-button-bg-hover: SelectedItem;
|
||||
--clear-signature-button-bg-active: var(--bg);
|
||||
--clear-signature-button-bg-focus: var(--bg);
|
||||
--clear-signature-button-bg-disabled: var(--bg);
|
||||
--thickness-bg: Canvas;
|
||||
--thickness-label-color: CanvasText;
|
||||
--thickness-slider-color: ButtonText;
|
||||
}
|
||||
|
||||
width: 570px;
|
||||
max-width: 100%;
|
||||
min-width: 300px;
|
||||
padding: 16px 0;
|
||||
|
||||
#addSignatureDialogLabel {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&.waiting::after {
|
||||
content: "";
|
||||
cursor: wait;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
font: menu;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.mainContainer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
|
||||
.title {
|
||||
margin-inline-start: 16px;
|
||||
font-weight: 590;
|
||||
}
|
||||
|
||||
[role="tablist"] {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0;
|
||||
|
||||
> [role="tab"] {
|
||||
flex: 1 0 0;
|
||||
align-self: stretch;
|
||||
background-color: var(--tab-bg);
|
||||
padding-inline: 0;
|
||||
cursor: default;
|
||||
|
||||
border-inline: 0;
|
||||
border-block-width: 1px;
|
||||
border-block-style: solid;
|
||||
border-block-start-color: var(--tab-top-line-inactive-color);
|
||||
border-block-end-color: var(--tab-bottom-line-inactive-color);
|
||||
border-radius: 0;
|
||||
|
||||
font: menu;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
font-weight: 400;
|
||||
color: var(--tab-text-color);
|
||||
|
||||
&:hover {
|
||||
border-block-start-width: 2px;
|
||||
border-block-start-color: var(--tab-top-line-hover-color);
|
||||
border-block-end-color: var(--tab-bottom-line-hover-color);
|
||||
background-color: var(--tab-bg-hover);
|
||||
color: var(--tab-text-hover-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--tab-top-line-active-color);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&[aria-selected="true"] {
|
||||
border-block-start-width: 2px;
|
||||
border-block-start-color: var(--tab-top-line-active-color);
|
||||
border-block-end-color: var(--tab-bottom-line-active-color);
|
||||
background-color: var(--tab-bg-active-color);
|
||||
font-weight: 590;
|
||||
color: var(--tab-text-active-color);
|
||||
|
||||
&:hover {
|
||||
border-block-start-color: var(--tab-top-line-active-hover-color);
|
||||
background-color: var(--tab-bg-active-hover-color);
|
||||
color: var(--tab-text-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#addSignatureActionContainer {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
align-self: stretch;
|
||||
gap: 12px;
|
||||
padding-inline: 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
> [role="tabpanel"] {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
background-color: var(--signature-bg);
|
||||
border-radius: 4px;
|
||||
|
||||
> svg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&#addSignatureTypeContainer {
|
||||
display: none;
|
||||
|
||||
#addSignatureTypeInput {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
color: var(--signature-color);
|
||||
background-color: transparent;
|
||||
|
||||
font-family: "Brush script", "Apple Chancery", "Segoe script",
|
||||
"Freestyle Script", "Palace Script MT", "Brush Script MT", TK,
|
||||
cursive, serif;
|
||||
font-size: 44px;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--signature-placeholder-color);
|
||||
text-align: center;
|
||||
|
||||
font: menu;
|
||||
font-style: normal;
|
||||
font-weight: 274;
|
||||
font-size: 44px;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&#addSignatureDrawContainer {
|
||||
display: none;
|
||||
|
||||
> span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background-color: transparent;
|
||||
color: var(--signature-placeholder-color);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
> svg {
|
||||
stroke: var(--primary-color);
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 10;
|
||||
|
||||
&:hover {
|
||||
cursor: var(--draw-cursor);
|
||||
}
|
||||
}
|
||||
|
||||
#thickness {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
inset-block-end: 0;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
|
||||
> span {
|
||||
color: var(--signature-draw-placeholder-color);
|
||||
}
|
||||
|
||||
> div {
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
margin: 0;
|
||||
background-color: var(--thickness-bg);
|
||||
border-radius: 4px 4px 0 0;
|
||||
pointer-events: auto;
|
||||
|
||||
> label {
|
||||
color: var(--thickness-label-color);
|
||||
}
|
||||
|
||||
> input {
|
||||
width: 100px;
|
||||
height: 14px;
|
||||
background-color: transparent;
|
||||
|
||||
/*#if !MOZCENTRAL*/
|
||||
&::-webkit-slider-runnable-track,
|
||||
/*#endif*/
|
||||
&::-moz-range-track,
|
||||
&::-moz-range-progress {
|
||||
background-color: var(--thickness-slider-color);
|
||||
}
|
||||
|
||||
/*#if !MOZCENTRAL*/
|
||||
&::-webkit-slider-thumb,
|
||||
/*#endif*/
|
||||
&::-moz-range-thumb {
|
||||
background-color: var(--thickness-bg);
|
||||
}
|
||||
|
||||
border-radius: 4.5px;
|
||||
border: 0;
|
||||
color: var(--signature-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&#addSignatureImageContainer {
|
||||
display: none;
|
||||
|
||||
> svg {
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
fill: var(--primary-color);
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
#addSignatureImagePlaceholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#addSignatureFilePicker {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[data-selected="type"] > #addSignatureTypeContainer,
|
||||
&[data-selected="draw"] > #addSignatureDrawContainer,
|
||||
&[data-selected="image"] > #addSignatureImageContainer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#addSignatureControls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
align-self: stretch;
|
||||
|
||||
#horizontalContainer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
align-self: stretch;
|
||||
|
||||
#addSignatureDescriptionContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
flex: 1 0 0;
|
||||
|
||||
> #inputWithClearButton {
|
||||
--button-dimension: 24px;
|
||||
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 8px 4px 8px 8px;
|
||||
padding-block: 8px;
|
||||
padding-inline: 8px calc(4px + var(--button-dimension));
|
||||
box-sizing: border-box;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--description-input-color);
|
||||
}
|
||||
|
||||
#addSignatureDescriptionClearButton {
|
||||
position: absolute;
|
||||
inset-block-start: 4px;
|
||||
inset-inline-end: 4px;
|
||||
display: inline-block;
|
||||
width: var(--button-dimension);
|
||||
height: var(--button-dimension);
|
||||
background-color: var(--closing-button-color);
|
||||
mask-size: cover;
|
||||
mask-image: var(--closing-button-icon);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> label {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#clearSignatureButton {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
padding: 4px 8px;
|
||||
align-items: center;
|
||||
background-color: var(--clear-signature-button-bg);
|
||||
border-width: var(--clear-signature-button-border-width);
|
||||
border-style: var(--clear-signature-button-border-style);
|
||||
border-color: var(--clear-signature-button-border-color);
|
||||
border-radius: 4px;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
height: 24px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
|
||||
color: var(--clear-signature-button-color);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-image: var(--clear-signature-button-icon);
|
||||
mask-size: cover;
|
||||
background-color: var(--clear-signature-button-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clear-signature-button-bg-hover);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-hover-color);
|
||||
&::after {
|
||||
background-color: var(--clear-signature-button-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--clear-signature-button-bg-active);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-active-color);
|
||||
&::after {
|
||||
background-color: var(--clear-signature-button-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background-color: var(--clear-signature-button-bg-focus);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-focus-color);
|
||||
&::after {
|
||||
background-color: var(--clear-signature-button-focus-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--clear-signature-button-bg-disabled);
|
||||
border-color: var(--clear-signature-button-border-disabled-color);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-disabled-color);
|
||||
&::after {
|
||||
background-color: var(
|
||||
--clear-signature-button-disabled-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#addSignatureSaveContainer {
|
||||
display: grid;
|
||||
grid-template-columns: max-content max-content;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
|
||||
> input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> label {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#addSignatureSaveWarning {
|
||||
color: var(--save-warning-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
682
web/signature_manager.js
Normal file
682
web/signature_manager.js
Normal file
|
@ -0,0 +1,682 @@
|
|||
/* Copyright 2025 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 {
|
||||
DOMSVGFactory,
|
||||
noContextMenu,
|
||||
stopEvent,
|
||||
SupportedImageMimeTypes,
|
||||
} from "pdfjs-lib";
|
||||
|
||||
class SignatureManager {
|
||||
#addButton;
|
||||
|
||||
#tabsToAltText = null;
|
||||
|
||||
#clearButton;
|
||||
|
||||
#clearDescription;
|
||||
|
||||
#currentEditor;
|
||||
|
||||
#description;
|
||||
|
||||
#dialog;
|
||||
|
||||
#drawCurves = null;
|
||||
|
||||
#drawPlaceholder;
|
||||
|
||||
#drawPath = null;
|
||||
|
||||
#drawPathString = "";
|
||||
|
||||
#drawPoints = null;
|
||||
|
||||
#drawSVG;
|
||||
|
||||
#drawThickness;
|
||||
|
||||
#errorBar;
|
||||
|
||||
#extractedSignatureData = null;
|
||||
|
||||
#imagePath = null;
|
||||
|
||||
#imagePicker;
|
||||
|
||||
#imagePickerLink;
|
||||
|
||||
#imagePlaceholder;
|
||||
|
||||
#imageSVG;
|
||||
|
||||
#saveCheckbox;
|
||||
|
||||
#saveContainer;
|
||||
|
||||
#tabButtons;
|
||||
|
||||
#typeInput;
|
||||
|
||||
#currentTab = null;
|
||||
|
||||
#currentTabAC = null;
|
||||
|
||||
#hasDescriptionChanged = false;
|
||||
|
||||
#l10n;
|
||||
|
||||
#overlayManager;
|
||||
|
||||
#uiManager = null;
|
||||
|
||||
static #l10nDescription = null;
|
||||
|
||||
constructor(
|
||||
{
|
||||
dialog,
|
||||
panels,
|
||||
typeButton,
|
||||
typeInput,
|
||||
drawButton,
|
||||
drawPlaceholder,
|
||||
drawSVG,
|
||||
drawThickness,
|
||||
imageButton,
|
||||
imageSVG,
|
||||
imagePlaceholder,
|
||||
imagePicker,
|
||||
imagePickerLink,
|
||||
description,
|
||||
clearDescription,
|
||||
clearButton,
|
||||
cancelButton,
|
||||
addButton,
|
||||
errorCloseButton,
|
||||
errorBar,
|
||||
saveCheckbox,
|
||||
saveContainer,
|
||||
},
|
||||
overlayManager,
|
||||
l10n
|
||||
) {
|
||||
this.#addButton = addButton;
|
||||
this.#clearButton = clearButton;
|
||||
this.#clearDescription = clearDescription;
|
||||
this.#description = description;
|
||||
this.#dialog = dialog;
|
||||
this.#drawSVG = drawSVG;
|
||||
this.#drawPlaceholder = drawPlaceholder;
|
||||
this.#drawThickness = drawThickness;
|
||||
this.#errorBar = errorBar;
|
||||
this.#imageSVG = imageSVG;
|
||||
this.#imagePlaceholder = imagePlaceholder;
|
||||
this.#imagePicker = imagePicker;
|
||||
this.#imagePickerLink = imagePickerLink;
|
||||
this.#overlayManager = overlayManager;
|
||||
this.#saveCheckbox = saveCheckbox;
|
||||
this.#saveContainer = saveContainer;
|
||||
this.#typeInput = typeInput;
|
||||
this.#l10n = l10n;
|
||||
|
||||
SignatureManager.#l10nDescription ||= Object.freeze({
|
||||
signature: "pdfjs-editor-add-signature-description-default-when-drawing",
|
||||
});
|
||||
|
||||
dialog.addEventListener("close", this.#close.bind(this));
|
||||
dialog.addEventListener("contextmenu", e => {
|
||||
const { target } = e;
|
||||
if (target !== this.#typeInput && target !== this.#description) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
dialog.addEventListener("drop", e => {
|
||||
stopEvent(e);
|
||||
});
|
||||
cancelButton.addEventListener("click", this.#cancel.bind(this));
|
||||
addButton.addEventListener("click", this.#add.bind(this));
|
||||
clearButton.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
this.#initTab(null);
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
description.addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
clearDescription.disabled = description.value === "";
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
clearDescription.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
this.#description.value = "";
|
||||
clearDescription.disabled = true;
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
errorCloseButton.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
errorBar.hidden = true;
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
this.#initTabButtons(typeButton, drawButton, imageButton, panels);
|
||||
imagePicker.accept = SupportedImageMimeTypes.join(",");
|
||||
|
||||
overlayManager.register(dialog);
|
||||
}
|
||||
|
||||
#initTabButtons(typeButton, drawButton, imageButton, panels) {
|
||||
const buttons = (this.#tabButtons = new Map([
|
||||
["type", typeButton],
|
||||
["draw", drawButton],
|
||||
["image", imageButton],
|
||||
]));
|
||||
const tabCallback = e => {
|
||||
for (const [name, button] of buttons) {
|
||||
if (button === e.target) {
|
||||
button.setAttribute("aria-selected", true);
|
||||
button.setAttribute("tabindex", 0);
|
||||
panels.setAttribute("data-selected", name);
|
||||
this.#initTab(name);
|
||||
} else {
|
||||
button.setAttribute("aria-selected", false);
|
||||
// Only the active tab is focusable: the others can be
|
||||
// reached by keyboard navigation (left/right arrows).
|
||||
button.setAttribute("tabindex", -1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const buttonsArray = Array.from(buttons.values());
|
||||
for (let i = 0, ii = buttonsArray.length; i < ii; i++) {
|
||||
const button = buttonsArray[i];
|
||||
button.addEventListener("click", tabCallback, { passive: true });
|
||||
button.addEventListener(
|
||||
"keydown",
|
||||
({ key }) => {
|
||||
if (key !== "ArrowLeft" && key !== "ArrowRight") {
|
||||
return;
|
||||
}
|
||||
buttonsArray[i + (key === "ArrowLeft" ? -1 : 1)]?.focus();
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#resetCommon() {
|
||||
this.#hasDescriptionChanged = false;
|
||||
this.#description.value = "";
|
||||
this.#tabsToAltText.set(this.#currentTab, "");
|
||||
}
|
||||
|
||||
#resetTab(name) {
|
||||
switch (name) {
|
||||
case "type":
|
||||
this.#typeInput.value = "";
|
||||
break;
|
||||
case "draw":
|
||||
this.#drawCurves = null;
|
||||
this.#drawPoints = null;
|
||||
this.#drawPathString = "";
|
||||
this.#drawPath?.remove();
|
||||
this.#drawPath = null;
|
||||
this.#drawPlaceholder.hidden = false;
|
||||
this.#drawThickness.value = 1;
|
||||
break;
|
||||
case "image":
|
||||
this.#imagePlaceholder.hidden = false;
|
||||
this.#imagePath?.remove();
|
||||
this.#imagePath = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#initTab(name) {
|
||||
if (name && this.#currentTab === name) {
|
||||
return;
|
||||
}
|
||||
if (this.#currentTab) {
|
||||
this.#tabsToAltText.set(this.#currentTab, this.#description.value);
|
||||
}
|
||||
if (name) {
|
||||
this.#currentTab = name;
|
||||
}
|
||||
|
||||
const reset = !name;
|
||||
if (reset) {
|
||||
this.#resetCommon();
|
||||
} else {
|
||||
this.#description.value = this.#tabsToAltText.get(this.#currentTab);
|
||||
}
|
||||
this.#clearDescription.disabled = this.#description.value === "";
|
||||
this.#currentTabAC?.abort();
|
||||
this.#currentTabAC = new AbortController();
|
||||
switch (this.#currentTab) {
|
||||
case "type":
|
||||
this.#initTypeTab(reset);
|
||||
break;
|
||||
case "draw":
|
||||
this.#initDrawTab(reset);
|
||||
break;
|
||||
case "image":
|
||||
this.#initImageTab(reset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#disableButtons(value) {
|
||||
this.#clearButton.disabled = this.#addButton.disabled = !value;
|
||||
if (value) {
|
||||
this.#saveContainer.removeAttribute("disabled");
|
||||
} else {
|
||||
this.#saveContainer.setAttribute("disabled", true);
|
||||
}
|
||||
}
|
||||
|
||||
#initTypeTab(reset) {
|
||||
if (reset) {
|
||||
this.#resetTab("type");
|
||||
}
|
||||
|
||||
this.#disableButtons(this.#typeInput.value);
|
||||
|
||||
const { signal } = this.#currentTabAC;
|
||||
const options = { passive: true, signal };
|
||||
this.#typeInput.addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
const { value } = this.#typeInput;
|
||||
if (!this.#hasDescriptionChanged) {
|
||||
this.#description.value = value;
|
||||
this.#clearDescription.disabled = value === "";
|
||||
}
|
||||
this.#disableButtons(value);
|
||||
},
|
||||
options
|
||||
);
|
||||
this.#description.addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
this.#hasDescriptionChanged =
|
||||
this.#typeInput.value !== this.#description.value;
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
#initDrawTab(reset) {
|
||||
if (reset) {
|
||||
this.#resetTab("draw");
|
||||
}
|
||||
|
||||
this.#disableButtons(this.#drawPath);
|
||||
|
||||
const { signal } = this.#currentTabAC;
|
||||
const options = { signal };
|
||||
let currentPointerId = NaN;
|
||||
const drawCallback = e => {
|
||||
const { pointerId } = e;
|
||||
if (!isNaN(currentPointerId) && currentPointerId !== pointerId) {
|
||||
return;
|
||||
}
|
||||
currentPointerId = pointerId;
|
||||
e.preventDefault();
|
||||
this.#drawSVG.setPointerCapture(pointerId);
|
||||
|
||||
const { width: drawWidth, height: drawHeight } =
|
||||
this.#drawSVG.getBoundingClientRect();
|
||||
let { offsetX, offsetY } = e;
|
||||
offsetX = Math.round(offsetX);
|
||||
offsetY = Math.round(offsetY);
|
||||
if (e.target === this.#drawPlaceholder) {
|
||||
this.#drawPlaceholder.hidden = true;
|
||||
}
|
||||
if (!this.#drawCurves) {
|
||||
this.#drawCurves = {
|
||||
width: drawWidth,
|
||||
height: drawHeight,
|
||||
thickness: this.#drawThickness.value,
|
||||
curves: [],
|
||||
};
|
||||
this.#disableButtons(true);
|
||||
|
||||
const svgFactory = new DOMSVGFactory();
|
||||
const path = (this.#drawPath = svgFactory.createElement("path"));
|
||||
path.setAttribute("stroke-width", this.#drawThickness.value);
|
||||
this.#drawSVG.append(path);
|
||||
this.#drawSVG.addEventListener("pointerdown", drawCallback, options);
|
||||
this.#drawPlaceholder.removeEventListener("pointerdown", drawCallback);
|
||||
if (this.#description.value === "") {
|
||||
this.#l10n
|
||||
.get(SignatureManager.#l10nDescription.signature)
|
||||
.then(description => {
|
||||
this.#description.value ||= description;
|
||||
this.#clearDescription.disabled = this.#description.value === "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.#drawPoints = [offsetX, offsetY];
|
||||
this.#drawCurves.curves.push({ points: this.#drawPoints });
|
||||
this.#drawPathString += `M ${offsetX} ${offsetY}`;
|
||||
this.#drawPath.setAttribute("d", this.#drawPathString);
|
||||
|
||||
const finishDrawAC = new AbortController();
|
||||
const listenerDrawOptions = {
|
||||
signal: AbortSignal.any([signal, finishDrawAC.signal]),
|
||||
};
|
||||
this.#drawSVG.addEventListener(
|
||||
"contextmenu",
|
||||
noContextMenu,
|
||||
listenerDrawOptions
|
||||
);
|
||||
this.#drawSVG.addEventListener(
|
||||
"pointermove",
|
||||
evt => {
|
||||
evt.preventDefault();
|
||||
let { offsetX: x, offsetY: y } = evt;
|
||||
x = Math.round(x);
|
||||
y = Math.round(y);
|
||||
const drawPoints = this.#drawPoints;
|
||||
if (
|
||||
x < 0 ||
|
||||
y < 0 ||
|
||||
x > drawWidth ||
|
||||
y > drawHeight ||
|
||||
(x === drawPoints.at(-2) && y === drawPoints.at(-1))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (drawPoints.length >= 4) {
|
||||
const [x1, y1, x2, y2] = drawPoints.slice(-4);
|
||||
this.#drawPathString += `C${(x1 + 5 * x2) / 6} ${(y1 + 5 * y2) / 6} ${(5 * x2 + x) / 6} ${(5 * y2 + y) / 6} ${(x2 + x) / 2} ${(y2 + y) / 2}`;
|
||||
} else {
|
||||
this.#drawPathString += `L${x} ${y}`;
|
||||
}
|
||||
drawPoints.push(x, y);
|
||||
this.#drawPath.setAttribute("d", this.#drawPathString);
|
||||
},
|
||||
listenerDrawOptions
|
||||
);
|
||||
this.#drawSVG.addEventListener(
|
||||
"pointerup",
|
||||
evt => {
|
||||
const { pointerId: pId } = evt;
|
||||
if (!isNaN(currentPointerId) && currentPointerId !== pId) {
|
||||
return;
|
||||
}
|
||||
currentPointerId = NaN;
|
||||
evt.preventDefault();
|
||||
this.#drawSVG.releasePointerCapture(pId);
|
||||
finishDrawAC.abort();
|
||||
if (this.#drawPoints.length === 2) {
|
||||
this.#drawPathString += `L${this.#drawPoints[0]} ${this.#drawPoints[1]}`;
|
||||
this.#drawPath.setAttribute("d", this.#drawPathString);
|
||||
}
|
||||
},
|
||||
listenerDrawOptions
|
||||
);
|
||||
};
|
||||
if (this.#drawCurves) {
|
||||
this.#drawSVG.addEventListener("pointerdown", drawCallback, options);
|
||||
} else {
|
||||
this.#drawPlaceholder.addEventListener(
|
||||
"pointerdown",
|
||||
drawCallback,
|
||||
options
|
||||
);
|
||||
}
|
||||
this.#drawThickness.addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
const { value: thickness } = this.#drawThickness;
|
||||
this.#drawThickness.setAttribute(
|
||||
"data-l10n-args",
|
||||
JSON.stringify({ thickness })
|
||||
);
|
||||
if (!this.#drawCurves) {
|
||||
return;
|
||||
}
|
||||
this.#drawPath.setAttribute("stroke-width", thickness);
|
||||
this.#drawCurves.thickness = thickness;
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
#initImageTab(reset) {
|
||||
if (reset) {
|
||||
this.#resetTab("image");
|
||||
}
|
||||
|
||||
this.#disableButtons(this.#imagePath);
|
||||
|
||||
const { signal } = this.#currentTabAC;
|
||||
const options = { signal };
|
||||
const passiveOptions = { passive: true, signal };
|
||||
this.#imagePickerLink.addEventListener(
|
||||
"keydown",
|
||||
e => {
|
||||
const { key } = e;
|
||||
if (key === "Enter" || key === " ") {
|
||||
stopEvent(e);
|
||||
this.#imagePicker.click();
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
this.#imagePicker.addEventListener(
|
||||
"click",
|
||||
() => {
|
||||
this.#dialog.classList.toggle("waiting", true);
|
||||
},
|
||||
passiveOptions
|
||||
);
|
||||
this.#imagePicker.addEventListener(
|
||||
"change",
|
||||
async () => {
|
||||
const file = this.#imagePicker.files?.[0];
|
||||
if (!file || !SupportedImageMimeTypes.includes(file.type)) {
|
||||
this.#errorBar.hidden = false;
|
||||
this.#dialog.classList.toggle("waiting", false);
|
||||
return;
|
||||
}
|
||||
await this.#extractSignature(file);
|
||||
},
|
||||
passiveOptions
|
||||
);
|
||||
this.#imagePicker.addEventListener(
|
||||
"cancel",
|
||||
() => {
|
||||
this.#dialog.classList.toggle("waiting", false);
|
||||
},
|
||||
passiveOptions
|
||||
);
|
||||
this.#imagePlaceholder.addEventListener(
|
||||
"dragover",
|
||||
e => {
|
||||
const { dataTransfer } = e;
|
||||
for (const { type } of dataTransfer.items) {
|
||||
if (!SupportedImageMimeTypes.includes(type)) {
|
||||
continue;
|
||||
}
|
||||
dataTransfer.dropEffect =
|
||||
dataTransfer.effectAllowed === "copy" ? "copy" : "move";
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
dataTransfer.dropEffect = "none";
|
||||
},
|
||||
options
|
||||
);
|
||||
this.#imagePlaceholder.addEventListener(
|
||||
"drop",
|
||||
e => {
|
||||
const {
|
||||
dataTransfer: { files },
|
||||
} = e;
|
||||
if (!files?.length) {
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
if (SupportedImageMimeTypes.includes(file.type)) {
|
||||
this.#extractSignature(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
stopEvent(e);
|
||||
this.#dialog.classList.toggle("waiting", true);
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
async #extractSignature(file) {
|
||||
let data;
|
||||
try {
|
||||
data = await this.#uiManager.imageManager.getFromFile(file);
|
||||
} catch (e) {
|
||||
console.error("SignatureManager.#extractSignature.", e);
|
||||
}
|
||||
if (!data) {
|
||||
this.#errorBar.hidden = false;
|
||||
this.#dialog.classList.toggle("waiting", false);
|
||||
return;
|
||||
}
|
||||
|
||||
const outline = (this.#extractedSignatureData =
|
||||
this.#currentEditor.getFromImage(data.bitmap));
|
||||
|
||||
if (!outline) {
|
||||
this.#dialog.classList.toggle("waiting", false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.#imagePlaceholder.hidden = true;
|
||||
this.#disableButtons(true);
|
||||
|
||||
const svgFactory = new DOMSVGFactory();
|
||||
const path = (this.#imagePath = svgFactory.createElement("path"));
|
||||
this.#imageSVG.setAttribute("viewBox", outline.viewBox);
|
||||
this.#imageSVG.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
||||
this.#imageSVG.append(path);
|
||||
path.setAttribute("d", outline.toSVGPath());
|
||||
if (this.#description.value === "") {
|
||||
this.#description.value = file.name || "";
|
||||
this.#clearDescription.disabled = this.#description.value === "";
|
||||
}
|
||||
|
||||
this.#dialog.classList.toggle("waiting", false);
|
||||
}
|
||||
|
||||
#getOutlineForType() {
|
||||
return this.#currentEditor.getFromText(
|
||||
this.#typeInput.value,
|
||||
window.getComputedStyle(this.#typeInput)
|
||||
);
|
||||
}
|
||||
|
||||
#getOutlineForDraw() {
|
||||
const { width, height } = this.#drawSVG.getBoundingClientRect();
|
||||
return this.#currentEditor.getDrawnSignature(
|
||||
this.#drawCurves,
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
getSignature(params) {
|
||||
return this.open(params);
|
||||
}
|
||||
|
||||
async open({ uiManager, editor }) {
|
||||
this.#tabsToAltText ||= new Map(
|
||||
this.#tabButtons.keys().map(name => [name, ""])
|
||||
);
|
||||
this.#uiManager = uiManager;
|
||||
this.#currentEditor = editor;
|
||||
this.#uiManager.removeEditListeners();
|
||||
|
||||
await this.#overlayManager.open(this.#dialog);
|
||||
|
||||
const tabType = this.#tabButtons.get("type");
|
||||
tabType.focus();
|
||||
tabType.click();
|
||||
}
|
||||
|
||||
#cancel() {
|
||||
this.#finish();
|
||||
}
|
||||
|
||||
#finish() {
|
||||
if (this.#overlayManager.active === this.#dialog) {
|
||||
this.#overlayManager.close(this.#dialog);
|
||||
}
|
||||
}
|
||||
|
||||
#close() {
|
||||
if (this.#currentEditor._drawId === null) {
|
||||
this.#currentEditor.remove();
|
||||
}
|
||||
this.#uiManager?.addEditListeners();
|
||||
this.#currentTabAC?.abort();
|
||||
this.#currentTabAC = null;
|
||||
this.#uiManager = null;
|
||||
this.#currentEditor = null;
|
||||
|
||||
this.#resetCommon();
|
||||
for (const [name] of this.#tabButtons) {
|
||||
this.#resetTab(name);
|
||||
}
|
||||
this.#disableButtons(false);
|
||||
this.#currentTab = null;
|
||||
this.#tabsToAltText = null;
|
||||
}
|
||||
|
||||
#add() {
|
||||
let data;
|
||||
switch (this.#currentTab) {
|
||||
case "type":
|
||||
data = this.#getOutlineForType();
|
||||
break;
|
||||
case "draw":
|
||||
data = this.#getOutlineForDraw();
|
||||
break;
|
||||
case "image":
|
||||
data = this.#extractedSignatureData;
|
||||
break;
|
||||
}
|
||||
this.#currentEditor.addSignature(data, /* heightInPage */ 40);
|
||||
if (this.#saveCheckbox.checked) {
|
||||
// TODO
|
||||
}
|
||||
this.#finish();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#uiManager = null;
|
||||
this.#finish();
|
||||
}
|
||||
}
|
||||
|
||||
export { SignatureManager };
|
|
@ -27,6 +27,7 @@ const PDFPresentationMode = null;
|
|||
const PDFSidebar = null;
|
||||
const PDFThumbnailViewer = null;
|
||||
const SecondaryToolbar = null;
|
||||
const SignatureManager = null;
|
||||
|
||||
export {
|
||||
AltTextManager,
|
||||
|
@ -43,4 +44,5 @@ export {
|
|||
PDFSidebar,
|
||||
PDFThumbnailViewer,
|
||||
SecondaryToolbar,
|
||||
SignatureManager,
|
||||
};
|
||||
|
|
|
@ -85,6 +85,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
"web-preferences": "./genericcom.js",
|
||||
"web-print_service": "./pdf_print_service.js",
|
||||
"web-secondary_toolbar": "./stubs-geckoview.js",
|
||||
"web-signature_manager": "./stubs-geckoview.js",
|
||||
"web-toolbar": "./toolbar-geckoview.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
"web-preferences": "./genericcom.js",
|
||||
"web-print_service": "./pdf_print_service.js",
|
||||
"web-secondary_toolbar": "./secondary_toolbar.js",
|
||||
"web-signature_manager": "./signature_manager.js",
|
||||
"web-toolbar": "./toolbar.js"
|
||||
}
|
||||
}
|
||||
|
@ -682,6 +683,85 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="dialog" id="addSignatureDialog" aria-labelledby="addSignatureDialogLabel">
|
||||
<span id="addSignatureDialogLabel" data-l10n-id="pdfjs-editor-add-signature-dialog-label">
|
||||
This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.
|
||||
</span>
|
||||
<div id="addSignatureContainer" class="mainContainer">
|
||||
<div class="title">
|
||||
<span id="addSignatureTitle" role="sectionhead" data-l10n-id="pdfjs-editor-add-signature-dialog-title" tabindex="0" class="title">Add a signature</span>
|
||||
</div>
|
||||
<div role="tablist" id="addSignatureOptions">
|
||||
<button id="addSignatureTypeButton" type="button" role="tab" aria-selected="true" aria-controls="addSignatureTypeContainer" data-l10n-id="pdfjs-editor-add-signature-type-button" tabindex="0">
|
||||
Type
|
||||
</button>
|
||||
<button id="addSignatureDrawButton" type="button" role="tab" aria-selected="false" aria-controls="addSignatureDrawContainer" data-l10n-id="pdfjs-editor-add-signature-draw-button" tabindex="0">
|
||||
Draw
|
||||
</button>
|
||||
<button id="addSignatureImageButton" type="button" role="tab" aria-selected="false" aria-controls="addSignatureImageContainer" data-l10n-id="pdfjs-editor-add-signature-image-button" tabindex="-1">
|
||||
Image
|
||||
</button>
|
||||
</div>
|
||||
<div id="addSignatureActionContainer" data-selected="type">
|
||||
<div id="addSignatureTypeContainer" role="tabpanel" aria-labelledby="addSignatureTypeContainer">
|
||||
<input id="addSignatureTypeInput" type="text" data-l10n-id="pdfjs-editor-add-signature-type-input" aria-label="Type your signature" placeholder="Type your signature" tabindex="0"></input>
|
||||
</div>
|
||||
<div id="addSignatureDrawContainer" role="tabpanel" aria-labelledby="addSignatureDrawButton" tabindex="-1">
|
||||
<svg id="addSignatureDraw" xmlns="http://www.w3.org/2000/svg" aria-labelledby="addSignatureDrawPlaceholder"></svg>
|
||||
<span id="addSignatureDrawPlaceholder" data-l10n-id="pdfjs-editor-add-signature-draw-placeholder">Draw your signature</span>
|
||||
<div id="thickness">
|
||||
<div>
|
||||
<label for="addSignatureDrawThickness" data-l10n-id="pdfjs-editor-add-signature-draw-thickness-range-label">Thickness</label>
|
||||
<input type="range" id="addSignatureDrawThickness" min="1" max="5" step="1" value="1" data-l10n-id="pdfjs-editor-add-signature-draw-thickness-range" data-l10n-args='{ "thickness": 1 }' tabindex="0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addSignatureImageContainer" role="tabpanel" aria-labelledby="addSignatureImageButton" tabindex="-1">
|
||||
<svg id="addSignatureImage" xmlns="http://www.w3.org/2000/svg" aria-labelledby="addSignatureImagePlaceholder"></svg>
|
||||
<div id="addSignatureImagePlaceholder">
|
||||
<span data-l10n-id="pdfjs-editor-add-signature-image-placeholder">Drag a file here to upload</span>
|
||||
<label id="addSignatureImageBrowse" for="addSignatureFilePicker" tabindex="0">
|
||||
<a data-l10n-id="pdfjs-editor-add-signature-image-browse-link">Or browse image files</a>
|
||||
</label>
|
||||
<input id="addSignatureFilePicker" type="file"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addSignatureControls" disabled>
|
||||
<div id="horizontalContainer">
|
||||
<div id="addSignatureDescriptionContainer">
|
||||
<label for="addSignatureDescription" data-l10n-id="pdfjs-editor-add-signature-description-label">Description (alt text)</span></label>
|
||||
<span id="inputWithClearButton">
|
||||
<input type="text" id="addSignatureDescription" data-l10n-id="pdfjs-editor-add-signature-description-input" data-l10n-args='{ "default": "", "useDefault": true }' tabindex="0"></input>
|
||||
<button id="addSignatureDescriptionClearButton" type="button" tabindex="0" aria-hidden="true"></button>
|
||||
</span>
|
||||
</div>
|
||||
<button id="clearSignatureButton" type="button" data-l10n-id="pdfjs-editor-add-signature-clear-button" tabindex="0"><span data-l10n-id="pdfjs-editor-add-signature-clear-button-label">Clear signature</span></button>
|
||||
</div>
|
||||
<div id="addSignatureSaveContainer">
|
||||
<input type="checkbox" id="addSignatureSaveCheckbox" checked="true" tabindex="0"></input>
|
||||
<label for="addSignatureSaveCheckbox" data-l10n-id="pdfjs-editor-add-signature-save-checkbox">Save signature</label>
|
||||
<span></span>
|
||||
<span id="addSignatureSaveWarning" hidden="true" data-l10n-id="pdfjs-editor-add-signature-save-warning-message">You’ve reached the limit of 5 saved signatures. Remove one to save more.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addSignatureError" hidden="true" class="messageBar">
|
||||
<div>
|
||||
<div>
|
||||
<span class="title" data-l10n-id="pdfjs-editor-add-signature-image-upload-error-title">Couldn’t upload image</span>
|
||||
<span class="description" data-l10n-id="pdfjs-editor-add-signature-image-upload-error-description">Check your network connection or try another image.</span>
|
||||
</div>
|
||||
<button id="addSignatureErrorCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span data-l10n-id="pdfjs-editor-add-signature-error-close-button">Close</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="buttons" class="dialogButtonsGroup">
|
||||
<button id="addSignatureCancelButton" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-add-signature-cancel-button">Cancel</span></button>
|
||||
<button id="addSignatureAddButton" type="button" class="primaryButton" disabled tabindex="0"><span data-l10n-id="pdfjs-editor-add-signature-add-button">Add</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!--#if !MOZCENTRAL-->
|
||||
<dialog id="printServiceDialog" style="min-width: 200px;">
|
||||
<div class="row">
|
||||
|
|
|
@ -214,6 +214,32 @@ function getViewerConfiguration() {
|
|||
),
|
||||
closeButton: document.getElementById("altTextSettingsCloseButton"),
|
||||
},
|
||||
addSignatureDialog: {
|
||||
dialog: document.getElementById("addSignatureDialog"),
|
||||
panels: document.getElementById("addSignatureActionContainer"),
|
||||
typeButton: document.getElementById("addSignatureTypeButton"),
|
||||
typeInput: document.getElementById("addSignatureTypeInput"),
|
||||
drawButton: document.getElementById("addSignatureDrawButton"),
|
||||
drawSVG: document.getElementById("addSignatureDraw"),
|
||||
drawPlaceholder: document.getElementById("addSignatureDrawPlaceholder"),
|
||||
drawThickness: document.getElementById("addSignatureDrawThickness"),
|
||||
imageButton: document.getElementById("addSignatureImageButton"),
|
||||
imageSVG: document.getElementById("addSignatureImage"),
|
||||
imagePlaceholder: document.getElementById("addSignatureImagePlaceholder"),
|
||||
imagePicker: document.getElementById("addSignatureFilePicker"),
|
||||
imagePickerLink: document.getElementById("addSignatureImageBrowse"),
|
||||
description: document.getElementById("addSignatureDescription"),
|
||||
clearDescription: document.getElementById(
|
||||
"addSignatureDescriptionClearButton"
|
||||
),
|
||||
clearButton: document.getElementById("clearSignatureButton"),
|
||||
saveContainer: document.getElementById("addSignatureSaveContainer"),
|
||||
saveCheckbox: document.getElementById("addSignatureSaveCheckbox"),
|
||||
errorBar: document.getElementById("addSignatureError"),
|
||||
errorCloseButton: document.getElementById("addSignatureErrorCloseButton"),
|
||||
cancelButton: document.getElementById("addSignatureCancelButton"),
|
||||
addButton: document.getElementById("addSignatureAddButton"),
|
||||
},
|
||||
annotationEditorParams: {
|
||||
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
|
||||
editorFreeTextColor: document.getElementById("editorFreeTextColor"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue