XFA - Add a layer to display XFA forms (#13069)
- add an option to enable XFA rendering if any; - for now, let the canvas layer: it could be useful to implement XFAF forms (embedded pdf in xml stream for the background and xfa form for the foreground); - ui elements in template DOM are pretty close to their html counterpart so we generate a fake html DOM from template one: - it makes easier to translate template properties to html ones; - it makes faster the creation of the html element in the main thread.
This commit is contained in:
parent
a164941351
commit
24e598a895
@ -15,6 +15,7 @@
|
||||
|
||||
import {
|
||||
assert,
|
||||
bytesToString,
|
||||
FormatError,
|
||||
info,
|
||||
InvalidPDFException,
|
||||
@ -28,6 +29,7 @@ import {
|
||||
shadow,
|
||||
stringToBytes,
|
||||
stringToPDFString,
|
||||
stringToUTF8String,
|
||||
unreachable,
|
||||
Util,
|
||||
warn,
|
||||
@ -56,6 +58,7 @@ import { calculateMD5 } from "./crypto.js";
|
||||
import { Linearization } from "./parser.js";
|
||||
import { OperatorList } from "./operator_list.js";
|
||||
import { PartialEvaluator } from "./evaluator.js";
|
||||
import { XFAFactory } from "./xfa/factory.js";
|
||||
|
||||
const DEFAULT_USER_UNIT = 1.0;
|
||||
const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
|
||||
@ -79,6 +82,7 @@ class Page {
|
||||
builtInCMapCache,
|
||||
globalImageCache,
|
||||
nonBlendModesSet,
|
||||
xfaFactory,
|
||||
}) {
|
||||
this.pdfManager = pdfManager;
|
||||
this.pageIndex = pageIndex;
|
||||
@ -91,6 +95,7 @@ class Page {
|
||||
this.nonBlendModesSet = nonBlendModesSet;
|
||||
this.evaluatorOptions = pdfManager.evaluatorOptions;
|
||||
this.resourcesPromise = null;
|
||||
this.xfaFactory = xfaFactory;
|
||||
|
||||
const idCounters = {
|
||||
obj: 0,
|
||||
@ -137,6 +142,11 @@ class Page {
|
||||
}
|
||||
|
||||
_getBoundingBox(name) {
|
||||
if (this.xfaData) {
|
||||
const { width, height } = this.xfaData.attributes.style;
|
||||
return [0, 0, parseInt(width), parseInt(height)];
|
||||
}
|
||||
|
||||
const box = this._getInheritableProperty(name, /* getArray = */ true);
|
||||
|
||||
if (Array.isArray(box) && box.length === 4) {
|
||||
@ -231,6 +241,13 @@ class Page {
|
||||
return stream;
|
||||
}
|
||||
|
||||
get xfaData() {
|
||||
if (this.xfaFactory) {
|
||||
return shadow(this, "xfaData", this.xfaFactory.getPage(this.pageIndex));
|
||||
}
|
||||
return shadow(this, "xfaData", null);
|
||||
}
|
||||
|
||||
save(handler, task, annotationStorage) {
|
||||
const partialEvaluator = new PartialEvaluator({
|
||||
xref: this.xref,
|
||||
@ -695,6 +712,9 @@ class PDFDocument {
|
||||
}
|
||||
|
||||
get numPages() {
|
||||
if (this.xfaFactory) {
|
||||
return shadow(this, "numPages", this.xfaFactory.numberPages);
|
||||
}
|
||||
const linearization = this.linearization;
|
||||
const num = linearization ? linearization.numPages : this.catalog.numPages;
|
||||
return shadow(this, "numPages", num);
|
||||
@ -732,6 +752,80 @@ class PDFDocument {
|
||||
});
|
||||
}
|
||||
|
||||
get xfaData() {
|
||||
const acroForm = this.catalog.acroForm;
|
||||
if (!acroForm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const xfa = acroForm.get("XFA");
|
||||
const entries = {
|
||||
"xdp:xdp": "",
|
||||
template: "",
|
||||
datasets: "",
|
||||
config: "",
|
||||
connectionSet: "",
|
||||
localeSet: "",
|
||||
stylesheet: "",
|
||||
"/xdp:xdp": "",
|
||||
};
|
||||
if (isStream(xfa) && !xfa.isEmpty) {
|
||||
try {
|
||||
entries["xdp:xdp"] = stringToUTF8String(bytesToString(xfa.getBytes()));
|
||||
return entries;
|
||||
} catch (_) {
|
||||
warn("XFA - Invalid utf-8 string.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(xfa) || xfa.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
|
||||
let name;
|
||||
if (i === 0) {
|
||||
name = "xdp:xdp";
|
||||
} else if (i === ii - 2) {
|
||||
name = "/xdp:xdp";
|
||||
} else {
|
||||
name = xfa[i];
|
||||
}
|
||||
|
||||
if (!entries.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
const data = this.xref.fetchIfRef(xfa[i + 1]);
|
||||
if (!isStream(data) || data.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
entries[name] = stringToUTF8String(bytesToString(data.getBytes()));
|
||||
} catch (_) {
|
||||
warn("XFA - Invalid utf-8 string.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
get xfaFactory() {
|
||||
if (
|
||||
this.pdfManager.enableXfa &&
|
||||
this.formInfo.hasXfa &&
|
||||
!this.formInfo.hasAcroForm
|
||||
) {
|
||||
const data = this.xfaData;
|
||||
return shadow(this, "xfaFactory", data ? new XFAFactory(data) : null);
|
||||
}
|
||||
return shadow(this, "xfaFaxtory", null);
|
||||
}
|
||||
|
||||
get isPureXfa() {
|
||||
return this.xfaFactory !== null;
|
||||
}
|
||||
|
||||
get formInfo() {
|
||||
const formInfo = { hasFields: false, hasAcroForm: false, hasXfa: false };
|
||||
const acroForm = this.catalog.acroForm;
|
||||
@ -918,6 +1012,24 @@ class PDFDocument {
|
||||
}
|
||||
const { catalog, linearization } = this;
|
||||
|
||||
if (this.xfaFactory) {
|
||||
return Promise.resolve(
|
||||
new Page({
|
||||
pdfManager: this.pdfManager,
|
||||
xref: this.xref,
|
||||
pageIndex,
|
||||
pageDict: Dict.empty,
|
||||
ref: null,
|
||||
globalIdFactory: this._globalIdFactory,
|
||||
fontCache: catalog.fontCache,
|
||||
builtInCMapCache: catalog.builtInCMapCache,
|
||||
globalImageCache: catalog.globalImageCache,
|
||||
nonBlendModesSet: catalog.nonBlendModesSet,
|
||||
xfaFactory: this.xfaFactory,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const promise =
|
||||
linearization && linearization.pageFirst === pageIndex
|
||||
? this._getLinearizationPage(pageIndex)
|
||||
@ -935,6 +1047,7 @@ class PDFDocument {
|
||||
builtInCMapCache: catalog.builtInCMapCache,
|
||||
globalImageCache: catalog.globalImageCache,
|
||||
nonBlendModesSet: catalog.nonBlendModesSet,
|
||||
xfaFactory: null,
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
@ -106,13 +106,14 @@ class BasePdfManager {
|
||||
}
|
||||
|
||||
class LocalPdfManager extends BasePdfManager {
|
||||
constructor(docId, data, password, evaluatorOptions, docBaseUrl) {
|
||||
constructor(docId, data, password, evaluatorOptions, enableXfa, docBaseUrl) {
|
||||
super();
|
||||
|
||||
this._docId = docId;
|
||||
this._password = password;
|
||||
this._docBaseUrl = docBaseUrl;
|
||||
this.evaluatorOptions = evaluatorOptions;
|
||||
this.enableXfa = enableXfa;
|
||||
|
||||
const stream = new Stream(data);
|
||||
this.pdfDocument = new PDFDocument(this, stream);
|
||||
@ -141,7 +142,14 @@ class LocalPdfManager extends BasePdfManager {
|
||||
}
|
||||
|
||||
class NetworkPdfManager extends BasePdfManager {
|
||||
constructor(docId, pdfNetworkStream, args, evaluatorOptions, docBaseUrl) {
|
||||
constructor(
|
||||
docId,
|
||||
pdfNetworkStream,
|
||||
args,
|
||||
evaluatorOptions,
|
||||
enableXfa,
|
||||
docBaseUrl
|
||||
) {
|
||||
super();
|
||||
|
||||
this._docId = docId;
|
||||
@ -149,6 +157,7 @@ class NetworkPdfManager extends BasePdfManager {
|
||||
this._docBaseUrl = docBaseUrl;
|
||||
this.msgHandler = args.msgHandler;
|
||||
this.evaluatorOptions = evaluatorOptions;
|
||||
this.enableXfa = enableXfa;
|
||||
|
||||
this.streamManager = new ChunkedStreamManager(pdfNetworkStream, {
|
||||
msgHandler: args.msgHandler,
|
||||
|
@ -188,14 +188,15 @@ class WorkerMessageHandler {
|
||||
await pdfManager.ensureDoc("checkFirstPage");
|
||||
}
|
||||
|
||||
const [numPages, fingerprint] = await Promise.all([
|
||||
const [numPages, fingerprint, isPureXfa] = await Promise.all([
|
||||
pdfManager.ensureDoc("numPages"),
|
||||
pdfManager.ensureDoc("fingerprint"),
|
||||
pdfManager.ensureDoc("isPureXfa"),
|
||||
]);
|
||||
return { numPages, fingerprint };
|
||||
return { numPages, fingerprint, isPureXfa };
|
||||
}
|
||||
|
||||
function getPdfManager(data, evaluatorOptions) {
|
||||
function getPdfManager(data, evaluatorOptions, enableXfa) {
|
||||
var pdfManagerCapability = createPromiseCapability();
|
||||
let newPdfManager;
|
||||
|
||||
@ -207,6 +208,7 @@ class WorkerMessageHandler {
|
||||
source.data,
|
||||
source.password,
|
||||
evaluatorOptions,
|
||||
enableXfa,
|
||||
docBaseUrl
|
||||
);
|
||||
pdfManagerCapability.resolve(newPdfManager);
|
||||
@ -246,6 +248,7 @@ class WorkerMessageHandler {
|
||||
rangeChunkSize: source.rangeChunkSize,
|
||||
},
|
||||
evaluatorOptions,
|
||||
enableXfa,
|
||||
docBaseUrl
|
||||
);
|
||||
// There may be a chance that `newPdfManager` is not initialized for
|
||||
@ -277,6 +280,7 @@ class WorkerMessageHandler {
|
||||
pdfFile,
|
||||
source.password,
|
||||
evaluatorOptions,
|
||||
enableXfa,
|
||||
docBaseUrl
|
||||
);
|
||||
pdfManagerCapability.resolve(newPdfManager);
|
||||
@ -399,7 +403,7 @@ class WorkerMessageHandler {
|
||||
fontExtraProperties: data.fontExtraProperties,
|
||||
};
|
||||
|
||||
getPdfManager(data, evaluatorOptions)
|
||||
getPdfManager(data, evaluatorOptions, data.enableXfa)
|
||||
.then(function (newPdfManager) {
|
||||
if (terminated) {
|
||||
// We were in a process of setting up the manager, but it got
|
||||
@ -487,6 +491,16 @@ class WorkerMessageHandler {
|
||||
});
|
||||
});
|
||||
|
||||
handler.on("GetPageXfa", function wphSetupGetXfa({ pageIndex }) {
|
||||
return pdfManager.getPage(pageIndex).then(function (page) {
|
||||
return pdfManager.ensure(page, "xfaData");
|
||||
});
|
||||
});
|
||||
|
||||
handler.on("GetIsPureXfa", function wphSetupGetIsPureXfa(data) {
|
||||
return pdfManager.ensureDoc("isPureXfa");
|
||||
});
|
||||
|
||||
handler.on("GetOutline", function wphSetupGetOutline(data) {
|
||||
return pdfManager.ensureCatalog("documentOutline");
|
||||
});
|
||||
|
@ -13,13 +13,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { $toHTML } from "./xfa_object.js";
|
||||
import { Binder } from "./bind.js";
|
||||
import { XFAParser } from "./parser.js";
|
||||
|
||||
class XFAFactory {
|
||||
constructor(data) {
|
||||
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
|
||||
this.form = new Binder(this.root).bind();
|
||||
try {
|
||||
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
|
||||
this.form = new Binder(this.root).bind();
|
||||
this.pages = this.form[$toHTML]();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
getPage(pageIndex) {
|
||||
return this.pages.children[pageIndex];
|
||||
}
|
||||
|
||||
get numberPages() {
|
||||
return this.pages.children.length;
|
||||
}
|
||||
|
||||
static _createDocument(data) {
|
||||
|
69
src/core/xfa/html_utils.js
Normal file
69
src/core/xfa/html_utils.js
Normal file
@ -0,0 +1,69 @@
|
||||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const converters = {
|
||||
pt: x => x,
|
||||
cm: x => Math.round((x / 2.54) * 72),
|
||||
mm: x => Math.round((x / (10 * 2.54)) * 72),
|
||||
in: x => Math.round(x * 72),
|
||||
};
|
||||
|
||||
function measureToString(m) {
|
||||
const conv = converters[m.unit];
|
||||
if (conv) {
|
||||
return `${conv(m.value)}px`;
|
||||
}
|
||||
return `${m.value}${m.unit}`;
|
||||
}
|
||||
|
||||
function setWidthHeight(node, style) {
|
||||
if (node.w) {
|
||||
style.width = measureToString(node.w);
|
||||
} else {
|
||||
if (node.maxW && node.maxW.value > 0) {
|
||||
style.maxWidth = measureToString(node.maxW);
|
||||
}
|
||||
if (node.minW && node.minW.value > 0) {
|
||||
style.minWidth = measureToString(node.minW);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.h) {
|
||||
style.height = measureToString(node.h);
|
||||
} else {
|
||||
if (node.maxH && node.maxH.value > 0) {
|
||||
style.maxHeight = measureToString(node.maxH);
|
||||
}
|
||||
if (node.minH && node.minH.value > 0) {
|
||||
style.minHeight = measureToString(node.minH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPosition(node, style) {
|
||||
style.transform = "";
|
||||
if (node.rotate) {
|
||||
style.transform = `rotate(-${node.rotate}deg) `;
|
||||
style.transformOrigin = "top left";
|
||||
}
|
||||
|
||||
if (node.x !== "" || node.y !== "") {
|
||||
style.position = "absolute";
|
||||
style.left = node.x ? measureToString(node.x) : "0pt";
|
||||
style.top = node.y ? measureToString(node.y) : "0pt";
|
||||
}
|
||||
}
|
||||
|
||||
export { measureToString, setPosition, setWidthHeight };
|
@ -15,8 +15,11 @@
|
||||
|
||||
import {
|
||||
$appendChild,
|
||||
$childrenToHTML,
|
||||
$content,
|
||||
$extra,
|
||||
$finalize,
|
||||
$getParent,
|
||||
$hasItem,
|
||||
$hasSettableValue,
|
||||
$isTransparent,
|
||||
@ -26,6 +29,8 @@ import {
|
||||
$removeChild,
|
||||
$setSetAttributes,
|
||||
$setValue,
|
||||
$toHTML,
|
||||
$uid,
|
||||
ContentObject,
|
||||
Option01,
|
||||
OptionObject,
|
||||
@ -45,6 +50,7 @@ import {
|
||||
getRelevant,
|
||||
getStringOption,
|
||||
} from "./utils.js";
|
||||
import { measureToString, setPosition, setWidthHeight } from "./html_utils.js";
|
||||
import { warn } from "../../shared/util.js";
|
||||
|
||||
const TEMPLATE_NS_ID = NamespaceIds.template.id;
|
||||
@ -656,6 +662,29 @@ class ContentArea extends XFAObject {
|
||||
this.desc = null;
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
// TODO: incomplete.
|
||||
const left = measureToString(this.x);
|
||||
const top = measureToString(this.y);
|
||||
|
||||
const style = {
|
||||
position: "absolute",
|
||||
left,
|
||||
top,
|
||||
width: measureToString(this.w),
|
||||
height: measureToString(this.h),
|
||||
};
|
||||
return {
|
||||
name: "div",
|
||||
children: [],
|
||||
attributes: {
|
||||
style,
|
||||
className: "xfa-contentarea",
|
||||
id: this[$uid],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Corner extends XFAObject {
|
||||
@ -1946,6 +1975,41 @@ class PageArea extends XFAObject {
|
||||
this.field = new XFAObjectArray();
|
||||
this.subform = new XFAObjectArray();
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
// TODO: incomplete.
|
||||
if (this.contentArea.children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = this[$childrenToHTML]({
|
||||
filter: new Set(["area", "draw", "field", "subform", "contentArea"]),
|
||||
include: true,
|
||||
});
|
||||
|
||||
// TODO: handle the case where there are several content areas.
|
||||
const contentArea = children.find(
|
||||
node => node.attributes.className === "xfa-contentarea"
|
||||
);
|
||||
|
||||
const style = Object.create(null);
|
||||
if (this.medium && this.medium.short.value && this.medium.long.value) {
|
||||
style.width = measureToString(this.medium.short);
|
||||
style.height = measureToString(this.medium.long);
|
||||
} else {
|
||||
// TODO: compute it from contentAreas
|
||||
}
|
||||
|
||||
return {
|
||||
name: "div",
|
||||
children,
|
||||
attributes: {
|
||||
id: this[$uid],
|
||||
style,
|
||||
},
|
||||
contentArea,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class PageSet extends XFAObject {
|
||||
@ -1970,6 +2034,20 @@ class PageSet extends XFAObject {
|
||||
this.pageArea = new XFAObjectArray();
|
||||
this.pageSet = new XFAObjectArray();
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
// TODO: incomplete.
|
||||
return {
|
||||
name: "div",
|
||||
children: this[$childrenToHTML]({
|
||||
filter: new Set(["pageArea", "pageSet"]),
|
||||
include: true,
|
||||
}),
|
||||
attributes: {
|
||||
id: this[$uid],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Para extends XFAObject {
|
||||
@ -2465,6 +2543,64 @@ class Subform extends XFAObject {
|
||||
this.subform = new XFAObjectArray();
|
||||
this.subformSet = new XFAObjectArray();
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
// TODO: incomplete.
|
||||
this[$extra] = Object.create(null);
|
||||
|
||||
const parent = this[$getParent]();
|
||||
let page = null;
|
||||
if (parent[$nodeName] === "template") {
|
||||
// Root subform: should have page info.
|
||||
if (this.pageSet !== null) {
|
||||
this[$extra].pageNumber = 0;
|
||||
} else {
|
||||
// TODO
|
||||
warn("XFA - No pageSet in root subform");
|
||||
}
|
||||
} else if (parent[$extra] && parent[$extra].pageNumber !== undefined) {
|
||||
// This subform is a child of root subform
|
||||
// so push it in a new page.
|
||||
const pageNumber = parent[$extra].pageNumber;
|
||||
const pageAreas = parent.pageSet.pageArea.children;
|
||||
parent[$extra].pageNumber =
|
||||
(parent[$extra].pageNumber + 1) % pageAreas.length;
|
||||
page = pageAreas[pageNumber][$toHTML]();
|
||||
}
|
||||
|
||||
const style = Object.create(null);
|
||||
setWidthHeight(this, style);
|
||||
setPosition(this, style);
|
||||
|
||||
const attributes = {
|
||||
style,
|
||||
id: this[$uid],
|
||||
};
|
||||
|
||||
if (this.name) {
|
||||
attributes["xfa-name"] = this.name;
|
||||
}
|
||||
|
||||
const children = this[$childrenToHTML]({
|
||||
// TODO: exObject & exclGroup
|
||||
filter: new Set(["area", "draw", "field", "subform", "subformSet"]),
|
||||
include: true,
|
||||
});
|
||||
|
||||
const html = {
|
||||
name: "div",
|
||||
attributes,
|
||||
children,
|
||||
};
|
||||
|
||||
if (page) {
|
||||
page.contentArea.children.push(html);
|
||||
delete page.contentArea;
|
||||
return page;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
class SubformSet extends XFAObject {
|
||||
@ -2580,8 +2716,32 @@ class Template extends XFAObject {
|
||||
"interactiveForms",
|
||||
]);
|
||||
this.extras = null;
|
||||
|
||||
// Spec is unclear:
|
||||
// A container element that describes a single subform capable of
|
||||
// enclosing other containers.
|
||||
// Can we have more than one subform ?
|
||||
this.subform = new XFAObjectArray();
|
||||
}
|
||||
|
||||
[$finalize]() {
|
||||
if (this.subform.children.length === 0) {
|
||||
warn("XFA - No subforms in template node.");
|
||||
}
|
||||
if (this.subform.children.length >= 2) {
|
||||
warn("XFA - Several subforms in template node: please file a bug.");
|
||||
}
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
if (this.subform.children.length > 0) {
|
||||
return this.subform.children[0][$toHTML]();
|
||||
}
|
||||
return {
|
||||
name: "div",
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Text extends ContentObject {
|
||||
|
@ -74,7 +74,7 @@ function getMeasurement(str, def = "0") {
|
||||
}
|
||||
return {
|
||||
value: sign === "-" ? -value : value,
|
||||
unit,
|
||||
unit: unit || "pt",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import { NamespaceIds } from "./namespaces.js";
|
||||
// We use these symbols to avoid name conflict between tags
|
||||
// and properties/methods names.
|
||||
const $appendChild = Symbol();
|
||||
const $childrenToHTML = Symbol();
|
||||
const $clean = Symbol();
|
||||
const $cleanup = Symbol();
|
||||
const $clone = Symbol();
|
||||
@ -27,6 +28,7 @@ const $consumed = Symbol();
|
||||
const $content = Symbol("content");
|
||||
const $data = Symbol("data");
|
||||
const $dump = Symbol();
|
||||
const $extra = Symbol("extra");
|
||||
const $finalize = Symbol();
|
||||
const $getAttributeIt = Symbol();
|
||||
const $getChildrenByClass = Symbol();
|
||||
@ -56,6 +58,8 @@ const $setId = Symbol();
|
||||
const $setSetAttributes = Symbol();
|
||||
const $setValue = Symbol();
|
||||
const $text = Symbol();
|
||||
const $toHTML = Symbol();
|
||||
const $uid = Symbol("uid");
|
||||
|
||||
const _applyPrototype = Symbol();
|
||||
const _attributes = Symbol();
|
||||
@ -73,6 +77,8 @@ const _parent = Symbol("parent");
|
||||
const _setAttributes = Symbol();
|
||||
const _validator = Symbol();
|
||||
|
||||
let uid = 0;
|
||||
|
||||
class XFAObject {
|
||||
constructor(nsId, name, hasChildren = false) {
|
||||
this[$namespaceId] = nsId;
|
||||
@ -80,6 +86,7 @@ class XFAObject {
|
||||
this[_hasChildren] = hasChildren;
|
||||
this[_parent] = null;
|
||||
this[_children] = [];
|
||||
this[$uid] = `${name}${uid++}`;
|
||||
}
|
||||
|
||||
[$onChild](child) {
|
||||
@ -252,6 +259,23 @@ class XFAObject {
|
||||
return dumped;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$childrenToHTML]({ filter = null, include = true }) {
|
||||
const res = [];
|
||||
this[$getChildren]().forEach(node => {
|
||||
if (!filter || include === filter.has(node[$nodeName])) {
|
||||
const html = node[$toHTML]();
|
||||
if (html) {
|
||||
res.push(html);
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
[$setSetAttributes](attributes) {
|
||||
if (attributes.use || attributes.id) {
|
||||
// Just keep set attributes because this node uses a proto or is a proto.
|
||||
@ -604,6 +628,17 @@ class XmlObject extends XFAObject {
|
||||
}
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
if (this[$nodeName] === "#text") {
|
||||
return {
|
||||
name: "#text",
|
||||
value: this[$content],
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[$getChildren](name = null) {
|
||||
if (!name) {
|
||||
return this[_children];
|
||||
@ -766,6 +801,7 @@ class Option10 extends IntegerObject {
|
||||
|
||||
export {
|
||||
$appendChild,
|
||||
$childrenToHTML,
|
||||
$clean,
|
||||
$cleanup,
|
||||
$clone,
|
||||
@ -773,6 +809,7 @@ export {
|
||||
$content,
|
||||
$data,
|
||||
$dump,
|
||||
$extra,
|
||||
$finalize,
|
||||
$getAttributeIt,
|
||||
$getChildren,
|
||||
@ -801,6 +838,8 @@ export {
|
||||
$setSetAttributes,
|
||||
$setValue,
|
||||
$text,
|
||||
$toHTML,
|
||||
$uid,
|
||||
ContentObject,
|
||||
IntegerObject,
|
||||
Option01,
|
||||
|
@ -162,6 +162,8 @@ function setPDFNetworkStreamFactory(pdfNetworkStreamFactory) {
|
||||
* parsed font data from the worker-thread. This may be useful for debugging
|
||||
* purposes (and backwards compatibility), but note that it will lead to
|
||||
* increased memory usage. The default value is `false`.
|
||||
* @property {boolean} [enableXfa] - Render Xfa forms if any.
|
||||
* The default value is `false`.
|
||||
* @property {HTMLDocument} [ownerDocument] - Specify an explicit document
|
||||
* context to create elements with and to load resources, such as fonts,
|
||||
* into. Defaults to the current document.
|
||||
@ -284,6 +286,7 @@ function getDocument(src) {
|
||||
params.ignoreErrors = params.stopAtErrors !== true;
|
||||
params.fontExtraProperties = params.fontExtraProperties === true;
|
||||
params.pdfBug = params.pdfBug === true;
|
||||
params.enableXfa = params.enableXfa === true;
|
||||
|
||||
if (!Number.isInteger(params.maxImageSize)) {
|
||||
params.maxImageSize = -1;
|
||||
@ -438,6 +441,7 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
|
||||
ignoreErrors: source.ignoreErrors,
|
||||
isEvalSupported: source.isEvalSupported,
|
||||
fontExtraProperties: source.fontExtraProperties,
|
||||
enableXfa: source.enableXfa,
|
||||
})
|
||||
.then(function (workerId) {
|
||||
if (worker.destroyed) {
|
||||
@ -674,6 +678,13 @@ class PDFDocumentProxy {
|
||||
return this._pdfInfo.fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean} True if only XFA form.
|
||||
*/
|
||||
get isPureXfa() {
|
||||
return this._pdfInfo.isPureXfa;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pageNumber - The page number to get. The first page is 1.
|
||||
* @returns {Promise<PDFPageProxy>} A promise that is resolved with
|
||||
@ -1165,6 +1176,16 @@ class PDFPageProxy {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object | null>} A promise that is resolved with
|
||||
* an {Object} with a fake DOM object (a tree structure where elements
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins the process of rendering a page to the desired context.
|
||||
*
|
||||
@ -2709,6 +2730,12 @@ class WorkerTransport {
|
||||
});
|
||||
}
|
||||
|
||||
getPageXfa(pageIndex) {
|
||||
return this.messageHandler.sendWithPromise("GetPageXfa", {
|
||||
pageIndex,
|
||||
});
|
||||
}
|
||||
|
||||
getOutline() {
|
||||
return this.messageHandler.sendWithPromise("GetOutline", null);
|
||||
}
|
||||
|
89
src/display/xfa_layer.js
Normal file
89
src/display/xfa_layer.js
Normal file
@ -0,0 +1,89 @@
|
||||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class XfaLayer {
|
||||
static setAttributes(html, attrs) {
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
if (value === null || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key !== "style") {
|
||||
html.setAttribute(key, value);
|
||||
} else {
|
||||
Object.assign(html.style, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static render(parameters) {
|
||||
const root = parameters.xfa;
|
||||
const rootHtml = document.createElement(root.name);
|
||||
if (root.attributes) {
|
||||
XfaLayer.setAttributes(rootHtml, root.attributes);
|
||||
}
|
||||
const stack = [[root, -1, rootHtml]];
|
||||
|
||||
parameters.div.appendChild(rootHtml);
|
||||
const coeffs = parameters.viewport.transform.join(",");
|
||||
parameters.div.style.transform = `matrix(${coeffs})`;
|
||||
|
||||
while (stack.length > 0) {
|
||||
const [parent, i, html] = stack[stack.length - 1];
|
||||
if (i + 1 === parent.children.length) {
|
||||
stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
const child = parent.children[++stack[stack.length - 1][1]];
|
||||
if (child === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name } = child;
|
||||
if (name === "#text") {
|
||||
html.appendChild(document.createTextNode(child.value));
|
||||
continue;
|
||||
}
|
||||
|
||||
const childHtml = document.createElement(name);
|
||||
html.appendChild(childHtml);
|
||||
if (child.attributes) {
|
||||
XfaLayer.setAttributes(childHtml, child.attributes);
|
||||
}
|
||||
|
||||
if (child.children && child.children.length > 0) {
|
||||
stack.push([child, -1, childHtml]);
|
||||
} else if (child.value) {
|
||||
childHtml.appendChild(document.createTextNode(child.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the xfa layer.
|
||||
*
|
||||
* @public
|
||||
* @param {XfaLayerParameters} parameters
|
||||
* @memberof XfaLayer
|
||||
*/
|
||||
static update(parameters) {
|
||||
const transform = `matrix(${parameters.viewport.transform.join(",")})`;
|
||||
parameters.div.style.transform = transform;
|
||||
parameters.div.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
export { XfaLayer };
|
@ -56,6 +56,7 @@ import { apiCompatibilityParams } from "./display/api_compatibility.js";
|
||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||
import { renderTextLayer } from "./display/text_layer.js";
|
||||
import { SVGGraphics } from "./display/svg.js";
|
||||
import { XfaLayer } from "./display/xfa_layer.js";
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const pdfjsVersion =
|
||||
@ -167,4 +168,6 @@ export {
|
||||
renderTextLayer,
|
||||
// From "./display/svg.js":
|
||||
SVGGraphics,
|
||||
// From "./display/xfa_layer.js":
|
||||
XfaLayer,
|
||||
};
|
||||
|
@ -81,9 +81,9 @@ describe("XFAParser", function () {
|
||||
};
|
||||
const mediumAttributes = {
|
||||
id: "",
|
||||
long: { value: 0, unit: "" },
|
||||
long: { value: 0, unit: "pt" },
|
||||
orientation: "portrait",
|
||||
short: { value: 0, unit: "" },
|
||||
short: { value: 0, unit: "pt" },
|
||||
stock: "",
|
||||
trayIn: "auto",
|
||||
trayOut: "auto",
|
||||
@ -116,17 +116,17 @@ describe("XFAParser", function () {
|
||||
allowMacro: 0,
|
||||
anchorType: "topLeft",
|
||||
colSpan: 1,
|
||||
columnWidths: [{ value: 0, unit: "" }],
|
||||
h: { value: 0, unit: "" },
|
||||
columnWidths: [{ value: 0, unit: "pt" }],
|
||||
h: { value: 0, unit: "pt" },
|
||||
hAlign: "left",
|
||||
id: "",
|
||||
layout: "position",
|
||||
locale: "",
|
||||
maxH: { value: 0, unit: "" },
|
||||
maxW: { value: 0, unit: "" },
|
||||
maxH: { value: 0, unit: "pt" },
|
||||
maxW: { value: 0, unit: "pt" },
|
||||
mergeMode: "consumeData",
|
||||
minH: { value: 0, unit: "" },
|
||||
minW: { value: 0, unit: "" },
|
||||
minH: { value: 0, unit: "pt" },
|
||||
minW: { value: 0, unit: "pt" },
|
||||
name: "",
|
||||
presence: "visible",
|
||||
relevant: [],
|
||||
@ -134,14 +134,14 @@ describe("XFAParser", function () {
|
||||
scope: "name",
|
||||
use: "",
|
||||
usehref: "",
|
||||
w: { value: 0, unit: "" },
|
||||
x: { value: 0, unit: "" },
|
||||
y: { value: 0, unit: "" },
|
||||
w: { value: 0, unit: "pt" },
|
||||
x: { value: 0, unit: "pt" },
|
||||
y: { value: 0, unit: "pt" },
|
||||
proto: {
|
||||
area: {
|
||||
...attributes,
|
||||
colSpan: 1,
|
||||
x: { value: 0, unit: "" },
|
||||
x: { value: 0, unit: "pt" },
|
||||
y: { value: -3.14, unit: "in" },
|
||||
relevant: [
|
||||
{ excluded: true, viewname: "foo" },
|
||||
@ -162,7 +162,7 @@ describe("XFAParser", function () {
|
||||
{
|
||||
...mediumAttributes,
|
||||
imagingBBox: {
|
||||
x: { value: 1, unit: "" },
|
||||
x: { value: 1, unit: "pt" },
|
||||
y: { value: 2, unit: "in" },
|
||||
width: { value: 3.4, unit: "cm" },
|
||||
height: { value: 5.67, unit: "px" },
|
||||
@ -171,10 +171,10 @@ describe("XFAParser", function () {
|
||||
{
|
||||
...mediumAttributes,
|
||||
imagingBBox: {
|
||||
x: { value: -1, unit: "" },
|
||||
y: { value: -1, unit: "" },
|
||||
width: { value: -1, unit: "" },
|
||||
height: { value: -1, unit: "" },
|
||||
x: { value: -1, unit: "pt" },
|
||||
y: { value: -1, unit: "pt" },
|
||||
width: { value: -1, unit: "pt" },
|
||||
height: { value: -1, unit: "pt" },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -518,6 +518,7 @@ const PDFViewerApplication = {
|
||||
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||
enableScripting: AppOptions.get("enableScripting"),
|
||||
enableXfa: AppOptions.get("enableXfa"),
|
||||
});
|
||||
pdfRenderingQueue.setViewer(this.pdfViewer);
|
||||
pdfLinkService.setViewer(this.pdfViewer);
|
||||
|
@ -205,6 +205,11 @@ const defaultOptions = {
|
||||
value: "",
|
||||
kind: OptionKind.API,
|
||||
},
|
||||
enableXfa: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
kind: OptionKind.API,
|
||||
},
|
||||
fontExtraProperties: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
|
@ -42,6 +42,7 @@ import { NullL10n } from "./l10n_utils.js";
|
||||
import { PDFPageView } from "./pdf_page_view.js";
|
||||
import { SimpleLinkService } from "./pdf_link_service.js";
|
||||
import { TextLayerBuilder } from "./text_layer_builder.js";
|
||||
import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
||||
|
||||
const DEFAULT_CACHE_SIZE = 10;
|
||||
|
||||
@ -478,6 +479,7 @@ class BaseViewer {
|
||||
if (!pdfDocument) {
|
||||
return;
|
||||
}
|
||||
const isPureXfa = pdfDocument.isPureXfa;
|
||||
const pagesCount = pdfDocument.numPages;
|
||||
const firstPagePromise = pdfDocument.getPage(1);
|
||||
// Rendering (potentially) depends on this, hence fetching it immediately.
|
||||
@ -523,6 +525,7 @@ class BaseViewer {
|
||||
const viewport = firstPdfPage.getViewport({ scale: scale * CSS_UNITS });
|
||||
const textLayerFactory =
|
||||
this.textLayerMode !== TextLayerMode.DISABLE ? this : null;
|
||||
const xfaLayerFactory = isPureXfa ? this : null;
|
||||
|
||||
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
|
||||
const pageView = new PDFPageView({
|
||||
@ -536,6 +539,7 @@ class BaseViewer {
|
||||
textLayerFactory,
|
||||
textLayerMode: this.textLayerMode,
|
||||
annotationLayerFactory: this,
|
||||
xfaLayerFactory,
|
||||
imageResourcesPath: this.imageResourcesPath,
|
||||
renderInteractiveForms: this.renderInteractiveForms,
|
||||
renderer: this.renderer,
|
||||
@ -1308,6 +1312,18 @@ class BaseViewer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} pageDiv
|
||||
* @param {PDFPage} pdfPage
|
||||
* @returns {XfaLayerBuilder}
|
||||
*/
|
||||
createXfaLayerBuilder(pageDiv, pdfPage) {
|
||||
return new XfaLayerBuilder({
|
||||
pageDiv,
|
||||
pdfPage,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean} Whether all pages of the PDF document have identical
|
||||
* widths and heights.
|
||||
|
@ -204,6 +204,18 @@ class IPDFAnnotationLayerFactory {
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
class IPDFXfaLayerFactory {
|
||||
/**
|
||||
* @param {HTMLDivElement} pageDiv
|
||||
* @param {PDFPage} pdfPage
|
||||
* @returns {XfaLayerBuilder}
|
||||
*/
|
||||
createXfaLayerBuilder(pageDiv, pdfPage) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
@ -243,5 +255,6 @@ export {
|
||||
IPDFHistory,
|
||||
IPDFLinkService,
|
||||
IPDFTextLayerFactory,
|
||||
IPDFXfaLayerFactory,
|
||||
IRenderableView,
|
||||
};
|
||||
|
@ -48,6 +48,7 @@ import { viewerCompatibilityParams } from "./viewer_compatibility.js";
|
||||
* behaviour is enabled. The constants from {TextLayerMode} should be used.
|
||||
* The default value is `TextLayerMode.ENABLE`.
|
||||
* @property {IPDFAnnotationLayerFactory} annotationLayerFactory
|
||||
* @property {IPDFXfaLayerFactory} xfaLayerFactory
|
||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||
* for annotation icons. Include trailing slash.
|
||||
* @property {boolean} renderInteractiveForms - Turns on rendering of
|
||||
@ -102,6 +103,7 @@ class PDFPageView {
|
||||
this.renderingQueue = options.renderingQueue;
|
||||
this.textLayerFactory = options.textLayerFactory;
|
||||
this.annotationLayerFactory = options.annotationLayerFactory;
|
||||
this.xfaLayerFactory = options.xfaLayerFactory;
|
||||
this.renderer = options.renderer || RendererType.CANVAS;
|
||||
this.enableWebGL = options.enableWebGL || false;
|
||||
this.l10n = options.l10n || NullL10n;
|
||||
@ -116,6 +118,7 @@ class PDFPageView {
|
||||
this.annotationLayer = null;
|
||||
this.textLayer = null;
|
||||
this.zoomLayer = null;
|
||||
this.xfaLayer = null;
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = "page";
|
||||
@ -164,6 +167,24 @@ class PDFPageView {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _renderXfaLayer() {
|
||||
let error = null;
|
||||
try {
|
||||
await this.xfaLayer.render(this.viewport, "display");
|
||||
} catch (ex) {
|
||||
error = ex;
|
||||
} finally {
|
||||
this.eventBus.dispatch("xfalayerrendered", {
|
||||
source: this,
|
||||
pageNumber: this.id,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -197,9 +218,14 @@ class PDFPageView {
|
||||
const currentZoomLayerNode = (keepZoomLayer && this.zoomLayer) || null;
|
||||
const currentAnnotationNode =
|
||||
(keepAnnotations && this.annotationLayer?.div) || null;
|
||||
const currentXfaLayerNode = this.xfaLayer?.div || null;
|
||||
for (let i = childNodes.length - 1; i >= 0; i--) {
|
||||
const node = childNodes[i];
|
||||
if (currentZoomLayerNode === node || currentAnnotationNode === node) {
|
||||
if (
|
||||
currentZoomLayerNode === node ||
|
||||
currentAnnotationNode === node ||
|
||||
currentXfaLayerNode === node
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
div.removeChild(node);
|
||||
@ -393,6 +419,10 @@ class PDFPageView {
|
||||
if (redrawAnnotations && this.annotationLayer) {
|
||||
this._renderAnnotationLayer();
|
||||
}
|
||||
|
||||
if (this.xfaLayer) {
|
||||
this._renderXfaLayer();
|
||||
}
|
||||
}
|
||||
|
||||
get width() {
|
||||
@ -553,6 +583,17 @@ class PDFPageView {
|
||||
}
|
||||
this._renderAnnotationLayer();
|
||||
}
|
||||
|
||||
if (this.xfaLayerFactory) {
|
||||
if (!this.xfaLayer) {
|
||||
this.xfaLayer = this.xfaLayerFactory.createXfaLayerBuilder(
|
||||
div,
|
||||
pdfPage
|
||||
);
|
||||
}
|
||||
this._renderXfaLayer();
|
||||
}
|
||||
|
||||
div.setAttribute("data-loaded", true);
|
||||
|
||||
this.eventBus.dispatch("pagerender", {
|
||||
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
@import url(text_layer_builder.css);
|
||||
@import url(annotation_layer_builder.css);
|
||||
@import url(xfa_layer_builder.css);
|
||||
|
||||
.pdfViewer .canvasWrapper {
|
||||
overflow: hidden;
|
||||
|
22
web/xfa_layer_builder.css
Normal file
22
web/xfa_layer_builder.css
Normal file
@ -0,0 +1,22 @@
|
||||
*/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.xfaLayer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 200;
|
||||
transform-origin: 0 0;
|
||||
}
|
97
web/xfa_layer_builder.js
Normal file
97
web/xfa_layer_builder.js
Normal file
@ -0,0 +1,97 @@
|
||||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { XfaLayer } from "pdfjs-lib";
|
||||
|
||||
/**
|
||||
* @typedef {Object} XfaLayerBuilderOptions
|
||||
* @property {HTMLDivElement} pageDiv
|
||||
* @property {PDFPage} pdfPage
|
||||
*/
|
||||
|
||||
class XfaLayerBuilder {
|
||||
/**
|
||||
* @param {XfaLayerBuilderOptions} options
|
||||
*/
|
||||
constructor({ pageDiv, pdfPage }) {
|
||||
this.pageDiv = pageDiv;
|
||||
this.pdfPage = pdfPage;
|
||||
|
||||
this.div = null;
|
||||
this._cancelled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PageViewport} viewport
|
||||
* @param {string} intent (default value is 'display')
|
||||
* @returns {Promise<void>} A promise that is resolved when rendering of the
|
||||
* annotations is complete.
|
||||
*/
|
||||
render(viewport, intent = "display") {
|
||||
return this.pdfPage.getXfa().then(xfa => {
|
||||
if (this._cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parameters = {
|
||||
viewport: viewport.clone({ dontFlip: true }),
|
||||
div: this.div,
|
||||
xfa,
|
||||
page: this.pdfPage,
|
||||
};
|
||||
|
||||
if (this.div) {
|
||||
XfaLayer.update(parameters);
|
||||
} else {
|
||||
// Create an xfa layer div and render the form
|
||||
this.div = document.createElement("div");
|
||||
this.div.className = "xfaLayer";
|
||||
this.pageDiv.appendChild(this.div);
|
||||
parameters.div = this.div;
|
||||
|
||||
XfaLayer.render(parameters);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._cancelled = true;
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.div) {
|
||||
return;
|
||||
}
|
||||
this.div.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @implements IPDFXfaLayerFactory
|
||||
*/
|
||||
class DefaultXfaLayerFactory {
|
||||
/**
|
||||
* @param {HTMLDivElement} pageDiv
|
||||
* @param {PDFPage} pdfPage
|
||||
*/
|
||||
createXfaLayerBuilder(pageDiv, pdfPage) {
|
||||
return new XfaLayerBuilder({
|
||||
pageDiv,
|
||||
pdfPage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { DefaultXfaLayerFactory, XfaLayerBuilder };
|
Loading…
x
Reference in New Issue
Block a user