Merge pull request #13402 from calixteman/xfa_layout1
XFA - Fix lot of layout issues
This commit is contained in:
commit
4d26623e59
@ -22,6 +22,7 @@ import {
|
|||||||
$finalize,
|
$finalize,
|
||||||
$getAttributeIt,
|
$getAttributeIt,
|
||||||
$getChildren,
|
$getChildren,
|
||||||
|
$getDataValue,
|
||||||
$getParent,
|
$getParent,
|
||||||
$getRealChildrenByNameIt,
|
$getRealChildrenByNameIt,
|
||||||
$global,
|
$global,
|
||||||
@ -88,7 +89,7 @@ class Binder {
|
|||||||
|
|
||||||
if (formNode[$hasSettableValue]()) {
|
if (formNode[$hasSettableValue]()) {
|
||||||
if (data[$isDataValue]()) {
|
if (data[$isDataValue]()) {
|
||||||
const value = data[$content].trim();
|
const value = data[$getDataValue]();
|
||||||
// TODO: use picture.
|
// TODO: use picture.
|
||||||
formNode[$setValue](createText(value));
|
formNode[$setValue](createText(value));
|
||||||
formNode[$data] = data;
|
formNode[$data] = data;
|
||||||
@ -114,7 +115,7 @@ class Binder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_findDataByNameToConsume(name, dataNode, global) {
|
_findDataByNameToConsume(name, isValue, dataNode, global) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -130,9 +131,16 @@ class Binder {
|
|||||||
/* allTransparent = */ false,
|
/* allTransparent = */ false,
|
||||||
/* skipConsumed = */ true
|
/* skipConsumed = */ true
|
||||||
);
|
);
|
||||||
match = generator.next().value;
|
// Try to find a match of the same kind.
|
||||||
if (match) {
|
while (true) {
|
||||||
return match;
|
match = generator.next().value;
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValue === match[$isDataValue]()) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
dataNode[$namespaceId] === NamespaceIds.datasets.id &&
|
dataNode[$namespaceId] === NamespaceIds.datasets.id &&
|
||||||
@ -149,7 +157,7 @@ class Binder {
|
|||||||
|
|
||||||
// Secondly, if global try to find it just under the root of datasets
|
// Secondly, if global try to find it just under the root of datasets
|
||||||
// (which is the location of global variables).
|
// (which is the location of global variables).
|
||||||
generator = this.datasets[$getRealChildrenByNameIt](
|
generator = this.data[$getRealChildrenByNameIt](
|
||||||
name,
|
name,
|
||||||
/* allTransparent = */ false,
|
/* allTransparent = */ false,
|
||||||
/* skipConsumed = */ false
|
/* skipConsumed = */ false
|
||||||
@ -478,6 +486,7 @@ class Binder {
|
|||||||
if (child.bind) {
|
if (child.bind) {
|
||||||
switch (child.bind.match) {
|
switch (child.bind.match) {
|
||||||
case "none":
|
case "none":
|
||||||
|
this._bindElement(child, dataNode);
|
||||||
continue;
|
continue;
|
||||||
case "global":
|
case "global":
|
||||||
global = true;
|
global = true;
|
||||||
@ -485,6 +494,7 @@ class Binder {
|
|||||||
case "dataRef":
|
case "dataRef":
|
||||||
if (!child.bind.ref) {
|
if (!child.bind.ref) {
|
||||||
warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
|
warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
|
||||||
|
this._bindElement(child, dataNode);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ref = child.bind.ref;
|
ref = child.bind.ref;
|
||||||
@ -545,6 +555,7 @@ class Binder {
|
|||||||
while (matches.length < max) {
|
while (matches.length < max) {
|
||||||
const found = this._findDataByNameToConsume(
|
const found = this._findDataByNameToConsume(
|
||||||
child.name,
|
child.name,
|
||||||
|
child[$hasSettableValue](),
|
||||||
dataNode,
|
dataNode,
|
||||||
global
|
global
|
||||||
);
|
);
|
||||||
@ -580,6 +591,8 @@ class Binder {
|
|||||||
}
|
}
|
||||||
this._bindOccurrences(child, match, picture);
|
this._bindOccurrences(child, match, picture);
|
||||||
} else if (min > 0) {
|
} else if (min > 0) {
|
||||||
|
this._setProperties(child, dataNode);
|
||||||
|
this._bindItems(child, dataNode);
|
||||||
this._bindElement(child, dataNode);
|
this._bindElement(child, dataNode);
|
||||||
} else {
|
} else {
|
||||||
uselessNodes.push(child);
|
uselessNodes.push(child);
|
||||||
|
@ -17,6 +17,7 @@ import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
|||||||
import {
|
import {
|
||||||
$cleanup,
|
$cleanup,
|
||||||
$finalize,
|
$finalize,
|
||||||
|
$ids,
|
||||||
$nsAttributes,
|
$nsAttributes,
|
||||||
$onChild,
|
$onChild,
|
||||||
$resolvePrototypes,
|
$resolvePrototypes,
|
||||||
@ -27,13 +28,11 @@ import { Template } from "./template.js";
|
|||||||
import { UnknownNamespace } from "./unknown.js";
|
import { UnknownNamespace } from "./unknown.js";
|
||||||
import { warn } from "../../shared/util.js";
|
import { warn } from "../../shared/util.js";
|
||||||
|
|
||||||
const _ids = Symbol();
|
|
||||||
|
|
||||||
class Root extends XFAObject {
|
class Root extends XFAObject {
|
||||||
constructor(ids) {
|
constructor(ids) {
|
||||||
super(-1, "root", Object.create(null));
|
super(-1, "root", Object.create(null));
|
||||||
this.element = null;
|
this.element = null;
|
||||||
this[_ids] = ids;
|
this[$ids] = ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onChild](child) {
|
[$onChild](child) {
|
||||||
@ -44,7 +43,8 @@ class Root extends XFAObject {
|
|||||||
[$finalize]() {
|
[$finalize]() {
|
||||||
super[$finalize]();
|
super[$finalize]();
|
||||||
if (this.element.template instanceof Template) {
|
if (this.element.template instanceof Template) {
|
||||||
this.element.template[$resolvePrototypes](this[_ids]);
|
this.element.template[$resolvePrototypes](this[$ids]);
|
||||||
|
this.element.template[$ids] = this[$ids];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,18 +13,38 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $extra, $getParent, $toStyle, XFAObject } from "./xfa_object.js";
|
import {
|
||||||
|
$extra,
|
||||||
|
$getParent,
|
||||||
|
$nodeName,
|
||||||
|
$toStyle,
|
||||||
|
XFAObject,
|
||||||
|
} from "./xfa_object.js";
|
||||||
|
import { getMeasurement } from "./utils.js";
|
||||||
import { warn } from "../../shared/util.js";
|
import { warn } from "../../shared/util.js";
|
||||||
|
|
||||||
|
const wordNonWordRegex = new RegExp(
|
||||||
|
"([\\p{N}\\p{L}\\p{M}]+)|([^\\p{N}\\p{L}\\p{M}]+)",
|
||||||
|
"gu"
|
||||||
|
);
|
||||||
|
const wordFirstRegex = new RegExp("^[\\p{N}\\p{L}\\p{M}]", "u");
|
||||||
|
|
||||||
function measureToString(m) {
|
function measureToString(m) {
|
||||||
if (typeof m === "string") {
|
if (typeof m === "string") {
|
||||||
return "0px";
|
return "0px";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
|
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const converters = {
|
const converters = {
|
||||||
anchorType(node, style) {
|
anchorType(node, style) {
|
||||||
|
const parent = node[$getParent]();
|
||||||
|
if (!parent || (parent.layout && parent.layout !== "position")) {
|
||||||
|
// anchorType is only used in a positioned layout.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!("transform" in style)) {
|
if (!("transform" in style)) {
|
||||||
style.transform = "";
|
style.transform = "";
|
||||||
}
|
}
|
||||||
@ -57,12 +77,28 @@ const converters = {
|
|||||||
},
|
},
|
||||||
dimensions(node, style) {
|
dimensions(node, style) {
|
||||||
const parent = node[$getParent]();
|
const parent = node[$getParent]();
|
||||||
const extra = parent[$extra];
|
|
||||||
let width = node.w;
|
let width = node.w;
|
||||||
if (extra && extra.columnWidths) {
|
const height = node.h;
|
||||||
width = extra.columnWidths[extra.currentColumn];
|
if (parent.layout && parent.layout.includes("row")) {
|
||||||
extra.currentColumn =
|
const extra = parent[$extra];
|
||||||
(extra.currentColumn + 1) % extra.columnWidths.length;
|
const colSpan = node.colSpan;
|
||||||
|
let w;
|
||||||
|
if (colSpan === -1) {
|
||||||
|
w = extra.columnWidths
|
||||||
|
.slice(extra.currentColumn)
|
||||||
|
.reduce((a, x) => a + x, 0);
|
||||||
|
extra.currentColumn = 0;
|
||||||
|
} else {
|
||||||
|
w = extra.columnWidths
|
||||||
|
.slice(extra.currentColumn, extra.currentColumn + colSpan)
|
||||||
|
.reduce((a, x) => a + x, 0);
|
||||||
|
extra.currentColumn =
|
||||||
|
(extra.currentColumn + node.colSpan) % extra.columnWidths.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNaN(w)) {
|
||||||
|
width = node.w = w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width !== "") {
|
if (width !== "") {
|
||||||
@ -72,17 +108,21 @@ const converters = {
|
|||||||
if (node.maxW > 0) {
|
if (node.maxW > 0) {
|
||||||
style.maxWidth = measureToString(node.maxW);
|
style.maxWidth = measureToString(node.maxW);
|
||||||
}
|
}
|
||||||
style.minWidth = measureToString(node.minW);
|
if (parent.layout === "position") {
|
||||||
|
style.minWidth = measureToString(node.minW);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.h !== "") {
|
if (height !== "") {
|
||||||
style.height = measureToString(node.h);
|
style.height = measureToString(height);
|
||||||
} else {
|
} else {
|
||||||
style.height = "auto";
|
style.height = "auto";
|
||||||
if (node.maxH > 0) {
|
if (node.maxH > 0) {
|
||||||
style.maxHeight = measureToString(node.maxH);
|
style.maxHeight = measureToString(node.maxH);
|
||||||
}
|
}
|
||||||
style.minHeight = measureToString(node.minH);
|
if (parent.layout === "position") {
|
||||||
|
style.minHeight = measureToString(node.minH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
position(node, style) {
|
position(node, style) {
|
||||||
@ -118,22 +158,31 @@ const converters = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
hAlign(node, style) {
|
hAlign(node, style) {
|
||||||
switch (node.hAlign) {
|
if (node[$nodeName] === "para") {
|
||||||
case "justifyAll":
|
switch (node.hAlign) {
|
||||||
style.textAlign = "justify-all";
|
case "justifyAll":
|
||||||
break;
|
style.textAlign = "justify-all";
|
||||||
case "radix":
|
break;
|
||||||
// TODO: implement this correctly !
|
case "radix":
|
||||||
style.textAlign = "left";
|
// TODO: implement this correctly !
|
||||||
break;
|
style.textAlign = "left";
|
||||||
default:
|
break;
|
||||||
style.textAlign = node.hAlign;
|
default:
|
||||||
|
style.textAlign = node.hAlign;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (node.hAlign) {
|
||||||
|
case "right":
|
||||||
|
case "center":
|
||||||
|
style.justifyContent = node.hAlign;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
borderMarginPadding(node, style) {
|
borderMarginPadding(node, style) {
|
||||||
// Get border width in order to compute margin and padding.
|
// Get border width in order to compute margin and padding.
|
||||||
const borderWidths = [0, 0, 0, 0];
|
const borderWidths = [0, 0, 0, 0];
|
||||||
const marginWidths = [0, 0, 0, 0];
|
const borderInsets = [0, 0, 0, 0];
|
||||||
const marginNode = node.margin
|
const marginNode = node.margin
|
||||||
? [
|
? [
|
||||||
node.margin.topInset,
|
node.margin.topInset,
|
||||||
@ -142,30 +191,211 @@ const converters = {
|
|||||||
node.margin.leftInset,
|
node.margin.leftInset,
|
||||||
]
|
]
|
||||||
: [0, 0, 0, 0];
|
: [0, 0, 0, 0];
|
||||||
|
|
||||||
|
let borderMargin;
|
||||||
if (node.border) {
|
if (node.border) {
|
||||||
Object.assign(style, node.border[$toStyle](borderWidths, marginWidths));
|
Object.assign(style, node.border[$toStyle](borderWidths, borderInsets));
|
||||||
|
borderMargin = style.margin;
|
||||||
|
delete style.margin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (borderWidths.every(x => x === 0)) {
|
if (borderWidths.every(x => x === 0)) {
|
||||||
// No border: margin & padding are padding
|
if (marginNode.every(x => x === 0)) {
|
||||||
if (node.margin) {
|
return;
|
||||||
Object.assign(style, node.margin[$toStyle]());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No border: margin & padding are padding
|
||||||
|
Object.assign(style, node.margin[$toStyle]());
|
||||||
style.padding = style.margin;
|
style.padding = style.margin;
|
||||||
delete style.margin;
|
delete style.margin;
|
||||||
} else {
|
delete style.outline;
|
||||||
style.padding =
|
delete style.outlineOffset;
|
||||||
measureToString(marginNode[0] - borderWidths[0] - marginWidths[0]) +
|
return;
|
||||||
" " +
|
|
||||||
measureToString(marginNode[1] - borderWidths[1] - marginWidths[1]) +
|
|
||||||
" " +
|
|
||||||
measureToString(marginNode[2] - borderWidths[2] - marginWidths[2]) +
|
|
||||||
" " +
|
|
||||||
measureToString(marginNode[3] - borderWidths[3] - marginWidths[3]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.margin) {
|
||||||
|
Object.assign(style, node.margin[$toStyle]());
|
||||||
|
style.padding = style.margin;
|
||||||
|
delete style.margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!style.borderWidth) {
|
||||||
|
// We've an outline so no need to fake one.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
style.borderData = {
|
||||||
|
borderWidth: style.borderWidth,
|
||||||
|
borderColor: style.borderColor,
|
||||||
|
borderStyle: style.borderStyle,
|
||||||
|
margin: borderMargin,
|
||||||
|
};
|
||||||
|
|
||||||
|
delete style.borderWidth;
|
||||||
|
delete style.borderColor;
|
||||||
|
delete style.borderStyle;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function layoutText(text, fontSize, space) {
|
||||||
|
// Try to guess width and height for the given text in taking into
|
||||||
|
// account the space where the text should fit.
|
||||||
|
// The computed dimensions are just an overestimation.
|
||||||
|
// TODO: base this estimation on real metrics.
|
||||||
|
let width = 0;
|
||||||
|
let height = 0;
|
||||||
|
let totalWidth = 0;
|
||||||
|
const lineHeight = fontSize * 1.5;
|
||||||
|
const averageCharSize = fontSize * 0.4;
|
||||||
|
const maxCharOnLine = Math.floor(space.width / averageCharSize);
|
||||||
|
const chunks = text.match(wordNonWordRegex);
|
||||||
|
let treatedChars = 0;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
let chunk = chunks[0];
|
||||||
|
while (chunk) {
|
||||||
|
const w = chunk.length * averageCharSize;
|
||||||
|
if (width + w <= space.width) {
|
||||||
|
width += w;
|
||||||
|
treatedChars += chunk.length;
|
||||||
|
chunk = chunks[i++];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wordFirstRegex.test(chunk) || chunk.length > maxCharOnLine) {
|
||||||
|
const numOfCharOnLine = Math.floor(
|
||||||
|
(space.width - width) / averageCharSize
|
||||||
|
);
|
||||||
|
chunk = chunk.slice(numOfCharOnLine);
|
||||||
|
treatedChars += numOfCharOnLine;
|
||||||
|
if (height + lineHeight > space.height) {
|
||||||
|
return { width: 0, height: 0, splitPos: treatedChars };
|
||||||
|
}
|
||||||
|
totalWidth = Math.max(width, totalWidth);
|
||||||
|
width = 0;
|
||||||
|
height += lineHeight;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height + lineHeight > space.height) {
|
||||||
|
return { width: 0, height: 0, splitPos: treatedChars };
|
||||||
|
}
|
||||||
|
|
||||||
|
totalWidth = Math.max(width, totalWidth);
|
||||||
|
width = w;
|
||||||
|
height += lineHeight;
|
||||||
|
chunk = chunks[i++];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWidth === 0) {
|
||||||
|
totalWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWidth !== 0) {
|
||||||
|
height += lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { width: totalWidth, height, splitPos: -1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeBbox(node, html, availableSpace) {
|
||||||
|
let bbox;
|
||||||
|
if (node.w !== "" && node.h !== "") {
|
||||||
|
bbox = [node.x, node.y, node.w, node.h];
|
||||||
|
} else {
|
||||||
|
if (!availableSpace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let width = node.w;
|
||||||
|
if (width === "") {
|
||||||
|
if (node.maxW === 0) {
|
||||||
|
const parent = node[$getParent]();
|
||||||
|
if (parent.layout === "position" && parent.w !== "") {
|
||||||
|
width = 0;
|
||||||
|
} else {
|
||||||
|
width = node.minW;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
width = Math.min(node.maxW, availableSpace.width);
|
||||||
|
}
|
||||||
|
html.attributes.style.width = measureToString(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
let height = node.h;
|
||||||
|
if (height === "") {
|
||||||
|
if (node.maxH === 0) {
|
||||||
|
const parent = node[$getParent]();
|
||||||
|
if (parent.layout === "position" && parent.h !== "") {
|
||||||
|
height = 0;
|
||||||
|
} else {
|
||||||
|
height = node.minH;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
height = Math.min(node.maxH, availableSpace.height);
|
||||||
|
}
|
||||||
|
html.attributes.style.height = measureToString(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bbox = [node.x, node.y, width, height];
|
||||||
|
}
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixDimensions(node) {
|
||||||
|
const parent = node[$getParent]();
|
||||||
|
if (parent.layout && parent.layout.includes("row")) {
|
||||||
|
const extra = parent[$extra];
|
||||||
|
const colSpan = node.colSpan;
|
||||||
|
let width;
|
||||||
|
if (colSpan === -1) {
|
||||||
|
width = extra.columnWidths
|
||||||
|
.slice(extra.currentColumn)
|
||||||
|
.reduce((a, w) => a + w, 0);
|
||||||
|
} else {
|
||||||
|
width = extra.columnWidths
|
||||||
|
.slice(extra.currentColumn, extra.currentColumn + colSpan)
|
||||||
|
.reduce((a, w) => a + w, 0);
|
||||||
|
}
|
||||||
|
if (!isNaN(width)) {
|
||||||
|
node.w = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.w && node.w) {
|
||||||
|
node.w = Math.min(parent.w, node.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.h && node.h) {
|
||||||
|
node.h = Math.min(parent.h, node.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.layout && parent.layout !== "position") {
|
||||||
|
// Useless in this context.
|
||||||
|
node.x = node.y = 0;
|
||||||
|
if (parent.layout === "tb") {
|
||||||
|
if (
|
||||||
|
parent.w !== "" &&
|
||||||
|
(node.w === "" || node.w === 0 || node.w > parent.w)
|
||||||
|
) {
|
||||||
|
node.w = parent.w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.layout === "position") {
|
||||||
|
// Acrobat doesn't take into account min, max values
|
||||||
|
// for containers with positioned layout (which makes sense).
|
||||||
|
node.minW = node.minH = 0;
|
||||||
|
node.maxW = node.maxH = Infinity;
|
||||||
|
} else {
|
||||||
|
if (node.layout === "table") {
|
||||||
|
if (node.w === "" && Array.isArray(node.columnWidths)) {
|
||||||
|
node.w = node.columnWidths.reduce((a, x) => a + x, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function layoutClass(node) {
|
function layoutClass(node) {
|
||||||
switch (node.layout) {
|
switch (node.layout) {
|
||||||
case "position":
|
case "position":
|
||||||
@ -211,26 +441,145 @@ function toStyle(node, ...names) {
|
|||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addExtraDivForMargin(html) {
|
function addExtraDivForBorder(html) {
|
||||||
const style = html.attributes.style;
|
const style = html.attributes.style;
|
||||||
if (style.margin) {
|
const data = style.borderData;
|
||||||
const padding = style.margin;
|
const children = [];
|
||||||
delete style.margin;
|
|
||||||
const width = style.width || "auto";
|
|
||||||
const height = style.height || "auto";
|
|
||||||
|
|
||||||
style.width = "100%";
|
const attributes = {
|
||||||
style.height = "100%";
|
class: "xfaWrapper",
|
||||||
|
style: Object.create(null),
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
for (const key of ["top", "left"]) {
|
||||||
|
if (style[key] !== undefined) {
|
||||||
|
attributes.style[key] = style[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete style.top;
|
||||||
|
delete style.left;
|
||||||
|
|
||||||
|
if (style.position === "absolute") {
|
||||||
|
attributes.style.position = "absolute";
|
||||||
|
} else {
|
||||||
|
attributes.style.position = "relative";
|
||||||
|
}
|
||||||
|
delete style.position;
|
||||||
|
|
||||||
|
if (style.justifyContent) {
|
||||||
|
attributes.style.justifyContent = style.justifyContent;
|
||||||
|
delete style.justifyContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
delete style.borderData;
|
||||||
|
|
||||||
|
let insets;
|
||||||
|
if (data.margin) {
|
||||||
|
insets = data.margin.split(" ");
|
||||||
|
delete data.margin;
|
||||||
|
} else {
|
||||||
|
insets = ["0px", "0px", "0px", "0px"];
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = "100%";
|
||||||
|
let height = width;
|
||||||
|
|
||||||
|
if (insets[1] !== "0px" || insets[3] !== "0px") {
|
||||||
|
width = `calc(100% - ${parseInt(insets[1]) + parseInt(insets[3])}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insets[0] !== "0px" || insets[2] !== "0px") {
|
||||||
|
height = `calc(100% - ${parseInt(insets[0]) + parseInt(insets[2])}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderStyle = {
|
||||||
|
top: insets[0],
|
||||||
|
left: insets[3],
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
borderStyle[k] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.transform) {
|
||||||
|
borderStyle.transform = style.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderDiv = {
|
||||||
name: "div",
|
name: "div",
|
||||||
attributes: {
|
attributes: {
|
||||||
style: { padding, width, height },
|
class: "xfaBorderDiv",
|
||||||
|
style: borderStyle,
|
||||||
},
|
},
|
||||||
children: [html],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
children.push(borderDiv);
|
||||||
}
|
}
|
||||||
return html;
|
|
||||||
|
children.push(html);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "div",
|
||||||
|
attributes,
|
||||||
|
children,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { addExtraDivForMargin, layoutClass, measureToString, toStyle };
|
function fixTextIndent(styles) {
|
||||||
|
const indent = getMeasurement(styles.textIndent, "0px");
|
||||||
|
if (indent >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const align = styles.textAlign || "left";
|
||||||
|
if (align === "left" || align === "right") {
|
||||||
|
const name = "margin" + (align === "left" ? "Left" : "Right");
|
||||||
|
const margin = getMeasurement(styles[name], "0px");
|
||||||
|
styles[name] = `${margin - indent}pt`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFonts(family) {
|
||||||
|
if (family.startsWith("'")) {
|
||||||
|
family = `"${family.slice(1, family.length - 1)}"`;
|
||||||
|
} else if (family.includes(" ") && !family.startsWith('"')) {
|
||||||
|
family = `"${family}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO in case Myriad is not available we should generate a new
|
||||||
|
// font based on helvetica but where glyphs have been rescaled in order
|
||||||
|
// to have the exact same metrics.
|
||||||
|
const fonts = [family];
|
||||||
|
switch (family) {
|
||||||
|
case `"Myriad Pro"`:
|
||||||
|
fonts.push(
|
||||||
|
`"Roboto Condensed"`,
|
||||||
|
`"Ubuntu Condensed"`,
|
||||||
|
`"Microsoft Sans Serif"`,
|
||||||
|
`"Apple Symbols"`,
|
||||||
|
"Helvetica",
|
||||||
|
`"sans serif"`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "Arial":
|
||||||
|
fonts.push("Helvetica", `"Liberation Sans"`, "Arimo", `"sans serif"`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return fonts.join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
addExtraDivForBorder,
|
||||||
|
computeBbox,
|
||||||
|
fixDimensions,
|
||||||
|
fixTextIndent,
|
||||||
|
getFonts,
|
||||||
|
layoutClass,
|
||||||
|
layoutText,
|
||||||
|
measureToString,
|
||||||
|
toStyle,
|
||||||
|
};
|
||||||
|
190
src/core/xfa/layout.js
Normal file
190
src/core/xfa/layout.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/* 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 { $extra, $flushHTML } from "./xfa_object.js";
|
||||||
|
import { measureToString } from "./html_utils.js";
|
||||||
|
|
||||||
|
// Subform and ExclGroup have a layout so they share these functions.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How layout works ?
|
||||||
|
*
|
||||||
|
* A container has an initial space (with a width and a height) to fit in,
|
||||||
|
* which means that once all the children have been added then
|
||||||
|
* the total width/height must be lower than the given ones in
|
||||||
|
* the initial space.
|
||||||
|
* So if the container has known dimensions and these ones are ok with the
|
||||||
|
* space then continue else we return HTMLResult.FAILURE: it's up to the
|
||||||
|
* parent to deal with this failure (e.g. if parent layout is lr-tb and
|
||||||
|
* we fail to add a child at end of line (lr) then we try to add it on the
|
||||||
|
* next line).
|
||||||
|
* And then we run through the children, each child gets its initial space
|
||||||
|
* in calling its parent $getAvailableSpace method
|
||||||
|
* (see _filteredChildrenGenerator and $childrenToHTML in xfa_object.js)
|
||||||
|
* then we try to layout child in its space. If everything is ok then we add
|
||||||
|
* the result to its parent through $addHTML which will recompute the available
|
||||||
|
* space in parent according to its layout property else we return
|
||||||
|
* HTMLResult.Failure.
|
||||||
|
* Before a failure some children may have been layed out: they've been saved in
|
||||||
|
* [$extra].children and [$extra] has properties generator and failingNode
|
||||||
|
* in order to save the state where we were before a failure.
|
||||||
|
* This [$extra].children property is useful when a container has to be splited.
|
||||||
|
* So if a container is unbreakable, we must delete its [$extra] property before
|
||||||
|
* returning.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function flushHTML(node) {
|
||||||
|
const attributes = node[$extra].attributes;
|
||||||
|
const html = {
|
||||||
|
name: "div",
|
||||||
|
attributes,
|
||||||
|
children: node[$extra].children,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node[$extra].failingNode) {
|
||||||
|
const htmlFromFailing = node[$extra].failingNode[$flushHTML]();
|
||||||
|
if (htmlFromFailing) {
|
||||||
|
html.children.push(htmlFromFailing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (html.children.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
node[$extra].children = [];
|
||||||
|
delete node[$extra].line;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHTML(node, html, bbox) {
|
||||||
|
const extra = node[$extra];
|
||||||
|
const availableSpace = extra.availableSpace;
|
||||||
|
|
||||||
|
switch (node.layout) {
|
||||||
|
case "position": {
|
||||||
|
const [x, y, w, h] = bbox;
|
||||||
|
extra.width = Math.max(extra.width, x + w);
|
||||||
|
extra.height = Math.max(extra.height, y + h);
|
||||||
|
extra.children.push(html);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "lr-tb":
|
||||||
|
case "rl-tb":
|
||||||
|
if (!extra.line || extra.attempt === 1) {
|
||||||
|
extra.line = {
|
||||||
|
name: "div",
|
||||||
|
attributes: {
|
||||||
|
class: node.layout === "lr-tb" ? "xfaLr" : "xfaRl",
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
extra.children.push(extra.line);
|
||||||
|
}
|
||||||
|
extra.line.children.push(html);
|
||||||
|
|
||||||
|
if (extra.attempt === 0) {
|
||||||
|
// Add the element on the line
|
||||||
|
const [, , w, h] = bbox;
|
||||||
|
extra.currentWidth += w;
|
||||||
|
extra.height = Math.max(extra.height, extra.prevHeight + h);
|
||||||
|
} else {
|
||||||
|
const [, , w, h] = bbox;
|
||||||
|
extra.width = Math.max(extra.width, extra.currentWidth);
|
||||||
|
extra.currentWidth = w;
|
||||||
|
extra.prevHeight = extra.height;
|
||||||
|
extra.height += h;
|
||||||
|
|
||||||
|
// The element has been added on a new line so switch to line mode now.
|
||||||
|
extra.attempt = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "rl-row":
|
||||||
|
case "row": {
|
||||||
|
extra.children.push(html);
|
||||||
|
const [, , w, h] = bbox;
|
||||||
|
extra.width += w;
|
||||||
|
extra.height = Math.max(extra.height, h);
|
||||||
|
const height = measureToString(extra.height);
|
||||||
|
for (const child of extra.children) {
|
||||||
|
if (child.attributes.class === "xfaWrapper") {
|
||||||
|
child.children[child.children.length - 1].attributes.style.height =
|
||||||
|
height;
|
||||||
|
} else {
|
||||||
|
child.attributes.style.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "table": {
|
||||||
|
const [, , w, h] = bbox;
|
||||||
|
extra.width = Math.min(availableSpace.width, Math.max(extra.width, w));
|
||||||
|
extra.height += h;
|
||||||
|
extra.children.push(html);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tb": {
|
||||||
|
const [, , , h] = bbox;
|
||||||
|
extra.width = availableSpace.width;
|
||||||
|
extra.height += h;
|
||||||
|
extra.children.push(html);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAvailableSpace(node) {
|
||||||
|
const availableSpace = node[$extra].availableSpace;
|
||||||
|
|
||||||
|
switch (node.layout) {
|
||||||
|
case "lr-tb":
|
||||||
|
case "rl-tb":
|
||||||
|
switch (node[$extra].attempt) {
|
||||||
|
case 0:
|
||||||
|
return {
|
||||||
|
width: availableSpace.width - node[$extra].currentWidth,
|
||||||
|
height: availableSpace.height - node[$extra].prevHeight,
|
||||||
|
};
|
||||||
|
case 1:
|
||||||
|
return {
|
||||||
|
width: availableSpace.width,
|
||||||
|
height: availableSpace.height - node[$extra].height,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
width: Infinity,
|
||||||
|
height: availableSpace.height - node[$extra].prevHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "rl-row":
|
||||||
|
case "row":
|
||||||
|
const width = node[$extra].columnWidths
|
||||||
|
.slice(node[$extra].currentColumn)
|
||||||
|
.reduce((a, x) => a + x);
|
||||||
|
return { width, height: availableSpace.height };
|
||||||
|
case "table":
|
||||||
|
case "tb":
|
||||||
|
return {
|
||||||
|
width: availableSpace.width,
|
||||||
|
height: availableSpace.height - node[$extra].height,
|
||||||
|
};
|
||||||
|
case "position":
|
||||||
|
default:
|
||||||
|
return availableSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { addHTML, flushHTML, getAvailableSpace };
|
@ -35,6 +35,7 @@ class XFAParser extends XMLParserBase {
|
|||||||
this._current = this._builder.buildRoot(this._ids);
|
this._current = this._builder.buildRoot(this._ids);
|
||||||
this._errorCode = XMLParserErrorCode.NoError;
|
this._errorCode = XMLParserErrorCode.NoError;
|
||||||
this._whiteRegex = /^\s+$/;
|
this._whiteRegex = /^\s+$/;
|
||||||
|
this._nbsps = /\xa0+/g;
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(data) {
|
parse(data) {
|
||||||
@ -50,6 +51,9 @@ class XFAParser extends XMLParserBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onText(text) {
|
onText(text) {
|
||||||
|
// Normally by definition a   is unbreakable
|
||||||
|
// but in real life Acrobat can break strings on  .
|
||||||
|
text = text.replace(this._nbsps, match => match.slice(1) + " ");
|
||||||
if (this._current[$acceptWhitespace]()) {
|
if (this._current[$acceptWhitespace]()) {
|
||||||
this._current[$onText](text);
|
this._current[$onText](text);
|
||||||
return;
|
return;
|
||||||
|
@ -275,19 +275,32 @@ function createDataNode(root, container, expr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let ii = parsed.length; i < ii; i++) {
|
for (let ii = parsed.length; i < ii; i++) {
|
||||||
const { cacheName, index } = parsed[i];
|
const { name, operator, index } = parsed[i];
|
||||||
if (!isFinite(index)) {
|
if (!isFinite(index)) {
|
||||||
parsed[i].index = 0;
|
parsed[i].index = 0;
|
||||||
return createNodes(root, parsed.slice(i));
|
return createNodes(root, parsed.slice(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
const cached = somCache.get(root);
|
let children;
|
||||||
if (!cached) {
|
switch (operator) {
|
||||||
warn(`XFA - createDataNode must be called after searchNode.`);
|
case operators.dot:
|
||||||
return null;
|
children = root[$getChildrenByName](name, false);
|
||||||
|
break;
|
||||||
|
case operators.dotDot:
|
||||||
|
children = root[$getChildrenByName](name, true);
|
||||||
|
break;
|
||||||
|
case operators.dotHash:
|
||||||
|
children = root[$getChildrenByClass](name);
|
||||||
|
if (children instanceof XFAObjectArray) {
|
||||||
|
children = children.children;
|
||||||
|
} else {
|
||||||
|
children = [children];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = cached.get(cacheName);
|
|
||||||
if (children.length === 0) {
|
if (children.length === 0) {
|
||||||
return createNodes(root, parsed.slice(i));
|
return createNodes(root, parsed.slice(i));
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@ const dimConverters = {
|
|||||||
cm: x => (x / 2.54) * 72,
|
cm: x => (x / 2.54) * 72,
|
||||||
mm: x => (x / (10 * 2.54)) * 72,
|
mm: x => (x / (10 * 2.54)) * 72,
|
||||||
in: x => x * 72,
|
in: x => x * 72,
|
||||||
|
px: x => x,
|
||||||
};
|
};
|
||||||
const measurementPattern = /([+-]?[0-9]+\.?[0-9]*)(.*)/;
|
const measurementPattern = /([+-]?[0-9]+\.?[0-9]*)(.*)/;
|
||||||
|
|
||||||
@ -163,6 +164,21 @@ function getBBox(data) {
|
|||||||
return { x, y, width, height };
|
return { x, y, width, height };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HTMLResult {
|
||||||
|
constructor(success, html, bbox) {
|
||||||
|
this.success = success;
|
||||||
|
this.html = html;
|
||||||
|
this.bbox = bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static success(html, bbox = null) {
|
||||||
|
return new HTMLResult(true, html, bbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLResult.FAILURE = new HTMLResult(false, null, null);
|
||||||
|
HTMLResult.EMPTY = new HTMLResult(true, null, null);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getBBox,
|
getBBox,
|
||||||
getColor,
|
getColor,
|
||||||
@ -173,4 +189,5 @@ export {
|
|||||||
getRatio,
|
getRatio,
|
||||||
getRelevant,
|
getRelevant,
|
||||||
getStringOption,
|
getStringOption,
|
||||||
|
HTMLResult,
|
||||||
};
|
};
|
||||||
|
@ -13,14 +13,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInteger, getKeyword } from "./utils.js";
|
import { getInteger, getKeyword, HTMLResult } from "./utils.js";
|
||||||
import { shadow, warn } from "../../shared/util.js";
|
import { shadow, warn } from "../../shared/util.js";
|
||||||
import { NamespaceIds } from "./namespaces.js";
|
import { NamespaceIds } from "./namespaces.js";
|
||||||
|
|
||||||
// We use these symbols to avoid name conflict between tags
|
// We use these symbols to avoid name conflict between tags
|
||||||
// and properties/methods names.
|
// and properties/methods names.
|
||||||
const $acceptWhitespace = Symbol();
|
const $acceptWhitespace = Symbol();
|
||||||
|
const $addHTML = Symbol();
|
||||||
const $appendChild = Symbol();
|
const $appendChild = Symbol();
|
||||||
|
const $break = Symbol();
|
||||||
const $childrenToHTML = Symbol();
|
const $childrenToHTML = Symbol();
|
||||||
const $clean = Symbol();
|
const $clean = Symbol();
|
||||||
const $cleanup = Symbol();
|
const $cleanup = Symbol();
|
||||||
@ -31,16 +33,21 @@ const $data = Symbol("data");
|
|||||||
const $dump = Symbol();
|
const $dump = Symbol();
|
||||||
const $extra = Symbol("extra");
|
const $extra = Symbol("extra");
|
||||||
const $finalize = Symbol();
|
const $finalize = Symbol();
|
||||||
|
const $flushHTML = Symbol();
|
||||||
const $getAttributeIt = Symbol();
|
const $getAttributeIt = Symbol();
|
||||||
|
const $getAvailableSpace = Symbol();
|
||||||
const $getChildrenByClass = Symbol();
|
const $getChildrenByClass = Symbol();
|
||||||
const $getChildrenByName = Symbol();
|
const $getChildrenByName = Symbol();
|
||||||
const $getChildrenByNameIt = Symbol();
|
const $getChildrenByNameIt = Symbol();
|
||||||
|
const $getDataValue = Symbol();
|
||||||
const $getRealChildrenByNameIt = Symbol();
|
const $getRealChildrenByNameIt = Symbol();
|
||||||
const $getChildren = Symbol();
|
const $getChildren = Symbol();
|
||||||
|
const $getNextPage = Symbol();
|
||||||
const $getParent = Symbol();
|
const $getParent = Symbol();
|
||||||
const $global = Symbol();
|
const $global = Symbol();
|
||||||
const $hasItem = Symbol();
|
const $hasItem = Symbol();
|
||||||
const $hasSettableValue = Symbol();
|
const $hasSettableValue = Symbol();
|
||||||
|
const $ids = Symbol();
|
||||||
const $indexOf = Symbol();
|
const $indexOf = Symbol();
|
||||||
const $insertAt = Symbol();
|
const $insertAt = Symbol();
|
||||||
const $isDataValue = Symbol();
|
const $isDataValue = Symbol();
|
||||||
@ -55,6 +62,7 @@ const $onChildCheck = Symbol();
|
|||||||
const $onText = Symbol();
|
const $onText = Symbol();
|
||||||
const $removeChild = Symbol();
|
const $removeChild = Symbol();
|
||||||
const $resolvePrototypes = Symbol();
|
const $resolvePrototypes = Symbol();
|
||||||
|
const $searchNode = Symbol();
|
||||||
const $setId = Symbol();
|
const $setId = Symbol();
|
||||||
const $setSetAttributes = Symbol();
|
const $setSetAttributes = Symbol();
|
||||||
const $setValue = Symbol();
|
const $setValue = Symbol();
|
||||||
@ -70,6 +78,7 @@ const _children = Symbol("_children");
|
|||||||
const _cloneAttribute = Symbol();
|
const _cloneAttribute = Symbol();
|
||||||
const _dataValue = Symbol();
|
const _dataValue = Symbol();
|
||||||
const _defaultValue = Symbol();
|
const _defaultValue = Symbol();
|
||||||
|
const _filteredChildrenGenerator = Symbol();
|
||||||
const _getPrototype = Symbol();
|
const _getPrototype = Symbol();
|
||||||
const _getUnsetAttributes = Symbol();
|
const _getUnsetAttributes = Symbol();
|
||||||
const _hasChildren = Symbol();
|
const _hasChildren = Symbol();
|
||||||
@ -270,20 +279,67 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML]() {
|
||||||
|
return HTMLResult.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[_filteredChildrenGenerator](filter, include) {
|
||||||
|
for (const node of this[$getChildren]()) {
|
||||||
|
if (!filter || include === filter.has(node[$nodeName])) {
|
||||||
|
const availableSpace = this[$getAvailableSpace]();
|
||||||
|
const res = node[$toHTML](availableSpace);
|
||||||
|
if (!res.success) {
|
||||||
|
this[$extra].failingNode = node;
|
||||||
|
}
|
||||||
|
yield res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$flushHTML]() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$addHTML](html, bbox) {
|
||||||
|
this[$extra].children.push(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$getAvailableSpace]() {}
|
||||||
|
|
||||||
[$childrenToHTML]({ filter = null, include = true }) {
|
[$childrenToHTML]({ filter = null, include = true }) {
|
||||||
const res = [];
|
if (!this[$extra].generator) {
|
||||||
this[$getChildren]().forEach(node => {
|
this[$extra].generator = this[_filteredChildrenGenerator](
|
||||||
if (!filter || include === filter.has(node[$nodeName])) {
|
filter,
|
||||||
const html = node[$toHTML]();
|
include
|
||||||
if (html) {
|
);
|
||||||
res.push(html);
|
} else {
|
||||||
}
|
const availableSpace = this[$getAvailableSpace]();
|
||||||
|
const res = this[$extra].failingNode[$toHTML](availableSpace);
|
||||||
|
if (!res.success) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
});
|
if (res.html) {
|
||||||
return res;
|
this[$addHTML](res.html, res.bbox);
|
||||||
|
}
|
||||||
|
delete this[$extra].failingNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const gen = this[$extra].generator.next();
|
||||||
|
if (gen.done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const res = gen.value;
|
||||||
|
if (!res.success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (res.html) {
|
||||||
|
this[$addHTML](res.html, res.bbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this[$extra].generator = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$setSetAttributes](attributes) {
|
[$setSetAttributes](attributes) {
|
||||||
@ -640,13 +696,13 @@ class XmlObject extends XFAObject {
|
|||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML]() {
|
||||||
if (this[$nodeName] === "#text") {
|
if (this[$nodeName] === "#text") {
|
||||||
return {
|
return HTMLResult.success({
|
||||||
name: "#text",
|
name: "#text",
|
||||||
value: this[$content],
|
value: this[$content],
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return HTMLResult.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$getChildren](name = null) {
|
[$getChildren](name = null) {
|
||||||
@ -710,11 +766,27 @@ class XmlObject extends XFAObject {
|
|||||||
|
|
||||||
[$isDataValue]() {
|
[$isDataValue]() {
|
||||||
if (this[_dataValue] === null) {
|
if (this[_dataValue] === null) {
|
||||||
return this[_children].length === 0;
|
return (
|
||||||
|
this[_children].length === 0 ||
|
||||||
|
this[_children][0][$namespaceId] === NamespaceIds.xhtml.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this[_dataValue];
|
return this[_dataValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$getDataValue]() {
|
||||||
|
if (this[_dataValue] === null) {
|
||||||
|
if (this[_children].length === 0) {
|
||||||
|
return this[$content].trim();
|
||||||
|
}
|
||||||
|
if (this[_children][0][$namespaceId] === NamespaceIds.xhtml.id) {
|
||||||
|
return this[_children][0][$text]().trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this[$content].trim();
|
||||||
|
}
|
||||||
|
|
||||||
[$dump]() {
|
[$dump]() {
|
||||||
const dumped = Object.create(null);
|
const dumped = Object.create(null);
|
||||||
if (this[$content]) {
|
if (this[$content]) {
|
||||||
@ -811,7 +883,9 @@ class Option10 extends IntegerObject {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
$acceptWhitespace,
|
$acceptWhitespace,
|
||||||
|
$addHTML,
|
||||||
$appendChild,
|
$appendChild,
|
||||||
|
$break,
|
||||||
$childrenToHTML,
|
$childrenToHTML,
|
||||||
$clean,
|
$clean,
|
||||||
$cleanup,
|
$cleanup,
|
||||||
@ -822,16 +896,21 @@ export {
|
|||||||
$dump,
|
$dump,
|
||||||
$extra,
|
$extra,
|
||||||
$finalize,
|
$finalize,
|
||||||
|
$flushHTML,
|
||||||
$getAttributeIt,
|
$getAttributeIt,
|
||||||
|
$getAvailableSpace,
|
||||||
$getChildren,
|
$getChildren,
|
||||||
$getChildrenByClass,
|
$getChildrenByClass,
|
||||||
$getChildrenByName,
|
$getChildrenByName,
|
||||||
$getChildrenByNameIt,
|
$getChildrenByNameIt,
|
||||||
|
$getDataValue,
|
||||||
|
$getNextPage,
|
||||||
$getParent,
|
$getParent,
|
||||||
$getRealChildrenByNameIt,
|
$getRealChildrenByNameIt,
|
||||||
$global,
|
$global,
|
||||||
$hasItem,
|
$hasItem,
|
||||||
$hasSettableValue,
|
$hasSettableValue,
|
||||||
|
$ids,
|
||||||
$indexOf,
|
$indexOf,
|
||||||
$insertAt,
|
$insertAt,
|
||||||
$isDataValue,
|
$isDataValue,
|
||||||
@ -845,6 +924,7 @@ export {
|
|||||||
$onText,
|
$onText,
|
||||||
$removeChild,
|
$removeChild,
|
||||||
$resolvePrototypes,
|
$resolvePrototypes,
|
||||||
|
$searchNode,
|
||||||
$setId,
|
$setId,
|
||||||
$setSetAttributes,
|
$setSetAttributes,
|
||||||
$setValue,
|
$setValue,
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
$acceptWhitespace,
|
$acceptWhitespace,
|
||||||
$childrenToHTML,
|
$childrenToHTML,
|
||||||
$content,
|
$content,
|
||||||
|
$extra,
|
||||||
$nodeName,
|
$nodeName,
|
||||||
$onText,
|
$onText,
|
||||||
$text,
|
$text,
|
||||||
@ -24,8 +25,8 @@ import {
|
|||||||
XmlObject,
|
XmlObject,
|
||||||
} from "./xfa_object.js";
|
} from "./xfa_object.js";
|
||||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||||
import { getMeasurement } from "./utils.js";
|
import { fixTextIndent, getFonts, measureToString } from "./html_utils.js";
|
||||||
import { measureToString } from "./html_utils.js";
|
import { getMeasurement, HTMLResult } from "./utils.js";
|
||||||
|
|
||||||
const XHTML_NS_ID = NamespaceIds.xhtml.id;
|
const XHTML_NS_ID = NamespaceIds.xhtml.id;
|
||||||
|
|
||||||
@ -79,14 +80,16 @@ const StyleMapping = new Map([
|
|||||||
],
|
],
|
||||||
["xfa-spacerun", ""],
|
["xfa-spacerun", ""],
|
||||||
["xfa-tab-stops", ""],
|
["xfa-tab-stops", ""],
|
||||||
["font-size", value => measureToString(getMeasurement(value))],
|
["font-size", value => measureToString(1 * getMeasurement(value))],
|
||||||
["letter-spacing", value => measureToString(getMeasurement(value))],
|
["letter-spacing", value => measureToString(getMeasurement(value))],
|
||||||
["line-height", value => measureToString(getMeasurement(value))],
|
["line-height", value => measureToString(0.99 * getMeasurement(value))],
|
||||||
["margin", value => measureToString(getMeasurement(value))],
|
["margin", value => measureToString(getMeasurement(value))],
|
||||||
["margin-bottom", value => measureToString(getMeasurement(value))],
|
["margin-bottom", value => measureToString(getMeasurement(value))],
|
||||||
["margin-left", value => measureToString(getMeasurement(value))],
|
["margin-left", value => measureToString(getMeasurement(value))],
|
||||||
["margin-right", value => measureToString(getMeasurement(value))],
|
["margin-right", value => measureToString(getMeasurement(value))],
|
||||||
["margin-top", value => measureToString(getMeasurement(value))],
|
["margin-top", value => measureToString(getMeasurement(value))],
|
||||||
|
["text-indent", value => measureToString(getMeasurement(value))],
|
||||||
|
["font-family", value => getFonts(value)],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const spacesRegExp = /\s+/g;
|
const spacesRegExp = /\s+/g;
|
||||||
@ -121,6 +124,8 @@ function mapStyle(styleStr) {
|
|||||||
newValue;
|
newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fixTextIndent(style);
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,16 +167,27 @@ class XhtmlObject extends XmlObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML](availableSpace) {
|
||||||
return {
|
const children = [];
|
||||||
|
this[$extra] = {
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
|
||||||
|
this[$childrenToHTML]({});
|
||||||
|
|
||||||
|
if (children.length === 0 && !this[$content]) {
|
||||||
|
return HTMLResult.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTMLResult.success({
|
||||||
name: this[$nodeName],
|
name: this[$nodeName],
|
||||||
attributes: {
|
attributes: {
|
||||||
href: this.href,
|
href: this.href,
|
||||||
style: mapStyle(this.style),
|
style: mapStyle(this.style),
|
||||||
},
|
},
|
||||||
children: this[$childrenToHTML]({}),
|
children,
|
||||||
value: this[$content] || "",
|
value: this[$content] || "",
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,10 +209,15 @@ class Body extends XhtmlObject {
|
|||||||
super(attributes, "body");
|
super(attributes, "body");
|
||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML](availableSpace) {
|
||||||
const html = super[$toHTML]();
|
const res = super[$toHTML](availableSpace);
|
||||||
|
const { html } = res;
|
||||||
|
if (!html) {
|
||||||
|
return HTMLResult.EMPTY;
|
||||||
|
}
|
||||||
|
html.name = "div";
|
||||||
html.attributes.class = "xfaRich";
|
html.attributes.class = "xfaRich";
|
||||||
return html;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,10 +230,10 @@ class Br extends XhtmlObject {
|
|||||||
return "\n";
|
return "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML](availableSpace) {
|
||||||
return {
|
return HTMLResult.success({
|
||||||
name: "br",
|
name: "br",
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,34 +242,39 @@ class Html extends XhtmlObject {
|
|||||||
super(attributes, "html");
|
super(attributes, "html");
|
||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML](availableSpace) {
|
||||||
const children = this[$childrenToHTML]({});
|
const children = [];
|
||||||
|
this[$extra] = {
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
|
||||||
|
this[$childrenToHTML]({});
|
||||||
if (children.length === 0) {
|
if (children.length === 0) {
|
||||||
return {
|
return HTMLResult.success({
|
||||||
name: "div",
|
name: "div",
|
||||||
attributes: {
|
attributes: {
|
||||||
class: "xfaRich",
|
class: "xfaRich",
|
||||||
style: {},
|
style: {},
|
||||||
},
|
},
|
||||||
value: this[$content] || "",
|
value: this[$content] || "",
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children.length === 1) {
|
if (children.length === 1) {
|
||||||
const child = children[0];
|
const child = children[0];
|
||||||
if (child.attributes && child.attributes.class === "xfaRich") {
|
if (child.attributes && child.attributes.class === "xfaRich") {
|
||||||
return child;
|
return HTMLResult.success(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return HTMLResult.success({
|
||||||
name: "div",
|
name: "div",
|
||||||
attributes: {
|
attributes: {
|
||||||
class: "xfaRich",
|
class: "xfaRich",
|
||||||
style: {},
|
style: {},
|
||||||
},
|
},
|
||||||
children,
|
children,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +300,10 @@ class P extends XhtmlObject {
|
|||||||
constructor(attributes) {
|
constructor(attributes) {
|
||||||
super(attributes, "p");
|
super(attributes, "p");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$text]() {
|
||||||
|
return super[$text]() + "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Span extends XhtmlObject {
|
class Span extends XhtmlObject {
|
||||||
|
@ -21,7 +21,11 @@ class XfaLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key !== "style") {
|
if (key !== "style") {
|
||||||
html.setAttribute(key, value);
|
if (key === "textContent") {
|
||||||
|
html.textContent = value;
|
||||||
|
} else {
|
||||||
|
html.setAttribute(key, value);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Object.assign(html.style, value);
|
Object.assign(html.style, value);
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ describe("XFAParser", function () {
|
|||||||
[
|
[
|
||||||
" The first line of this paragraph is indented a half-inch.\n",
|
" The first line of this paragraph is indented a half-inch.\n",
|
||||||
" Successive lines are not indented.\n",
|
" Successive lines are not indented.\n",
|
||||||
" This is the last line of the paragraph.\n ",
|
" This is the last line of the paragraph.\n \n",
|
||||||
].join("")
|
].join("")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ describe("XFAFactory", function () {
|
|||||||
<contentArea x="123pt" w="456pt" h="789pt"/>
|
<contentArea x="123pt" w="456pt" h="789pt"/>
|
||||||
<medium stock="default" short="456pt" long="789pt"/>
|
<medium stock="default" short="456pt" long="789pt"/>
|
||||||
<draw y="1pt" w="11pt" h="22pt" rotate="90" x="2pt">
|
<draw y="1pt" w="11pt" h="22pt" rotate="90" x="2pt">
|
||||||
<font size="7pt" typeface="Arial" baselineShift="2pt">
|
<font size="7pt" typeface="FooBar" baselineShift="2pt">
|
||||||
<fill>
|
<fill>
|
||||||
<color value="12,23,34"/>
|
<color value="12,23,34"/>
|
||||||
<solid/>
|
<solid/>
|
||||||
@ -43,6 +43,7 @@ describe("XFAFactory", function () {
|
|||||||
<subform name="first">
|
<subform name="first">
|
||||||
</subform>
|
</subform>
|
||||||
<subform name="second">
|
<subform name="second">
|
||||||
|
<breakBefore targetType="pageArea" startNew="1"/>
|
||||||
</subform>
|
</subform>
|
||||||
</subform>
|
</subform>
|
||||||
</template>
|
</template>
|
||||||
@ -73,18 +74,23 @@ describe("XFAFactory", function () {
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
});
|
});
|
||||||
|
|
||||||
const draw = page1.children[1];
|
const wrapper = page1.children[1];
|
||||||
|
const draw = wrapper.children[0];
|
||||||
|
|
||||||
|
expect(wrapper.attributes.class).toEqual("xfaWrapper");
|
||||||
|
expect(wrapper.attributes.style).toEqual({
|
||||||
|
left: "2px",
|
||||||
|
position: "absolute",
|
||||||
|
top: "1px",
|
||||||
|
});
|
||||||
|
|
||||||
expect(draw.attributes.class).toEqual("xfaDraw xfaFont");
|
expect(draw.attributes.class).toEqual("xfaDraw xfaFont");
|
||||||
expect(draw.attributes.style).toEqual({
|
expect(draw.attributes.style).toEqual({
|
||||||
color: "#0c1722",
|
color: "#0c1722",
|
||||||
fontFamily: "Arial",
|
fontFamily: "FooBar",
|
||||||
fontSize: "7px",
|
fontSize: "6.93px",
|
||||||
height: "22px",
|
height: "22px",
|
||||||
left: "2px",
|
|
||||||
padding: "1px 4px 2px 3px",
|
padding: "1px 4px 2px 3px",
|
||||||
position: "absolute",
|
|
||||||
textAlign: "left",
|
|
||||||
top: "1px",
|
|
||||||
transform: "rotate(-90deg)",
|
transform: "rotate(-90deg)",
|
||||||
transformOrigin: "top left",
|
transformOrigin: "top left",
|
||||||
verticalAlign: "2px",
|
verticalAlign: "2px",
|
||||||
@ -93,7 +99,7 @@ describe("XFAFactory", function () {
|
|||||||
|
|
||||||
// draw element must be on each page.
|
// draw element must be on each page.
|
||||||
expect(draw.attributes.style).toEqual(
|
expect(draw.attributes.style).toEqual(
|
||||||
factory.getPage(1).children[1].attributes.style
|
factory.getPage(1).children[1].children[0].attributes.style
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
.xfaLayer * {
|
.xfaLayer * {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
font-style: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
font-kerning: inherit;
|
font-kerning: inherit;
|
||||||
letter-spacing: inherit;
|
letter-spacing: inherit;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
@ -33,6 +35,14 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xfaLayer a {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaRich li {
|
||||||
|
margin-left: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
.xfaFont {
|
.xfaFont {
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -58,6 +68,7 @@
|
|||||||
|
|
||||||
.xfaRich {
|
.xfaRich {
|
||||||
z-index: 300;
|
z-index: 300;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xfaSubform {
|
.xfaSubform {
|
||||||
@ -76,23 +87,52 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xfaBorderDiv {
|
||||||
|
background: transparent;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaWrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaContentArea {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaTextfield,
|
||||||
|
.xfaSelect {
|
||||||
|
background-color: rgba(0, 54, 255, 0.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaTextfield:focus,
|
||||||
|
.xfaSelect:focus {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.xfaTextfield,
|
.xfaTextfield,
|
||||||
.xfaSelect {
|
.xfaSelect {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 1 1 auto;
|
flex: 100 1 0;
|
||||||
border: none;
|
border: none;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xfaLabel > input[type="checkbox"] {
|
.xfaLabel > input[type="radio"] {
|
||||||
/* Use this trick to make the checkbox invisible but
|
/* Use this trick to make the checkbox invisible but
|
||||||
but still focusable. */
|
but still focusable. */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -99999px;
|
left: -99999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xfaLabel > input[type="checkbox"]:focus + .xfaCheckboxMark {
|
.xfaLabel > input[type="radio"]:focus + .xfaCheckboxMark {
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,19 +173,48 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xfaImage,
|
.xfaImage {
|
||||||
.xfaRich {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xfaLrTb,
|
.xfaRich {
|
||||||
.xfaRlTb,
|
width: 100%;
|
||||||
.xfaTb,
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.xfaPosition {
|
.xfaPosition {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xfaLrTb,
|
||||||
|
.xfaRlTb,
|
||||||
|
.xfaTb {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaLr,
|
||||||
|
.xfaRl,
|
||||||
|
.xfaTb > div {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaTb > div {
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaLr > div {
|
||||||
|
display: inline;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaRl > div {
|
||||||
|
display: inline;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.xfaPosition {
|
.xfaPosition {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user