Compare commits

...

10 Commits

Author SHA1 Message Date
Jonas Jenwald
aa70b28365
Merge pull request #19555 from rossj/master
Pad rev 4 encryption keys to be >= 16 bytes (issue 19484)
2025-02-25 10:03:56 +01:00
calixteman
e999f77959
Merge pull request #19554 from calixteman/signature_telemetry
[Editor] Add some telemetry for the signature editor (bug 1945827)
2025-02-24 22:53:49 +01:00
Ross Johnson
4f25d7f6cd Fix decryption of R=4, V=4 files with < 16-byte keys by 0-padding - undocumented but matches Acrobat behavior (issue #19484) 2025-02-24 15:36:37 -06:00
Calixte Denizet
9e672ff05e [Editor] Add some telemetry for the signature editor (bug 1945827) 2025-02-24 21:10:04 +01:00
calixteman
fef706233d
Merge pull request #19551 from Snuffleupagus/loadSystemFont-fix-assert
Fix the `assert` in `FontLoader.prototype.loadSystemFont`
2025-02-24 16:49:03 +01:00
calixteman
12d2f1b04a
Merge pull request #19552 from calixteman/add_generated_header_openjpeg
Add a comment 'THIS FILE IS GENERATED - DO NOT EDIT' in openjpeg files generated with emscripten
2025-02-24 16:17:48 +01:00
Jonas Jenwald
2966171a2b Fix the assert in FontLoader.prototype.loadSystemFont
Currently this `assert` isn't actually doing what it's supposed to, since the `FontLoader`-class doesn't have a `disableFontFace`-field.
The `FontFaceObject`-class on the other hand has such a field, hence we update the method-signature to be able to check the intended thing.
2025-02-24 16:09:07 +01:00
calixteman
f9aa8e1d15
Merge pull request #19539 from calixteman/paste_signature
[Editor] Shift the signature editor when pasted
2025-02-24 16:07:50 +01:00
Calixte Denizet
c099245daa Add a comment 'THIS FILE IS GENERATED - DO NOT EDIT' in openjpeg files generated with emscripten 2025-02-24 16:05:19 +01:00
Calixte Denizet
cc3d6ab539 [Editor] Shift the signature editor when pasted
The idea is to avoid to have the pasted editor hidding the copied one:
the user could think that nothing happened.
So the top-left corner of the pasted one is moved to the bottom-right corner of the copied one.
2025-02-23 21:35:01 +01:00
18 changed files with 186 additions and 53 deletions

View File

@ -1,3 +1,4 @@
/* THIS FILE IS GENERATED - DO NOT EDIT */
var OpenJPEG = (() => { var OpenJPEG = (() => {
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined; var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;

View File

@ -1,3 +1,4 @@
/* THIS FILE IS GENERATED - DO NOT EDIT */
var OpenJPEG = (() => { var OpenJPEG = (() => {
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined; var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;

View File

@ -1787,7 +1787,14 @@ class CipherTransformFactory {
); );
} }
this.encryptionKey = encryptionKey; if (algorithm === 4 && encryptionKey.length < 16) {
// Extend key to 16 byte minimum (undocumented),
// fixes issue19484_1.pdf and issue19484_2.pdf.
this.encryptionKey = new Uint8Array(16);
this.encryptionKey.set(encryptionKey);
} else {
this.encryptionKey = encryptionKey;
}
if (algorithm >= 4) { if (algorithm >= 4) {
const cf = dict.get("CF"); const cf = dict.get("CF");

View File

@ -628,6 +628,12 @@ class DrawingEditor extends AnnotationEditor {
return this.div; return this.div;
} }
let baseX, baseY;
if (this._isCopy) {
baseX = this.x;
baseY = this.y;
}
const div = super.render(); const div = super.render();
div.classList.add("draw"); div.classList.add("draw");
@ -640,6 +646,10 @@ class DrawingEditor extends AnnotationEditor {
this._uiManager.addShouldRescale(this); this._uiManager.addShouldRescale(this);
this.disableEditing(); this.disableEditing();
if (this._isCopy) {
this._moveAfterPaste(baseX, baseY);
}
return div; return div;
} }

View File

@ -85,6 +85,8 @@ class AnnotationEditor {
#touchManager = null; #touchManager = null;
_isCopy = false;
_editToolbar = null; _editToolbar = null;
_initialOptions = Object.create(null); _initialOptions = Object.create(null);
@ -442,6 +444,17 @@ class AnnotationEditor {
this.fixAndSetPosition(); this.fixAndSetPosition();
} }
_moveAfterPaste(baseX, baseY) {
const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt(
baseX * parentWidth,
baseY * parentHeight,
this.width * parentWidth,
this.height * parentHeight
);
this._onTranslated();
}
#translate([width, height], x, y) { #translate([width, height], x, y) {
[x, y] = this.screenToPageTranslation(x, y); [x, y] = this.screenToPageTranslation(x, y);
@ -1597,6 +1610,7 @@ class AnnotationEditor {
}); });
editor.rotation = data.rotation; editor.rotation = data.rotation;
editor.#accessibilityData = data.accessibilityData; editor.#accessibilityData = data.accessibilityData;
editor._isCopy = data.isCopy || false;
const [pageWidth, pageHeight] = editor.pageDimensions; const [pageWidth, pageHeight] = editor.pageDimensions;
const [x, y, width, height] = editor.getRectInCurrentCoords( const [x, y, width, height] = editor.getRectInCurrentCoords(

View File

@ -553,7 +553,7 @@ class FreeTextEditor extends AnnotationEditor {
} }
let baseX, baseY; let baseX, baseY;
if (this.width) { if (this._isCopy || this.annotationElementId) {
baseX = this.x; baseX = this.x;
baseY = this.y; baseY = this.y;
} }
@ -581,7 +581,7 @@ class FreeTextEditor extends AnnotationEditor {
bindEvents(this, this.div, ["dblclick", "keydown"]); bindEvents(this, this.div, ["dblclick", "keydown"]);
if (this.width) { if (this._isCopy || this.annotationElementId) {
// This editor was created in using copy (ctrl+c). // This editor was created in using copy (ctrl+c).
const [parentWidth, parentHeight] = this.parentDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
if (this.annotationElementId) { if (this.annotationElementId) {
@ -627,12 +627,7 @@ class FreeTextEditor extends AnnotationEditor {
} }
this.setAt(posX * parentWidth, posY * parentHeight, tx, ty); this.setAt(posX * parentWidth, posY * parentHeight, tx, ty);
} else { } else {
this.setAt( this._moveAfterPaste(baseX, baseY);
baseX * parentWidth,
baseY * parentHeight,
this.width * parentWidth,
this.height * parentHeight
);
} }
this.#setContent(); this.#setContent();
@ -847,6 +842,7 @@ class FreeTextEditor extends AnnotationEditor {
if (isForCopying) { if (isForCopying) {
// Don't add the id when copying because the pasted editor mustn't be // Don't add the id when copying because the pasted editor mustn't be
// linked to an existing annotation. // linked to an existing annotation.
serialized.isCopy = true;
return serialized; return serialized;
} }

View File

@ -246,6 +246,7 @@ class InkEditor extends DrawingEditor {
}; };
if (isForCopying) { if (isForCopying) {
serialized.isCopy = true;
return serialized; return serialized;
} }

View File

@ -111,6 +111,22 @@ class SignatureEditor extends DrawingEditor {
return false; return false;
} }
/** @inheritdoc */
get telemetryFinalData() {
return {
type: "signature",
hasDescription: !!this.#description,
};
}
static computeTelemetryFinalData(data) {
const hasDescriptionStats = data.get("hasDescription");
return {
hasAltText: hasDescriptionStats.get(true) ?? 0,
hasNoAltText: hasDescriptionStats.get(false) ?? 0,
};
}
/** @inheritdoc */ /** @inheritdoc */
get isResizable() { get isResizable() {
return true; return true;
@ -130,6 +146,15 @@ class SignatureEditor extends DrawingEditor {
return this.div; return this.div;
} }
let baseX, baseY;
const { _isCopy } = this;
if (_isCopy) {
// No need to adjust the position when rendering in DrawingEditor.
this._isCopy = false;
baseX = this.x;
baseY = this.y;
}
super.render(); super.render();
this.div.setAttribute("role", "figure"); this.div.setAttribute("role", "figure");
@ -163,6 +188,11 @@ class SignatureEditor extends DrawingEditor {
} }
} }
if (_isCopy) {
this._isCopy = true;
this._moveAfterPaste(baseX, baseY);
}
return this.div; return this.div;
} }
@ -265,6 +295,14 @@ class SignatureEditor extends DrawingEditor {
this._uiManager.addToAnnotationStorage(this); this._uiManager.addToAnnotationStorage(this);
this.setUuid(uuid); this.setUuid(uuid);
this._reportTelemetry({
action: "pdfjs.signature.inserted",
data: {
hasBeenSaved: !!uuid,
hasDescription: !!description,
},
});
this.div.hidden = false; this.div.hidden = false;
} }
@ -348,6 +386,7 @@ class SignatureEditor extends DrawingEditor {
if (isForCopying) { if (isForCopying) {
serialized.paths = { lines, points }; serialized.paths = { lines, points };
serialized.uuid = this.#signatureUUID; serialized.uuid = this.#signatureUUID;
serialized.isCopy = true;
} else { } else {
serialized.lines = lines; serialized.lines = lines;
} }

View File

@ -346,7 +346,7 @@ class StampEditor extends AnnotationEditor {
} }
let baseX, baseY; let baseX, baseY;
if (this.width) { if (this._isCopy) {
baseX = this.x; baseX = this.x;
baseY = this.y; baseY = this.y;
} }
@ -365,15 +365,8 @@ class StampEditor extends AnnotationEditor {
} }
} }
if (this.width && !this.annotationElementId) { if (this._isCopy) {
// This editor was created in using copy (ctrl+c). this._moveAfterPaste(baseX, baseY);
const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt(
baseX * parentWidth,
baseY * parentHeight,
this.width * parentWidth,
this.height * parentHeight
);
} }
this._uiManager.addShouldRescale(this); this._uiManager.addShouldRescale(this);
@ -854,6 +847,7 @@ class StampEditor extends AnnotationEditor {
// hence we serialize the bitmap to a data url. // hence we serialize the bitmap to a data url.
serialized.bitmapUrl = this.#serializeBitmap(/* toUrl = */ true); serialized.bitmapUrl = this.#serializeBitmap(/* toUrl = */ true);
serialized.accessibilityData = this.serializeAltText(true); serialized.accessibilityData = this.serializeAltText(true);
serialized.isCopy = true;
return serialized; return serialized;
} }

View File

@ -80,12 +80,16 @@ class FontLoader {
} }
} }
async loadSystemFont({ systemFontInfo: info, _inspectFont }) { async loadSystemFont({
systemFontInfo: info,
disableFontFace,
_inspectFont,
}) {
if (!info || this.#systemFonts.has(info.loadedName)) { if (!info || this.#systemFonts.has(info.loadedName)) {
return; return;
} }
assert( assert(
!this.disableFontFace, !disableFontFace,
"loadSystemFont shouldn't be called when `disableFontFace` is set." "loadSystemFont shouldn't be called when `disableFontFace` is set."
); );

View File

@ -348,13 +348,7 @@ describe("Signature Editor", () => {
const editorSelector = getEditorSelector(0); const editorSelector = getEditorSelector(0);
await page.waitForSelector(editorSelector, { visible: true }); await page.waitForSelector(editorSelector, { visible: true });
await page.waitForSelector( const originalRect = await getRect(page, editorSelector);
`.canvasWrapper > svg use[href="#path_p1_0"]`,
{ visible: true }
);
const originalPath = await page.$eval("#path_p1_0", el =>
el.getAttribute("d")
);
const originalDescription = await page.$eval( const originalDescription = await page.$eval(
`${editorSelector} .altText.editDescription`, `${editorSelector} .altText.editDescription`,
el => el.title el => el.title
@ -365,21 +359,15 @@ describe("Signature Editor", () => {
const pastedEditorSelector = getEditorSelector(1); const pastedEditorSelector = getEditorSelector(1);
await page.waitForSelector(pastedEditorSelector, { visible: true }); await page.waitForSelector(pastedEditorSelector, { visible: true });
await page.waitForSelector( const pastedRect = await getRect(page, pastedEditorSelector);
`.canvasWrapper > svg use[href="#path_p1_1"]`,
{ visible: true }
);
const pastedPath = await page.$eval("#path_p1_1", el =>
el.getAttribute("d")
);
const pastedDescription = await page.$eval( const pastedDescription = await page.$eval(
`${pastedEditorSelector} .altText.editDescription`, `${pastedEditorSelector} .altText.editDescription`,
el => el.title el => el.title
); );
expect(pastedPath) expect(pastedRect)
.withContext(`In ${browserName}`) .withContext(`In ${browserName}`)
.toEqual(originalPath); .not.toEqual(originalRect);
expect(pastedDescription) expect(pastedDescription)
.withContext(`In ${browserName}`) .withContext(`In ${browserName}`)
.toEqual(originalDescription); .toEqual(originalDescription);

View File

@ -106,6 +106,7 @@
!issue9084.pdf !issue9084.pdf
!issue12963.pdf !issue12963.pdf
!issue9105_reduced.pdf !issue9105_reduced.pdf
!issue19484_2.pdf
!issue9105_other.pdf !issue9105_other.pdf
!issue9252.pdf !issue9252.pdf
!issue9262_reduced.pdf !issue9262_reduced.pdf
@ -481,6 +482,7 @@
!openoffice.pdf !openoffice.pdf
!js-buttons.pdf !js-buttons.pdf
!issue7014.pdf !issue7014.pdf
!issue19484_1.pdf
!issue8187.pdf !issue8187.pdf
!annotation-link-text-popup.pdf !annotation-link-text-popup.pdf
!issue9278.pdf !issue9278.pdf

BIN
test/pdfs/issue19484_1.pdf Normal file

Binary file not shown.

BIN
test/pdfs/issue19484_2.pdf Normal file

Binary file not shown.

View File

@ -3629,6 +3629,14 @@
"link": false, "link": false,
"type": "text" "type": "text"
}, },
{
"id": "issue19484_1",
"file": "pdfs/issue19484_1.pdf",
"md5": "4e9e78a84226dbdddbd735388ccc2dcd",
"rounds": 1,
"link": false,
"type": "eq"
},
{ {
"id": "issue5644", "id": "issue5644",
"file": "pdfs/issue5644.pdf", "file": "pdfs/issue5644.pdf",
@ -5043,6 +5051,14 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{
"id": "issue19484_2",
"file": "pdfs/issue19484_2.pdf",
"md5": "cd3050eda9fa18a7e6a78c702f9890bb",
"rounds": 1,
"link": false,
"type": "eq"
},
{ {
"id": "bug894572", "id": "bug894572",
"file": "pdfs/bug894572.pdf", "file": "pdfs/bug894572.pdf",

View File

@ -537,7 +537,11 @@ class SignatureStorage {
async isFull() { async isFull() {
// We want to store at most 5 signatures. // We want to store at most 5 signatures.
return (await this.getAll()).size === 5; return (await this.size()) === 5;
}
async size() {
return (await this.getAll()).size;
} }
async create(data) { async create(data) {

View File

@ -70,7 +70,11 @@ class SignatureStorage {
async isFull() { async isFull() {
// Only allow 5 signatures to be saved. // Only allow 5 signatures to be saved.
return (await this.getAll()).size === 5; return (await this.size()) === 5;
}
async size() {
return (await this.getAll()).size;
} }
async create(data) { async create(data) {

View File

@ -176,6 +176,12 @@ class SignatureManager {
clearButton.addEventListener( clearButton.addEventListener(
"click", "click",
() => { () => {
this.#reportTelemetry({
action: "pdfjs.signature.clear",
data: {
type: this.#currentTab,
},
});
this.#initTab(null); this.#initTab(null);
}, },
{ passive: true } { passive: true }
@ -253,7 +259,9 @@ class SignatureManager {
#resetCommon() { #resetCommon() {
this.#hasDescriptionChanged = false; this.#hasDescriptionChanged = false;
this.#description.value = ""; this.#description.value = "";
this.#tabsToAltText.set(this.#currentTab, ""); if (this.#currentTab) {
this.#tabsToAltText.get(this.#currentTab).value = "";
}
} }
#resetTab(name) { #resetTab(name) {
@ -283,7 +291,7 @@ class SignatureManager {
return; return;
} }
if (this.#currentTab) { if (this.#currentTab) {
this.#tabsToAltText.set(this.#currentTab, this.#description.value); this.#tabsToAltText.get(this.#currentTab).value = this.#description.value;
} }
if (name) { if (name) {
this.#currentTab = name; this.#currentTab = name;
@ -294,7 +302,7 @@ class SignatureManager {
if (reset) { if (reset) {
this.#resetCommon(); this.#resetCommon();
} else { } else {
this.#description.value = this.#tabsToAltText.get(this.#currentTab); this.#description.value = this.#tabsToAltText.get(this.#currentTab).value;
} }
this.#clearDescription.disabled = this.#description.value === ""; this.#clearDescription.disabled = this.#description.value === "";
this.#currentTabAC?.abort(); this.#currentTabAC?.abort();
@ -335,7 +343,8 @@ class SignatureManager {
() => { () => {
const { value } = this.#typeInput; const { value } = this.#typeInput;
if (!this.#hasDescriptionChanged) { if (!this.#hasDescriptionChanged) {
this.#description.value = value; this.#tabsToAltText.get("type").default = this.#description.value =
value;
this.#clearDescription.disabled = value === ""; this.#clearDescription.disabled = value === "";
} }
this.#disableButtons(value); this.#disableButtons(value);
@ -398,6 +407,7 @@ class SignatureManager {
this.#l10n this.#l10n
.get(SignatureManager.#l10nDescription.signature) .get(SignatureManager.#l10nDescription.signature)
.then(description => { .then(description => {
this.#tabsToAltText.get("draw").default = description;
this.#description.value ||= description; this.#description.value ||= description;
this.#clearDescription.disabled = this.#description.value === ""; this.#clearDescription.disabled = this.#description.value === "";
}); });
@ -609,6 +619,7 @@ class SignatureManager {
this.#imageSVG.setAttribute("preserveAspectRatio", "xMidYMid meet"); this.#imageSVG.setAttribute("preserveAspectRatio", "xMidYMid meet");
this.#imageSVG.append(path); this.#imageSVG.append(path);
path.setAttribute("d", outline.toSVGPath()); path.setAttribute("d", outline.toSVGPath());
this.#tabsToAltText.get("image").default = file.name;
if (this.#description.value === "") { if (this.#description.value === "") {
this.#description.value = file.name || ""; this.#description.value = file.name || "";
this.#clearDescription.disabled = this.#description.value === ""; this.#clearDescription.disabled = this.#description.value === "";
@ -633,6 +644,16 @@ class SignatureManager {
); );
} }
#reportTelemetry(data) {
this.#eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data,
},
});
}
#addToolbarButton(signatureData, uuid, description) { #addToolbarButton(signatureData, uuid, description) {
const { curves, areContours, thickness, width, height } = signatureData; const { curves, areContours, thickness, width, height } = signatureData;
const maxDim = Math.max(width, height); const maxDim = Math.max(width, height);
@ -716,6 +737,12 @@ class SignatureManager {
deleteButton.addEventListener("click", async () => { deleteButton.addEventListener("click", async () => {
if (await this.#signatureStorage.delete(uuid)) { if (await this.#signatureStorage.delete(uuid)) {
div.remove(); div.remove();
this.#reportTelemetry({
action: "pdfjs.signature.delete_saved",
data: {
savedCount: await this.#signatureStorage.size(),
},
});
} }
}); });
const deleteSpan = document.createElement("span"); const deleteSpan = document.createElement("span");
@ -805,7 +832,7 @@ class SignatureManager {
async open({ uiManager, editor }) { async open({ uiManager, editor }) {
this.#tabsToAltText ||= new Map( this.#tabsToAltText ||= new Map(
this.#tabButtons.keys().map(name => [name, ""]) this.#tabButtons.keys().map(name => [name, { value: "", default: "" }])
); );
this.#uiManager = uiManager; this.#uiManager = uiManager;
this.#currentEditor = editor; this.#currentEditor = editor;
@ -851,7 +878,8 @@ class SignatureManager {
async #add() { async #add() {
let data; let data;
switch (this.#currentTab) { const type = this.#currentTab;
switch (type) {
case "type": case "type":
data = this.#getOutlineForType(); data = this.#getOutlineForType();
break; break;
@ -863,8 +891,8 @@ class SignatureManager {
break; break;
} }
let uuid = null; let uuid = null;
const description = this.#description.value;
if (this.#saveCheckbox.checked) { if (this.#saveCheckbox.checked) {
const description = this.#description.value;
const { newCurves, areContours, thickness, width, height } = data; const { newCurves, areContours, thickness, width, height } = data;
const signatureData = await SignatureExtractor.compressSignature({ const signatureData = await SignatureExtractor.compressSignature({
outlines: newCurves, outlines: newCurves,
@ -893,6 +921,18 @@ class SignatureManager {
console.warn("SignatureManager.add: cannot save the signature."); console.warn("SignatureManager.add: cannot save the signature.");
} }
} }
const altText = this.#tabsToAltText.get(type);
this.#reportTelemetry({
action: "pdfjs.signature.created",
data: {
type,
saved: !!uuid,
savedCount: await this.#signatureStorage.size(),
descriptionChanged: description !== altText.default,
},
});
this.#currentEditor.addSignature( this.#currentEditor.addSignature(
data, data,
DEFAULT_HEIGHT_IN_PAGE, DEFAULT_HEIGHT_IN_PAGE,
@ -940,7 +980,7 @@ class EditDescriptionDialog {
e.preventDefault(); e.preventDefault();
} }
}); });
cancelButton.addEventListener("click", this.#finish.bind(this)); cancelButton.addEventListener("click", this.#cancel.bind(this));
updateButton.addEventListener("click", this.#update.bind(this)); updateButton.addEventListener("click", this.#update.bind(this));
const clearDescription = description.lastElementChild; const clearDescription = description.lastElementChild;
@ -983,12 +1023,24 @@ class EditDescriptionDialog {
} }
async #update() { async #update() {
const description = this.#description.value; // The description has been changed because the button isn't disabled.
if (this.#previousDescription === description) { this.#currentEditor._reportTelemetry({
this.#finish(); action: "pdfjs.signature.edit_description",
return; data: {
} hasBeenChanged: true,
this.#currentEditor.description = description; },
});
this.#currentEditor.description = this.#description.value;
this.#finish();
}
#cancel() {
this.#currentEditor._reportTelemetry({
action: "pdfjs.signature.edit_description",
data: {
hasBeenChanged: false,
},
});
this.#finish(); this.#finish();
} }