diff --git a/src/core/xfa/html_utils.js b/src/core/xfa/html_utils.js index 6e34f0cc5..3aa06df08 100644 --- a/src/core/xfa/html_utils.js +++ b/src/core/xfa/html_utils.js @@ -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 }; diff --git a/src/core/xfa/parser.js b/src/core/xfa/parser.js index ebc83e827..5bb7c0010 100644 --- a/src/core/xfa/parser.js +++ b/src/core/xfa/parser.js @@ -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; } diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index 65678819f..e147cd5b8 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -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
.
+ html.name = "div";
+ html.children = [];
+ this[$content]
+ .split("\u2029")
+ .map(para =>
+ // Convert a paragraph into a set of (for lines)
+ // separated by
.
+ 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 (for lines)
+ // separated by
.
+ 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,
},
};
diff --git a/src/core/xfa/xfa_object.js b/src/core/xfa/xfa_object.js
index 94b083baf..8c167a679 100644
--- a/src/core/xfa/xfa_object.js
+++ b/src/core/xfa/xfa_object.js
@@ -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,
diff --git a/src/core/xfa/xhtml.js b/src/core/xfa/xhtml.js
index 961544877..f87d266cb 100644
--- a/src/core/xfa/xhtml.js
+++ b/src/core/xfa/xhtml.js
@@ -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");
}
}
diff --git a/test/unit/clitests.json b/test/unit/clitests.json
index 32c2d4977..00c16be26 100644
--- a/test/unit/clitests.json
+++ b/test/unit/clitests.json
@@ -42,6 +42,7 @@
"writer_spec.js",
"xfa_formcalc_spec.js",
"xfa_parser_spec.js",
+ "xfa_tohtml_spec.js",
"xml_spec.js"
]
}
diff --git a/test/unit/xfa_parser_spec.js b/test/unit/xfa_parser_spec.js
index ad07a3b58..a83f661e5 100644
--- a/test/unit/xfa_parser_spec.js
+++ b/test/unit/xfa_parser_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("")
);
});
diff --git a/test/unit/xfa_tohtml_spec.js b/test/unit/xfa_tohtml_spec.js
index 299dce95a..ee408a064 100644
--- a/test/unit/xfa_tohtml_spec.js
+++ b/test/unit/xfa_tohtml_spec.js
@@ -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",
diff --git a/web/xfa_layer_builder.css b/web/xfa_layer_builder.css
index a691ac909..ab1deb375 100644
--- a/web/xfa_layer_builder.css
+++ b/web/xfa_layer_builder.css
@@ -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;