mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 06:38: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
|
@ -13,10 +13,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fromBase64Util, toBase64Util, warn } from "../../../shared/util.js";
|
||||
import { ContourDrawOutline } from "./contour.js";
|
||||
import { InkDrawOutline } from "./inkdraw.js";
|
||||
import { Outline } from "./outline.js";
|
||||
|
||||
const BASE_HEADER_LENGTH = 8;
|
||||
const POINTS_PROPERTIES_NUMBER = 3;
|
||||
|
||||
/**
|
||||
* Basic text editor in order to create a Signature annotation.
|
||||
*/
|
||||
|
@ -607,12 +611,14 @@ class SignatureExtractor {
|
|||
const ratio = Math.min(pageWidth / width, pageHeight / height);
|
||||
const xScale = ratio / pageWidth;
|
||||
const yScale = ratio / pageHeight;
|
||||
const newCurves = [];
|
||||
|
||||
for (const { points } of curves) {
|
||||
const reducedPoints = mustSmooth ? this.#douglasPeucker(points) : points;
|
||||
if (!reducedPoints) {
|
||||
continue;
|
||||
}
|
||||
newCurves.push(reducedPoints);
|
||||
|
||||
const len = reducedPoints.length;
|
||||
const newPoints = new Float32Array(len);
|
||||
|
@ -660,7 +666,185 @@ class SignatureExtractor {
|
|||
innerMargin
|
||||
);
|
||||
|
||||
return outline;
|
||||
return { outline, newCurves, areContours, thickness, width, height };
|
||||
}
|
||||
|
||||
static async compressSignature({
|
||||
outlines,
|
||||
areContours,
|
||||
thickness,
|
||||
width,
|
||||
height,
|
||||
}) {
|
||||
// We create a single array containing all the outlines.
|
||||
// The format is the following:
|
||||
// - 4 bytes: data length.
|
||||
// - 4 bytes: version.
|
||||
// - 4 bytes: width.
|
||||
// - 4 bytes: height.
|
||||
// - 4 bytes: 0 if it's a contour, 1 if it's an ink.
|
||||
// - 4 bytes: thickness.
|
||||
// - 4 bytes: number of drawings.
|
||||
// - 4 bytes: size of the buffer containing the diff of the coordinates.
|
||||
// - 4 bytes: number of points in the first drawing.
|
||||
// - 4 bytes: x coordinate of the first point.
|
||||
// - 4 bytes: y coordinate of the first point.
|
||||
// - 4 bytes: number of points in the second drawing.
|
||||
// - 4 bytes: x coordinate of the first point.
|
||||
// - 4 bytes: y coordinate of the first point.
|
||||
// - ...
|
||||
// - The buffer containing the diff of the coordinates.
|
||||
|
||||
// The coordinates are supposed to be positive integers.
|
||||
|
||||
// We also compute the min and max difference between two points.
|
||||
// This will help us to determine the type of the buffer (Int8, Int16 or
|
||||
// Int32) in order to minimize the amount of data we have.
|
||||
let minDiff = Infinity;
|
||||
let maxDiff = -Infinity;
|
||||
let outlinesLength = 0;
|
||||
for (const points of outlines) {
|
||||
outlinesLength += points.length;
|
||||
for (let i = 2, ii = points.length; i < ii; i++) {
|
||||
const dx = points[i] - points[i - 2];
|
||||
minDiff = Math.min(minDiff, dx);
|
||||
maxDiff = Math.max(maxDiff, dx);
|
||||
}
|
||||
}
|
||||
|
||||
let bufferType;
|
||||
if (minDiff >= -128 && maxDiff <= 127) {
|
||||
bufferType = Int8Array;
|
||||
} else if (minDiff >= -32768 && maxDiff <= 32767) {
|
||||
bufferType = Int16Array;
|
||||
} else {
|
||||
bufferType = Int32Array;
|
||||
}
|
||||
|
||||
const len = outlines.length;
|
||||
const headerLength = BASE_HEADER_LENGTH + POINTS_PROPERTIES_NUMBER * len;
|
||||
const header = new Uint32Array(headerLength);
|
||||
|
||||
let offset = 0;
|
||||
header[offset++] =
|
||||
headerLength * Uint32Array.BYTES_PER_ELEMENT +
|
||||
(outlinesLength - 2 * len) * bufferType.BYTES_PER_ELEMENT;
|
||||
header[offset++] = 0; // Version.
|
||||
header[offset++] = width;
|
||||
header[offset++] = height;
|
||||
header[offset++] = areContours ? 0 : 1;
|
||||
header[offset++] = Math.max(0, Math.floor(thickness ?? 0));
|
||||
header[offset++] = len;
|
||||
header[offset++] = bufferType.BYTES_PER_ELEMENT;
|
||||
for (const points of outlines) {
|
||||
header[offset++] = points.length - 2;
|
||||
header[offset++] = points[0];
|
||||
header[offset++] = points[1];
|
||||
}
|
||||
|
||||
const cs = new CompressionStream("deflate-raw");
|
||||
const writer = cs.writable.getWriter();
|
||||
await writer.ready;
|
||||
|
||||
writer.write(header);
|
||||
const BufferCtor = bufferType.prototype.constructor;
|
||||
for (const points of outlines) {
|
||||
const diffs = new BufferCtor(points.length - 2);
|
||||
for (let i = 2, ii = points.length; i < ii; i++) {
|
||||
diffs[i - 2] = points[i] - points[i - 2];
|
||||
}
|
||||
writer.write(diffs);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
|
||||
const buf = await new Response(cs.readable).arrayBuffer();
|
||||
const bytes = new Uint8Array(buf);
|
||||
|
||||
return toBase64Util(bytes);
|
||||
}
|
||||
|
||||
static async decompressSignature(signatureData) {
|
||||
try {
|
||||
const bytes = fromBase64Util(signatureData);
|
||||
const { readable, writable } = new DecompressionStream("deflate-raw");
|
||||
const writer = writable.getWriter();
|
||||
await writer.ready;
|
||||
|
||||
// We can't await writer.write() because it'll block until the reader
|
||||
// starts which happens few lines below.
|
||||
writer
|
||||
.write(bytes)
|
||||
.then(async () => {
|
||||
await writer.ready;
|
||||
await writer.close();
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
let data = null;
|
||||
let offset = 0;
|
||||
for await (const chunk of readable) {
|
||||
data ||= new Uint8Array(new Uint32Array(chunk.buffer)[0]);
|
||||
data.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
// We take a bit too much data for the header but it's fine.
|
||||
const header = new Uint32Array(data.buffer, 0, data.length >> 2);
|
||||
const version = header[1];
|
||||
if (version !== 0) {
|
||||
throw new Error(`Invalid version: ${version}`);
|
||||
}
|
||||
const width = header[2];
|
||||
const height = header[3];
|
||||
const areContours = header[4] === 0;
|
||||
const thickness = header[5];
|
||||
const numberOfDrawings = header[6];
|
||||
const bufferType = header[7];
|
||||
const outlines = [];
|
||||
const diffsOffset =
|
||||
(BASE_HEADER_LENGTH + POINTS_PROPERTIES_NUMBER * numberOfDrawings) *
|
||||
Uint32Array.BYTES_PER_ELEMENT;
|
||||
let diffs;
|
||||
|
||||
switch (bufferType) {
|
||||
case Int8Array.BYTES_PER_ELEMENT:
|
||||
diffs = new Int8Array(data.buffer, diffsOffset);
|
||||
break;
|
||||
case Int16Array.BYTES_PER_ELEMENT:
|
||||
diffs = new Int16Array(data.buffer, diffsOffset);
|
||||
break;
|
||||
case Int32Array.BYTES_PER_ELEMENT:
|
||||
diffs = new Int32Array(data.buffer, diffsOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
for (let i = 0; i < numberOfDrawings; i++) {
|
||||
const len = header[POINTS_PROPERTIES_NUMBER * i + BASE_HEADER_LENGTH];
|
||||
const points = new Float32Array(len + 2);
|
||||
outlines.push(points);
|
||||
|
||||
for (let j = 0; j < POINTS_PROPERTIES_NUMBER - 1; j++) {
|
||||
points[j] =
|
||||
header[POINTS_PROPERTIES_NUMBER * i + BASE_HEADER_LENGTH + j + 1];
|
||||
}
|
||||
for (let j = 0; j < len; j++) {
|
||||
points[j + 2] = points[j] + diffs[offset++];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
areContours,
|
||||
thickness,
|
||||
outlines,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
} catch (e) {
|
||||
warn(`decompressSignature: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ class SignatureEditor extends DrawingEditor {
|
|||
super({ ...params, mustBeCommitted: true, name: "signatureEditor" });
|
||||
this._willKeepAspectRatio = true;
|
||||
this._description = "";
|
||||
this._signatureUUID = null;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
AnnotationType,
|
||||
createValidAbsoluteUrl,
|
||||
FeatureTest,
|
||||
getUuid,
|
||||
ImageKind,
|
||||
InvalidPDFException,
|
||||
normalizeUnicode,
|
||||
|
@ -72,6 +73,7 @@ import { DOMSVGFactory } from "./display/svg_factory.js";
|
|||
import { DrawLayer } from "./display/draw_layer.js";
|
||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
||||
import { SignatureExtractor } from "./display/editor/drawers/signaturedraw.js";
|
||||
import { TextLayer } from "./display/text_layer.js";
|
||||
import { TouchManager } from "./display/touch_manager.js";
|
||||
import { XfaLayer } from "./display/xfa_layer.js";
|
||||
|
@ -108,6 +110,7 @@ export {
|
|||
getDocument,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
getUuid,
|
||||
getXfaPageViewport,
|
||||
GlobalWorkerOptions,
|
||||
ImageKind,
|
||||
|
@ -128,6 +131,7 @@ export {
|
|||
ResponseException,
|
||||
setLayerDimensions,
|
||||
shadow,
|
||||
SignatureExtractor,
|
||||
stopEvent,
|
||||
SupportedImageMimeTypes,
|
||||
TextLayer,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import { CommandManager } from "../../src/display/editor/tools.js";
|
||||
import { SignatureExtractor } from "../../src/display/editor/drawers/signaturedraw.js";
|
||||
|
||||
describe("editor", function () {
|
||||
describe("Command Manager", function () {
|
||||
|
@ -90,4 +91,51 @@ describe("editor", function () {
|
|||
manager.add({ ...makeDoUndo(5), mustExec: true });
|
||||
expect(x).toEqual(11);
|
||||
});
|
||||
|
||||
it("should check signature compression/decompression", async () => {
|
||||
let gen = n => new Float32Array(crypto.getRandomValues(new Uint16Array(n)));
|
||||
let outlines = [102, 28, 254, 4536, 10, 14532, 512].map(gen);
|
||||
const signature = {
|
||||
outlines,
|
||||
areContours: false,
|
||||
thickness: 1,
|
||||
width: 123,
|
||||
height: 456,
|
||||
};
|
||||
let compressed = await SignatureExtractor.compressSignature(signature);
|
||||
let decompressed = await SignatureExtractor.decompressSignature(compressed);
|
||||
expect(decompressed).toEqual(signature);
|
||||
|
||||
signature.thickness = 2;
|
||||
compressed = await SignatureExtractor.compressSignature(signature);
|
||||
decompressed = await SignatureExtractor.decompressSignature(compressed);
|
||||
expect(decompressed).toEqual(signature);
|
||||
|
||||
signature.areContours = true;
|
||||
compressed = await SignatureExtractor.compressSignature(signature);
|
||||
decompressed = await SignatureExtractor.decompressSignature(compressed);
|
||||
expect(decompressed).toEqual(signature);
|
||||
|
||||
// Numbers are small enough to be compressed with Uint8Array.
|
||||
gen = n =>
|
||||
new Float32Array(
|
||||
crypto.getRandomValues(new Uint8Array(n)).map(x => x / 10)
|
||||
);
|
||||
outlines = [100, 200, 300, 10, 80].map(gen);
|
||||
signature.outlines = outlines;
|
||||
compressed = await SignatureExtractor.compressSignature(signature);
|
||||
decompressed = await SignatureExtractor.decompressSignature(compressed);
|
||||
expect(decompressed).toEqual(signature);
|
||||
|
||||
// Numbers are large enough to be compressed with Uint16Array.
|
||||
gen = n =>
|
||||
new Float32Array(
|
||||
crypto.getRandomValues(new Uint16Array(n)).map(x => x / 10)
|
||||
);
|
||||
outlines = [100, 200, 300, 10, 80].map(gen);
|
||||
signature.outlines = outlines;
|
||||
compressed = await SignatureExtractor.compressSignature(signature);
|
||||
decompressed = await SignatureExtractor.decompressSignature(compressed);
|
||||
expect(decompressed).toEqual(signature);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
AnnotationType,
|
||||
createValidAbsoluteUrl,
|
||||
FeatureTest,
|
||||
getUuid,
|
||||
ImageKind,
|
||||
InvalidPDFException,
|
||||
normalizeUnicode,
|
||||
|
@ -62,6 +63,7 @@ import { ColorPicker } from "../../src/display/editor/color_picker.js";
|
|||
import { DOMSVGFactory } from "../../src/display/svg_factory.js";
|
||||
import { DrawLayer } from "../../src/display/draw_layer.js";
|
||||
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
||||
import { SignatureExtractor } from "../../src/display/editor/drawers/signaturedraw.js";
|
||||
import { TextLayer } from "../../src/display/text_layer.js";
|
||||
import { TouchManager } from "../../src/display/touch_manager.js";
|
||||
import { XfaLayer } from "../../src/display/xfa_layer.js";
|
||||
|
@ -85,6 +87,7 @@ const expectedAPI = Object.freeze({
|
|||
getDocument,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
getUuid,
|
||||
getXfaPageViewport,
|
||||
GlobalWorkerOptions,
|
||||
ImageKind,
|
||||
|
@ -105,6 +108,7 @@ const expectedAPI = Object.freeze({
|
|||
ResponseException,
|
||||
setLayerDimensions,
|
||||
shadow,
|
||||
SignatureExtractor,
|
||||
stopEvent,
|
||||
SupportedImageMimeTypes,
|
||||
TextLayer,
|
||||
|
|
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