Merge pull request #13141 from calixteman/xfa_text
XFA -- Display text content
This commit is contained in:
commit
a53cd1cc1e
@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { $getParent, $toStyle, XFAObject } from "./xfa_object.js";
|
||||
import { $extra, $getParent, $toStyle, XFAObject } from "./xfa_object.js";
|
||||
import { warn } from "../../shared/util.js";
|
||||
|
||||
function measureToString(m) {
|
||||
@ -56,8 +56,17 @@ const converters = {
|
||||
}
|
||||
},
|
||||
dimensions(node, style) {
|
||||
if (node.w) {
|
||||
style.width = measureToString(node.w);
|
||||
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;
|
||||
}
|
||||
|
||||
if (width !== "") {
|
||||
style.width = measureToString(width);
|
||||
} else {
|
||||
style.width = "auto";
|
||||
if (node.maxW > 0) {
|
||||
@ -66,7 +75,7 @@ const converters = {
|
||||
style.minWidth = measureToString(node.minW);
|
||||
}
|
||||
|
||||
if (node.h) {
|
||||
if (node.h !== "") {
|
||||
style.height = measureToString(node.h);
|
||||
} else {
|
||||
style.height = "auto";
|
||||
@ -108,6 +117,53 @@ const converters = {
|
||||
break;
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
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 marginNode = node.margin
|
||||
? [
|
||||
node.margin.topInset,
|
||||
node.margin.rightInset,
|
||||
node.margin.bottomInset,
|
||||
node.margin.leftInset,
|
||||
]
|
||||
: [0, 0, 0, 0];
|
||||
if (node.border) {
|
||||
Object.assign(style, node.border[$toStyle](borderWidths, marginWidths));
|
||||
}
|
||||
|
||||
if (borderWidths.every(x => x === 0)) {
|
||||
// No border: margin & padding are padding
|
||||
if (node.margin) {
|
||||
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]);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function layoutClass(node) {
|
||||
@ -155,4 +211,26 @@ function toStyle(node, ...names) {
|
||||
return style;
|
||||
}
|
||||
|
||||
export { layoutClass, measureToString, toStyle };
|
||||
function addExtraDivForMargin(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";
|
||||
|
||||
style.width = "100%";
|
||||
style.height = "100%";
|
||||
|
||||
return {
|
||||
name: "div",
|
||||
attributes: {
|
||||
style: { padding, width, height },
|
||||
},
|
||||
children: [html],
|
||||
};
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
export { addExtraDivForMargin, layoutClass, measureToString, toStyle };
|
||||
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
$acceptWhitespace,
|
||||
$clean,
|
||||
$finalize,
|
||||
$nsAttributes,
|
||||
@ -49,6 +50,11 @@ class XFAParser extends XMLParserBase {
|
||||
}
|
||||
|
||||
onText(text) {
|
||||
if (this._current[$acceptWhitespace]()) {
|
||||
this._current[$onText](text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._whiteRegex.test(text)) {
|
||||
return;
|
||||
}
|
||||
|
@ -40,6 +40,12 @@ import {
|
||||
XFAObjectArray,
|
||||
} from "./xfa_object.js";
|
||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||
import {
|
||||
addExtraDivForMargin,
|
||||
layoutClass,
|
||||
measureToString,
|
||||
toStyle,
|
||||
} from "./html_utils.js";
|
||||
import {
|
||||
getBBox,
|
||||
getColor,
|
||||
@ -51,7 +57,6 @@ import {
|
||||
getRelevant,
|
||||
getStringOption,
|
||||
} from "./utils.js";
|
||||
import { layoutClass, measureToString, toStyle } from "./html_utils.js";
|
||||
import { Util, warn } from "../../shared/util.js";
|
||||
|
||||
const TEMPLATE_NS_ID = NamespaceIds.template.id;
|
||||
@ -131,6 +136,36 @@ class Area extends XFAObject {
|
||||
[$isTransparent]() {
|
||||
return true;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
// TODO: incomplete.
|
||||
this[$extra] = Object.create(null);
|
||||
|
||||
const style = toStyle(this, "position");
|
||||
const attributes = {
|
||||
style,
|
||||
id: this[$uid],
|
||||
class: "xfaArea",
|
||||
};
|
||||
|
||||
if (this.name) {
|
||||
attributes.xfaName = this.name;
|
||||
}
|
||||
|
||||
const children = this[$childrenToHTML]({
|
||||
// TODO: exObject & exclGroup
|
||||
filter: new Set(["area", "draw", "field", "subform", "subformSet"]),
|
||||
include: true,
|
||||
});
|
||||
|
||||
const html = {
|
||||
name: "div",
|
||||
attributes,
|
||||
children,
|
||||
};
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
class Assist extends XFAObject {
|
||||
@ -376,56 +411,42 @@ class Border extends XFAObject {
|
||||
|
||||
[$toStyle](widths, margins) {
|
||||
// TODO: incomplete.
|
||||
const edgeStyles = this.edge.children.map(node => node[$toStyle]());
|
||||
const cornerStyles = this.edge.children.map(node => node[$toStyle]());
|
||||
const edges = this.edge.children.slice();
|
||||
if (edges.length < 4) {
|
||||
const defaultEdge = edges[edges.length - 1] || new Edge({});
|
||||
for (let i = edges.length; i < 4; i++) {
|
||||
edges.push(defaultEdge);
|
||||
}
|
||||
}
|
||||
|
||||
if (widths) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
widths[i] = edges[i].thickness;
|
||||
}
|
||||
}
|
||||
|
||||
const edgeStyles = edges.map(node => node[$toStyle]());
|
||||
const cornerStyles = this.corner.children.map(node => node[$toStyle]());
|
||||
let style;
|
||||
if (this.margin) {
|
||||
style = this.margin[$toStyle]();
|
||||
if (margins) {
|
||||
margins.push(
|
||||
this.margin.topInset,
|
||||
this.margin.rightInset,
|
||||
this.margin.bottomInset,
|
||||
this.margin.leftInset
|
||||
);
|
||||
margins[0] = this.margin.topInset;
|
||||
margins[1] = this.margin.rightInset;
|
||||
margins[2] = this.margin.bottomInset;
|
||||
margins[3] = this.margin.leftInset;
|
||||
}
|
||||
} else {
|
||||
style = Object.create(null);
|
||||
if (margins) {
|
||||
margins.push(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fill) {
|
||||
Object.assign(style, this.fill[$toStyle]());
|
||||
}
|
||||
|
||||
if (edgeStyles.length > 0) {
|
||||
if (widths) {
|
||||
this.edge.children.forEach(node => widths.push(node.thickness));
|
||||
if (widths.length < 4) {
|
||||
const last = widths[widths.length - 1];
|
||||
for (let i = widths.length; i < 4; i++) {
|
||||
widths.push(last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edgeStyles.length === 2 || edgeStyles.length === 3) {
|
||||
const last = edgeStyles[edgeStyles.length - 1];
|
||||
for (let i = edgeStyles.length; i < 4; i++) {
|
||||
edgeStyles.push(last);
|
||||
}
|
||||
}
|
||||
|
||||
style.borderWidth = edgeStyles.map(s => s.width).join(" ");
|
||||
style.borderColor = edgeStyles.map(s => s.color).join(" ");
|
||||
style.borderStyle = edgeStyles.map(s => s.style).join(" ");
|
||||
} else {
|
||||
if (widths) {
|
||||
widths.push(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
style.borderWidth = edgeStyles.map(s => s.width).join(" ");
|
||||
style.borderColor = edgeStyles.map(s => s.color).join(" ");
|
||||
style.borderStyle = edgeStyles.map(s => s.style).join(" ");
|
||||
|
||||
if (cornerStyles.length > 0) {
|
||||
if (cornerStyles.length === 2 || cornerStyles.length === 3) {
|
||||
@ -718,7 +739,7 @@ class CheckButton extends XFAObject {
|
||||
|
||||
let mark, radius;
|
||||
if (this.shape === "square") {
|
||||
mark = "■";
|
||||
mark = "▪";
|
||||
radius = "10%";
|
||||
} else {
|
||||
mark = "●";
|
||||
@ -746,7 +767,7 @@ class CheckButton extends XFAObject {
|
||||
mark = "♦";
|
||||
break;
|
||||
case "square":
|
||||
mark = "■";
|
||||
mark = "▪";
|
||||
break;
|
||||
case "star":
|
||||
mark = "★";
|
||||
@ -822,7 +843,7 @@ class ChoiceList extends XFAObject {
|
||||
{
|
||||
name: "select",
|
||||
attributes: {
|
||||
class: "xfaSxelect",
|
||||
class: "xfaSelect",
|
||||
multiple: this.open === "multiSelect",
|
||||
style,
|
||||
},
|
||||
@ -1211,11 +1232,13 @@ class Draw extends XFAObject {
|
||||
const style = toStyle(
|
||||
this,
|
||||
"font",
|
||||
"hAlign",
|
||||
"dimensions",
|
||||
"position",
|
||||
"presence",
|
||||
"rotate",
|
||||
"anchorType"
|
||||
"anchorType",
|
||||
"borderMarginPadding"
|
||||
);
|
||||
|
||||
const clazz = ["xfaDraw"];
|
||||
@ -1233,11 +1256,35 @@ class Draw extends XFAObject {
|
||||
attributes.xfaName = this.name;
|
||||
}
|
||||
|
||||
return {
|
||||
let html = {
|
||||
name: "div",
|
||||
attributes,
|
||||
children: [],
|
||||
};
|
||||
|
||||
const value = this.value ? this.value[$toHTML]() : null;
|
||||
if (value === null) {
|
||||
return html;
|
||||
}
|
||||
|
||||
html.children.push(value);
|
||||
|
||||
if (this.para && value.attributes.class === "xfaRich") {
|
||||
const paraStyle = this.para[$toStyle]();
|
||||
if (!value.attributes.style) {
|
||||
value.attributes.style = paraStyle;
|
||||
} else {
|
||||
for (const [key, val] of Object.entries(paraStyle)) {
|
||||
if (!(key in value.attributes.style)) {
|
||||
value.attributes.style[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html = addExtraDivForMargin(html);
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1494,6 +1541,15 @@ class ExData extends ContentObject {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
if (this.contentType !== "text/html" || !this[$content]) {
|
||||
// TODO: fix other cases.
|
||||
return null;
|
||||
}
|
||||
|
||||
return this[$content][$toHTML]();
|
||||
}
|
||||
}
|
||||
|
||||
class ExObject extends XFAObject {
|
||||
@ -1793,36 +1849,10 @@ class Field extends XFAObject {
|
||||
"position",
|
||||
"rotate",
|
||||
"anchorType",
|
||||
"presence"
|
||||
"presence",
|
||||
"borderMarginPadding"
|
||||
);
|
||||
|
||||
// Get border width in order to compute margin and padding.
|
||||
const borderWidths = [];
|
||||
const marginWidths = [];
|
||||
if (this.border) {
|
||||
Object.assign(style, this.border[$toStyle](borderWidths, marginWidths));
|
||||
}
|
||||
|
||||
if (this.margin) {
|
||||
style.paddingTop = measureToString(
|
||||
this.margin.topInset - borderWidths[0] - marginWidths[0]
|
||||
);
|
||||
style.paddingRight = measureToString(
|
||||
this.margin.rightInset - borderWidths[1] - marginWidths[1]
|
||||
);
|
||||
style.paddingBottom = measureToString(
|
||||
this.margin.bottomInset - borderWidths[2] - marginWidths[2]
|
||||
);
|
||||
style.paddingLeft = measureToString(
|
||||
this.margin.leftInset - borderWidths[3] - marginWidths[3]
|
||||
);
|
||||
} else {
|
||||
style.paddingTop = measureToString(-borderWidths[0] - marginWidths[0]);
|
||||
style.paddingRight = measureToString(-borderWidths[1] - marginWidths[1]);
|
||||
style.paddingBottom = measureToString(-borderWidths[2] - marginWidths[2]);
|
||||
style.paddingLeft = measureToString(-borderWidths[3] - marginWidths[3]);
|
||||
}
|
||||
|
||||
const clazz = ["xfaField"];
|
||||
// If no font, font properties are inherited.
|
||||
if (this.font) {
|
||||
@ -1840,7 +1870,7 @@ class Field extends XFAObject {
|
||||
}
|
||||
|
||||
const children = [];
|
||||
const html = {
|
||||
let html = {
|
||||
name: "div",
|
||||
attributes,
|
||||
children,
|
||||
@ -1857,8 +1887,7 @@ class Field extends XFAObject {
|
||||
children.push(ui);
|
||||
|
||||
if (this.value && ui.name !== "button") {
|
||||
// TODO: should be ok with string but html ??
|
||||
ui.children[0].attributes.value = this.value[$toHTML]();
|
||||
ui.children[0].attributes.value = this.value[$toHTML]().value;
|
||||
}
|
||||
|
||||
const caption = this.caption ? this.caption[$toHTML]() : null;
|
||||
@ -1867,8 +1896,8 @@ class Field extends XFAObject {
|
||||
}
|
||||
|
||||
if (ui.name === "button") {
|
||||
ui.attributes.style.background = style.color;
|
||||
delete style.color;
|
||||
ui.attributes.style.background = style.background;
|
||||
delete style.background;
|
||||
if (caption.name === "div") {
|
||||
caption.name = "span";
|
||||
}
|
||||
@ -1896,6 +1925,8 @@ class Field extends XFAObject {
|
||||
break;
|
||||
}
|
||||
|
||||
html = addExtraDivForMargin(html);
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
@ -1938,9 +1969,13 @@ class Fill extends XFAObject {
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
color: this.color ? this.color[$toStyle]() : "#000000",
|
||||
};
|
||||
if (this.color) {
|
||||
return {
|
||||
background: this.color[$toStyle](),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2053,15 +2088,18 @@ class Font extends XFAObject {
|
||||
|
||||
[$toStyle]() {
|
||||
const style = toStyle(this, "fill");
|
||||
if (style.color) {
|
||||
if (!style.color.startsWith("#")) {
|
||||
const color = style.background;
|
||||
if (color) {
|
||||
if (color === "#000000") {
|
||||
delete style.background;
|
||||
} else if (!color.startsWith("#")) {
|
||||
// We've a gradient which is not possible for a font color
|
||||
// so use a workaround.
|
||||
style.backgroundClip = "text";
|
||||
style.background = style.color;
|
||||
style.color = "transparent";
|
||||
} else if (style.color === "#000000") {
|
||||
delete style.color;
|
||||
} else {
|
||||
style.color = color;
|
||||
delete style.background;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2213,6 +2251,7 @@ class Image extends StringObject {
|
||||
const html = {
|
||||
name: "img",
|
||||
attributes: {
|
||||
class: "xfaImage",
|
||||
style: {},
|
||||
},
|
||||
};
|
||||
@ -2441,10 +2480,14 @@ class Margin extends XFAObject {
|
||||
|
||||
[$toStyle]() {
|
||||
return {
|
||||
marginLeft: measureToString(this.leftInset),
|
||||
marginRight: measureToString(this.rightInset),
|
||||
marginTop: measureToString(this.topInset),
|
||||
marginBottom: measureToString(this.bottomInset),
|
||||
margin:
|
||||
measureToString(this.topInset) +
|
||||
" " +
|
||||
measureToString(this.rightInset) +
|
||||
" " +
|
||||
measureToString(this.bottomInset) +
|
||||
" " +
|
||||
measureToString(this.leftInset),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -2730,26 +2773,40 @@ class Para extends XFAObject {
|
||||
"right",
|
||||
]);
|
||||
this.id = attributes.id || "";
|
||||
this.lineHeight = getMeasurement(attributes.lineHeight, "0pt");
|
||||
this.marginLeft = getMeasurement(attributes.marginLeft, "0");
|
||||
this.marginRight = getMeasurement(attributes.marginRight, "0");
|
||||
this.lineHeight = attributes.lineHeight
|
||||
? getMeasurement(attributes.lineHeight, "0pt")
|
||||
: "";
|
||||
this.marginLeft = attributes.marginLeft
|
||||
? getMeasurement(attributes.marginLeft, "0pt")
|
||||
: "";
|
||||
this.marginRight = attributes.marginRight
|
||||
? getMeasurement(attributes.marginRight, "0pt")
|
||||
: "";
|
||||
this.orphans = getInteger({
|
||||
data: attributes.orphans,
|
||||
defaultValue: 0,
|
||||
validate: x => x >= 0,
|
||||
});
|
||||
this.preserve = attributes.preserve || "";
|
||||
this.radixOffset = getMeasurement(attributes.radixOffset, "0");
|
||||
this.spaceAbove = getMeasurement(attributes.spaceAbove, "0");
|
||||
this.spaceBelow = getMeasurement(attributes.spaceBelow, "0");
|
||||
this.radixOffset = attributes.radixOffset
|
||||
? getMeasurement(attributes.radixOffset, "0pt")
|
||||
: "";
|
||||
this.spaceAbove = attributes.spaceAbove
|
||||
? getMeasurement(attributes.spaceAbove, "0pt")
|
||||
: "";
|
||||
this.spaceBelow = attributes.spaceBelow
|
||||
? getMeasurement(attributes.spaceBelow, "0pt")
|
||||
: "";
|
||||
this.tabDefault = attributes.tabDefault
|
||||
? getMeasurement(this.tabDefault)
|
||||
: null;
|
||||
: "";
|
||||
this.tabStops = (attributes.tabStops || "")
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.map((x, i) => (i % 2 === 1 ? getMeasurement(x) : x));
|
||||
this.textIndent = getMeasurement(attributes.textIndent, "0");
|
||||
this.textIndent = attributes.textIndent
|
||||
? getMeasurement(attributes.textIndent, "0pt")
|
||||
: "";
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.vAlign = getStringOption(attributes.vAlign, [
|
||||
@ -2765,21 +2822,31 @@ class Para extends XFAObject {
|
||||
this.hyphenation = null;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
const style = {
|
||||
marginLeft: measureToString(this.marginLeft),
|
||||
marginRight: measureToString(this.marginRight),
|
||||
paddingTop: measureToString(this.spaceAbove),
|
||||
paddingBottom: measureToString(this.spaceBelow),
|
||||
textIndent: measureToString(this.textIndent),
|
||||
verticalAlign: this.vAlign,
|
||||
};
|
||||
[$toStyle]() {
|
||||
const style = toStyle(this, "hAlign");
|
||||
if (this.marginLeft !== "") {
|
||||
style.marginLeft = measureToString(this.marginLeft);
|
||||
}
|
||||
if (this.marginRight !== "") {
|
||||
style.marginRight = measureToString(this.marginRight);
|
||||
}
|
||||
if (this.spaceAbove !== "") {
|
||||
style.marginTop = measureToString(this.spaceAbove);
|
||||
}
|
||||
if (this.spaceBelow !== "") {
|
||||
style.marginBottom = measureToString(this.spaceBelow);
|
||||
}
|
||||
if (this.textIndent !== "") {
|
||||
style.textIndent = measureToString(this.textIndent);
|
||||
}
|
||||
|
||||
if (this.lineHeight.value >= 0) {
|
||||
// TODO: vAlign
|
||||
|
||||
if (this.lineHeight !== "") {
|
||||
style.lineHeight = measureToString(this.lineHeight);
|
||||
}
|
||||
|
||||
if (this.tabDefault) {
|
||||
if (this.tabDefault !== "") {
|
||||
style.tabSize = measureToString(this.tabDefault);
|
||||
}
|
||||
|
||||
@ -2788,7 +2855,7 @@ class Para extends XFAObject {
|
||||
}
|
||||
|
||||
if (this.hyphenatation) {
|
||||
Object.assign(style, this.hyphenatation[$toHTML]());
|
||||
Object.assign(style, this.hyphenatation[$toStyle]());
|
||||
}
|
||||
|
||||
return style;
|
||||
@ -3294,6 +3361,14 @@ class Subform extends XFAObject {
|
||||
// TODO: incomplete.
|
||||
this[$extra] = Object.create(null);
|
||||
|
||||
if (this.layout === "row") {
|
||||
const columnWidths = this[$getParent]().columnWidths;
|
||||
if (Array.isArray(columnWidths) && columnWidths.length > 0) {
|
||||
this[$extra].columnWidths = columnWidths;
|
||||
this[$extra].currentColumn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const parent = this[$getParent]();
|
||||
let page = null;
|
||||
if (parent[$nodeName] === "template") {
|
||||
@ -3520,8 +3595,67 @@ class Text extends ContentObject {
|
||||
|
||||
[$toHTML]() {
|
||||
if (typeof this[$content] === "string") {
|
||||
return this[$content];
|
||||
// \u2028 is a line separator.
|
||||
// \u2029 is a paragraph separator.
|
||||
const html = {
|
||||
name: "span",
|
||||
attributes: {
|
||||
class: "xfaRich",
|
||||
style: {},
|
||||
},
|
||||
value: this[$content],
|
||||
};
|
||||
|
||||
if (this[$content].includes("\u2029")) {
|
||||
// We've plain text containing a paragraph separator
|
||||
// so convert it into a set of <p>.
|
||||
html.name = "div";
|
||||
html.children = [];
|
||||
this[$content]
|
||||
.split("\u2029")
|
||||
.map(para =>
|
||||
// Convert a paragraph into a set of <span> (for lines)
|
||||
// separated by <br>.
|
||||
para.split(/[\u2028\n]/).reduce((acc, line) => {
|
||||
acc.push(
|
||||
{
|
||||
name: "span",
|
||||
value: line,
|
||||
},
|
||||
{
|
||||
name: "br",
|
||||
}
|
||||
);
|
||||
return acc;
|
||||
}, [])
|
||||
)
|
||||
.forEach(lines => {
|
||||
html.children.push({
|
||||
name: "p",
|
||||
children: lines,
|
||||
});
|
||||
});
|
||||
} else if (/[\u2028\n]/.test(this[$content])) {
|
||||
html.name = "div";
|
||||
html.children = [];
|
||||
// Convert plain text into a set of <span> (for lines)
|
||||
// separated by <br>.
|
||||
this[$content].split(/[\u2028\n]/).forEach(line => {
|
||||
html.children.push(
|
||||
{
|
||||
name: "span",
|
||||
value: line,
|
||||
},
|
||||
{
|
||||
name: "br",
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
return this[$content][$toHTML]();
|
||||
}
|
||||
}
|
||||
@ -3562,10 +3696,11 @@ class TextEdit extends XFAObject {
|
||||
// TODO: incomplete.
|
||||
const style = toStyle(this, "border", "font", "margin");
|
||||
let html;
|
||||
if (this.multiline === 1) {
|
||||
if (this.multiLine === 1) {
|
||||
html = {
|
||||
name: "textarea",
|
||||
attributes: {
|
||||
class: "xfaTextfield",
|
||||
style,
|
||||
},
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ import { NamespaceIds } from "./namespaces.js";
|
||||
|
||||
// We use these symbols to avoid name conflict between tags
|
||||
// and properties/methods names.
|
||||
const $acceptWhitespace = Symbol();
|
||||
const $appendChild = Symbol();
|
||||
const $childrenToHTML = Symbol();
|
||||
const $clean = Symbol();
|
||||
@ -131,6 +132,10 @@ class XFAObject {
|
||||
);
|
||||
}
|
||||
|
||||
[$acceptWhitespace]() {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$setId](ids) {
|
||||
if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
|
||||
ids.set(this.id, this);
|
||||
@ -805,6 +810,7 @@ class Option10 extends IntegerObject {
|
||||
}
|
||||
|
||||
export {
|
||||
$acceptWhitespace,
|
||||
$appendChild,
|
||||
$childrenToHTML,
|
||||
$clean,
|
||||
|
@ -13,8 +13,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
$acceptWhitespace,
|
||||
$childrenToHTML,
|
||||
$content,
|
||||
$nodeName,
|
||||
$onText,
|
||||
$text,
|
||||
$toHTML,
|
||||
XmlObject,
|
||||
} from "./xfa_object.js";
|
||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||
import { $text, XmlObject } from "./xfa_object.js";
|
||||
import { getMeasurement } from "./utils.js";
|
||||
import { measureToString } from "./html_utils.js";
|
||||
|
||||
const XHTML_NS_ID = NamespaceIds.xhtml.id;
|
||||
|
||||
@ -39,6 +50,7 @@ const VALID_STYLES = new Set([
|
||||
"page-break-inside",
|
||||
"tab-interval",
|
||||
"tab-stop",
|
||||
"text-align",
|
||||
"text-decoration",
|
||||
"text-indent",
|
||||
"vertical-align",
|
||||
@ -46,9 +58,73 @@ const VALID_STYLES = new Set([
|
||||
"kerning-mode",
|
||||
"xfa-font-horizontal-scale",
|
||||
"xfa-font-vertical-scale",
|
||||
"xfa-spacerun",
|
||||
"xfa-tab-stops",
|
||||
]);
|
||||
|
||||
const StyleMapping = new Map([
|
||||
["page-break-after", "breakAfter"],
|
||||
["page-break-before", "breakBefore"],
|
||||
["page-break-inside", "breakInside"],
|
||||
["kerning-mode", value => (value === "none" ? "none" : "normal")],
|
||||
[
|
||||
"xfa-font-horizontal-scale",
|
||||
value =>
|
||||
`scaleX(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`,
|
||||
],
|
||||
[
|
||||
"xfa-font-vertical-scale",
|
||||
value =>
|
||||
`scaleY(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`,
|
||||
],
|
||||
["xfa-spacerun", ""],
|
||||
["xfa-tab-stops", ""],
|
||||
["font-size", value => measureToString(getMeasurement(value))],
|
||||
["letter-spacing", value => measureToString(getMeasurement(value))],
|
||||
["line-height", value => measureToString(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))],
|
||||
]);
|
||||
|
||||
const spacesRegExp = /\s+/g;
|
||||
const crlfRegExp = /[\r\n]+/g;
|
||||
|
||||
function mapStyle(styleStr) {
|
||||
const style = Object.create(null);
|
||||
if (!styleStr) {
|
||||
return style;
|
||||
}
|
||||
for (const [key, value] of styleStr.split(";").map(s => s.split(":", 2))) {
|
||||
const mapping = StyleMapping.get(key);
|
||||
if (mapping === "") {
|
||||
continue;
|
||||
}
|
||||
let newValue = value;
|
||||
if (mapping) {
|
||||
if (typeof mapping === "string") {
|
||||
newValue = mapping;
|
||||
} else {
|
||||
newValue = mapping(value);
|
||||
}
|
||||
}
|
||||
if (key.endsWith("scale")) {
|
||||
if (style.transform) {
|
||||
style.transform = `${style[key]} ${newValue}`;
|
||||
} else {
|
||||
style.transform = newValue;
|
||||
}
|
||||
} else {
|
||||
style[
|
||||
key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())
|
||||
] = newValue;
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function checkStyle(style) {
|
||||
if (!style) {
|
||||
return "";
|
||||
@ -65,99 +141,163 @@ function checkStyle(style) {
|
||||
.join(";");
|
||||
}
|
||||
|
||||
class A extends XmlObject {
|
||||
const NoWhites = new Set(["body", "html"]);
|
||||
|
||||
class XhtmlObject extends XmlObject {
|
||||
constructor(attributes, name) {
|
||||
super(XHTML_NS_ID, name);
|
||||
this.style = checkStyle(attributes.style);
|
||||
}
|
||||
|
||||
[$acceptWhitespace]() {
|
||||
return !NoWhites.has(this[$nodeName]);
|
||||
}
|
||||
|
||||
[$onText](str) {
|
||||
str = str.replace(crlfRegExp, "");
|
||||
if (!this.style.includes("xfa-spacerun:yes")) {
|
||||
str = str.replace(spacesRegExp, " ");
|
||||
}
|
||||
if (str) {
|
||||
this[$content] += str;
|
||||
}
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return {
|
||||
name: this[$nodeName],
|
||||
attributes: {
|
||||
href: this.href,
|
||||
style: mapStyle(this.style),
|
||||
},
|
||||
children: this[$childrenToHTML]({}),
|
||||
value: this[$content] || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class A extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "a");
|
||||
super(attributes, "a");
|
||||
this.href = attributes.href || "";
|
||||
this.style = checkStyle(attributes.style);
|
||||
}
|
||||
}
|
||||
|
||||
class B extends XmlObject {
|
||||
class B extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "b");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "b");
|
||||
}
|
||||
}
|
||||
|
||||
class Body extends XmlObject {
|
||||
class Body extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "body");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "body");
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
const html = super[$toHTML]();
|
||||
html.attributes.class = "xfaRich";
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
class Br extends XmlObject {
|
||||
class Br extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "br");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "br");
|
||||
}
|
||||
|
||||
[$text]() {
|
||||
return "\n";
|
||||
}
|
||||
}
|
||||
|
||||
class Html extends XmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "html");
|
||||
this.style = checkStyle(attributes.style);
|
||||
[$toHTML]() {
|
||||
return {
|
||||
name: "br",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class I extends XmlObject {
|
||||
class Html extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "i");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "html");
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
const children = this[$childrenToHTML]({});
|
||||
if (children.length === 0) {
|
||||
return {
|
||||
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 {
|
||||
name: "div",
|
||||
attributes: {
|
||||
class: "xfaRich",
|
||||
style: {},
|
||||
},
|
||||
children,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Li extends XmlObject {
|
||||
class I extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "li");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "i");
|
||||
}
|
||||
}
|
||||
|
||||
class Ol extends XmlObject {
|
||||
class Li extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "ol");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "li");
|
||||
}
|
||||
}
|
||||
|
||||
class P extends XmlObject {
|
||||
class Ol extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "p");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "ol");
|
||||
}
|
||||
}
|
||||
|
||||
class Span extends XmlObject {
|
||||
class P extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "span");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "p");
|
||||
}
|
||||
}
|
||||
|
||||
class Sub extends XmlObject {
|
||||
class Span extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "sub");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "span");
|
||||
}
|
||||
}
|
||||
|
||||
class Sup extends XmlObject {
|
||||
class Sub extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "sup");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "sub");
|
||||
}
|
||||
}
|
||||
|
||||
class Ul extends XmlObject {
|
||||
class Sup extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(XHTML_NS_ID, "ul");
|
||||
this.style = checkStyle(attributes.style);
|
||||
super(attributes, "sup");
|
||||
}
|
||||
}
|
||||
|
||||
class Ul extends XhtmlObject {
|
||||
constructor(attributes) {
|
||||
super(attributes, "ul");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@
|
||||
"writer_spec.js",
|
||||
"xfa_formcalc_spec.js",
|
||||
"xfa_parser_spec.js",
|
||||
"xfa_tohtml_spec.js",
|
||||
"xml_spec.js"
|
||||
]
|
||||
}
|
||||
|
@ -330,9 +330,9 @@ describe("XFAParser", function () {
|
||||
);
|
||||
expect(p[$text]()).toEqual(
|
||||
[
|
||||
"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",
|
||||
" 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 ",
|
||||
].join("")
|
||||
);
|
||||
});
|
||||
|
@ -81,7 +81,9 @@ describe("XFAFactory", function () {
|
||||
fontSize: "7px",
|
||||
height: "22px",
|
||||
left: "2px",
|
||||
padding: "1px 4px 2px 3px",
|
||||
position: "absolute",
|
||||
textAlign: "left",
|
||||
top: "1px",
|
||||
transform: "rotate(-90deg)",
|
||||
transformOrigin: "top left",
|
||||
|
@ -1,4 +1,4 @@
|
||||
*/* Copyright 2021 Mozilla Foundation
|
||||
/* 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.
|
||||
@ -30,6 +30,7 @@
|
||||
text-decoration: inherit;
|
||||
vertical-align: inherit;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.xfaFont {
|
||||
@ -44,19 +45,23 @@
|
||||
}
|
||||
|
||||
.xfaDraw {
|
||||
z-index: 200;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.xfaExclgroup {
|
||||
z-index: 300;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.xfaField {
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.xfaRich {
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.xfaSubform {
|
||||
z-index: 100;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.xfaLabel {
|
||||
@ -77,6 +82,7 @@
|
||||
height: 100%;
|
||||
flex: 1 1 auto;
|
||||
border: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.xfaLabel > input[type="checkbox"] {
|
||||
@ -123,6 +129,16 @@
|
||||
background: Highlight;
|
||||
}
|
||||
|
||||
.xfaRich {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.xfaImage,
|
||||
.xfaRich {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.xfaLrTb,
|
||||
.xfaRlTb,
|
||||
.xfaTb,
|
||||
@ -134,6 +150,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xfaArea {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xfaValignMiddle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
Loading…
x
Reference in New Issue
Block a user