XFA - Add support for few ui elements (#13115)
- input; - layout; - border; - margin; - color.
This commit is contained in:
parent
84d7cccb1d
commit
b3528868c1
@ -13,10 +13,13 @@
|
||||
* 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";
|
||||
|
||||
function measureToString(m) {
|
||||
if (typeof m === "string") {
|
||||
return "0px";
|
||||
}
|
||||
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
|
||||
}
|
||||
|
||||
@ -56,31 +59,34 @@ const converters = {
|
||||
if (node.w) {
|
||||
style.width = measureToString(node.w);
|
||||
} else {
|
||||
if (node.maxW && node.maxW.value > 0) {
|
||||
style.width = "auto";
|
||||
if (node.maxW > 0) {
|
||||
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) {
|
||||
style.height = measureToString(node.h);
|
||||
} else {
|
||||
if (node.maxH && node.maxH.value > 0) {
|
||||
style.height = "auto";
|
||||
if (node.maxH > 0) {
|
||||
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) {
|
||||
if (node.x !== "" || node.y !== "") {
|
||||
style.position = "absolute";
|
||||
style.left = measureToString(node.x);
|
||||
style.top = measureToString(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.left = measureToString(node.x);
|
||||
style.top = measureToString(node.y);
|
||||
},
|
||||
rotate(node, style) {
|
||||
if (node.rotate) {
|
||||
@ -91,8 +97,40 @@ const converters = {
|
||||
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) {
|
||||
const style = Object.create(null);
|
||||
for (const name of names) {
|
||||
@ -117,4 +155,4 @@ function toStyle(node, ...names) {
|
||||
return style;
|
||||
}
|
||||
|
||||
export { measureToString, toStyle };
|
||||
export { layoutClass, measureToString, toStyle };
|
||||
|
@ -51,7 +51,7 @@ import {
|
||||
getRelevant,
|
||||
getStringOption,
|
||||
} 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";
|
||||
|
||||
const TEMPLATE_NS_ID = NamespaceIds.template.id;
|
||||
@ -115,8 +115,8 @@ class Area extends XFAObject {
|
||||
this.relevant = getRelevant(attributes.relevant);
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.x = getMeasurement(attributes.x);
|
||||
this.y = getMeasurement(attributes.y);
|
||||
this.x = getMeasurement(attributes.x, "0pt");
|
||||
this.y = getMeasurement(attributes.y, "0pt");
|
||||
this.desc = null;
|
||||
this.extras = null;
|
||||
this.area = new XFAObjectArray();
|
||||
@ -346,6 +346,10 @@ class BooleanElement extends Option01 {
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content] === 1;
|
||||
}
|
||||
}
|
||||
|
||||
class Border extends XFAObject {
|
||||
@ -369,6 +373,83 @@ class Border extends XFAObject {
|
||||
this.fill = 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 {
|
||||
@ -471,6 +552,17 @@ class Button extends XFAObject {
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
// TODO: highlight.
|
||||
return {
|
||||
name: "button",
|
||||
attributes: {
|
||||
class: "xfaButton",
|
||||
style: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Calculate extends XFAObject {
|
||||
@ -521,6 +613,47 @@ class Caption extends XFAObject {
|
||||
[$setValue](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 {
|
||||
@ -575,6 +708,83 @@ class CheckButton extends XFAObject {
|
||||
this.extras = 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 {
|
||||
@ -599,6 +809,27 @@ class ChoiceList extends XFAObject {
|
||||
this.extras = 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 {
|
||||
@ -662,8 +893,8 @@ class ContentArea extends XFAObject {
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.w = getMeasurement(attributes.w);
|
||||
this.x = getMeasurement(attributes.x);
|
||||
this.y = getMeasurement(attributes.y);
|
||||
this.x = getMeasurement(attributes.x, "0pt");
|
||||
this.y = getMeasurement(attributes.y, "0pt");
|
||||
this.desc = null;
|
||||
this.extras = null;
|
||||
}
|
||||
@ -727,12 +958,19 @@ class Corner extends XFAObject {
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
[$finalize]() {
|
||||
this.color = this.color || getColor(null, [0, 0, 0]);
|
||||
[$toStyle]() {
|
||||
// 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) {
|
||||
super(TEMPLATE_NS_ID, "date");
|
||||
this.id = attributes.id || "";
|
||||
@ -744,6 +982,10 @@ class Date extends ContentObject {
|
||||
[$finalize]() {
|
||||
this[$content] = new Date(this[$content].trim());
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content].toString();
|
||||
}
|
||||
}
|
||||
|
||||
class DateTime extends ContentObject {
|
||||
@ -758,6 +1000,10 @@ class DateTime extends ContentObject {
|
||||
[$finalize]() {
|
||||
this[$content] = new Date(this[$content].trim());
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content].toString();
|
||||
}
|
||||
}
|
||||
|
||||
class DateTimeEdit extends XFAObject {
|
||||
@ -777,6 +1023,29 @@ class DateTimeEdit extends XFAObject {
|
||||
this.extras = 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 {
|
||||
@ -802,6 +1071,10 @@ class Decimal extends ContentObject {
|
||||
const number = parseFloat(this[$content].trim());
|
||||
this[$content] = isNaN(number) ? null : number;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content] !== null ? this[$content].toString() : "";
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultUi extends XFAObject {
|
||||
@ -878,7 +1151,7 @@ class Draw extends XFAObject {
|
||||
defaultValue: 1,
|
||||
validate: x => x >= 1,
|
||||
});
|
||||
this.h = getMeasurement(attributes.h);
|
||||
this.h = attributes.h ? getMeasurement(attributes.h) : "";
|
||||
this.hAlign = getStringOption(attributes.hAlign, [
|
||||
"left",
|
||||
"center",
|
||||
@ -889,10 +1162,10 @@ class Draw extends XFAObject {
|
||||
]);
|
||||
this.id = attributes.id || "";
|
||||
this.locale = attributes.locale || "";
|
||||
this.maxH = getMeasurement(attributes.maxH);
|
||||
this.maxW = getMeasurement(attributes.maxW);
|
||||
this.minH = getMeasurement(attributes.minH);
|
||||
this.minW = getMeasurement(attributes.minW);
|
||||
this.maxH = getMeasurement(attributes.maxH, "0pt");
|
||||
this.maxW = getMeasurement(attributes.maxW, "0pt");
|
||||
this.minH = getMeasurement(attributes.minH, "0pt");
|
||||
this.minW = getMeasurement(attributes.minW, "0pt");
|
||||
this.name = attributes.name || "";
|
||||
this.presence = getStringOption(attributes.presence, [
|
||||
"visible",
|
||||
@ -908,9 +1181,9 @@ class Draw extends XFAObject {
|
||||
});
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.w = getMeasurement(attributes.w);
|
||||
this.x = getMeasurement(attributes.x);
|
||||
this.y = getMeasurement(attributes.y);
|
||||
this.w = attributes.w ? getMeasurement(attributes.w) : "";
|
||||
this.x = getMeasurement(attributes.x, "0pt");
|
||||
this.y = getMeasurement(attributes.y, "0pt");
|
||||
this.assist = null;
|
||||
this.border = null;
|
||||
this.caption = null;
|
||||
@ -940,6 +1213,7 @@ class Draw extends XFAObject {
|
||||
"font",
|
||||
"dimensions",
|
||||
"position",
|
||||
"presence",
|
||||
"rotate",
|
||||
"anchorType"
|
||||
);
|
||||
@ -955,6 +1229,10 @@ class Draw extends XFAObject {
|
||||
class: clazz.join(" "),
|
||||
};
|
||||
|
||||
if (this.name) {
|
||||
attributes.xfaName = this.name;
|
||||
}
|
||||
|
||||
return {
|
||||
name: "div",
|
||||
attributes,
|
||||
@ -992,8 +1270,50 @@ class Edge extends XFAObject {
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
[$finalize]() {
|
||||
this.color = this.color || getColor(null, [0, 0, 0]);
|
||||
[$toStyle]() {
|
||||
// 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,
|
||||
validate: x => x >= 1,
|
||||
});
|
||||
this.h = getMeasurement(attributes.h);
|
||||
this.h = attributes.h ? getMeasurement(attributes.h) : "";
|
||||
this.hAlign = getStringOption(attributes.hAlign, [
|
||||
"left",
|
||||
"center",
|
||||
@ -1247,10 +1567,10 @@ class ExclGroup extends XFAObject {
|
||||
"table",
|
||||
"tb",
|
||||
]);
|
||||
this.maxH = getMeasurement(attributes.maxH);
|
||||
this.maxW = getMeasurement(attributes.maxW);
|
||||
this.minH = getMeasurement(attributes.minH);
|
||||
this.minW = getMeasurement(attributes.minW);
|
||||
this.maxH = getMeasurement(attributes.maxH, "0pt");
|
||||
this.maxW = getMeasurement(attributes.maxW, "0pt");
|
||||
this.minH = getMeasurement(attributes.minH, "0pt");
|
||||
this.minW = getMeasurement(attributes.minW, "0pt");
|
||||
this.name = attributes.name || "";
|
||||
this.presence = getStringOption(attributes.presence, [
|
||||
"visible",
|
||||
@ -1261,9 +1581,9 @@ class ExclGroup extends XFAObject {
|
||||
this.relevant = getRelevant(attributes.relevant);
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.w = getMeasurement(attributes.w);
|
||||
this.x = getMeasurement(attributes.x);
|
||||
this.y = getMeasurement(attributes.y);
|
||||
this.w = attributes.w ? getMeasurement(attributes.w) : "";
|
||||
this.x = getMeasurement(attributes.x, "0pt");
|
||||
this.y = getMeasurement(attributes.y, "0pt");
|
||||
this.assist = null;
|
||||
this.bind = null;
|
||||
this.border = null;
|
||||
@ -1399,7 +1719,7 @@ class Field extends XFAObject {
|
||||
defaultValue: 1,
|
||||
validate: x => x >= 1,
|
||||
});
|
||||
this.h = getMeasurement(attributes.h);
|
||||
this.h = attributes.h ? getMeasurement(attributes.h) : "";
|
||||
this.hAlign = getStringOption(attributes.hAlign, [
|
||||
"left",
|
||||
"center",
|
||||
@ -1410,10 +1730,10 @@ class Field extends XFAObject {
|
||||
]);
|
||||
this.id = attributes.id || "";
|
||||
this.locale = attributes.locale || "";
|
||||
this.maxH = getMeasurement(attributes.maxH);
|
||||
this.maxW = getMeasurement(attributes.maxW);
|
||||
this.minH = getMeasurement(attributes.minH);
|
||||
this.minW = getMeasurement(attributes.minW);
|
||||
this.maxH = getMeasurement(attributes.maxH, "0pt");
|
||||
this.maxW = getMeasurement(attributes.maxW, "0pt");
|
||||
this.minH = getMeasurement(attributes.minH, "0pt");
|
||||
this.minW = getMeasurement(attributes.minW, "0pt");
|
||||
this.name = attributes.name || "";
|
||||
this.presence = getStringOption(attributes.presence, [
|
||||
"visible",
|
||||
@ -1429,9 +1749,9 @@ class Field extends XFAObject {
|
||||
});
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.w = getMeasurement(attributes.w);
|
||||
this.x = getMeasurement(attributes.x);
|
||||
this.y = getMeasurement(attributes.y);
|
||||
this.w = attributes.w ? getMeasurement(attributes.w) : "";
|
||||
this.x = getMeasurement(attributes.x, "0pt");
|
||||
this.y = getMeasurement(attributes.y, "0pt");
|
||||
this.assist = null;
|
||||
this.bind = null;
|
||||
this.border = null;
|
||||
@ -1462,7 +1782,7 @@ class Field extends XFAObject {
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
if (!this.value) {
|
||||
if (!this.ui) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1472,10 +1792,39 @@ class Field extends XFAObject {
|
||||
"dimensions",
|
||||
"position",
|
||||
"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"];
|
||||
// If no font, font properties are inherited.
|
||||
if (this.font) {
|
||||
clazz.push("xfaFont");
|
||||
}
|
||||
@ -1486,11 +1835,68 @@ class Field extends XFAObject {
|
||||
class: clazz.join(" "),
|
||||
};
|
||||
|
||||
return {
|
||||
if (this.name) {
|
||||
attributes.xfaName = this.name;
|
||||
}
|
||||
|
||||
const children = [];
|
||||
const html = {
|
||||
name: "div",
|
||||
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.extras = null;
|
||||
|
||||
// One-of properties
|
||||
// One-of properties or none
|
||||
this.linear = null;
|
||||
this.pattern = null;
|
||||
this.radial = null;
|
||||
@ -1518,7 +1924,6 @@ class Fill extends XFAObject {
|
||||
}
|
||||
|
||||
[$toStyle]() {
|
||||
let fill = "#000000";
|
||||
for (const name of Object.getOwnPropertyNames(this)) {
|
||||
if (name === "extras" || name === "color") {
|
||||
continue;
|
||||
@ -1528,12 +1933,13 @@ class Fill extends XFAObject {
|
||||
continue;
|
||||
}
|
||||
|
||||
fill = obj[$toStyle](this.color);
|
||||
break;
|
||||
return {
|
||||
color: obj[$toStyle](this.color),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
color: fill,
|
||||
color: this.color ? this.color[$toStyle]() : "#000000",
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1582,6 +1988,10 @@ class Float extends ContentObject {
|
||||
const number = parseFloat(this[$content].trim());
|
||||
this[$content] = isNaN(number) ? null : number;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content] !== null ? this[$content].toString() : "";
|
||||
}
|
||||
}
|
||||
|
||||
class Font extends XFAObject {
|
||||
@ -1798,6 +2208,31 @@ class Image extends StringObject {
|
||||
this.use = attributes.use || "";
|
||||
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 {
|
||||
@ -1826,6 +2261,10 @@ class Integer extends ContentObject {
|
||||
const number = parseInt(this[$content].trim(), 10);
|
||||
this[$content] = isNaN(number) ? null : number;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content] !== null ? this[$content].toString() : "";
|
||||
}
|
||||
}
|
||||
|
||||
class Issuers extends XFAObject {
|
||||
@ -1999,6 +2438,15 @@ class Margin extends XFAObject {
|
||||
this.usehref = attributes.usehref || "";
|
||||
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 {
|
||||
@ -2068,6 +2516,27 @@ class NumericEdit extends XFAObject {
|
||||
this.extras = 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 {
|
||||
@ -2295,6 +2764,35 @@ 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,
|
||||
};
|
||||
|
||||
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 {
|
||||
@ -2714,7 +3212,7 @@ class Subform extends XFAObject {
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.map(x => (x === "-1" ? -1 : getMeasurement(x)));
|
||||
this.h = getMeasurement(attributes.h);
|
||||
this.h = attributes.h ? getMeasurement(attributes.h) : "";
|
||||
this.hAlign = getStringOption(attributes.hAlign, [
|
||||
"left",
|
||||
"center",
|
||||
@ -2734,14 +3232,14 @@ class Subform extends XFAObject {
|
||||
"tb",
|
||||
]);
|
||||
this.locale = attributes.locale || "";
|
||||
this.maxH = getMeasurement(attributes.maxH);
|
||||
this.maxW = getMeasurement(attributes.maxW);
|
||||
this.maxH = getMeasurement(attributes.maxH, "0pt");
|
||||
this.maxW = getMeasurement(attributes.maxW, "0pt");
|
||||
this.mergeMode = getStringOption(attributes.mergeMode, [
|
||||
"consumeData",
|
||||
"matchTemplate",
|
||||
]);
|
||||
this.minH = getMeasurement(attributes.minH);
|
||||
this.minW = getMeasurement(attributes.minW);
|
||||
this.minH = getMeasurement(attributes.minH, "0pt");
|
||||
this.minW = getMeasurement(attributes.minW, "0pt");
|
||||
this.name = attributes.name || "";
|
||||
this.presence = getStringOption(attributes.presence, [
|
||||
"visible",
|
||||
@ -2757,9 +3255,9 @@ class Subform extends XFAObject {
|
||||
this.scope = getStringOption(attributes.scope, ["name", "none"]);
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.w = getMeasurement(attributes.w);
|
||||
this.x = getMeasurement(attributes.x);
|
||||
this.y = getMeasurement(attributes.y);
|
||||
this.w = attributes.w ? getMeasurement(attributes.w) : "";
|
||||
this.x = getMeasurement(attributes.x, "0pt");
|
||||
this.y = getMeasurement(attributes.y, "0pt");
|
||||
this.assist = null;
|
||||
this.bind = null;
|
||||
this.bookend = null;
|
||||
@ -2816,12 +3314,17 @@ class Subform extends XFAObject {
|
||||
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 = {
|
||||
style,
|
||||
id: this[$uid],
|
||||
class: "xfaSubform",
|
||||
class: clazz.join(" "),
|
||||
};
|
||||
|
||||
if (this.name) {
|
||||
@ -3014,6 +3517,13 @@ class Text extends ContentObject {
|
||||
warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
if (typeof this[$content] === "string") {
|
||||
return this[$content];
|
||||
}
|
||||
return this[$content][$toHTML]();
|
||||
}
|
||||
}
|
||||
|
||||
class TextEdit extends XFAObject {
|
||||
@ -3047,6 +3557,37 @@ class TextEdit extends XFAObject {
|
||||
this.extras = 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 {
|
||||
@ -3062,6 +3603,10 @@ class Time extends StringObject {
|
||||
// TODO
|
||||
this[$content] = new Date(this[$content]);
|
||||
}
|
||||
|
||||
[$toHTML]() {
|
||||
return this[$content].toString();
|
||||
}
|
||||
}
|
||||
|
||||
class TimeStamp extends XFAObject {
|
||||
@ -3148,6 +3693,22 @@ class Ui extends XFAObject {
|
||||
this.signature = 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 {
|
||||
@ -3226,6 +3787,19 @@ class Value extends XFAObject {
|
||||
this[value[$nodeName]] = 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 {
|
||||
@ -3364,7 +3938,7 @@ class TemplateNamespace {
|
||||
}
|
||||
|
||||
static date(attrs) {
|
||||
return new Date(attrs);
|
||||
return new DateElement(attrs);
|
||||
}
|
||||
|
||||
static dateTime(attrs) {
|
||||
|
@ -117,7 +117,7 @@ describe("XFAParser", function () {
|
||||
anchorType: "topLeft",
|
||||
colSpan: 1,
|
||||
columnWidths: [0],
|
||||
h: 0,
|
||||
h: "",
|
||||
hAlign: "left",
|
||||
id: "",
|
||||
layout: "position",
|
||||
@ -134,7 +134,7 @@ describe("XFAParser", function () {
|
||||
scope: "name",
|
||||
use: "",
|
||||
usehref: "",
|
||||
w: 0,
|
||||
w: "",
|
||||
x: 0,
|
||||
y: 0,
|
||||
proto: {
|
||||
|
@ -26,8 +26,10 @@
|
||||
font: inherit;
|
||||
font-kerning: inherit;
|
||||
letter-spacing: inherit;
|
||||
text-align: inherit;
|
||||
text-decoration: inherit;
|
||||
vertical-align: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.xfaFont {
|
||||
@ -43,20 +45,131 @@
|
||||
|
||||
.xfaDraw {
|
||||
z-index: 200;
|
||||
background-color: #ff000080;
|
||||
}
|
||||
|
||||
.xfaExclgroup {
|
||||
z-index: 300;
|
||||
background-color: #0000ff80;
|
||||
}
|
||||
|
||||
.xfaField {
|
||||
z-index: 300;
|
||||
background-color: #00ff0080;
|
||||
}
|
||||
|
||||
.xfaSubform {
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user