From 0c1ec946aa3aa8d9bdfc30858570a0cc63afc6ea Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Tue, 13 Dec 2022 00:07:45 +0100 Subject: [PATCH] [JS] Handle correctly choice widgets where the display and the export values are different (issue #15815) --- src/core/annotation.js | 10 +++++- src/display/annotation_layer.js | 35 ++++++++++++-------- src/scripting_api/event.js | 11 +++++++ src/scripting_api/field.js | 51 ++++++++++++++++++++--------- test/integration/scripting_spec.js | 50 ++++++++++++++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/issue15815.pdf | Bin 0 -> 7424 bytes test/test_manifest.json | 27 +++++++++++++++ 8 files changed, 155 insertions(+), 30 deletions(-) create mode 100755 test/pdfs/issue15815.pdf diff --git a/src/core/annotation.js b/src/core/annotation.js index 50cd6ba09..1355f08b4 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -1978,7 +1978,15 @@ class WidgetAnnotation extends Annotation { assert(typeof value === "string", "Expected `value` to be a string."); - value = value.trim(); + if (!this.data.combo) { + value = value.trim(); + } else { + // The value is supposed to be one of the exportValue. + const option = + this.data.options.find(({ exportValue }) => value === exportValue) || + this.data.options[0]; + value = (option && option.displayValue) || ""; + } if (value === "") { // the field is empty: nothing to render diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 1eb5f9c5d..dce12bf6f 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -1612,10 +1612,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { selectElement.addEventListener("input", removeEmptyEntry); } - const getValue = (event, isExport) => { + const getValue = isExport => { const name = isExport ? "value" : "textContent"; - const options = event.target.options; - if (!event.target.multiple) { + const { options, multiple } = selectElement; + if (!multiple) { return options.selectedIndex === -1 ? null : options[options.selectedIndex][name]; @@ -1625,6 +1625,8 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { .map(option => option[name]); }; + let selectedValues = getValue(/* isExport */ false); + const getItems = event => { const options = event.target.options; return Array.prototype.map.call(options, option => { @@ -1643,8 +1645,9 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { option.selected = values.has(option.value); } storage.setValue(id, { - value: getValue(event, /* isExport */ true), + value: getValue(/* isExport */ true), }); + selectedValues = getValue(/* isExport */ false); }, multipleSelection(event) { selectElement.multiple = true; @@ -1664,15 +1667,17 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { } } storage.setValue(id, { - value: getValue(event, /* isExport */ true), + value: getValue(/* isExport */ true), items: getItems(event), }); + selectedValues = getValue(/* isExport */ false); }, clear(event) { while (selectElement.length !== 0) { selectElement.remove(0); } storage.setValue(id, { value: null, items: [] }); + selectedValues = getValue(/* isExport */ false); }, insert(event) { const { index, displayValue, exportValue } = event.detail.insert; @@ -1687,9 +1692,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { selectElement.append(optionElement); } storage.setValue(id, { - value: getValue(event, /* isExport */ true), + value: getValue(/* isExport */ true), items: getItems(event), }); + selectedValues = getValue(/* isExport */ false); }, items(event) { const { items } = event.detail; @@ -1707,9 +1713,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { selectElement.options[0].selected = true; } storage.setValue(id, { - value: getValue(event, /* isExport */ true), + value: getValue(/* isExport */ true), items: getItems(event), }); + selectedValues = getValue(/* isExport */ false); }, indices(event) { const indices = new Set(event.detail.indices); @@ -1717,8 +1724,9 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { option.selected = indices.has(option.index); } storage.setValue(id, { - value: getValue(event, /* isExport */ true), + value: getValue(/* isExport */ true), }); + selectedValues = getValue(/* isExport */ false); }, editable(event) { event.target.disabled = !event.detail.editable; @@ -1728,18 +1736,19 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { }); selectElement.addEventListener("input", event => { - const exportValue = getValue(event, /* isExport */ true); - const value = getValue(event, /* isExport */ false); + const exportValue = getValue(/* isExport */ true); storage.setValue(id, { value: exportValue }); + event.preventDefault(); + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", - value, + value: selectedValues, changeEx: exportValue, - willCommit: true, + willCommit: false, commitKey: 1, keyDown: false, }, @@ -1761,7 +1770,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { ); } else { selectElement.addEventListener("input", function (event) { - storage.setValue(id, { value: getValue(event, /* isExport */ true) }); + storage.setValue(id, { value: getValue(/* isExport */ true) }); }); } diff --git a/src/scripting_api/event.js b/src/scripting_api/event.js index 7b0fdecb8..d85f28fe1 100644 --- a/src/scripting_api/event.js +++ b/src/scripting_api/event.js @@ -137,6 +137,7 @@ class EventDispatcher { case "Keystroke": savedChange = { value: event.value, + changeEx: event.changeEx, change: event.change, selStart: event.selStart, selEnd: event.selEnd, @@ -170,6 +171,16 @@ class EventDispatcher { if (event.willCommit) { this.runValidation(source, event); } else { + if (source.obj._isChoice) { + source.obj.value = savedChange.changeEx; + source.obj._send({ + id: source.obj._id, + siblings: source.obj._siblings, + value: source.obj.value, + }); + return; + } + const value = (source.obj.value = this.mergeChange(event)); let selStart, selEnd; if ( diff --git a/src/scripting_api/field.js b/src/scripting_api/field.js index 59501b912..af67c9931 100644 --- a/src/scripting_api/field.js +++ b/src/scripting_api/field.js @@ -242,6 +242,11 @@ class Field extends PDFObject { } set value(value) { + if (this._isChoice) { + this._setChoiceValue(value); + return; + } + if (value === "") { this._value = ""; } else if (typeof value === "string") { @@ -260,23 +265,37 @@ class Field extends PDFObject { } else { this._value = value; } - if (this._isChoice) { - if (this.multipleSelection) { - const values = new Set(value); - if (Array.isArray(this._currentValueIndices)) { - this._currentValueIndices.length = 0; - } else { - this._currentValueIndices = []; - } - this._items.forEach(({ displayValue }, i) => { - if (values.has(displayValue)) { - this._currentValueIndices.push(i); - } - }); + } + + _setChoiceValue(value) { + if (this.multipleSelection) { + if (!Array.isArray(value)) { + value = [value]; + } + const values = new Set(value); + if (Array.isArray(this._currentValueIndices)) { + this._currentValueIndices.length = 0; + this._value.length = 0; } else { - this._currentValueIndices = this._items.findIndex( - ({ displayValue }) => value === displayValue - ); + this._currentValueIndices = []; + this._value = []; + } + this._items.forEach((item, i) => { + if (values.has(item.exportValue)) { + this._currentValueIndices.push(i); + this._value.push(item.exportValue); + } + }); + } else { + if (Array.isArray(value)) { + value = value[0]; + } + const index = this._items.findIndex( + ({ exportValue }) => value === exportValue + ); + if (index !== -1) { + this._currentValueIndices = index; + this._value = this._items[index].exportValue; } } } diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index e63063abb..f759584e6 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -1651,4 +1651,54 @@ describe("Interaction", () => { ); }); }); + + describe("in issue15815.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("issue15815.pdf", getSelector("24R")); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check field value is correctly updated when committed with ENTER key", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + let value = "A"; + for (const [displayValue, exportValue] of [ + ["B", "x2"], + ["C", "x3"], + ["A", "x1"], + ]) { + await clearInput(page, getSelector("27R")); + await page.select(getSelector("24R"), exportValue); + await page.waitForFunction( + `${getQuerySelector("27R")}.value !== ""` + ); + const text = await page.$eval(getSelector("27R"), el => el.value); + expect(text) + .withContext(`In ${browserName}`) + .toEqual(`value=${value}, changeEx=${exportValue}`); + value = displayValue; + } + + for (const exportValue of ["x3", "x2", "x1"]) { + await clearInput(page, getSelector("27R")); + await page.type(getSelector("27R"), exportValue); + await page.click("[data-annotation-id='28R']"); + await page.waitForTimeout(10); + + value = await page.$eval(getSelector("24R"), el => el.value); + expect(value).withContext(`In ${browserName}`).toEqual(exportValue); + } + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 92a9c74a4..6fbb661d2 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -561,3 +561,4 @@ !issue15753.pdf !issue15789.pdf !fields_order.pdf +!issue15815.pdf diff --git a/test/pdfs/issue15815.pdf b/test/pdfs/issue15815.pdf new file mode 100755 index 0000000000000000000000000000000000000000..266b4ba3cd6c3e8fe383617a5b85010179d648d3 GIT binary patch literal 7424 zcmeHMd010tx<}dc2&2`C0mphK3W|`NB_|s~5CV}cfDi>`&>WHzh$cBP31ziK#C_x1 zDlSwlQn1#s(yE1)dfgoh%BZNQRE1V;sZ~d9aiQ(~4haw??TpX8^E~$t^N>6_=e+Ch z`+aZTm(MpbLdLPHx=M&*!0=_^X1X9unDS?TEl5`~6pr&%uK;k&c zTxe+E5(Jop#8HM!OBT!*G10uqBq~6XXxai}MECP#01eFJA&NXBg`_4?8YCqRgK-!G z5ST3{iqv~TS=TZ$)Wej@4CPk?hkMjaDW1<=mgxN2gefHxN|ou*_=CGp6vG?cKU68l zZ+_fV`*8As&2*2QMc&J1vP3?KE|<4&TzI&2 z;g-*ytmr(pX4EimnVZ{P*`U+LQOh>5Sh?-nyPha_Y%FjsA_ANjH4Aq><`+3RI}K(* zCI`uMgJ_F3Ol6{Fv`G)+R^;WBg;bLk5@y(#gd}8JbbyOySlE4;NM?fX7{j6@EBI0% ztD`gfn1}KEpa?S6ob!nL=H$u z{_a!2T41RskR-}XXPZ%rLy!BDuo5YkLXP;sEVhjw&!L3z znZ)t#1?mqIE_x}L>=F65!9d}u5XuFhf;UEpVNlRO)@G24qdY{vlZX9k_b+e#Gs7_a zrBdrWRUZ2o){(?Wul2q@cQ0L! z8F)#*?yW%=?@F2uMqggd9$q@pj~pAiV7T9F%g;oYSB;N}3LP9xOrgq4C;GN2mpv#g zIZFHmYhP5EkfB(-sHncc{hi&;V>hsF2T_ z@UFT8bN!J8d$IK`-uix_Hti2N^R#xSmL9d-q z_)gaZ>^uv{&aC-(J31+g3(4C5QpijC)Mt9}e~|+}s3=&nI-aCT4z)wGHpGnyJs#sU zqWIq2+0IwJ_FZ%Os5O%_K4{7Qin?1HK zCJ)yiIoSU2^-qpH8};+TNB66h^AdM|%lA48wVZ49Jr;AEb*JRR1s^}knK@d=DuW)m z=~qXXcX;bO2cKt^@7bGjum0xl8{vtE3h4VAn>JjmdyV@(vHt|UWRkk&nmCacaY?wH$N8;7_U^HHPffL8GegCns#pf3Podt6+D)+PViDKk~Y@>u}sp zZid3zO-q}C3ojNottncvpCA74J<*}YId>OTOL9A;E1&+uC@J`+aNI})GM{y_jtbkTS$UlxwSEP)BeBjCg-nhd*f&t>za~v;Qa?}l}lrXmT_u> z2l~u7+}S#*aKx$!J_!V;Ld$ZeoA(S}`cD4$Q_pGNs;jYVZdr6a?#?$mw}y_%e0ZGw zUE8tFx8Icg7F@g|t0lJBCG&nB-E#7#Q1sP54}FDrw22GOO)GGE)_jM%Z+3#XD)i>E zl|SaKQKesAu $x|yYUpYPek=yOF=1 zZG(Tl?x09@cNMlGWh+EzU(s=(VAl&f=-I4A4I_Gs8ZHDi&DL`EFKKuRnz%O+tx-6I zt6WEqUU0kXT>WrY>4MK1E82%|e|KSc>A?hcxp&gc(p~rF%v$%(rm|VOO!wv+gPNgr zgQEgtTsw%R6B`0gMPC>c6_+r5VnZA!CgFn1N7>5-glogp2-yIwId#}fo{L2Axf3US)=J`NBb5sI5gKts^(D^ID^X68ba9jzW8r8tW}4wQ}}XJZj@44 z+NgXJm7E{1Q0324iPh{-2^5?gl&d%DL30b|>U9QlP;LmD1d|dK1imx35jNZ{Vwo4h z4r3m`sq#cvN}DKH#6>wOFe`x8l*_|K8liv+1g#j3Bd7>LF%BjU!q6b_J8yfjCCp?H zY*K52k|Ja_f8Z*Fon^5YgAgPqCx@HE~C$nwh(5di-Lk3u=5S}fv-Fd?Q9@l_&T1~Ax9 zx1Zf;0~opuDyqjWp;l`Mk|a4ig5q-sN+jfD;3_ePFQ(K2ktjomlj0t`cDif{c%E?QC`wFgCaJ>`)FD3knU9aGJDFj|h_!YbU z&)|akMj>!Y5yH*^gN}nyi#ai|jm5t^=Jrm4eDseeL4|b^JcFDLn1(iYo{95c1Ljl6 z?RdvhX7+O*UsdgO_3{80meZ}DhB%r_?M?^&bu<``{c$)+)^NBn^1xG#P3Gvp=nKXg zoN4M^!pzC4an5>YX|}U-wtU&HF1F{%ce^STt|z~(=&C$6d~s1l;bN92Z^YM?jq6x) z*Ky-QR3Y03c*!+s{DQRu>B aEN>_9-{ULC-PjJmu{%2h=XkxRXa56)6;uiU literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 552d44d4b..a2f856e5b 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -7240,5 +7240,32 @@ "value": "مرحبا بالعالم" } } + }, + { + "id": "issue15815-print", + "file": "pdfs/issue15815.pdf", + "md5": "48b8b057954d5b773421ac03b7fcd738", + "rounds": 1, + "type": "eq", + "print": true, + "annotationStorage": { + "24R": { + "value": "x3" + } + } + }, + { + "id": "issue15815-save-print", + "file": "pdfs/issue15815.pdf", + "md5": "48b8b057954d5b773421ac03b7fcd738", + "rounds": 1, + "type": "eq", + "save": true, + "print": true, + "annotationStorage": { + "24R": { + "value": "x2" + } + } } ]