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,
|
||||
$getAttributeIt,
|
||||
$getChildren,
|
||||
$getDataValue,
|
||||
$getParent,
|
||||
$getRealChildrenByNameIt,
|
||||
$global,
|
||||
@ -88,7 +89,7 @@ class Binder {
|
||||
|
||||
if (formNode[$hasSettableValue]()) {
|
||||
if (data[$isDataValue]()) {
|
||||
const value = data[$content].trim();
|
||||
const value = data[$getDataValue]();
|
||||
// TODO: use picture.
|
||||
formNode[$setValue](createText(value));
|
||||
formNode[$data] = data;
|
||||
@ -114,7 +115,7 @@ class Binder {
|
||||
}
|
||||
}
|
||||
|
||||
_findDataByNameToConsume(name, dataNode, global) {
|
||||
_findDataByNameToConsume(name, isValue, dataNode, global) {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
@ -130,9 +131,16 @@ class Binder {
|
||||
/* allTransparent = */ false,
|
||||
/* skipConsumed = */ true
|
||||
);
|
||||
match = generator.next().value;
|
||||
if (match) {
|
||||
return match;
|
||||
// Try to find a match of the same kind.
|
||||
while (true) {
|
||||
match = generator.next().value;
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (isValue === match[$isDataValue]()) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
if (
|
||||
dataNode[$namespaceId] === NamespaceIds.datasets.id &&
|
||||
@ -149,7 +157,7 @@ class Binder {
|
||||
|
||||
// Secondly, if global try to find it just under the root of datasets
|
||||
// (which is the location of global variables).
|
||||
generator = this.datasets[$getRealChildrenByNameIt](
|
||||
generator = this.data[$getRealChildrenByNameIt](
|
||||
name,
|
||||
/* allTransparent = */ false,
|
||||
/* skipConsumed = */ false
|
||||
@ -478,6 +486,7 @@ class Binder {
|
||||
if (child.bind) {
|
||||
switch (child.bind.match) {
|
||||
case "none":
|
||||
this._bindElement(child, dataNode);
|
||||
continue;
|
||||
case "global":
|
||||
global = true;
|
||||
@ -485,6 +494,7 @@ class Binder {
|
||||
case "dataRef":
|
||||
if (!child.bind.ref) {
|
||||
warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
|
||||
this._bindElement(child, dataNode);
|
||||
continue;
|
||||
}
|
||||
ref = child.bind.ref;
|
||||
@ -545,6 +555,7 @@ class Binder {
|
||||
while (matches.length < max) {
|
||||
const found = this._findDataByNameToConsume(
|
||||
child.name,
|
||||
child[$hasSettableValue](),
|
||||
dataNode,
|
||||
global
|
||||
);
|
||||
@ -580,6 +591,8 @@ class Binder {
|
||||
}
|
||||
this._bindOccurrences(child, match, picture);
|
||||
} else if (min > 0) {
|
||||
this._setProperties(child, dataNode);
|
||||
this._bindItems(child, dataNode);
|
||||
this._bindElement(child, dataNode);
|
||||
} else {
|
||||
uselessNodes.push(child);
|
||||
|
@ -17,6 +17,7 @@ import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||
import {
|
||||
$cleanup,
|
||||
$finalize,
|
||||
$ids,
|
||||
$nsAttributes,
|
||||
$onChild,
|
||||
$resolvePrototypes,
|
||||
@ -27,13 +28,11 @@ import { Template } from "./template.js";
|
||||
import { UnknownNamespace } from "./unknown.js";
|
||||
import { warn } from "../../shared/util.js";
|
||||
|
||||
const _ids = Symbol();
|
||||
|
||||
class Root extends XFAObject {
|
||||
constructor(ids) {
|
||||
super(-1, "root", Object.create(null));
|
||||
this.element = null;
|
||||
this[_ids] = ids;
|
||||
this[$ids] = ids;
|
||||
}
|
||||
|
||||
[$onChild](child) {
|
||||
@ -44,7 +43,8 @@ class Root extends XFAObject {
|
||||
[$finalize]() {
|
||||
super[$finalize]();
|
||||
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.
|
||||
*/
|
||||
|
||||
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";
|
||||
|
||||
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) {
|
||||
if (typeof m === "string") {
|
||||
return "0px";
|
||||
}
|
||||
|
||||
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
|
||||
}
|
||||
|
||||
const converters = {
|
||||
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)) {
|
||||
style.transform = "";
|
||||
}
|
||||
@ -57,12 +77,28 @@ const converters = {
|
||||
},
|
||||
dimensions(node, style) {
|
||||
const parent = node[$getParent]();
|
||||
const extra = parent[$extra];
|
||||
let width = node.w;
|
||||
if (extra && extra.columnWidths) {
|
||||
width = extra.columnWidths[extra.currentColumn];
|
||||
extra.currentColumn =
|
||||
(extra.currentColumn + 1) % extra.columnWidths.length;
|
||||
const height = node.h;
|
||||
if (parent.layout && parent.layout.includes("row")) {
|
||||
const extra = parent[$extra];
|
||||
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 !== "") {
|
||||
@ -72,17 +108,21 @@ const converters = {
|
||||
if (node.maxW > 0) {
|
||||
style.maxWidth = measureToString(node.maxW);
|
||||
}
|
||||
style.minWidth = measureToString(node.minW);
|
||||
if (parent.layout === "position") {
|
||||
style.minWidth = measureToString(node.minW);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.h !== "") {
|
||||
style.height = measureToString(node.h);
|
||||
if (height !== "") {
|
||||
style.height = measureToString(height);
|
||||
} else {
|
||||
style.height = "auto";
|
||||
if (node.maxH > 0) {
|
||||
style.maxHeight = measureToString(node.maxH);
|
||||
}
|
||||
style.minHeight = measureToString(node.minH);
|
||||
if (parent.layout === "position") {
|
||||
style.minHeight = measureToString(node.minH);
|
||||
}
|
||||
}
|
||||
},
|
||||
position(node, style) {
|
||||
@ -118,22 +158,31 @@ const converters = {
|
||||
}
|
||||
},
|
||||
hAlign(node, style) {
|
||||
switch (node.hAlign) {
|
||||
case "justifyAll":
|
||||
style.textAlign = "justify-all";
|
||||
break;
|
||||
case "radix":
|
||||
// TODO: implement this correctly !
|
||||
style.textAlign = "left";
|
||||
break;
|
||||
default:
|
||||
style.textAlign = node.hAlign;
|
||||
if (node[$nodeName] === "para") {
|
||||
switch (node.hAlign) {
|
||||
case "justifyAll":
|
||||
style.textAlign = "justify-all";
|
||||
break;
|
||||
case "radix":
|
||||
// TODO: implement this correctly !
|
||||
style.textAlign = "left";
|
||||
break;
|
||||
default:
|
||||
style.textAlign = node.hAlign;
|
||||
}
|
||||
} else {
|
||||
switch (node.hAlign) {
|
||||
case "right":
|
||||
case "center":
|
||||
style.justifyContent = node.hAlign;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
borderMarginPadding(node, style) {
|
||||
// Get border width in order to compute margin and padding.
|
||||
const borderWidths = [0, 0, 0, 0];
|
||||
const marginWidths = [0, 0, 0, 0];
|
||||
const borderInsets = [0, 0, 0, 0];
|
||||
const marginNode = node.margin
|
||||
? [
|
||||
node.margin.topInset,
|
||||
@ -142,30 +191,211 @@ const converters = {
|
||||
node.margin.leftInset,
|
||||
]
|
||||
: [0, 0, 0, 0];
|
||||
|
||||
let borderMargin;
|
||||
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)) {
|
||||
// No border: margin & padding are padding
|
||||
if (node.margin) {
|
||||
Object.assign(style, node.margin[$toStyle]());
|
||||
if (marginNode.every(x => x === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No border: margin & padding are padding
|
||||
Object.assign(style, node.margin[$toStyle]());
|
||||
style.padding = style.margin;
|
||||
delete style.margin;
|
||||
} else {
|
||||
style.padding =
|
||||
measureToString(marginNode[0] - borderWidths[0] - marginWidths[0]) +
|
||||
" " +
|
||||
measureToString(marginNode[1] - borderWidths[1] - marginWidths[1]) +
|
||||
" " +
|
||||
measureToString(marginNode[2] - borderWidths[2] - marginWidths[2]) +
|
||||
" " +
|
||||
measureToString(marginNode[3] - borderWidths[3] - marginWidths[3]);
|
||||
delete style.outline;
|
||||
delete style.outlineOffset;
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (node.layout) {
|
||||
case "position":
|
||||
@ -211,26 +441,145 @@ function toStyle(node, ...names) {
|
||||
return style;
|
||||
}
|
||||
|
||||
function addExtraDivForMargin(html) {
|
||||
function addExtraDivForBorder(html) {
|
||||
const style = html.attributes.style;
|
||||
if (style.margin) {
|
||||
const padding = style.margin;
|
||||
delete style.margin;
|
||||
const width = style.width || "auto";
|
||||
const height = style.height || "auto";
|
||||
const data = style.borderData;
|
||||
const children = [];
|
||||
|
||||
style.width = "100%";
|
||||
style.height = "100%";
|
||||
const attributes = {
|
||||
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",
|
||||
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._errorCode = XMLParserErrorCode.NoError;
|
||||
this._whiteRegex = /^\s+$/;
|
||||
this._nbsps = /\xa0+/g;
|
||||
}
|
||||
|
||||
parse(data) {
|
||||
@ -50,6 +51,9 @@ class XFAParser extends XMLParserBase {
|
||||
}
|
||||
|
||||
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]()) {
|
||||
this._current[$onText](text);
|
||||
return;
|
||||
|
@ -275,19 +275,32 @@ function createDataNode(root, container, expr) {
|
||||
}
|
||||
|
||||
for (let ii = parsed.length; i < ii; i++) {
|
||||
const { cacheName, index } = parsed[i];
|
||||
const { name, operator, index } = parsed[i];
|
||||
if (!isFinite(index)) {
|
||||
parsed[i].index = 0;
|
||||
return createNodes(root, parsed.slice(i));
|
||||
}
|
||||
|
||||
const cached = somCache.get(root);
|
||||
if (!cached) {
|
||||
warn(`XFA - createDataNode must be called after searchNode.`);
|
||||
return null;
|
||||
let children;
|
||||
switch (operator) {
|
||||
case operators.dot:
|
||||
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) {
|
||||
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,
|
||||
mm: x => (x / (10 * 2.54)) * 72,
|
||||
in: x => x * 72,
|
||||
px: x => x,
|
||||
};
|
||||
const measurementPattern = /([+-]?[0-9]+\.?[0-9]*)(.*)/;
|
||||
|
||||
@ -163,6 +164,21 @@ function getBBox(data) {
|
||||
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 {
|
||||
getBBox,
|
||||
getColor,
|
||||
@ -173,4 +189,5 @@ export {
|
||||
getRatio,
|
||||
getRelevant,
|
||||
getStringOption,
|
||||
HTMLResult,
|
||||
};
|
||||
|
@ -13,14 +13,16 @@
|
||||
* 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 { NamespaceIds } from "./namespaces.js";
|
||||
|
||||
// We use these symbols to avoid name conflict between tags
|
||||
// and properties/methods names.
|
||||
const $acceptWhitespace = Symbol();
|
||||
const $addHTML = Symbol();
|
||||
const $appendChild = Symbol();
|
||||
const $break = Symbol();
|
||||
const $childrenToHTML = Symbol();
|
||||
const $clean = Symbol();
|
||||
const $cleanup = Symbol();
|
||||
@ -31,16 +33,21 @@ const $data = Symbol("data");
|
||||
const $dump = Symbol();
|
||||
const $extra = Symbol("extra");
|
||||
const $finalize = Symbol();
|
||||
const $flushHTML = Symbol();
|
||||
const $getAttributeIt = Symbol();
|
||||
const $getAvailableSpace = Symbol();
|
||||
const $getChildrenByClass = Symbol();
|
||||
const $getChildrenByName = Symbol();
|
||||
const $getChildrenByNameIt = Symbol();
|
||||
const $getDataValue = Symbol();
|
||||
const $getRealChildrenByNameIt = Symbol();
|
||||
const $getChildren = Symbol();
|
||||
const $getNextPage = Symbol();
|
||||
const $getParent = Symbol();
|
||||
const $global = Symbol();
|
||||
const $hasItem = Symbol();
|
||||
const $hasSettableValue = Symbol();
|
||||
const $ids = Symbol();
|
||||
const $indexOf = Symbol();
|
||||
const $insertAt = Symbol();
|
||||
const $isDataValue = Symbol();
|
||||
@ -55,6 +62,7 @@ const $onChildCheck = Symbol();
|
||||
const $onText = Symbol();
|
||||
const $removeChild = Symbol();
|
||||
const $resolvePrototypes = Symbol();
|
||||
const $searchNode = Symbol();
|
||||
const $setId = Symbol();
|
||||
const $setSetAttributes = Symbol();
|
||||
const $setValue = Symbol();
|
||||
@ -70,6 +78,7 @@ const _children = Symbol("_children");
|
||||
const _cloneAttribute = Symbol();
|
||||
const _dataValue = Symbol();
|
||||
const _defaultValue = Symbol();
|
||||
const _filteredChildrenGenerator = Symbol();
|
||||
const _getPrototype = Symbol();
|
||||
const _getUnsetAttributes = Symbol();
|
||||
const _hasChildren = Symbol();
|
||||
@ -270,20 +279,67 @@ class XFAObject {
|
||||
}
|
||||
|
||||
[$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;
|
||||
}
|
||||
|
||||
[$addHTML](html, bbox) {
|
||||
this[$extra].children.push(html);
|
||||
}
|
||||
|
||||
[$getAvailableSpace]() {}
|
||||
|
||||
[$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);
|
||||
}
|
||||
if (!this[$extra].generator) {
|
||||
this[$extra].generator = this[_filteredChildrenGenerator](
|
||||
filter,
|
||||
include
|
||||
);
|
||||
} else {
|
||||
const availableSpace = this[$getAvailableSpace]();
|
||||
const res = this[$extra].failingNode[$toHTML](availableSpace);
|
||||
if (!res.success) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
if (res.html) {
|
||||
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) {
|
||||
@ -640,13 +696,13 @@ class XmlObject extends XFAObject {
|
||||
|
||||
[$toHTML]() {
|
||||
if (this[$nodeName] === "#text") {
|
||||
return {
|
||||
return HTMLResult.success({
|
||||
name: "#text",
|
||||
value: this[$content],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
return HTMLResult.EMPTY;
|
||||
}
|
||||
|
||||
[$getChildren](name = null) {
|
||||
@ -710,11 +766,27 @@ class XmlObject extends XFAObject {
|
||||
|
||||
[$isDataValue]() {
|
||||
if (this[_dataValue] === null) {
|
||||
return this[_children].length === 0;
|
||||
return (
|
||||
this[_children].length === 0 ||
|
||||
this[_children][0][$namespaceId] === NamespaceIds.xhtml.id
|
||||
);
|
||||
}
|
||||
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]() {
|
||||
const dumped = Object.create(null);
|
||||
if (this[$content]) {
|
||||
@ -811,7 +883,9 @@ class Option10 extends IntegerObject {
|
||||
|
||||
export {
|
||||
$acceptWhitespace,
|
||||
$addHTML,
|
||||
$appendChild,
|
||||
$break,
|
||||
$childrenToHTML,
|
||||
$clean,
|
||||
$cleanup,
|
||||
@ -822,16 +896,21 @@ export {
|
||||
$dump,
|
||||
$extra,
|
||||
$finalize,
|
||||
$flushHTML,
|
||||
$getAttributeIt,
|
||||
$getAvailableSpace,
|
||||
$getChildren,
|
||||
$getChildrenByClass,
|
||||
$getChildrenByName,
|
||||
$getChildrenByNameIt,
|
||||
$getDataValue,
|
||||
$getNextPage,
|
||||
$getParent,
|
||||
$getRealChildrenByNameIt,
|
||||
$global,
|
||||
$hasItem,
|
||||
$hasSettableValue,
|
||||
$ids,
|
||||
$indexOf,
|
||||
$insertAt,
|
||||
$isDataValue,
|
||||
@ -845,6 +924,7 @@ export {
|
||||
$onText,
|
||||
$removeChild,
|
||||
$resolvePrototypes,
|
||||
$searchNode,
|
||||
$setId,
|
||||
$setSetAttributes,
|
||||
$setValue,
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
$acceptWhitespace,
|
||||
$childrenToHTML,
|
||||
$content,
|
||||
$extra,
|
||||
$nodeName,
|
||||
$onText,
|
||||
$text,
|
||||
@ -24,8 +25,8 @@ import {
|
||||
XmlObject,
|
||||
} from "./xfa_object.js";
|
||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||
import { getMeasurement } from "./utils.js";
|
||||
import { measureToString } from "./html_utils.js";
|
||||
import { fixTextIndent, getFonts, measureToString } from "./html_utils.js";
|
||||
import { getMeasurement, HTMLResult } from "./utils.js";
|
||||
|
||||
const XHTML_NS_ID = NamespaceIds.xhtml.id;
|
||||
|
||||
@ -79,14 +80,16 @@ const StyleMapping = new Map([
|
||||
],
|
||||
["xfa-spacerun", ""],
|
||||
["xfa-tab-stops", ""],
|
||||
["font-size", value => measureToString(getMeasurement(value))],
|
||||
["font-size", value => measureToString(1 * 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-bottom", value => measureToString(getMeasurement(value))],
|
||||
["margin-left", value => measureToString(getMeasurement(value))],
|
||||
["margin-right", 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;
|
||||
@ -121,6 +124,8 @@ function mapStyle(styleStr) {
|
||||
newValue;
|
||||
}
|
||||
}
|
||||
|
||||
fixTextIndent(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
@ -162,16 +167,27 @@ class XhtmlObject extends XmlObject {
|
||||
}
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return {
|
||||
[$toHTML](availableSpace) {
|
||||
const children = [];
|
||||
this[$extra] = {
|
||||
children,
|
||||
};
|
||||
|
||||
this[$childrenToHTML]({});
|
||||
|
||||
if (children.length === 0 && !this[$content]) {
|
||||
return HTMLResult.EMPTY;
|
||||
}
|
||||
|
||||
return HTMLResult.success({
|
||||
name: this[$nodeName],
|
||||
attributes: {
|
||||
href: this.href,
|
||||
style: mapStyle(this.style),
|
||||
},
|
||||
children: this[$childrenToHTML]({}),
|
||||
children,
|
||||
value: this[$content] || "",
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,10 +209,15 @@ class Body extends XhtmlObject {
|
||||
super(attributes, "body");
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
const html = super[$toHTML]();
|
||||
[$toHTML](availableSpace) {
|
||||
const res = super[$toHTML](availableSpace);
|
||||
const { html } = res;
|
||||
if (!html) {
|
||||
return HTMLResult.EMPTY;
|
||||
}
|
||||
html.name = "div";
|
||||
html.attributes.class = "xfaRich";
|
||||
return html;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,10 +230,10 @@ class Br extends XhtmlObject {
|
||||
return "\n";
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return {
|
||||
[$toHTML](availableSpace) {
|
||||
return HTMLResult.success({
|
||||
name: "br",
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,34 +242,39 @@ class Html extends XhtmlObject {
|
||||
super(attributes, "html");
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
const children = this[$childrenToHTML]({});
|
||||
[$toHTML](availableSpace) {
|
||||
const children = [];
|
||||
this[$extra] = {
|
||||
children,
|
||||
};
|
||||
|
||||
this[$childrenToHTML]({});
|
||||
if (children.length === 0) {
|
||||
return {
|
||||
return HTMLResult.success({
|
||||
name: "div",
|
||||
attributes: {
|
||||
class: "xfaRich",
|
||||
style: {},
|
||||
},
|
||||
value: this[$content] || "",
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (children.length === 1) {
|
||||
const child = children[0];
|
||||
if (child.attributes && child.attributes.class === "xfaRich") {
|
||||
return child;
|
||||
return HTMLResult.success(child);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
return HTMLResult.success({
|
||||
name: "div",
|
||||
attributes: {
|
||||
class: "xfaRich",
|
||||
style: {},
|
||||
},
|
||||
children,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +300,10 @@ class P extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(attributes, "p");
|
||||
}
|
||||
|
||||
[$text]() {
|
||||
return super[$text]() + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
class Span extends XhtmlObject {
|
||||
|
@ -21,7 +21,11 @@ class XfaLayer {
|
||||
}
|
||||
|
||||
if (key !== "style") {
|
||||
html.setAttribute(key, value);
|
||||
if (key === "textContent") {
|
||||
html.textContent = value;
|
||||
} else {
|
||||
html.setAttribute(key, value);
|
||||
}
|
||||
} else {
|
||||
Object.assign(html.style, value);
|
||||
}
|
||||
|
@ -332,7 +332,7 @@ describe("XFAParser", function () {
|
||||
[
|
||||
" The first line of this paragraph is indented a half-inch.\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("")
|
||||
);
|
||||
});
|
||||
|
@ -28,7 +28,7 @@ describe("XFAFactory", function () {
|
||||
<contentArea x="123pt" w="456pt" h="789pt"/>
|
||||
<medium stock="default" short="456pt" long="789pt"/>
|
||||
<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>
|
||||
<color value="12,23,34"/>
|
||||
<solid/>
|
||||
@ -43,6 +43,7 @@ describe("XFAFactory", function () {
|
||||
<subform name="first">
|
||||
</subform>
|
||||
<subform name="second">
|
||||
<breakBefore targetType="pageArea" startNew="1"/>
|
||||
</subform>
|
||||
</subform>
|
||||
</template>
|
||||
@ -73,18 +74,23 @@ describe("XFAFactory", function () {
|
||||
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.style).toEqual({
|
||||
color: "#0c1722",
|
||||
fontFamily: "Arial",
|
||||
fontSize: "7px",
|
||||
fontFamily: "FooBar",
|
||||
fontSize: "6.93px",
|
||||
height: "22px",
|
||||
left: "2px",
|
||||
padding: "1px 4px 2px 3px",
|
||||
position: "absolute",
|
||||
textAlign: "left",
|
||||
top: "1px",
|
||||
transform: "rotate(-90deg)",
|
||||
transformOrigin: "top left",
|
||||
verticalAlign: "2px",
|
||||
@ -93,7 +99,7 @@ describe("XFAFactory", function () {
|
||||
|
||||
// draw element must be on each page.
|
||||
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 * {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
font-kerning: inherit;
|
||||
letter-spacing: inherit;
|
||||
text-align: inherit;
|
||||
@ -33,6 +35,14 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.xfaLayer a {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.xfaRich li {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
.xfaFont {
|
||||
color: black;
|
||||
font-weight: normal;
|
||||
@ -58,6 +68,7 @@
|
||||
|
||||
.xfaRich {
|
||||
z-index: 300;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.xfaSubform {
|
||||
@ -76,23 +87,52 @@
|
||||
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,
|
||||
.xfaSelect {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1 1 auto;
|
||||
flex: 100 1 0;
|
||||
border: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.xfaLabel > input[type="checkbox"] {
|
||||
.xfaLabel > input[type="radio"] {
|
||||
/* Use this trick to make the checkbox invisible but
|
||||
but still focusable. */
|
||||
position: absolute;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -133,19 +173,48 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.xfaImage,
|
||||
.xfaRich {
|
||||
.xfaImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.xfaLrTb,
|
||||
.xfaRlTb,
|
||||
.xfaTb,
|
||||
.xfaRich {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.xfaPosition {
|
||||
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 {
|
||||
position: relative;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user