Skip to content

XFA - Move the fake HTML representation of XFA from the worker to the main thread #13437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ class Page {

_getBoundingBox(name) {
if (this.xfaData) {
const { width, height } = this.xfaData.attributes.style;
return [0, 0, parseInt(width), parseInt(height)];
return this.xfaData.bbox;
}

const box = this._getInheritableProperty(name, /* getArray = */ true);
Expand Down Expand Up @@ -241,7 +240,9 @@ class Page {

get xfaData() {
if (this.xfaFactory) {
return shadow(this, "xfaData", this.xfaFactory.getPage(this.pageIndex));
return shadow(this, "xfaData", {
bbox: this.xfaFactory.getBoundingBox(this.pageIndex),
});
}
return shadow(this, "xfaData", null);
}
Expand Down Expand Up @@ -851,8 +852,11 @@ class PDFDocument {
return shadow(this, "xfaFaxtory", null);
}

get isPureXfa() {
return this.xfaFactory !== null;
get htmlForXfa() {
if (this.xfaFactory) {
return this.xfaFactory.getPages();
}
return null;
}

async loadXfaFonts(handler, task) {
Expand Down
14 changes: 4 additions & 10 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,13 @@ class WorkerMessageHandler {
await pdfManager.ensureDoc("checkFirstPage");
}

const [numPages, fingerprint, isPureXfa] = await Promise.all([
const [numPages, fingerprint, htmlForXfa] = await Promise.all([
pdfManager.ensureDoc("numPages"),
pdfManager.ensureDoc("fingerprint"),
pdfManager.ensureDoc("isPureXfa"),
pdfManager.ensureDoc("htmlForXfa"),
]);

if (isPureXfa) {
if (htmlForXfa) {
const task = new WorkerTask("loadXfaFonts");
startWorkerTask(task);
await pdfManager
Expand All @@ -203,7 +203,7 @@ class WorkerMessageHandler {
})
.then(() => finishWorkerTask(task));
}
return { numPages, fingerprint, isPureXfa };
return { numPages, fingerprint, htmlForXfa };
}

function getPdfManager(data, evaluatorOptions, enableXfa) {
Expand Down Expand Up @@ -501,12 +501,6 @@ class WorkerMessageHandler {
});
});

handler.on("GetPageXfa", function wphSetupGetXfa({ pageIndex }) {
return pdfManager.getPage(pageIndex).then(function (page) {
return pdfManager.ensure(page, "xfaData");
});
});

handler.on("GetOutline", function wphSetupGetOutline(data) {
return pdfManager.ensureCatalog("documentOutline");
});
Expand Down
25 changes: 21 additions & 4 deletions src/core/xfa/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,35 @@ class XFAFactory {
try {
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
this.form = new Binder(this.root).bind();
this.pages = this.form[$toHTML]();
this._createPages();
} catch (e) {
console.log(e);
}
}

getPage(pageIndex) {
return this.pages.children[pageIndex];
_createPages() {
this.pages = this.form[$toHTML]();
this.dims = this.pages.children.map(c => {
const { width, height } = c.attributes.style;
return [0, 0, parseInt(width), parseInt(height)];
});
}

getBoundingBox(pageIndex) {
return this.dims[pageIndex];
}

get numberPages() {
return this.pages.children.length;
return this.dims.length;
}

getPages() {
if (!this.pages) {
this._createPages();
}
const pages = this.pages;
this.pages = null;
return pages;
}

static _createDocument(data) {
Expand Down
8 changes: 8 additions & 0 deletions src/core/xfa/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -941,11 +941,14 @@ class CheckButton extends XFAObject {
style.height = size;
}

const fieldId = this[$getParent]()[$getParent]()[$uid];
const input = {
name: "input",
attributes: {
class: "xfaCheckbox",
fieldId,
type: "radio",
id: `${fieldId}-radio`,
},
};

Expand Down Expand Up @@ -1023,6 +1026,7 @@ class ChoiceList extends XFAObject {

const selectAttributes = {
class: "xfaSelect",
fieldId: this[$getParent]()[$getParent]()[$uid],
style,
};

Expand Down Expand Up @@ -1249,6 +1253,7 @@ class DateTimeEdit extends XFAObject {
name: "input",
attributes: {
type: "text",
fieldId: this[$getParent]()[$getParent]()[$uid],
class: "xfaTextfield",
style,
},
Expand Down Expand Up @@ -3012,6 +3017,7 @@ class NumericEdit extends XFAObject {
name: "input",
attributes: {
type: "text",
fieldId: this[$getParent]()[$getParent]()[$uid],
class: "xfaTextfield",
style,
},
Expand Down Expand Up @@ -4550,6 +4556,7 @@ class TextEdit extends XFAObject {
html = {
name: "textarea",
attributes: {
fieldId: this[$getParent]()[$getParent]()[$uid],
class: "xfaTextfield",
style,
},
Expand All @@ -4559,6 +4566,7 @@ class TextEdit extends XFAObject {
name: "input",
attributes: {
type: "text",
fieldId: this[$getParent]()[$getParent]()[$uid],
class: "xfaTextfield",
style,
},
Expand Down
24 changes: 13 additions & 11 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,15 @@ class PDFDocumentProxy {
* @type {boolean} True if only XFA form.
*/
get isPureXfa() {
return this._pdfInfo.isPureXfa;
return !!this._transport._htmlForXfa;
}

/**
* @type {Object | null} An object representing a HTML tree structure
* to render the XFA, or `null` when no XFA form exists.
*/
get allXfaHtml() {
return this._transport._htmlForXfa;
}

/**
Expand Down Expand Up @@ -1255,8 +1263,8 @@ class PDFPageProxy {
* are {Object} with a name, attributes (class, style, ...), value and
* children, very similar to a HTML DOM tree), or `null` if no XFA exists.
*/
getXfa() {
return (this._xfaPromise ||= this._transport.getPageXfa(this._pageIndex));
async getXfa() {
return this._transport._htmlForXfa?.children[this._pageIndex] || null;
}

/**
Expand Down Expand Up @@ -1552,7 +1560,6 @@ class PDFPageProxy {
this.objs.clear();
this._annotationsPromise = null;
this._jsActionsPromise = null;
this._xfaPromise = null;
this._structTreePromise = null;
this.pendingCleanup = false;
return Promise.all(waitOn);
Expand Down Expand Up @@ -1588,7 +1595,6 @@ class PDFPageProxy {
this.objs.clear();
this._annotationsPromise = null;
this._jsActionsPromise = null;
this._xfaPromise = null;
this._structTreePromise = null;
if (resetStats && this._stats) {
this._stats = new StatTimer();
Expand Down Expand Up @@ -2456,6 +2462,8 @@ class WorkerTransport {

messageHandler.on("GetDoc", ({ pdfInfo }) => {
this._numPages = pdfInfo.numPages;
this._htmlForXfa = pdfInfo.htmlForXfa;
delete pdfInfo.htmlForXfa;
loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
});

Expand Down Expand Up @@ -2812,12 +2820,6 @@ class WorkerTransport {
});
}

getPageXfa(pageIndex) {
return this.messageHandler.sendWithPromise("GetPageXfa", {
pageIndex,
});
}

getStructTree(pageIndex) {
return this.messageHandler.sendWithPromise("GetStructTree", {
pageIndex,
Expand Down
68 changes: 63 additions & 5 deletions src/display/xfa_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,60 @@
*/

class XfaLayer {
static setAttributes(html, attrs) {
for (const [key, value] of Object.entries(attrs)) {
if (value === null || value === undefined) {
static setupStorage(html, fieldId, element, storage) {
const storedData = storage.getValue(fieldId, { value: null });
switch (element.name) {
case "textarea":
html.textContent = storedData.value !== null ? storedData.value : "";
html.addEventListener("input", event => {
storage.setValue(fieldId, { value: event.target.value });
});
break;
case "input":
if (storedData.value !== null) {
html.setAttribute("value", storedData.value);
}
if (element.attributes.type === "radio") {
html.addEventListener("change", event => {
const { target } = event;
for (const radio of document.getElementsByName(target.name)) {
if (radio !== target) {
const id = radio.id;
storage.setValue(id.split("-")[0], { value: false });
}
}
storage.setValue(fieldId, { value: target.checked });
});
} else {
html.addEventListener("input", event => {
storage.setValue(fieldId, { value: event.target.value });
});
}
break;
case "select":
if (storedData.value !== null) {
for (const option of element.children) {
if (option.attributes.value === storedData.value) {
option.attributes.selected = true;
}
}
}
html.addEventListener("input", event => {
const options = event.target.options;
const value =
options.selectedIndex === -1
? null
: options[options.selectedIndex].value;
storage.setValue(fieldId, { value });
});
break;
}
}

static setAttributes(html, element, storage) {
const { attributes } = element;
for (const [key, value] of Object.entries(attributes)) {
if (value === null || value === undefined || key === "fieldId") {
continue;
}

Expand All @@ -30,13 +81,20 @@ class XfaLayer {
Object.assign(html.style, value);
}
}

// Set the value after the others to be sure overwrite
// any other values.
if (storage && attributes.fieldId !== undefined) {
this.setupStorage(html, attributes.fieldId, element, storage);
}
}

static render(parameters) {
const storage = parameters.annotationStorage;
const root = parameters.xfa;
const rootHtml = document.createElement(root.name);
if (root.attributes) {
XfaLayer.setAttributes(rootHtml, root.attributes);
this.setAttributes(rootHtml, root);
}
const stack = [[root, -1, rootHtml]];

Expand Down Expand Up @@ -69,7 +127,7 @@ class XfaLayer {
const childHtml = document.createElement(name);
html.appendChild(childHtml);
if (child.attributes) {
XfaLayer.setAttributes(childHtml, child.attributes);
this.setAttributes(childHtml, child, storage);
}

if (child.children && child.children.length > 0) {
Expand Down
5 changes: 3 additions & 2 deletions test/unit/xfa_tohtml_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ describe("XFAFactory", function () {

expect(factory.numberPages).toEqual(2);

const page1 = factory.getPage(0);
const pages = factory.getPages();
const page1 = pages.children[0];
expect(page1.attributes.style).toEqual({
height: "789px",
width: "456px",
Expand Down Expand Up @@ -99,7 +100,7 @@ describe("XFAFactory", function () {

// draw element must be on each page.
expect(draw.attributes.style).toEqual(
factory.getPage(1).children[1].children[0].attributes.style
pages.children[1].children[1].children[0].attributes.style
);
});
});
Expand Down
6 changes: 5 additions & 1 deletion web/base_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1317,12 +1317,16 @@ class BaseViewer {
/**
* @param {HTMLDivElement} pageDiv
* @param {PDFPage} pdfPage
* @param {AnnotationStorage} [annotationStorage] - Storage for annotation
* data in forms.
* @returns {XfaLayerBuilder}
*/
createXfaLayerBuilder(pageDiv, pdfPage) {
createXfaLayerBuilder(pageDiv, pdfPage, annotationStorage = null) {
return new XfaLayerBuilder({
pageDiv,
pdfPage,
annotationStorage:
annotationStorage || this.pdfDocument?.annotationStorage,
});
}

Expand Down
3 changes: 2 additions & 1 deletion web/pdf_page_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,8 @@ class PDFPageView {
if (!this.xfaLayer) {
this.xfaLayer = this.xfaLayerFactory.createXfaLayerBuilder(
div,
pdfPage
pdfPage,
/* annotationStorage = */ null
);
}
this._renderXfaLayer();
Expand Down
Loading