mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 06:38:07 +02:00
[Editor] Populate the 'Add signature' menu with the saved signatures (bug 1947828)
This commit is contained in:
parent
d6f63d0e4b
commit
68451fe17e
15 changed files with 474 additions and 67 deletions
|
@ -320,6 +320,9 @@ pdfjs-highlight-floating-button1 =
|
|||
.title = Highlight
|
||||
.aria-label = Highlight
|
||||
pdfjs-highlight-floating-button-label = Highlight
|
||||
pdfjs-editor-signature-button =
|
||||
.title = Add signature
|
||||
pdfjs-editor-signature-button-label = Add signature
|
||||
|
||||
## Remove button for the various kind of editor.
|
||||
|
||||
|
@ -349,6 +352,9 @@ pdfjs-editor-stamp-add-image-button-label = Add image
|
|||
pdfjs-editor-free-highlight-thickness-input = Thickness
|
||||
pdfjs-editor-free-highlight-thickness-title =
|
||||
.title = Change thickness when highlighting items other than text
|
||||
pdfjs-editor-signature-add-signature-button =
|
||||
.title = Add new signature
|
||||
pdfjs-editor-signature-add-signature-button-label = Add new signature
|
||||
|
||||
# .default-content is used as a placeholder in an empty text editor.
|
||||
pdfjs-free-text2 =
|
||||
|
@ -585,3 +591,9 @@ pdfjs-editor-add-signature-error-close-button = Close
|
|||
|
||||
pdfjs-editor-add-signature-cancel-button = Cancel
|
||||
pdfjs-editor-add-signature-add-button = Add
|
||||
|
||||
## Main menu for adding/removing signatures
|
||||
|
||||
pdfjs-editor-delete-signature-button =
|
||||
.title = Remove signature
|
||||
pdfjs-editor-delete-signature-button-label = Remove signature
|
||||
|
|
|
@ -698,8 +698,12 @@ class AnnotationEditorLayer {
|
|||
/**
|
||||
* Create and add a new editor.
|
||||
*/
|
||||
addNewEditor() {
|
||||
this.createAndAddNewEditor(this.#getCenterPoint(), /* isCentered = */ true);
|
||||
addNewEditor(data = {}) {
|
||||
this.createAndAddNewEditor(
|
||||
this.#getCenterPoint(),
|
||||
/* isCentered = */ true,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -784,7 +784,7 @@ class SignatureExtractor {
|
|||
let data = null;
|
||||
let offset = 0;
|
||||
for await (const chunk of readable) {
|
||||
data ||= new Uint8Array(new Uint32Array(chunk.buffer)[0]);
|
||||
data ||= new Uint8Array(new Uint32Array(chunk.buffer, 0, 4)[0]);
|
||||
data.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class SignatureOptions extends DrawingOptions {
|
|||
super();
|
||||
|
||||
super.updateProperties({
|
||||
fill: "black",
|
||||
fill: "CanvasText",
|
||||
"stroke-width": 0,
|
||||
});
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class DrawnSignatureOptions extends InkDrawingOptions {
|
|||
super(viewerParameters);
|
||||
|
||||
super.updateProperties({
|
||||
stroke: "black",
|
||||
stroke: "CanvasText",
|
||||
"stroke-width": 1,
|
||||
});
|
||||
}
|
||||
|
@ -62,6 +62,12 @@ class DrawnSignatureOptions extends InkDrawingOptions {
|
|||
class SignatureEditor extends DrawingEditor {
|
||||
#isExtracted = false;
|
||||
|
||||
#signatureData = null;
|
||||
|
||||
#description = null;
|
||||
|
||||
#signatureUUID = null;
|
||||
|
||||
static _type = "signature";
|
||||
|
||||
static _editorType = AnnotationEditorType.SIGNATURE;
|
||||
|
@ -71,8 +77,7 @@ class SignatureEditor extends DrawingEditor {
|
|||
constructor(params) {
|
||||
super({ ...params, mustBeCommitted: true, name: "signatureEditor" });
|
||||
this._willKeepAspectRatio = true;
|
||||
this._description = "";
|
||||
this._signatureUUID = null;
|
||||
this.#signatureData = params.signatureData || null;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -128,17 +133,52 @@ class SignatureEditor extends DrawingEditor {
|
|||
this.div.setAttribute("role", "figure");
|
||||
|
||||
if (this._drawId === null) {
|
||||
if (this.#signatureData) {
|
||||
const {
|
||||
lines,
|
||||
mustSmooth,
|
||||
areContours,
|
||||
description,
|
||||
uuid,
|
||||
heightInPage,
|
||||
} = this.#signatureData;
|
||||
const {
|
||||
rawDims: { pageWidth, pageHeight },
|
||||
rotation,
|
||||
} = this.parent.viewport;
|
||||
const outline = SignatureExtractor.processDrawnLines({
|
||||
lines,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
rotation,
|
||||
innerMargin: SignatureEditor._INNER_MARGIN,
|
||||
mustSmooth,
|
||||
areContours,
|
||||
});
|
||||
this.#signatureData = null;
|
||||
this.#signatureUUID = uuid;
|
||||
this.addSignature(outline.outline, heightInPage, description);
|
||||
} else {
|
||||
this.div.hidden = true;
|
||||
this._uiManager.getSignature(this);
|
||||
}
|
||||
}
|
||||
|
||||
return this.div;
|
||||
}
|
||||
|
||||
setUuid(uuid) {
|
||||
this.#signatureUUID = uuid;
|
||||
}
|
||||
|
||||
setDescription(description) {
|
||||
this.#description = description;
|
||||
}
|
||||
|
||||
addSignature(outline, heightInPage, description) {
|
||||
const { x: savedX, y: savedY } = this;
|
||||
this.#isExtracted = outline instanceof ContourDrawOutline;
|
||||
this._description = description;
|
||||
this.#description = description;
|
||||
let drawingOptions;
|
||||
if (this.#isExtracted) {
|
||||
drawingOptions = SignatureEditor.getDefaultDrawingOptions();
|
||||
|
@ -251,11 +291,12 @@ class SignatureEditor extends DrawingEditor {
|
|||
};
|
||||
if (isForCopying) {
|
||||
serialized.paths = { lines, points };
|
||||
serialized.uuid = this.#signatureUUID;
|
||||
} else {
|
||||
serialized.lines = lines;
|
||||
}
|
||||
if (this._description) {
|
||||
serialized.accessibilityData = { type: "Figure", alt: this._description };
|
||||
if (this.#description) {
|
||||
serialized.accessibilityData = { type: "Figure", alt: this.#description };
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
@ -294,7 +335,8 @@ class SignatureEditor extends DrawingEditor {
|
|||
static async deserialize(data, parent, uiManager) {
|
||||
const editor = await super.deserialize(data, parent, uiManager);
|
||||
editor.#isExtracted = data.areContours;
|
||||
editor._description = data.accessibilityData?.alt || "";
|
||||
editor.#description = data.accessibilityData?.alt || "";
|
||||
editor.#signatureUUID = data.uuid;
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1698,6 +1698,9 @@ class AnnotationEditorUIManager {
|
|||
this.#updateModeCapability.resolve();
|
||||
return;
|
||||
}
|
||||
if (mode === AnnotationEditorType.SIGNATURE) {
|
||||
await this.#signatureManager?.loadSignatures();
|
||||
}
|
||||
this.setEditingState(true);
|
||||
await this.#enableAll();
|
||||
this.unselectAll();
|
||||
|
@ -1758,7 +1761,7 @@ class AnnotationEditorUIManager {
|
|||
|
||||
switch (type) {
|
||||
case AnnotationEditorParamsType.CREATE:
|
||||
this.currentLayer.addNewEditor();
|
||||
this.currentLayer.addNewEditor(value);
|
||||
return;
|
||||
case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
|
||||
this.#mainHighlightColorPicker?.updateColor(value);
|
||||
|
|
|
@ -465,9 +465,12 @@ const PDFViewerApplication = {
|
|||
AppOptions.get("enableSignatureEditor") && appConfig.addSignatureDialog
|
||||
? new SignatureManager(
|
||||
appConfig.addSignatureDialog,
|
||||
appConfig.annotationEditorParams?.editorSignatureAddSignature ||
|
||||
null,
|
||||
this.overlayManager,
|
||||
this.l10n,
|
||||
externalServices.createSignatureStorage()
|
||||
l10n,
|
||||
externalServices.createSignatureStorage(),
|
||||
eventBus
|
||||
)
|
||||
: null;
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
--text-primary-color: #15141a;
|
||||
--text-secondary-color: #5b5b66;
|
||||
--hover-filter: brightness(0.9);
|
||||
--focus-ring-color: #0060df;
|
||||
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||
--link-fg-color: #0060df;
|
||||
--link-hover-fg-color: #0250bb;
|
||||
--separator-color: #f0f0f4;
|
||||
|
@ -58,7 +56,6 @@
|
|||
--dialog-shadow: 0 2px 14px 0 #15141a;
|
||||
--text-primary-color: #fbfbfe;
|
||||
--text-secondary-color: #cfcfd8;
|
||||
--focus-ring-color: #0df;
|
||||
--hover-filter: brightness(1.4);
|
||||
--link-fg-color: #0df;
|
||||
--link-hover-fg-color: #80ebff;
|
||||
|
@ -82,7 +79,6 @@
|
|||
--text-primary-color: CanvasText;
|
||||
--text-secondary-color: CanvasText;
|
||||
--hover-filter: none;
|
||||
--focus-ring-color: ButtonBorder;
|
||||
--link-fg-color: LinkText;
|
||||
--link-hover-fg-color: LinkText;
|
||||
--separator-color: CanvasText;
|
||||
|
|
|
@ -504,11 +504,11 @@ class SignatureStorage {
|
|||
|
||||
async getAll() {
|
||||
if (!this.#signatures) {
|
||||
this.#signatures = Object.create(null);
|
||||
this.#signatures = new Map();
|
||||
const data = await this.#handleSignature({ action: "get" });
|
||||
if (data) {
|
||||
for (const { uuid, description, signatureData } of data) {
|
||||
this.#signatures[uuid] = { description, signatureData };
|
||||
this.#signatures.set(uuid, { description, signatureData });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -517,7 +517,7 @@ class SignatureStorage {
|
|||
|
||||
async isFull() {
|
||||
// We want to store at most 5 signatures.
|
||||
return Object.keys(await this.getAll()).length === 5;
|
||||
return (await this.getAll()).size === 5;
|
||||
}
|
||||
|
||||
async create(data) {
|
||||
|
@ -531,17 +531,17 @@ class SignatureStorage {
|
|||
if (!uuid) {
|
||||
return null;
|
||||
}
|
||||
this.#signatures[uuid] = data;
|
||||
this.#signatures.set(uuid, data);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
async delete(uuid) {
|
||||
const signatures = await this.getAll();
|
||||
if (!signatures[uuid]) {
|
||||
if (!signatures.has(uuid)) {
|
||||
return false;
|
||||
}
|
||||
if (await this.#handleSignature({ action: "delete", uuid })) {
|
||||
delete signatures[uuid];
|
||||
signatures.delete(uuid);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -549,7 +549,7 @@ class SignatureStorage {
|
|||
|
||||
async update(uuid, data) {
|
||||
const signatures = await this.getAll();
|
||||
const oldData = signatures[uuid];
|
||||
const oldData = signatures.get(uuid);
|
||||
if (!oldData) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -23,19 +23,28 @@ class SignatureStorage {
|
|||
#signatures = null;
|
||||
|
||||
#save() {
|
||||
localStorage.setItem("pdfjs.signature", JSON.stringify(this.#signatures));
|
||||
localStorage.setItem(
|
||||
"pdfjs.signature",
|
||||
JSON.stringify(Object.fromEntries(this.#signatures.entries()))
|
||||
);
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
if (!this.#signatures) {
|
||||
this.#signatures = new Map();
|
||||
const data = localStorage.getItem("pdfjs.signature");
|
||||
this.#signatures = data ? JSON.parse(data) : Object.create(null);
|
||||
if (data) {
|
||||
for (const [key, value] of Object.entries(JSON.parse(data))) {
|
||||
this.#signatures.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.#signatures;
|
||||
}
|
||||
|
||||
async isFull() {
|
||||
return Object.keys(await this.getAll()).length === 5;
|
||||
// Only allow 5 signatures to be saved.
|
||||
return (await this.getAll()).size === 5;
|
||||
}
|
||||
|
||||
async create(data) {
|
||||
|
@ -43,7 +52,7 @@ class SignatureStorage {
|
|||
return null;
|
||||
}
|
||||
const uuid = getUuid();
|
||||
this.#signatures[uuid] = data;
|
||||
this.#signatures.set(uuid, data);
|
||||
this.#save();
|
||||
|
||||
return uuid;
|
||||
|
@ -51,10 +60,10 @@ class SignatureStorage {
|
|||
|
||||
async delete(uuid) {
|
||||
const signatures = await this.getAll();
|
||||
if (!signatures[uuid]) {
|
||||
if (!signatures.has(uuid)) {
|
||||
return false;
|
||||
}
|
||||
delete signatures[uuid];
|
||||
signatures.delete(uuid);
|
||||
this.#save();
|
||||
|
||||
return true;
|
||||
|
@ -62,7 +71,7 @@ class SignatureStorage {
|
|||
|
||||
async update(uuid, data) {
|
||||
const signatures = await this.getAll();
|
||||
const oldData = signatures[uuid];
|
||||
const oldData = signatures.get(uuid);
|
||||
if (!oldData) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -143,9 +143,6 @@
|
|||
--undo-button-fg-color-hover: var(--undo-button-fg-color);
|
||||
--undo-button-fg-color-active: var(--undo-button-fg-color);
|
||||
|
||||
--focus-ring-color: #0060df;
|
||||
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--text-primary-color: #fbfbfe;
|
||||
|
||||
|
@ -172,8 +169,6 @@
|
|||
--undo-button-fg-color: ButtonFace;
|
||||
--undo-button-fg-color-hover: SelectedItemText;
|
||||
--undo-button-fg-color-active: SelectedItemText;
|
||||
|
||||
--focus-ring-color: CanvasText;
|
||||
}
|
||||
|
||||
position: fixed;
|
||||
|
|
|
@ -27,14 +27,19 @@
|
|||
--page-border: 9px solid transparent;
|
||||
--spreadHorizontalWrapped-margin-LR: -3.5px;
|
||||
--loading-icon-delay: 400ms;
|
||||
}
|
||||
--focus-ring-color: #0060df;
|
||||
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
:root {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--focus-ring-color: #0df;
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--pdfViewer-padding-bottom: 9px;
|
||||
--page-margin: 8px auto -1px;
|
||||
--page-border: 1px solid CanvasText;
|
||||
--spreadHorizontalWrapped-margin-LR: 3.5px;
|
||||
--focus-ring-color: CanvasText;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -933,9 +933,7 @@ class PDFViewer {
|
|||
uiManager: this.#annotationEditorUIManager,
|
||||
});
|
||||
if (mode !== AnnotationEditorType.NONE) {
|
||||
if (mode === AnnotationEditorType.STAMP) {
|
||||
this.#mlManager?.loadModel("altText");
|
||||
}
|
||||
this.#preloadEditingData(mode);
|
||||
this.#annotationEditorUIManager.updateMode(mode);
|
||||
}
|
||||
} else {
|
||||
|
@ -2315,6 +2313,18 @@ class PDFViewer {
|
|||
}
|
||||
}
|
||||
|
||||
#preloadEditingData(mode) {
|
||||
switch (mode) {
|
||||
case AnnotationEditorType.STAMP:
|
||||
this.#mlManager?.loadModel("altText");
|
||||
break;
|
||||
case AnnotationEditorType.SIGNATURE:
|
||||
// Start to load the signature data.
|
||||
this.#signatureManager?.loadSignatures();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get annotationEditorMode() {
|
||||
return this.#annotationEditorUIManager
|
||||
? this.#annotationEditorMode
|
||||
|
@ -2345,9 +2355,7 @@ class PDFViewer {
|
|||
if (!this.pdfDocument) {
|
||||
return;
|
||||
}
|
||||
if (mode === AnnotationEditorType.STAMP) {
|
||||
this.#mlManager?.loadModel("altText");
|
||||
}
|
||||
this.#preloadEditingData(mode);
|
||||
|
||||
const { eventBus, pdfDocument } = this;
|
||||
const updater = async () => {
|
||||
|
|
|
@ -13,6 +13,39 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--clear-signature-button-icon: url(images/editor-toolbar-delete.svg);
|
||||
--signature-bg: #f9f9fb;
|
||||
--signature-hover-bg: #f0f0f4;
|
||||
--button-signature-bg: transparent;
|
||||
--button-signature-color: var(--main-color);
|
||||
--button-signature-active-bg: #cfcfd8;
|
||||
--button-signature-active-border: none;
|
||||
--button-signature-active-color: var(--button-signature-color);
|
||||
--button-signature-border: none;
|
||||
--button-signature-hover-bg: #e0e0e6;
|
||||
--button-signature-hover-color: var(--button-signature-color);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--signature-bg: #2b2a33;
|
||||
--signature-hover-bg: var(--signature-bg);
|
||||
--button-signature-active-bg: #5b5b66;
|
||||
--button-signature-hover-bg: #52525e;
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--signature-bg: HighlightText;
|
||||
--signature-hover-bg: var(--signature-bg);
|
||||
--button-signature-bg: HighlightText;
|
||||
--button-signature-color: ButtonText;
|
||||
--button-signature-active-bg: ButtonText;
|
||||
--button-signature-active-color: HighlightText;
|
||||
--button-signature-border: 1px solid ButtonText;
|
||||
--button-signature-hover-bg: Highlight;
|
||||
--button-signature-hover-color: HighlightText;
|
||||
}
|
||||
}
|
||||
|
||||
#addSignatureDialog {
|
||||
--border-color: #8f8f9d;
|
||||
--primary-color: var(--text-primary-color);
|
||||
|
@ -31,8 +64,8 @@
|
|||
--tab-bg-hover: var(--bg-hover);
|
||||
--tab-text-color: var(--primary-color);
|
||||
--tab-text-active-color: var(--tab-top-line-active-color);
|
||||
--tab-text-active-hover-color: var(--tab-text-hover-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);
|
||||
|
@ -43,7 +76,6 @@
|
|||
--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);
|
||||
|
@ -72,7 +104,6 @@
|
|||
--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(
|
||||
|
@ -99,8 +130,8 @@
|
|||
--tab-bg-active-hover-color: SelectedItem;
|
||||
--tab-text-color: ButtonText;
|
||||
--tab-text-active-color: HighlightText;
|
||||
--tab-text-active-hover-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;
|
||||
|
@ -214,7 +245,7 @@
|
|||
&: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);
|
||||
color: var(--tab-text-active-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -594,3 +625,138 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#editorSignatureParamsToolbar {
|
||||
padding: 10px;
|
||||
|
||||
#addSignatureDoorHanger {
|
||||
gap: 8px;
|
||||
overflow-y: unset;
|
||||
|
||||
.toolbarAddSignatureButtonContainer {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
border: var(--button-signature-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--button-signature-bg);
|
||||
color: var(--button-signature-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-signature-hover-bg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border: var(--button-signature-active-border);
|
||||
background-color: var(--button-signature-active-bg);
|
||||
color: var(--button-signature-active-color);
|
||||
|
||||
&::before {
|
||||
background-color: var(--button-signature-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--focus-ring-outline);
|
||||
|
||||
&::before {
|
||||
background-color: var(--button-signature-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
&::before {
|
||||
mask-image: var(--clear-signature-button-icon);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbarAddSignatureButton {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
min-height: var(--menuitem-height);
|
||||
aspect-ratio: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
font: message-box;
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
padding: 0;
|
||||
gap: 8px;
|
||||
text-align: start;
|
||||
white-space: normal;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
|
||||
> svg {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
aspect-ratio: 1;
|
||||
background-color: var(--signature-bg);
|
||||
flex: none;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
|
||||
> path {
|
||||
stroke: var(--button-signature-color);
|
||||
stroke-width: 1px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 10;
|
||||
vector-effect: non-scaling-stroke;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
&.contours > path {
|
||||
fill: var(--button-signature-color);
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:is(:hover, :active) > svg {
|
||||
border-radius: 4px 0 0 4px;
|
||||
background-color: var(--signature-hover-bg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> span {
|
||||
color: var(--button-signature-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--button-signature-active-bg);
|
||||
}
|
||||
|
||||
&:is([disabled="disabled"], [disabled]) {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
> span {
|
||||
height: auto;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
font: menu;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
AnnotationEditorParamsType,
|
||||
DOMSVGFactory,
|
||||
noContextMenu,
|
||||
SignatureExtractor,
|
||||
|
@ -21,6 +22,9 @@ import {
|
|||
SupportedImageMimeTypes,
|
||||
} from "pdfjs-lib";
|
||||
|
||||
// Default height of the added signature in page coordinates.
|
||||
const DEFAULT_HEIGHT_IN_PAGE = 40;
|
||||
|
||||
class SignatureManager {
|
||||
#addButton;
|
||||
|
||||
|
@ -70,6 +74,10 @@ class SignatureManager {
|
|||
|
||||
#tabButtons;
|
||||
|
||||
#addSignatureToolbarButton;
|
||||
|
||||
#loadSignaturesPromise = null;
|
||||
|
||||
#typeInput;
|
||||
|
||||
#currentTab = null;
|
||||
|
@ -78,6 +86,8 @@ class SignatureManager {
|
|||
|
||||
#hasDescriptionChanged = false;
|
||||
|
||||
#eventBus;
|
||||
|
||||
#l10n;
|
||||
|
||||
#overlayManager;
|
||||
|
@ -113,9 +123,11 @@ class SignatureManager {
|
|||
saveCheckbox,
|
||||
saveContainer,
|
||||
},
|
||||
addSignatureToolbarButton,
|
||||
overlayManager,
|
||||
l10n,
|
||||
signatureStorage
|
||||
signatureStorage,
|
||||
eventBus
|
||||
) {
|
||||
this.#addButton = addButton;
|
||||
this.#clearButton = clearButton;
|
||||
|
@ -133,9 +145,11 @@ class SignatureManager {
|
|||
this.#overlayManager = overlayManager;
|
||||
this.#saveCheckbox = saveCheckbox;
|
||||
this.#saveContainer = saveContainer;
|
||||
this.#addSignatureToolbarButton = addSignatureToolbarButton;
|
||||
this.#typeInput = typeInput;
|
||||
this.#l10n = l10n;
|
||||
this.#signatureStorage = signatureStorage;
|
||||
this.#eventBus = eventBus;
|
||||
|
||||
SignatureManager.#l10nDescription ||= Object.freeze({
|
||||
signature: "pdfjs-editor-add-signature-description-default-when-drawing",
|
||||
|
@ -360,7 +374,7 @@ class SignatureManager {
|
|||
this.#drawCurves = {
|
||||
width: drawWidth,
|
||||
height: drawHeight,
|
||||
thickness: this.#drawThickness.value,
|
||||
thickness: parseInt(this.#drawThickness.value),
|
||||
curves: [],
|
||||
};
|
||||
this.#disableButtons(true);
|
||||
|
@ -610,10 +624,147 @@ class SignatureManager {
|
|||
);
|
||||
}
|
||||
|
||||
#addToolbarButton(signatureData, uuid, description) {
|
||||
const { curves, areContours, thickness, width, height } = signatureData;
|
||||
const maxDim = Math.max(width, height);
|
||||
const outlineData = SignatureExtractor.processDrawnLines({
|
||||
lines: {
|
||||
curves,
|
||||
thickness,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
pageWidth: maxDim,
|
||||
pageHeight: maxDim,
|
||||
rotation: 0,
|
||||
innerMargin: 0,
|
||||
mustSmooth: false,
|
||||
areContours,
|
||||
});
|
||||
if (!outlineData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { outline } = outlineData;
|
||||
const svgFactory = new DOMSVGFactory();
|
||||
|
||||
const div = document.createElement("div");
|
||||
const button = document.createElement("button");
|
||||
button.addEventListener("click", () => {
|
||||
this.#eventBus.dispatch("switchannotationeditorparams", {
|
||||
source: this,
|
||||
type: AnnotationEditorParamsType.CREATE,
|
||||
value: {
|
||||
signatureData: {
|
||||
lines: {
|
||||
curves,
|
||||
thickness,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
mustSmooth: false,
|
||||
areContours,
|
||||
description,
|
||||
uuid,
|
||||
heightInPage: DEFAULT_HEIGHT_IN_PAGE,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
div.append(button);
|
||||
div.classList.add("toolbarAddSignatureButtonContainer");
|
||||
|
||||
const svg = svgFactory.create(1, 1, true);
|
||||
button.append(svg);
|
||||
|
||||
const span = document.createElement("span");
|
||||
button.append(span);
|
||||
|
||||
button.classList.add("toolbarAddSignatureButton");
|
||||
button.type = "button";
|
||||
button.title = span.textContent = description;
|
||||
button.tabIndex = 0;
|
||||
|
||||
const path = svgFactory.createElement("path");
|
||||
svg.append(path);
|
||||
svg.setAttribute("viewBox", outline.viewBox);
|
||||
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
||||
if (areContours) {
|
||||
svg.classList.add("contours");
|
||||
}
|
||||
path.setAttribute("d", outline.toSVGPath());
|
||||
|
||||
const deleteButton = document.createElement("button");
|
||||
div.append(deleteButton);
|
||||
deleteButton.classList.add("toolbarButton", "deleteButton");
|
||||
deleteButton.setAttribute(
|
||||
"data-l10n-id",
|
||||
"pdfjs-editor-delete-signature-button"
|
||||
);
|
||||
deleteButton.type = "button";
|
||||
deleteButton.tabIndex = 0;
|
||||
deleteButton.addEventListener("click", async () => {
|
||||
if (await this.#signatureStorage.delete(uuid)) {
|
||||
div.remove();
|
||||
}
|
||||
});
|
||||
const deleteSpan = document.createElement("span");
|
||||
deleteButton.append(deleteSpan);
|
||||
deleteSpan.setAttribute(
|
||||
"data-l10n-id",
|
||||
"pdfjs-editor-delete-signature-button-label"
|
||||
);
|
||||
|
||||
this.#addSignatureToolbarButton.before(div);
|
||||
}
|
||||
|
||||
getSignature(params) {
|
||||
return this.open(params);
|
||||
}
|
||||
|
||||
async loadSignatures() {
|
||||
if (
|
||||
!this.#addSignatureToolbarButton ||
|
||||
this.#addSignatureToolbarButton.previousElementSibling ||
|
||||
!this.#signatureStorage
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#loadSignaturesPromise) {
|
||||
// The first call of loadSignatures() starts loading the signatures.
|
||||
// The second one will wait until the signatures are loaded in the DOM.
|
||||
this.#loadSignaturesPromise = this.#signatureStorage
|
||||
.getAll()
|
||||
.then(async signatures => [
|
||||
signatures,
|
||||
await Promise.all(
|
||||
Array.from(
|
||||
signatures
|
||||
.values()
|
||||
.map(({ signatureData }) =>
|
||||
SignatureExtractor.decompressSignature(signatureData)
|
||||
)
|
||||
)
|
||||
),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const [signatures, signaturesData] = await this.#loadSignaturesPromise;
|
||||
this.#loadSignaturesPromise = null;
|
||||
|
||||
let i = 0;
|
||||
for (const [uuid, { description }] of signatures) {
|
||||
const data = signaturesData[i++];
|
||||
if (!data) {
|
||||
continue;
|
||||
}
|
||||
data.curves = data.outlines.map(points => ({ points }));
|
||||
delete data.outlines;
|
||||
this.#addToolbarButton(data, uuid, description);
|
||||
}
|
||||
}
|
||||
|
||||
async open({ uiManager, editor }) {
|
||||
this.#tabsToAltText ||= new Map(
|
||||
this.#tabButtons.keys().map(name => [name, ""])
|
||||
|
@ -677,9 +828,10 @@ class SignatureManager {
|
|||
}
|
||||
this.#currentEditor.addSignature(
|
||||
data.outline,
|
||||
/* heightInPage */ 40,
|
||||
DEFAULT_HEIGHT_IN_PAGE,
|
||||
this.#description.value
|
||||
);
|
||||
let uuid = null;
|
||||
if (this.#saveCheckbox.checked) {
|
||||
const description = this.#description.value;
|
||||
const { newCurves, areContours, thickness, width, height } = data;
|
||||
|
@ -690,15 +842,27 @@ class SignatureManager {
|
|||
width,
|
||||
height,
|
||||
});
|
||||
const uuid = (this.#currentEditor._signatureUUID =
|
||||
await this.#signatureStorage.create({
|
||||
uuid = await this.#signatureStorage.create({
|
||||
description,
|
||||
signatureData,
|
||||
}));
|
||||
if (!uuid) {
|
||||
});
|
||||
if (uuid) {
|
||||
this.#addToolbarButton(
|
||||
{
|
||||
curves: newCurves.map(points => ({ points })),
|
||||
areContours,
|
||||
thickness,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
uuid,
|
||||
description
|
||||
);
|
||||
} else {
|
||||
console.warn("SignatureManager.add: cannot save the signature.");
|
||||
}
|
||||
}
|
||||
this.#currentEditor.setUuid(uuid);
|
||||
this.#finish();
|
||||
}
|
||||
|
||||
|
|
|
@ -245,13 +245,13 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
<div id="toolbarViewerRight" class="toolbarHorizontalGroup">
|
||||
<div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">
|
||||
<div id="editorSignature" class="toolbarButtonWithContainer" hidden="true">
|
||||
<button id="editorSignatureButton" class="toolbarButton" type="button" disabled="disabled" title="Add or edit signatures" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorSignatureParamsToolbar" tabindex="0">
|
||||
<span>Add or edit signatures</span>
|
||||
<button id="editorSignatureButton" class="toolbarButton" type="button" disabled="disabled" title="Add signature" role="radio" aria-expanded="false" aria-haspopup="true" aria-controls="editorSignatureParamsToolbar" tabindex="0" data-l10n-id="pdfjs-editor-signature-button">
|
||||
<span data-l10n-id="pdfjs-editor-signature-button-label">Add signature</span>
|
||||
</button>
|
||||
<div class="editorParamsToolbar hidden doorHangerRight menu" id="editorSignatureParamsToolbar">
|
||||
<div class="menuContainer">
|
||||
<button id="editorSignatureAddSignature" class="toolbarButton labeled" type="button" title="Add signature" tabindex="0">
|
||||
<span class="editorParamsLabel">Add signature</span>
|
||||
<div id="addSignatureDoorHanger" class="menuContainer">
|
||||
<button id="editorSignatureAddSignature" class="toolbarButton labeled" type="button" title="Add new signature" tabindex="0" data-l10n-id="pdfjs-editor-signature-add-signature-button">
|
||||
<span data-l10n-id="pdfjs-editor-signature-add-signature-button-label" class="editorParamsLabel">Add new signature</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue