mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
Merge pull request #19425 from calixteman/signature_save
[Editor] Add the possibility to compress/decompress the signature data in order to store them in the logins storage in Firefox (bug 1946171)
This commit is contained in:
commit
e3cca6d513
15 changed files with 459 additions and 17 deletions
16
web/app.js
16
web/app.js
|
@ -461,13 +461,15 @@ const PDFViewerApplication = {
|
|||
this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus);
|
||||
}
|
||||
|
||||
const signatureManager = appConfig.addSignatureDialog
|
||||
? new SignatureManager(
|
||||
appConfig.addSignatureDialog,
|
||||
this.overlayManager,
|
||||
this.l10n
|
||||
)
|
||||
: null;
|
||||
const signatureManager =
|
||||
AppOptions.get("enableSignatureEditor") && appConfig.addSignatureDialog
|
||||
? new SignatureManager(
|
||||
appConfig.addSignatureDialog,
|
||||
this.overlayManager,
|
||||
this.l10n,
|
||||
externalServices.createSignatureStorage()
|
||||
)
|
||||
: null;
|
||||
|
||||
const enableHWA = AppOptions.get("enableHWA");
|
||||
const pdfViewer = new PDFViewer({
|
||||
|
|
|
@ -19,8 +19,10 @@ import { BaseExternalServices } from "./external_services.js";
|
|||
import { BasePreferences } from "./preferences.js";
|
||||
import { GenericL10n } from "./genericl10n.js";
|
||||
import { GenericScripting } from "./generic_scripting.js";
|
||||
import { SignatureStorage } from "./generic_signature_storage.js";
|
||||
|
||||
// These strings are from chrome/app/resources/generated_resources_*.xtb.
|
||||
// eslint-disable-next-line sort-imports
|
||||
import i18nFileAccessLabels from "./chrome-i18n-allow-access-to-file-urls.json" with { type: "json" };
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
|
||||
|
@ -419,6 +421,10 @@ class ExternalServices extends BaseExternalServices {
|
|||
createScripting() {
|
||||
return new GenericScripting(AppOptions.get("sandboxBundleSrc"));
|
||||
}
|
||||
|
||||
createSignatureStorage() {
|
||||
return new SignatureStorage();
|
||||
}
|
||||
}
|
||||
|
||||
class MLManager {
|
||||
|
|
|
@ -44,6 +44,10 @@ class BaseExternalServices {
|
|||
throw new Error("Not implemented: createScripting");
|
||||
}
|
||||
|
||||
createSignatureStorage() {
|
||||
throw new Error("Not implemented: createSignatureStorage");
|
||||
}
|
||||
|
||||
updateEditorStates(data) {
|
||||
throw new Error("Not implemented: updateEditorStates");
|
||||
}
|
||||
|
|
|
@ -495,6 +495,72 @@ class MLManager {
|
|||
}
|
||||
}
|
||||
|
||||
class SignatureStorage {
|
||||
#signatures = null;
|
||||
|
||||
#handleSignature(data) {
|
||||
return FirefoxCom.requestAsync("handleSignature", data);
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
if (!this.#signatures) {
|
||||
this.#signatures = Object.create(null);
|
||||
const data = await this.#handleSignature({ action: "get" });
|
||||
if (data) {
|
||||
for (const { uuid, description, signatureData } of data) {
|
||||
this.#signatures[uuid] = { description, signatureData };
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.#signatures;
|
||||
}
|
||||
|
||||
async isFull() {
|
||||
// We want to store at most 5 signatures.
|
||||
return Object.keys(await this.getAll()).length === 5;
|
||||
}
|
||||
|
||||
async create(data) {
|
||||
if (await this.isFull()) {
|
||||
return null;
|
||||
}
|
||||
const uuid = await this.#handleSignature({
|
||||
action: "create",
|
||||
...data,
|
||||
});
|
||||
if (!uuid) {
|
||||
return null;
|
||||
}
|
||||
this.#signatures[uuid] = data;
|
||||
return uuid;
|
||||
}
|
||||
|
||||
async delete(uuid) {
|
||||
const signatures = await this.getAll();
|
||||
if (!signatures[uuid]) {
|
||||
return false;
|
||||
}
|
||||
if (await this.#handleSignature({ action: "delete", uuid })) {
|
||||
delete signatures[uuid];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async update(uuid, data) {
|
||||
const signatures = await this.getAll();
|
||||
const oldData = signatures[uuid];
|
||||
if (!oldData) {
|
||||
return false;
|
||||
}
|
||||
if (await this.#handleSignature({ action: "update", uuid, ...data })) {
|
||||
Object.assign(oldData, data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalServices extends BaseExternalServices {
|
||||
updateFindControlState(data) {
|
||||
FirefoxCom.request("updateFindControlState", data);
|
||||
|
@ -581,6 +647,10 @@ class ExternalServices extends BaseExternalServices {
|
|||
return FirefoxScripting;
|
||||
}
|
||||
|
||||
createSignatureStorage() {
|
||||
return new SignatureStorage();
|
||||
}
|
||||
|
||||
dispatchGlobalEvent(event) {
|
||||
FirefoxCom.request("dispatchGlobalEvent", event);
|
||||
}
|
||||
|
|
76
web/generic_signature_storage.js
Normal file
76
web/generic_signature_storage.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
/* 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 { getUuid } from "pdfjs-lib";
|
||||
|
||||
class SignatureStorage {
|
||||
// TODO: Encrypt the data in using a password and add a UI for entering it.
|
||||
// We could use the Web Crypto API for this (see https://bradyjoslin.com/blog/encryption-webcrypto/
|
||||
// for an example).
|
||||
|
||||
#signatures = null;
|
||||
|
||||
#save() {
|
||||
localStorage.setItem("pdfjs.signature", JSON.stringify(this.#signatures));
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
if (!this.#signatures) {
|
||||
const data = localStorage.getItem("pdfjs.signature");
|
||||
this.#signatures = data ? JSON.parse(data) : Object.create(null);
|
||||
}
|
||||
return this.#signatures;
|
||||
}
|
||||
|
||||
async isFull() {
|
||||
return Object.keys(await this.getAll()).length === 5;
|
||||
}
|
||||
|
||||
async create(data) {
|
||||
if (await this.isFull()) {
|
||||
return null;
|
||||
}
|
||||
const uuid = getUuid();
|
||||
this.#signatures[uuid] = data;
|
||||
this.#save();
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
async delete(uuid) {
|
||||
const signatures = await this.getAll();
|
||||
if (!signatures[uuid]) {
|
||||
return false;
|
||||
}
|
||||
delete signatures[uuid];
|
||||
this.#save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async update(uuid, data) {
|
||||
const signatures = await this.getAll();
|
||||
const oldData = signatures[uuid];
|
||||
if (!oldData) {
|
||||
return false;
|
||||
}
|
||||
Object.assign(oldData, data);
|
||||
this.#save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { SignatureStorage };
|
|
@ -18,6 +18,7 @@ import { BaseExternalServices } from "./external_services.js";
|
|||
import { BasePreferences } from "./preferences.js";
|
||||
import { GenericL10n } from "./genericl10n.js";
|
||||
import { GenericScripting } from "./generic_scripting.js";
|
||||
import { SignatureStorage } from "./generic_signature_storage.js";
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
|
||||
throw new Error(
|
||||
|
@ -45,6 +46,10 @@ class ExternalServices extends BaseExternalServices {
|
|||
createScripting() {
|
||||
return new GenericScripting(AppOptions.get("sandboxBundleSrc"));
|
||||
}
|
||||
|
||||
createSignatureStorage() {
|
||||
return new SignatureStorage();
|
||||
}
|
||||
}
|
||||
|
||||
class MLManager {
|
||||
|
|
|
@ -32,6 +32,7 @@ const {
|
|||
getDocument,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
getUuid,
|
||||
getXfaPageViewport,
|
||||
GlobalWorkerOptions,
|
||||
ImageKind,
|
||||
|
@ -52,6 +53,7 @@ const {
|
|||
ResponseException,
|
||||
setLayerDimensions,
|
||||
shadow,
|
||||
SignatureExtractor,
|
||||
stopEvent,
|
||||
SupportedImageMimeTypes,
|
||||
TextLayer,
|
||||
|
@ -81,6 +83,7 @@ export {
|
|||
getDocument,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
getUuid,
|
||||
getXfaPageViewport,
|
||||
GlobalWorkerOptions,
|
||||
ImageKind,
|
||||
|
@ -101,6 +104,7 @@ export {
|
|||
ResponseException,
|
||||
setLayerDimensions,
|
||||
shadow,
|
||||
SignatureExtractor,
|
||||
stopEvent,
|
||||
SupportedImageMimeTypes,
|
||||
TextLayer,
|
||||
|
|
|
@ -571,14 +571,23 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
#addSignatureSaveWarning {
|
||||
&:not(.fullStorage) #addSignatureSaveWarning {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.fullStorage #addSignatureSaveWarning {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
color: var(--save-warning-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
&:is([disabled], .fullStorage) {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
|
||||
> :not(#addSignatureSaveWarning) {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import {
|
||||
DOMSVGFactory,
|
||||
noContextMenu,
|
||||
SignatureExtractor,
|
||||
stopEvent,
|
||||
SupportedImageMimeTypes,
|
||||
} from "pdfjs-lib";
|
||||
|
@ -81,6 +82,8 @@ class SignatureManager {
|
|||
|
||||
#overlayManager;
|
||||
|
||||
#signatureStorage;
|
||||
|
||||
#uiManager = null;
|
||||
|
||||
static #l10nDescription = null;
|
||||
|
@ -111,7 +114,8 @@ class SignatureManager {
|
|||
saveContainer,
|
||||
},
|
||||
overlayManager,
|
||||
l10n
|
||||
l10n,
|
||||
signatureStorage
|
||||
) {
|
||||
this.#addButton = addButton;
|
||||
this.#clearButton = clearButton;
|
||||
|
@ -131,6 +135,7 @@ class SignatureManager {
|
|||
this.#saveContainer = saveContainer;
|
||||
this.#typeInput = typeInput;
|
||||
this.#l10n = l10n;
|
||||
this.#signatureStorage = signatureStorage;
|
||||
|
||||
SignatureManager.#l10nDescription ||= Object.freeze({
|
||||
signature: "pdfjs-editor-add-signature-description-default-when-drawing",
|
||||
|
@ -564,7 +569,7 @@ class SignatureManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const outline = (this.#extractedSignatureData =
|
||||
const { outline } = (this.#extractedSignatureData =
|
||||
this.#currentEditor.getFromImage(data.bitmap));
|
||||
|
||||
if (!outline) {
|
||||
|
@ -617,6 +622,10 @@ class SignatureManager {
|
|||
this.#currentEditor = editor;
|
||||
this.#uiManager.removeEditListeners();
|
||||
|
||||
const isStorageFull = await this.#signatureStorage.isFull();
|
||||
this.#saveContainer.classList.toggle("fullStorage", isStorageFull);
|
||||
this.#saveCheckbox.checked = !isStorageFull;
|
||||
|
||||
await this.#overlayManager.open(this.#dialog);
|
||||
|
||||
const tabType = this.#tabButtons.get("type");
|
||||
|
@ -653,7 +662,7 @@ class SignatureManager {
|
|||
this.#tabsToAltText = null;
|
||||
}
|
||||
|
||||
#add() {
|
||||
async #add() {
|
||||
let data;
|
||||
switch (this.#currentTab) {
|
||||
case "type":
|
||||
|
@ -667,12 +676,28 @@ class SignatureManager {
|
|||
break;
|
||||
}
|
||||
this.#currentEditor.addSignature(
|
||||
data,
|
||||
data.outline,
|
||||
/* heightInPage */ 40,
|
||||
this.#description.value
|
||||
);
|
||||
if (this.#saveCheckbox.checked) {
|
||||
// TODO
|
||||
const description = this.#description.value;
|
||||
const { newCurves, areContours, thickness, width, height } = data;
|
||||
const signatureData = await SignatureExtractor.compressSignature({
|
||||
outlines: newCurves,
|
||||
areContours,
|
||||
thickness,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
const uuid = (this.#currentEditor._signatureUUID =
|
||||
await this.#signatureStorage.create({
|
||||
description,
|
||||
signatureData,
|
||||
}));
|
||||
if (!uuid) {
|
||||
console.warn("SignatureManager.add: cannot save the signature.");
|
||||
}
|
||||
}
|
||||
this.#finish();
|
||||
}
|
||||
|
|
|
@ -742,7 +742,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||
<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>
|
||||
<span id="addSignatureSaveWarning" 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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue