diff --git a/src/core/annotation.js b/src/core/annotation.js index be87b6c9b..38b43e4e5 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -71,22 +71,34 @@ class AnnotationFactory { * instance. */ static create(xref, ref, pdfManager, idFactory, collectFields) { - return pdfManager.ensureCatalog("acroForm").then(acroForm => { - return pdfManager.ensure(this, "_create", [ + return Promise.all([ + pdfManager.ensureCatalog("acroForm"), + collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1, + ]).then(([acroForm, pageIndex]) => + pdfManager.ensure(this, "_create", [ xref, ref, pdfManager, idFactory, acroForm, collectFields, - ]); - }); + pageIndex, + ]) + ); } /** * @private */ - static _create(xref, ref, pdfManager, idFactory, acroForm, collectFields) { + static _create( + xref, + ref, + pdfManager, + idFactory, + acroForm, + collectFields, + pageIndex = -1 + ) { const dict = xref.fetchIfRef(ref); if (!isDict(dict)) { return undefined; @@ -108,6 +120,7 @@ class AnnotationFactory { pdfManager, acroForm: acroForm instanceof Dict ? acroForm : Dict.empty, collectFields, + pageIndex, }; switch (subtype) { @@ -196,6 +209,26 @@ class AnnotationFactory { return new Annotation(parameters); } } + + static async _getPageIndex(xref, ref, pdfManager) { + try { + const annotDict = await xref.fetchIfRefAsync(ref); + if (!isDict(annotDict)) { + return -1; + } + const pageRef = annotDict.getRaw("P"); + if (!isRef(pageRef)) { + return -1; + } + const pageIndex = await pdfManager.ensureCatalog("getPageIndex", [ + pageRef, + ]); + return pageIndex; + } catch (ex) { + warn(`_getPageIndex: "${ex}".`); + return -1; + } + } } function getRgbColor(color) { @@ -373,6 +406,7 @@ class Annotation { AnnotationActionEventType ); this.data.fieldName = this._constructFieldName(dict); + this.data.pageIndex = params.pageIndex; } this._fallbackFontDict = null; @@ -681,6 +715,7 @@ class Annotation { name: this.data.fieldName, type: "", kidIds: this.data.kidIds, + page: this.data.pageIndex, }; } return null; @@ -1775,6 +1810,7 @@ class TextWidgetAnnotation extends WidgetAnnotation { name: this.data.fieldName, rect: this.data.rect, actions: this.data.actions, + page: this.data.pageIndex, type: "text", }; } @@ -2106,6 +2142,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation { rect: this.data.rect, hidden: this.data.hidden, actions: this.data.actions, + page: this.data.pageIndex, type, }; } @@ -2186,6 +2223,7 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation { hidden: this.data.hidden, actions: this.data.actions, items: this.data.options, + page: this.data.pageIndex, type, }; } @@ -2205,6 +2243,7 @@ class SignatureWidgetAnnotation extends WidgetAnnotation { return { id: this.data.id, value: null, + page: this.data.pageIndex, type: "signature", }; } diff --git a/src/core/catalog.js b/src/core/catalog.js index b5b93232b..0b8d3b04f 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -70,6 +70,7 @@ class Catalog { this.builtInCMapCache = new Map(); this.globalImageCache = new GlobalImageCache(); this.pageKidsCountCache = new RefSetCache(); + this.pageIndexCache = new RefSetCache(); this.nonBlendModesSet = new RefSet(); } @@ -983,6 +984,7 @@ class Catalog { clearPrimitiveCaches(); this.globalImageCache.clear(/* onlyData = */ manuallyTriggered); this.pageKidsCountCache.clear(); + this.pageIndexCache.clear(); this.nonBlendModesSet.clear(); const promises = []; @@ -1112,6 +1114,11 @@ class Catalog { } getPageIndex(pageRef) { + const cachedPageIndex = this.pageIndexCache.get(pageRef); + if (cachedPageIndex !== undefined) { + return Promise.resolve(cachedPageIndex); + } + // The page tree nodes have the count of all the leaves below them. To get // how many pages are before we just have to walk up the tree and keep // adding the count of siblings to the left of the node. @@ -1191,16 +1198,16 @@ class Catalog { } let total = 0; - function next(ref) { - return pagesBeforeRef(ref).then(function (args) { + const next = ref => + pagesBeforeRef(ref).then(args => { if (!args) { + this.pageIndexCache.put(pageRef, total); return total; } const [count, parentRef] = args; total += count; return next(parentRef); }); - } return next(pageRef); } diff --git a/src/scripting_api/field.js b/src/scripting_api/field.js index 0ae9e4cbc..e7154dc8c 100644 --- a/src/scripting_api/field.js +++ b/src/scripting_api/field.js @@ -49,7 +49,6 @@ class Field extends PDFObject { this.multiline = data.multiline; this.multipleSelection = !!data.multipleSelection; this.name = data.name; - this.page = data.page; this.password = data.password; this.print = data.print; this.radiosInUnison = data.radiosInUnison; @@ -78,6 +77,7 @@ class Field extends PDFObject { this._fillColor = data.fillColor || ["T"]; this._isChoice = Array.isArray(data.items); this._items = data.items || []; + this._page = data.page || 0; this._strokeColor = data.strokeColor || ["G", 0]; this._textColor = data.textColor || ["G", 0]; this._value = data.value || ""; @@ -180,6 +180,14 @@ class Field extends PDFObject { this.strokeColor = color; } + get page() { + return this._page; + } + + set page(_) { + throw new Error("field.page is read-only"); + } + get textColor() { return this._textColor; } diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index 7f115e39e..be45fee7f 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -762,4 +762,44 @@ describe("Interaction", () => { ); }); }); + + describe("Check field properties", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("evaljs.pdf", "#\\35 5R"); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check page index", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await clearInput(page, "#\\35 5R"); + await page.type( + "#\\35 5R", + ` + ['Text1', 'Text2', 'Text4', + 'List Box7', 'Group6'].map(x => this.getField(x).page).join(',') + ` + ); + + // Click on execute button to eval the above code. + await page.click("[data-annotation-id='57R']"); + await page.waitForFunction( + `document.querySelector("#\\\\35 6R").value !== ""` + ); + + const text = await page.$eval("#\\35 6R", el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("0,0,1,1,1"); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 6915b4600..43d39f2d3 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -403,6 +403,7 @@ !issue12418_reduced.pdf !annotation-freetext.pdf !annotation-line.pdf +!evaljs.pdf !annotation-line-without-appearance.pdf !bug1669099.pdf !annotation-square-circle.pdf diff --git a/test/pdfs/evaljs.pdf b/test/pdfs/evaljs.pdf new file mode 100644 index 000000000..31461dd31 Binary files /dev/null and b/test/pdfs/evaljs.pdf differ