XFA - Add support for few ui elements (#13115)

- input;
  - layout;
  - border;
  - margin;
  - color.
This commit is contained in:
calixteman 2021-03-31 15:42:21 +02:00 committed by GitHub
parent 84d7cccb1d
commit b3528868c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 799 additions and 74 deletions

View File

@ -13,10 +13,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { $toStyle, XFAObject } from "./xfa_object.js"; import { $getParent, $toStyle, XFAObject } from "./xfa_object.js";
import { warn } from "../../shared/util.js"; import { warn } from "../../shared/util.js";
function measureToString(m) { function measureToString(m) {
if (typeof m === "string") {
return "0px";
}
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`; return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
} }
@ -56,31 +59,34 @@ const converters = {
if (node.w) { if (node.w) {
style.width = measureToString(node.w); style.width = measureToString(node.w);
} else { } else {
if (node.maxW && node.maxW.value > 0) { style.width = "auto";
if (node.maxW > 0) {
style.maxWidth = measureToString(node.maxW); style.maxWidth = measureToString(node.maxW);
} }
if (node.minW && node.minW.value > 0) {
style.minWidth = measureToString(node.minW); style.minWidth = measureToString(node.minW);
} }
}
if (node.h) { if (node.h) {
style.height = measureToString(node.h); style.height = measureToString(node.h);
} else { } else {
if (node.maxH && node.maxH.value > 0) { style.height = "auto";
if (node.maxH > 0) {
style.maxHeight = measureToString(node.maxH); style.maxHeight = measureToString(node.maxH);
} }
if (node.minH && node.minH.value > 0) {
style.minHeight = measureToString(node.minH); style.minHeight = measureToString(node.minH);
} }
}
}, },
position(node, style) { position(node, style) {
if (node.x !== "" || node.y !== "") { const parent = node[$getParent]();
if (parent && parent.layout && parent.layout !== "position") {
// IRL, we've some x/y in tb layout.
// Specs say x/y is only used in positioned layout.
return;
}
style.position = "absolute"; style.position = "absolute";
style.left = measureToString(node.x); style.left = measureToString(node.x);
style.top = measureToString(node.y); style.top = measureToString(node.y);
}
}, },
rotate(node, style) { rotate(node, style) {
if (node.rotate) { if (node.rotate) {
@ -91,8 +97,40 @@ const converters = {
style.transformOrigin = "top left"; style.transformOrigin = "top left";
} }
}, },
presence(node, style) {
switch (node.presence) {
case "invisible":
style.visibility = "hidden";
break;
case "hidden":
case "inactive":
style.display = "none";
break;
}
},
}; };
function layoutClass(node) {
switch (node.layout) {
case "position":
return "xfaPosition";
case "lr-tb":
return "xfaLrTb";
case "rl-row":
return "xfaRlRow";
case "rl-tb":
return "xfaRlTb";
case "row":
return "xfaRow";
case "table":
return "xfaTable";
case "tb":
return "xfaTb";
default:
return "xfaPosition";
}
}
function toStyle(node, ...names) { function toStyle(node, ...names) {
const style = Object.create(null); const style = Object.create(null);
for (const name of names) { for (const name of names) {
@ -117,4 +155,4 @@ function toStyle(node, ...names) {
return style; return style;
} }
export { measureToString, toStyle }; export { layoutClass, measureToString, toStyle };

View File

@ -51,7 +51,7 @@ import {
getRelevant, getRelevant,
getStringOption, getStringOption,
} from "./utils.js"; } from "./utils.js";
import { measureToString, toStyle } from "./html_utils.js"; import { layoutClass, measureToString, toStyle } from "./html_utils.js";
import { Util, warn } from "../../shared/util.js"; import { Util, warn } from "../../shared/util.js";
const TEMPLATE_NS_ID = NamespaceIds.template.id; const TEMPLATE_NS_ID = NamespaceIds.template.id;
@ -115,8 +115,8 @@ class Area extends XFAObject {
this.relevant = getRelevant(attributes.relevant); this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.x = getMeasurement(attributes.x); this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y); this.y = getMeasurement(attributes.y, "0pt");
this.desc = null; this.desc = null;
this.extras = null; this.extras = null;
this.area = new XFAObjectArray(); this.area = new XFAObjectArray();
@ -346,6 +346,10 @@ class BooleanElement extends Option01 {
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
} }
[$toHTML]() {
return this[$content] === 1;
}
} }
class Border extends XFAObject { class Border extends XFAObject {
@ -369,6 +373,83 @@ class Border extends XFAObject {
this.fill = null; this.fill = null;
this.margin = null; this.margin = null;
} }
[$toStyle](widths, margins) {
// TODO: incomplete.
const edgeStyles = this.edge.children.map(node => node[$toStyle]());
const cornerStyles = this.edge.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
);
}
} 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);
}
}
if (cornerStyles.length > 0) {
if (cornerStyles.length === 2 || cornerStyles.length === 3) {
const last = cornerStyles[cornerStyles.length - 1];
for (let i = cornerStyles.length; i < 4; i++) {
cornerStyles.push(last);
}
}
style.borderRadius = cornerStyles.map(s => s.radius).join(" ");
}
switch (this.presence) {
case "invisible":
case "hidden":
style.borderStyle = "";
break;
case "inactive":
style.borderStyle = "none";
break;
}
return style;
}
} }
class Break extends XFAObject { class Break extends XFAObject {
@ -471,6 +552,17 @@ class Button extends XFAObject {
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.extras = null; this.extras = null;
} }
[$toHTML]() {
// TODO: highlight.
return {
name: "button",
attributes: {
class: "xfaButton",
style: {},
},
};
}
} }
class Calculate extends XFAObject { class Calculate extends XFAObject {
@ -521,6 +613,47 @@ class Caption extends XFAObject {
[$setValue](value) { [$setValue](value) {
_setValue(this, value); _setValue(this, value);
} }
[$toHTML]() {
// TODO: incomplete.
if (!this.value) {
return null;
}
const value = this.value[$toHTML]();
if (!value) {
return null;
}
const children = [];
if (typeof value === "string") {
children.push({
name: "#text",
value,
});
} else {
children.push(value);
}
const style = toStyle(this, "font", "margin", "para", "visibility");
switch (this.placement) {
case "left":
case "right":
style.minWidth = measureToString(this.reserve);
break;
case "top":
case "bottom":
style.minHeight = measureToString(this.reserve);
break;
}
return {
name: "div",
attributes: {
style,
},
children,
};
}
} }
class Certificate extends StringObject { class Certificate extends StringObject {
@ -575,6 +708,83 @@ class CheckButton extends XFAObject {
this.extras = null; this.extras = null;
this.margin = null; this.margin = null;
} }
[$toHTML]() {
// TODO: shape and mark == default.
const style = toStyle(this, "border", "margin");
const size = measureToString(this.size);
style.width = style.height = size;
let mark, radius;
if (this.shape === "square") {
mark = "■";
radius = "10%";
} else {
mark = "●";
radius = "50%";
}
if (!style.borderRadius) {
style.borderRadius = radius;
}
if (this.mark !== "default") {
// TODO: To avoid some rendering issues we should use svg
// to draw marks.
switch (this.mark) {
case "check":
mark = "✓";
break;
case "circle":
mark = "●";
break;
case "cross":
mark = "✕";
break;
case "diamond":
mark = "♦";
break;
case "square":
mark = "■";
break;
case "star":
mark = "★";
break;
}
}
if (size !== "10px") {
style.fontSize = size;
style.lineHeight = size;
style.width = size;
style.height = size;
}
return {
name: "label",
attributes: {
class: "xfaLabel",
},
children: [
{
name: "input",
attributes: {
class: "xfaCheckbox",
type: "checkbox",
},
},
{
name: "span",
attributes: {
class: "xfaCheckboxMark",
mark,
style,
},
},
],
};
}
} }
class ChoiceList extends XFAObject { class ChoiceList extends XFAObject {
@ -599,6 +809,27 @@ class ChoiceList extends XFAObject {
this.extras = null; this.extras = null;
this.margin = null; this.margin = null;
} }
[$toHTML]() {
// TODO: incomplete.
const style = toStyle(this, "border", "margin");
return {
name: "label",
attributes: {
class: "xfaLabel",
},
children: [
{
name: "select",
attributes: {
class: "xfaSxelect",
multiple: this.open === "multiSelect",
style,
},
},
],
};
}
} }
class Color extends XFAObject { class Color extends XFAObject {
@ -662,8 +893,8 @@ class ContentArea extends XFAObject {
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.w = getMeasurement(attributes.w); this.w = getMeasurement(attributes.w);
this.x = getMeasurement(attributes.x); this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y); this.y = getMeasurement(attributes.y, "0pt");
this.desc = null; this.desc = null;
this.extras = null; this.extras = null;
} }
@ -727,12 +958,19 @@ class Corner extends XFAObject {
this.extras = null; this.extras = null;
} }
[$finalize]() { [$toStyle]() {
this.color = this.color || getColor(null, [0, 0, 0]); // In using CSS it's only possible to handle radius
// (at least with basic css).
// Is there a real use (interest ?) of all these properties ?
// Maybe it's possible to implement them using svg and border-image...
// TODO: implement all the missing properties.
const style = toStyle(this, "visibility");
style.radius = measureToString(this.radius);
return style;
} }
} }
class Date extends ContentObject { class DateElement extends ContentObject {
constructor(attributes) { constructor(attributes) {
super(TEMPLATE_NS_ID, "date"); super(TEMPLATE_NS_ID, "date");
this.id = attributes.id || ""; this.id = attributes.id || "";
@ -744,6 +982,10 @@ class Date extends ContentObject {
[$finalize]() { [$finalize]() {
this[$content] = new Date(this[$content].trim()); this[$content] = new Date(this[$content].trim());
} }
[$toHTML]() {
return this[$content].toString();
}
} }
class DateTime extends ContentObject { class DateTime extends ContentObject {
@ -758,6 +1000,10 @@ class DateTime extends ContentObject {
[$finalize]() { [$finalize]() {
this[$content] = new Date(this[$content].trim()); this[$content] = new Date(this[$content].trim());
} }
[$toHTML]() {
return this[$content].toString();
}
} }
class DateTimeEdit extends XFAObject { class DateTimeEdit extends XFAObject {
@ -777,6 +1023,29 @@ class DateTimeEdit extends XFAObject {
this.extras = null; this.extras = null;
this.margin = null; this.margin = null;
} }
[$toHTML]() {
// TODO: incomplete.
// When the picker is host we should use type=date for the input
// but we need to put the buttons outside the text-field.
const style = toStyle(this, "border", "font", "margin");
const html = {
name: "input",
attributes: {
type: "text",
class: "xfaTextfield",
style,
},
};
return {
name: "label",
attributes: {
class: "xfaLabel",
},
children: [html],
};
}
} }
class Decimal extends ContentObject { class Decimal extends ContentObject {
@ -802,6 +1071,10 @@ class Decimal extends ContentObject {
const number = parseFloat(this[$content].trim()); const number = parseFloat(this[$content].trim());
this[$content] = isNaN(number) ? null : number; this[$content] = isNaN(number) ? null : number;
} }
[$toHTML]() {
return this[$content] !== null ? this[$content].toString() : "";
}
} }
class DefaultUi extends XFAObject { class DefaultUi extends XFAObject {
@ -878,7 +1151,7 @@ class Draw extends XFAObject {
defaultValue: 1, defaultValue: 1,
validate: x => x >= 1, validate: x => x >= 1,
}); });
this.h = getMeasurement(attributes.h); this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, [ this.hAlign = getStringOption(attributes.hAlign, [
"left", "left",
"center", "center",
@ -889,10 +1162,10 @@ class Draw extends XFAObject {
]); ]);
this.id = attributes.id || ""; this.id = attributes.id || "";
this.locale = attributes.locale || ""; this.locale = attributes.locale || "";
this.maxH = getMeasurement(attributes.maxH); this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW); this.maxW = getMeasurement(attributes.maxW, "0pt");
this.minH = getMeasurement(attributes.minH); this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW); this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || ""; this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, [ this.presence = getStringOption(attributes.presence, [
"visible", "visible",
@ -908,9 +1181,9 @@ class Draw extends XFAObject {
}); });
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.w = getMeasurement(attributes.w); this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x); this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y); this.y = getMeasurement(attributes.y, "0pt");
this.assist = null; this.assist = null;
this.border = null; this.border = null;
this.caption = null; this.caption = null;
@ -940,6 +1213,7 @@ class Draw extends XFAObject {
"font", "font",
"dimensions", "dimensions",
"position", "position",
"presence",
"rotate", "rotate",
"anchorType" "anchorType"
); );
@ -955,6 +1229,10 @@ class Draw extends XFAObject {
class: clazz.join(" "), class: clazz.join(" "),
}; };
if (this.name) {
attributes.xfaName = this.name;
}
return { return {
name: "div", name: "div",
attributes, attributes,
@ -992,8 +1270,50 @@ class Edge extends XFAObject {
this.extras = null; this.extras = null;
} }
[$finalize]() { [$toStyle]() {
this.color = this.color || getColor(null, [0, 0, 0]); // TODO: dashDot & dashDotDot.
const style = toStyle(this, "visibility");
Object.assign(style, {
linecap: this.cap,
width: measureToString(this.thickness),
color: this.color ? this.color[$toHTML]() : "#000000",
style: "",
});
if (this.presence !== "visible") {
style.style = "none";
} else {
switch (this.stroke) {
case "solid":
style.style = "solid";
break;
case "dashDot":
style.style = "dashed";
break;
case "dashDotDot":
style.style = "dashed";
break;
case "dashed":
style.style = "dashed";
break;
case "dotted":
style.style = "dotted";
break;
case "embossed":
style.style = "ridge";
break;
case "etched":
style.style = "groove";
break;
case "lowered":
style.style = "inset";
break;
case "raised":
style.style = "outset";
break;
}
}
return style;
} }
} }
@ -1228,7 +1548,7 @@ class ExclGroup extends XFAObject {
defaultValue: 1, defaultValue: 1,
validate: x => x >= 1, validate: x => x >= 1,
}); });
this.h = getMeasurement(attributes.h); this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, [ this.hAlign = getStringOption(attributes.hAlign, [
"left", "left",
"center", "center",
@ -1247,10 +1567,10 @@ class ExclGroup extends XFAObject {
"table", "table",
"tb", "tb",
]); ]);
this.maxH = getMeasurement(attributes.maxH); this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW); this.maxW = getMeasurement(attributes.maxW, "0pt");
this.minH = getMeasurement(attributes.minH); this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW); this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || ""; this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, [ this.presence = getStringOption(attributes.presence, [
"visible", "visible",
@ -1261,9 +1581,9 @@ class ExclGroup extends XFAObject {
this.relevant = getRelevant(attributes.relevant); this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.w = getMeasurement(attributes.w); this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x); this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y); this.y = getMeasurement(attributes.y, "0pt");
this.assist = null; this.assist = null;
this.bind = null; this.bind = null;
this.border = null; this.border = null;
@ -1399,7 +1719,7 @@ class Field extends XFAObject {
defaultValue: 1, defaultValue: 1,
validate: x => x >= 1, validate: x => x >= 1,
}); });
this.h = getMeasurement(attributes.h); this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, [ this.hAlign = getStringOption(attributes.hAlign, [
"left", "left",
"center", "center",
@ -1410,10 +1730,10 @@ class Field extends XFAObject {
]); ]);
this.id = attributes.id || ""; this.id = attributes.id || "";
this.locale = attributes.locale || ""; this.locale = attributes.locale || "";
this.maxH = getMeasurement(attributes.maxH); this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW); this.maxW = getMeasurement(attributes.maxW, "0pt");
this.minH = getMeasurement(attributes.minH); this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW); this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || ""; this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, [ this.presence = getStringOption(attributes.presence, [
"visible", "visible",
@ -1429,9 +1749,9 @@ class Field extends XFAObject {
}); });
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.w = getMeasurement(attributes.w); this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x); this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y); this.y = getMeasurement(attributes.y, "0pt");
this.assist = null; this.assist = null;
this.bind = null; this.bind = null;
this.border = null; this.border = null;
@ -1462,7 +1782,7 @@ class Field extends XFAObject {
} }
[$toHTML]() { [$toHTML]() {
if (!this.value) { if (!this.ui) {
return null; return null;
} }
@ -1472,10 +1792,39 @@ class Field extends XFAObject {
"dimensions", "dimensions",
"position", "position",
"rotate", "rotate",
"anchorType" "anchorType",
"presence"
); );
// 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"]; const clazz = ["xfaField"];
// If no font, font properties are inherited.
if (this.font) { if (this.font) {
clazz.push("xfaFont"); clazz.push("xfaFont");
} }
@ -1486,11 +1835,68 @@ class Field extends XFAObject {
class: clazz.join(" "), class: clazz.join(" "),
}; };
return { if (this.name) {
attributes.xfaName = this.name;
}
const children = [];
const html = {
name: "div", name: "div",
attributes, attributes,
children: [], children,
}; };
const ui = this.ui ? this.ui[$toHTML]() : null;
if (!ui) {
return html;
}
if (!ui.attributes.style) {
ui.attributes.style = Object.create(null);
}
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]();
}
const caption = this.caption ? this.caption[$toHTML]() : null;
if (!caption) {
return html;
}
if (ui.name === "button") {
ui.attributes.style.background = style.color;
delete style.color;
if (caption.name === "div") {
caption.name = "span";
}
ui.children = [caption];
return html;
}
ui.children.splice(0, 0, caption);
switch (this.caption.placement) {
case "left":
ui.attributes.style.flexDirection = "row";
break;
case "right":
ui.attributes.style.flexDirection = "row-reverse";
break;
case "top":
ui.attributes.style.flexDirection = "column";
break;
case "bottom":
ui.attributes.style.flexDirection = "column-reverse";
break;
case "inline":
delete ui.attributes.class;
caption.attributes.style.float = "left";
break;
}
return html;
} }
} }
@ -1509,7 +1915,7 @@ class Fill extends XFAObject {
this.color = null; this.color = null;
this.extras = null; this.extras = null;
// One-of properties // One-of properties or none
this.linear = null; this.linear = null;
this.pattern = null; this.pattern = null;
this.radial = null; this.radial = null;
@ -1518,7 +1924,6 @@ class Fill extends XFAObject {
} }
[$toStyle]() { [$toStyle]() {
let fill = "#000000";
for (const name of Object.getOwnPropertyNames(this)) { for (const name of Object.getOwnPropertyNames(this)) {
if (name === "extras" || name === "color") { if (name === "extras" || name === "color") {
continue; continue;
@ -1528,12 +1933,13 @@ class Fill extends XFAObject {
continue; continue;
} }
fill = obj[$toStyle](this.color); return {
break; color: obj[$toStyle](this.color),
};
} }
return { return {
color: fill, color: this.color ? this.color[$toStyle]() : "#000000",
}; };
} }
} }
@ -1582,6 +1988,10 @@ class Float extends ContentObject {
const number = parseFloat(this[$content].trim()); const number = parseFloat(this[$content].trim());
this[$content] = isNaN(number) ? null : number; this[$content] = isNaN(number) ? null : number;
} }
[$toHTML]() {
return this[$content] !== null ? this[$content].toString() : "";
}
} }
class Font extends XFAObject { class Font extends XFAObject {
@ -1798,6 +2208,31 @@ class Image extends StringObject {
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
} }
[$toHTML]() {
const html = {
name: "img",
attributes: {
style: {},
},
};
if (this.href) {
html.attributes.src = new URL(this.href);
return html;
}
if (this.transferEncoding === "base64") {
const buffer = Uint8Array.from(atob(this[$content]), c =>
c.charCodeAt(0)
);
const blob = new Blob([buffer], { type: this.contentType });
html.attributes.src = URL.createObjectURL(blob);
return html;
}
return null;
}
} }
class ImageEdit extends XFAObject { class ImageEdit extends XFAObject {
@ -1826,6 +2261,10 @@ class Integer extends ContentObject {
const number = parseInt(this[$content].trim(), 10); const number = parseInt(this[$content].trim(), 10);
this[$content] = isNaN(number) ? null : number; this[$content] = isNaN(number) ? null : number;
} }
[$toHTML]() {
return this[$content] !== null ? this[$content].toString() : "";
}
} }
class Issuers extends XFAObject { class Issuers extends XFAObject {
@ -1999,6 +2438,15 @@ class Margin extends XFAObject {
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.extras = null; this.extras = null;
} }
[$toStyle]() {
return {
marginLeft: measureToString(this.leftInset),
marginRight: measureToString(this.rightInset),
marginTop: measureToString(this.topInset),
marginBottom: measureToString(this.bottomInset),
};
}
} }
class Mdp extends XFAObject { class Mdp extends XFAObject {
@ -2068,6 +2516,27 @@ class NumericEdit extends XFAObject {
this.extras = null; this.extras = null;
this.margin = null; this.margin = null;
} }
[$toHTML]() {
// TODO: incomplete.
const style = toStyle(this, "border", "font", "margin");
const html = {
name: "input",
attributes: {
type: "text",
class: "xfaTextfield",
style,
},
};
return {
name: "label",
attributes: {
class: "xfaLabel",
},
children: [html],
};
}
} }
class Occur extends XFAObject { class Occur extends XFAObject {
@ -2295,6 +2764,35 @@ class Para extends XFAObject {
}); });
this.hyphenation = null; 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,
};
if (this.lineHeight.value >= 0) {
style.lineHeight = measureToString(this.lineHeight);
}
if (this.tabDefault) {
style.tabSize = measureToString(this.tabDefault);
}
if (this.tabStops.length > 0) {
// TODO
}
if (this.hyphenatation) {
Object.assign(style, this.hyphenatation[$toHTML]());
}
return style;
}
} }
class PasswordEdit extends XFAObject { class PasswordEdit extends XFAObject {
@ -2714,7 +3212,7 @@ class Subform extends XFAObject {
.trim() .trim()
.split(/\s+/) .split(/\s+/)
.map(x => (x === "-1" ? -1 : getMeasurement(x))); .map(x => (x === "-1" ? -1 : getMeasurement(x)));
this.h = getMeasurement(attributes.h); this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, [ this.hAlign = getStringOption(attributes.hAlign, [
"left", "left",
"center", "center",
@ -2734,14 +3232,14 @@ class Subform extends XFAObject {
"tb", "tb",
]); ]);
this.locale = attributes.locale || ""; this.locale = attributes.locale || "";
this.maxH = getMeasurement(attributes.maxH); this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW); this.maxW = getMeasurement(attributes.maxW, "0pt");
this.mergeMode = getStringOption(attributes.mergeMode, [ this.mergeMode = getStringOption(attributes.mergeMode, [
"consumeData", "consumeData",
"matchTemplate", "matchTemplate",
]); ]);
this.minH = getMeasurement(attributes.minH); this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW); this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || ""; this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, [ this.presence = getStringOption(attributes.presence, [
"visible", "visible",
@ -2757,9 +3255,9 @@ class Subform extends XFAObject {
this.scope = getStringOption(attributes.scope, ["name", "none"]); this.scope = getStringOption(attributes.scope, ["name", "none"]);
this.use = attributes.use || ""; this.use = attributes.use || "";
this.usehref = attributes.usehref || ""; this.usehref = attributes.usehref || "";
this.w = getMeasurement(attributes.w); this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x); this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y); this.y = getMeasurement(attributes.y, "0pt");
this.assist = null; this.assist = null;
this.bind = null; this.bind = null;
this.bookend = null; this.bookend = null;
@ -2816,12 +3314,17 @@ class Subform extends XFAObject {
page = pageAreas[pageNumber][$toHTML](); page = pageAreas[pageNumber][$toHTML]();
} }
const style = toStyle(this, "dimensions", "position"); const style = toStyle(this, "dimensions", "position", "presence");
const clazz = ["xfaSubform"];
const cl = layoutClass(this);
if (cl) {
clazz.push(cl);
}
const attributes = { const attributes = {
style, style,
id: this[$uid], id: this[$uid],
class: "xfaSubform", class: clazz.join(" "),
}; };
if (this.name) { if (this.name) {
@ -3014,6 +3517,13 @@ class Text extends ContentObject {
warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`); warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`);
return false; return false;
} }
[$toHTML]() {
if (typeof this[$content] === "string") {
return this[$content];
}
return this[$content][$toHTML]();
}
} }
class TextEdit extends XFAObject { class TextEdit extends XFAObject {
@ -3047,6 +3557,37 @@ class TextEdit extends XFAObject {
this.extras = null; this.extras = null;
this.margin = null; this.margin = null;
} }
[$toHTML]() {
// TODO: incomplete.
const style = toStyle(this, "border", "font", "margin");
let html;
if (this.multiline === 1) {
html = {
name: "textarea",
attributes: {
style,
},
};
} else {
html = {
name: "input",
attributes: {
type: "text",
class: "xfaTextfield",
style,
},
};
}
return {
name: "label",
attributes: {
class: "xfaLabel",
},
children: [html],
};
}
} }
class Time extends StringObject { class Time extends StringObject {
@ -3062,6 +3603,10 @@ class Time extends StringObject {
// TODO // TODO
this[$content] = new Date(this[$content]); this[$content] = new Date(this[$content]);
} }
[$toHTML]() {
return this[$content].toString();
}
} }
class TimeStamp extends XFAObject { class TimeStamp extends XFAObject {
@ -3148,6 +3693,22 @@ class Ui extends XFAObject {
this.signature = null; this.signature = null;
this.textEdit = null; this.textEdit = null;
} }
[$toHTML]() {
// TODO: picture.
for (const name of Object.getOwnPropertyNames(this)) {
if (name === "extras" || name === "picture") {
continue;
}
const obj = this[name];
if (!(obj instanceof XFAObject)) {
continue;
}
return obj[$toHTML]();
}
return null;
}
} }
class Validate extends XFAObject { class Validate extends XFAObject {
@ -3226,6 +3787,19 @@ class Value extends XFAObject {
this[value[$nodeName]] = value; this[value[$nodeName]] = value;
this[$appendChild](value); this[$appendChild](value);
} }
[$toHTML]() {
for (const name of Object.getOwnPropertyNames(this)) {
const obj = this[name];
if (!(obj instanceof XFAObject)) {
continue;
}
return obj[$toHTML]();
}
return null;
}
} }
class Variables extends XFAObject { class Variables extends XFAObject {
@ -3364,7 +3938,7 @@ class TemplateNamespace {
} }
static date(attrs) { static date(attrs) {
return new Date(attrs); return new DateElement(attrs);
} }
static dateTime(attrs) { static dateTime(attrs) {

View File

@ -117,7 +117,7 @@ describe("XFAParser", function () {
anchorType: "topLeft", anchorType: "topLeft",
colSpan: 1, colSpan: 1,
columnWidths: [0], columnWidths: [0],
h: 0, h: "",
hAlign: "left", hAlign: "left",
id: "", id: "",
layout: "position", layout: "position",
@ -134,7 +134,7 @@ describe("XFAParser", function () {
scope: "name", scope: "name",
use: "", use: "",
usehref: "", usehref: "",
w: 0, w: "",
x: 0, x: 0,
y: 0, y: 0,
proto: { proto: {

View File

@ -26,8 +26,10 @@
font: inherit; font: inherit;
font-kerning: inherit; font-kerning: inherit;
letter-spacing: inherit; letter-spacing: inherit;
text-align: inherit;
text-decoration: inherit; text-decoration: inherit;
vertical-align: inherit; vertical-align: inherit;
box-sizing: border-box;
} }
.xfaFont { .xfaFont {
@ -43,20 +45,131 @@
.xfaDraw { .xfaDraw {
z-index: 200; z-index: 200;
background-color: #ff000080;
} }
.xfaExclgroup { .xfaExclgroup {
z-index: 300; z-index: 300;
background-color: #0000ff80;
} }
.xfaField { .xfaField {
z-index: 300; z-index: 300;
background-color: #00ff0080;
} }
.xfaSubform { .xfaSubform {
z-index: 100; z-index: 100;
background-color: #ffff0080; }
.xfaLabel {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
height: 100%;
}
.xfaCaption {
flex: 1 1 auto;
}
.xfaTextfield,
.xfaSelect {
width: 100%;
height: 100%;
flex: 1 1 auto;
border: none;
}
.xfaLabel > input[type="checkbox"] {
/* Use this trick to make the checkbox invisible but
but still focusable. */
position: absolute;
left: -99999px;
}
.xfaLabel > input[type="checkbox"]:focus + .xfaCheckboxMark {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
}
.xfaCheckboxMark {
cursor: pointer;
flex: 0 0 auto;
border-style: solid;
border-width: 2px;
border-color: #8f8f9d;
font-size: 10px;
line-height: 10px;
width: 10px;
height: 10px;
text-align: center;
vertical-align: middle;
display: flex;
flex-direction: row;
align-items: center;
}
.xfaCheckbox:checked + .xfaCheckboxMark::after {
content: attr(mark);
}
.xfaButton {
cursor: pointer;
width: 100%;
height: 100%;
border: none;
text-align: center;
}
.xfaButton:hover {
background: Highlight;
}
.xfaLrTb,
.xfaRlTb,
.xfaTb,
.xfaPosition {
display: block;
}
.xfaPosition {
position: relative;
}
.xfaValignMiddle {
display: flex;
align-items: center;
}
.xfaLrTb > div {
display: inline;
float: left;
}
.xfaRlTb > div {
display: inline;
float: right;
}
.xfaTable {
display: flex;
flex-direction: column;
}
.xfaTable .xfaRow {
display: flex;
flex-direction: row;
flex: 1 1 auto;
}
.xfaTable .xfaRow > div {
flex: 1 1 auto;
}
.xfaTable .xfaRlRow {
display: flex;
flex-direction: row-reverse;
flex: 1;
}
.xfaTable .xfaRlRow > div {
flex: 1;
} }