From 177036e6aee6c7b31d8aa0b5ab0de3cd3aef824e Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Tue, 16 May 2023 23:03:01 +0200 Subject: [PATCH] For text widgets, get the text from the AP stream instead of from the format callback (bug 1825002) When fixing bug 1766987, I thought the field formatted value came from the result of the format callback: I was wrong. The format callback is ran but the value is unused (maybe it's useful to set some global vars... or it's just a bug in Acrobat). Anyway the value to display is the one rendered in the AP stream. The field value setter has been simplified and that fixes issue #16409. --- src/core/annotation.js | 4 ++ src/display/annotation_layer.js | 14 ++++-- src/scripting_api/event.js | 13 ++---- src/scripting_api/field.js | 30 ++++--------- test/integration/scripting_spec.js | 66 +++++++++++++++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/bug1825002.pdf | Bin 0 -> 6942 bytes 7 files changed, 93 insertions(+), 35 deletions(-) create mode 100755 test/pdfs/bug1825002.pdf diff --git a/src/core/annotation.js b/src/core/annotation.js index e642b3d83..f4c58e50e 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -2461,6 +2461,10 @@ class TextWidgetAnnotation extends WidgetAnnotation { this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL); } + get hasTextContent() { + return !!this.appearance; + } + _getCombAppearance( defaultAppearance, font, diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index ae5a873a3..e8755c502 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -1049,7 +1049,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const storedData = storage.getValue(id, { value: this.data.fieldValue, }); - let textContent = storedData.formattedValue || storedData.value || ""; + let textContent = storedData.value || ""; const maxLen = storage.getValue(id, { charLimit: this.data.maxLen, }).charLimit; @@ -1057,23 +1057,29 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { textContent = textContent.slice(0, maxLen); } + let fieldFormattedValues = + storedData.formattedValue || this.data.textContent?.join("\n") || null; + if (fieldFormattedValues && this.data.comb) { + fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, ""); + } + const elementData = { userValue: textContent, - formattedValue: null, + formattedValue: fieldFormattedValues, lastCommittedValue: null, commitKey: 1, }; if (this.data.multiLine) { element = document.createElement("textarea"); - element.textContent = textContent; + element.textContent = fieldFormattedValues ?? textContent; if (this.data.doNotScroll) { element.style.overflowY = "hidden"; } } else { element = document.createElement("input"); element.type = "text"; - element.setAttribute("value", textContent); + element.setAttribute("value", fieldFormattedValues ?? textContent); if (this.data.doNotScroll) { element.style.overflowX = "hidden"; } diff --git a/src/scripting_api/event.js b/src/scripting_api/event.js index 6a2047bc3..ba0aa8967 100644 --- a/src/scripting_api/event.js +++ b/src/scripting_api/event.js @@ -98,8 +98,9 @@ class EventDispatcher { // errors in the case where a formatter is using one of those named // actions (see #15818). this._document.obj._initActions(); - // Before running the Open event, we format all the fields - // (see bug 1766987). + // Before running the Open event, we run the format callbacks but + // without changing the value of the fields. + // Acrobat does the same thing. this.formatAll(); } if ( @@ -232,13 +233,7 @@ class EventDispatcher { const event = (globalThis.event = new Event({})); for (const source of Object.values(this._objects)) { event.value = source.obj.value; - if (this.runActions(source, source, event, "Format")) { - source.obj._send({ - id: source.obj._id, - siblings: source.obj._siblings, - formattedValue: event.value?.toString?.(), - }); - } + this.runActions(source, source, event, "Format"); } } diff --git a/src/scripting_api/field.js b/src/scripting_api/field.js index 869822222..cfd37da0f 100644 --- a/src/scripting_api/field.js +++ b/src/scripting_api/field.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { createActionsMap, FieldType, getFieldType } from "./common.js"; +import { createActionsMap, getFieldType } from "./common.js"; import { Color } from "./color.js"; import { PDFObject } from "./pdf_object.js"; @@ -247,29 +247,15 @@ class Field extends PDFObject { return; } - if (value === "") { - this._value = ""; - } else if (typeof value === "string") { - switch (this._fieldType) { - case FieldType.none: { - this._originalValue = value; - const _value = value.trim().replace(",", "."); - this._value = !isNaN(_value) ? parseFloat(_value) : value; - break; - } - case FieldType.number: - case FieldType.percent: { - const _value = value.trim().replace(",", "."); - const number = parseFloat(_value); - this._value = !isNaN(number) ? number : 0; - break; - } - default: - this._value = value; - } - } else { + if (value === "" || typeof value !== "string") { + this._originalValue = undefined; this._value = value; + return; } + + this._originalValue = value; + const _value = value.trim().replace(",", "."); + this._value = !isNaN(_value) ? parseFloat(_value) : value; } _getValue() { diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index 77b7b277d..b4afc9eac 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -1897,4 +1897,70 @@ describe("Interaction", () => { ); }); }); + + describe("in bug1825002.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("bug1825002.pdf", getSelector("23R")); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that a field has the correct formatted value", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + let text = await page.$eval(getSelector("23R"), el => el.value); + expect(text) + .withContext(`In ${browserName}`) + .toEqual("ABCDEFGHIJKLMN"); + + await page.click(getSelector("23R")); + await page.waitForFunction( + `${getQuerySelector("23R")}.value !== "ABCDEFGHIJKLMN"` + ); + + text = await page.$eval(getSelector("23R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("123,45.7A"); + }) + ); + }); + + it("must check that a field is empty", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + let text = await page.$eval(getSelector("26R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); + + await page.click(getSelector("26R")); + await page.type(getSelector("26R"), "abcde", { delay: 10 }); + + await page.click(getSelector("23R")); + await page.waitForTimeout(10); + await page.click(getSelector("26R")); + + await page.keyboard.down("Control"); + await page.keyboard.press("A"); + await page.keyboard.up("Control"); + await page.keyboard.press("Backspace"); + + await page.click(getSelector("23R")); + await page.waitForTimeout(10); + + text = await page.$eval(getSelector("26R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 98d799d1b..96ac967d7 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -591,3 +591,4 @@ !issue16316.pdf !issue14565.pdf !multiline.pdf +!bug1825002.pdf diff --git a/test/pdfs/bug1825002.pdf b/test/pdfs/bug1825002.pdf new file mode 100755 index 0000000000000000000000000000000000000000..abe08e51cae1cae336c9aa6bb19b01bb10c79ea3 GIT binary patch literal 6942 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 literal 0 HcmV?d00001