XFA - Convert some template properties into CSS ones (#13082)
- implement few positioning properties: position, width, height, anchor; - implement font element; - implement fill element (used by font) and its children (linear, radial, ...); - font property is inherited from ancestor container (see https://www.pdfa.org/wp-content/uploads/2020/07/XFA-3_3.pdf#page=43) so let CSS handles that stuff; - in order to reduce the number of properties to set, only set non default properties and put the default in CSS; - set a background to some containers to be able to see them (will be removed in a future commit).
This commit is contained in:
parent
9d0ce6e79f
commit
63471bcbbe
@ -13,57 +13,108 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const converters = {
|
import { $toStyle, XFAObject } from "./xfa_object.js";
|
||||||
pt: x => x,
|
import { warn } from "../../shared/util.js";
|
||||||
cm: x => Math.round((x / 2.54) * 72),
|
|
||||||
mm: x => Math.round((x / (10 * 2.54)) * 72),
|
|
||||||
in: x => Math.round(x * 72),
|
|
||||||
};
|
|
||||||
|
|
||||||
function measureToString(m) {
|
function measureToString(m) {
|
||||||
const conv = converters[m.unit];
|
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
|
||||||
if (conv) {
|
|
||||||
return `${conv(m.value)}px`;
|
|
||||||
}
|
|
||||||
return `${m.value}${m.unit}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWidthHeight(node, style) {
|
const converters = {
|
||||||
if (node.w) {
|
anchorType(node, style) {
|
||||||
style.width = measureToString(node.w);
|
if (!("transform" in style)) {
|
||||||
} else {
|
style.transform = "";
|
||||||
if (node.maxW && node.maxW.value > 0) {
|
|
||||||
style.maxWidth = measureToString(node.maxW);
|
|
||||||
}
|
}
|
||||||
if (node.minW && node.minW.value > 0) {
|
switch (node.anchorType) {
|
||||||
style.minWidth = measureToString(node.minW);
|
case "bottomCenter":
|
||||||
|
style.transform += "translate(-50%, -100%)";
|
||||||
|
break;
|
||||||
|
case "bottomLeft":
|
||||||
|
style.transform += "translate(0,-100%)";
|
||||||
|
break;
|
||||||
|
case "bottomRight":
|
||||||
|
style.transform += "translate(-100%,-100%)";
|
||||||
|
break;
|
||||||
|
case "middleCenter":
|
||||||
|
style.transform += "translate(-50%,-50%)";
|
||||||
|
break;
|
||||||
|
case "middleLeft":
|
||||||
|
style.transform += "translate(0,-50%)";
|
||||||
|
break;
|
||||||
|
case "middleRight":
|
||||||
|
style.transform += "translate(-100%,-50%)";
|
||||||
|
break;
|
||||||
|
case "topCenter":
|
||||||
|
style.transform += "translate(-50%,0)";
|
||||||
|
break;
|
||||||
|
case "topRight":
|
||||||
|
style.transform += "translate(-100%,0)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dimensions(node, style) {
|
||||||
|
if (node.w) {
|
||||||
|
style.width = measureToString(node.w);
|
||||||
|
} else {
|
||||||
|
if (node.maxW && node.maxW.value > 0) {
|
||||||
|
style.maxWidth = measureToString(node.maxW);
|
||||||
|
}
|
||||||
|
if (node.minW && node.minW.value > 0) {
|
||||||
|
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) {
|
if (node.maxH && node.maxH.value > 0) {
|
||||||
style.maxHeight = measureToString(node.maxH);
|
style.maxHeight = measureToString(node.maxH);
|
||||||
|
}
|
||||||
|
if (node.minH && node.minH.value > 0) {
|
||||||
|
style.minHeight = measureToString(node.minH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (node.minH && node.minH.value > 0) {
|
},
|
||||||
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rotate(node, style) {
|
||||||
|
if (node.rotate) {
|
||||||
|
if (!("transform" in style)) {
|
||||||
|
style.transform = "";
|
||||||
|
}
|
||||||
|
style.transform += `rotate(-${node.rotate}deg)`;
|
||||||
|
style.transformOrigin = "top left";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function toStyle(node, ...names) {
|
||||||
|
const style = Object.create(null);
|
||||||
|
for (const name of names) {
|
||||||
|
const value = node[name];
|
||||||
|
if (value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (value instanceof XFAObject) {
|
||||||
|
const newStyle = value[$toStyle]();
|
||||||
|
if (newStyle) {
|
||||||
|
Object.assign(style, newStyle);
|
||||||
|
} else {
|
||||||
|
warn(`(DEBUG) - XFA - style for ${name} not implemented yet`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (converters.hasOwnProperty(name)) {
|
||||||
|
converters[name](node, style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPosition(node, style) {
|
export { measureToString, toStyle };
|
||||||
style.transform = "";
|
|
||||||
if (node.rotate) {
|
|
||||||
style.transform = `rotate(-${node.rotate}deg) `;
|
|
||||||
style.transformOrigin = "top left";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.x !== "" || node.y !== "") {
|
|
||||||
style.position = "absolute";
|
|
||||||
style.left = node.x ? measureToString(node.x) : "0pt";
|
|
||||||
style.top = node.y ? measureToString(node.y) : "0pt";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { measureToString, setPosition, setWidthHeight };
|
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
$setSetAttributes,
|
$setSetAttributes,
|
||||||
$setValue,
|
$setValue,
|
||||||
$toHTML,
|
$toHTML,
|
||||||
|
$toStyle,
|
||||||
$uid,
|
$uid,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
Option01,
|
Option01,
|
||||||
@ -50,8 +51,8 @@ import {
|
|||||||
getRelevant,
|
getRelevant,
|
||||||
getStringOption,
|
getStringOption,
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { measureToString, setPosition, setWidthHeight } from "./html_utils.js";
|
import { measureToString, toStyle } from "./html_utils.js";
|
||||||
import { 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;
|
||||||
|
|
||||||
@ -614,6 +615,10 @@ class Color extends XFAObject {
|
|||||||
[$hasSettableValue]() {
|
[$hasSettableValue]() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle]() {
|
||||||
|
return Util.makeHexColor(this.value.r, this.value.g, this.value.b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Comb extends XFAObject {
|
class Comb extends XFAObject {
|
||||||
@ -680,7 +685,7 @@ class ContentArea extends XFAObject {
|
|||||||
children: [],
|
children: [],
|
||||||
attributes: {
|
attributes: {
|
||||||
style,
|
style,
|
||||||
className: "xfa-contentarea",
|
class: "xfaContentarea",
|
||||||
id: this[$uid],
|
id: this[$uid],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -896,10 +901,10 @@ class Draw extends XFAObject {
|
|||||||
"invisible",
|
"invisible",
|
||||||
]);
|
]);
|
||||||
this.relevant = getRelevant(attributes.relevant);
|
this.relevant = getRelevant(attributes.relevant);
|
||||||
this.rotate = getFloat({
|
this.rotate = getInteger({
|
||||||
data: attributes.rotate,
|
data: attributes.rotate,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
validate: x => true,
|
validate: x => x % 90 === 0,
|
||||||
});
|
});
|
||||||
this.use = attributes.use || "";
|
this.use = attributes.use || "";
|
||||||
this.usehref = attributes.usehref || "";
|
this.usehref = attributes.usehref || "";
|
||||||
@ -924,6 +929,38 @@ class Draw extends XFAObject {
|
|||||||
[$setValue](value) {
|
[$setValue](value) {
|
||||||
_setValue(this, value);
|
_setValue(this, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toHTML]() {
|
||||||
|
if (!this.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = toStyle(
|
||||||
|
this,
|
||||||
|
"font",
|
||||||
|
"dimensions",
|
||||||
|
"position",
|
||||||
|
"rotate",
|
||||||
|
"anchorType"
|
||||||
|
);
|
||||||
|
|
||||||
|
const clazz = ["xfaDraw"];
|
||||||
|
if (this.font) {
|
||||||
|
clazz.push("xfaFont");
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = {
|
||||||
|
style,
|
||||||
|
id: this[$uid],
|
||||||
|
class: clazz.join(" "),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "div",
|
||||||
|
attributes,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Edge extends XFAObject {
|
class Edge extends XFAObject {
|
||||||
@ -1269,6 +1306,31 @@ class ExclGroup extends XFAObject {
|
|||||||
field.value[$setValue](nodeBoolean);
|
field.value[$setValue](nodeBoolean);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toHTML]() {
|
||||||
|
if (!this.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = toStyle(this, "dimensions", "position", "anchorType");
|
||||||
|
const attributes = {
|
||||||
|
style,
|
||||||
|
id: this[$uid],
|
||||||
|
class: "xfaExclgroup",
|
||||||
|
};
|
||||||
|
|
||||||
|
const children = this[$childrenToHTML]({
|
||||||
|
// TODO: exObject & exclGroup
|
||||||
|
filter: new Set(["field"]),
|
||||||
|
include: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "div",
|
||||||
|
attributes,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Execute extends XFAObject {
|
class Execute extends XFAObject {
|
||||||
@ -1360,7 +1422,11 @@ class Field extends XFAObject {
|
|||||||
"invisible",
|
"invisible",
|
||||||
]);
|
]);
|
||||||
this.relevant = getRelevant(attributes.relevant);
|
this.relevant = getRelevant(attributes.relevant);
|
||||||
this.rotate = getStringOption(attributes.rotate, ["0", "angle"]);
|
this.rotate = getInteger({
|
||||||
|
data: attributes.rotate,
|
||||||
|
defaultValue: 0,
|
||||||
|
validate: x => x % 90 === 0,
|
||||||
|
});
|
||||||
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);
|
||||||
@ -1394,6 +1460,38 @@ class Field extends XFAObject {
|
|||||||
[$setValue](value) {
|
[$setValue](value) {
|
||||||
_setValue(this, value);
|
_setValue(this, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toHTML]() {
|
||||||
|
if (!this.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = toStyle(
|
||||||
|
this,
|
||||||
|
"font",
|
||||||
|
"dimensions",
|
||||||
|
"position",
|
||||||
|
"rotate",
|
||||||
|
"anchorType"
|
||||||
|
);
|
||||||
|
|
||||||
|
const clazz = ["xfaField"];
|
||||||
|
if (this.font) {
|
||||||
|
clazz.push("xfaFont");
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = {
|
||||||
|
style,
|
||||||
|
id: this[$uid],
|
||||||
|
class: clazz.join(" "),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "div",
|
||||||
|
attributes,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Fill extends XFAObject {
|
class Fill extends XFAObject {
|
||||||
@ -1418,6 +1516,26 @@ class Fill extends XFAObject {
|
|||||||
this.solid = null;
|
this.solid = null;
|
||||||
this.stipple = null;
|
this.stipple = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle]() {
|
||||||
|
let fill = "#000000";
|
||||||
|
for (const name of Object.getOwnPropertyNames(this)) {
|
||||||
|
if (name === "extras" || name === "color") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const obj = this[name];
|
||||||
|
if (!(obj instanceof XFAObject)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fill = obj[$toStyle](this.color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
color: fill,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Filter extends XFAObject {
|
class Filter extends XFAObject {
|
||||||
@ -1522,6 +1640,80 @@ class Font extends XFAObject {
|
|||||||
this.extras = null;
|
this.extras = null;
|
||||||
this.fill = null;
|
this.fill = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle]() {
|
||||||
|
const style = toStyle(this, "fill");
|
||||||
|
if (style.color) {
|
||||||
|
if (!style.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.baselineShift) {
|
||||||
|
style.verticalAlign = measureToString(this.baselineShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fontHorizontalScale
|
||||||
|
// TODO: fontVerticalScale
|
||||||
|
|
||||||
|
if (this.kerningMode !== "none") {
|
||||||
|
style.fontKerning = "normal";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.letterSpacing) {
|
||||||
|
style.letterSpacing = measureToString(this.letterSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lineThrough !== 0) {
|
||||||
|
style.textDecoration = "line-through";
|
||||||
|
if (this.lineThrough === 2) {
|
||||||
|
style.textDecorationStyle = "double";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: lineThroughPeriod
|
||||||
|
|
||||||
|
if (this.overline !== 0) {
|
||||||
|
style.textDecoration = "overline";
|
||||||
|
if (this.overline === 2) {
|
||||||
|
style.textDecorationStyle = "double";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: overlinePeriod
|
||||||
|
|
||||||
|
if (this.posture !== "normal") {
|
||||||
|
style.fontStyle = this.posture;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontSize = measureToString(this.size);
|
||||||
|
if (fontSize !== "10px") {
|
||||||
|
style.fontSize = fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
style.fontFamily = this.typeface;
|
||||||
|
|
||||||
|
if (this.underline !== 0) {
|
||||||
|
style.textDecoration = "underline";
|
||||||
|
if (this.underline === 2) {
|
||||||
|
style.textDecorationStyle = "double";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: underlinePeriod
|
||||||
|
|
||||||
|
if (this.weight !== "normal") {
|
||||||
|
style.fontWeight = this.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Format extends XFAObject {
|
class Format extends XFAObject {
|
||||||
@ -1755,6 +1947,13 @@ class Linear extends XFAObject {
|
|||||||
this.color = null;
|
this.color = null;
|
||||||
this.extras = null;
|
this.extras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle](startColor) {
|
||||||
|
startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
|
||||||
|
const transf = this.type.replace(/([RBLT])/, " $1").toLowerCase();
|
||||||
|
const endColor = this.color ? this.color[$toStyle]() : "#000000";
|
||||||
|
return `linear-gradient(${transf}, ${startColor}, ${endColor})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LockDocument extends ContentObject {
|
class LockDocument extends ContentObject {
|
||||||
@ -1989,11 +2188,11 @@ class PageArea extends XFAObject {
|
|||||||
|
|
||||||
// TODO: handle the case where there are several content areas.
|
// TODO: handle the case where there are several content areas.
|
||||||
const contentArea = children.find(
|
const contentArea = children.find(
|
||||||
node => node.attributes.className === "xfa-contentarea"
|
node => node.attributes.class === "xfaContentarea"
|
||||||
);
|
);
|
||||||
|
|
||||||
const style = Object.create(null);
|
const style = Object.create(null);
|
||||||
if (this.medium && this.medium.short.value && this.medium.long.value) {
|
if (this.medium && this.medium.short && this.medium.long) {
|
||||||
style.width = measureToString(this.medium.short);
|
style.width = measureToString(this.medium.short);
|
||||||
style.height = measureToString(this.medium.long);
|
style.height = measureToString(this.medium.long);
|
||||||
} else {
|
} else {
|
||||||
@ -2133,6 +2332,32 @@ class Pattern extends XFAObject {
|
|||||||
this.color = null;
|
this.color = null;
|
||||||
this.extras = null;
|
this.extras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle](startColor) {
|
||||||
|
startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
|
||||||
|
const endColor = this.color ? this.color[$toStyle]() : "#000000";
|
||||||
|
const width = 5;
|
||||||
|
const cmd = "repeating-linear-gradient";
|
||||||
|
const colors = `${startColor},${startColor} ${width}px,${endColor} ${width}px,${endColor} ${
|
||||||
|
2 * width
|
||||||
|
}px`;
|
||||||
|
switch (this.type) {
|
||||||
|
case "crossHatch":
|
||||||
|
return `${cmd}(to top,${colors}) ${cmd}(to right,${colors})`;
|
||||||
|
case "crossDiagonal":
|
||||||
|
return `${cmd}(45deg,${colors}) ${cmd}(-45deg,${colors})`;
|
||||||
|
case "diagonalLeft":
|
||||||
|
return `${cmd}(45deg,${colors})`;
|
||||||
|
case "diagonalRight":
|
||||||
|
return `${cmd}(-45deg,${colors})`;
|
||||||
|
case "horizontal":
|
||||||
|
return `${cmd}(to top,${colors})`;
|
||||||
|
case "vertical":
|
||||||
|
return `${cmd}(to right,${colors})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Picture extends StringObject {
|
class Picture extends StringObject {
|
||||||
@ -2270,6 +2495,16 @@ class Radial extends XFAObject {
|
|||||||
this.color = null;
|
this.color = null;
|
||||||
this.extras = null;
|
this.extras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle](startColor) {
|
||||||
|
startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
|
||||||
|
const endColor = this.color ? this.color[$toStyle]() : "#000000";
|
||||||
|
const colors =
|
||||||
|
this.type === "toEdge"
|
||||||
|
? `${startColor},${endColor}`
|
||||||
|
: `${endColor},${startColor}`;
|
||||||
|
return `radial-gradient(circle to center, ${colors})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Reason extends StringObject {
|
class Reason extends StringObject {
|
||||||
@ -2393,6 +2628,10 @@ class Solid extends XFAObject {
|
|||||||
this.usehref = attributes.usehref || "";
|
this.usehref = attributes.usehref || "";
|
||||||
this.extras = null;
|
this.extras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle](startColor) {
|
||||||
|
return startColor ? startColor[$toStyle]() : "#FFFFFF";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Speak extends StringObject {
|
class Speak extends StringObject {
|
||||||
@ -2430,6 +2669,15 @@ class Stipple extends XFAObject {
|
|||||||
this.color = null;
|
this.color = null;
|
||||||
this.extras = null;
|
this.extras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle](bgColor) {
|
||||||
|
const alpha = this.rate / 100;
|
||||||
|
return Util.makeHexColor(
|
||||||
|
Math.round(bgColor.value.r * (1 - alpha) + this.value.r * alpha),
|
||||||
|
Math.round(bgColor.value.g * (1 - alpha) + this.value.g * alpha),
|
||||||
|
Math.round(bgColor.value.b * (1 - alpha) + this.value.b * alpha)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Subform extends XFAObject {
|
class Subform extends XFAObject {
|
||||||
@ -2568,17 +2816,16 @@ class Subform extends XFAObject {
|
|||||||
page = pageAreas[pageNumber][$toHTML]();
|
page = pageAreas[pageNumber][$toHTML]();
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = Object.create(null);
|
const style = toStyle(this, "dimensions", "position");
|
||||||
setWidthHeight(this, style);
|
|
||||||
setPosition(this, style);
|
|
||||||
|
|
||||||
const attributes = {
|
const attributes = {
|
||||||
style,
|
style,
|
||||||
id: this[$uid],
|
id: this[$uid],
|
||||||
|
class: "xfaSubform",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.name) {
|
if (this.name) {
|
||||||
attributes["xfa-name"] = this.name;
|
attributes.xfaName = this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = this[$childrenToHTML]({
|
const children = this[$childrenToHTML]({
|
||||||
|
@ -13,7 +13,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const measurementPattern = /([+-]?)([0-9]+\.?[0-9]*)(.*)/;
|
const dimConverters = {
|
||||||
|
pt: x => x,
|
||||||
|
cm: x => (x / 2.54) * 72,
|
||||||
|
mm: x => (x / (10 * 2.54)) * 72,
|
||||||
|
in: x => x * 72,
|
||||||
|
};
|
||||||
|
const measurementPattern = /([+-]?[0-9]+\.?[0-9]*)(.*)/;
|
||||||
|
|
||||||
function getInteger({ data, defaultValue, validate }) {
|
function getInteger({ data, defaultValue, validate }) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@ -67,15 +73,22 @@ function getMeasurement(str, def = "0") {
|
|||||||
if (!match) {
|
if (!match) {
|
||||||
return getMeasurement(def);
|
return getMeasurement(def);
|
||||||
}
|
}
|
||||||
const [, sign, valueStr, unit] = match;
|
const [, valueStr, unit] = match;
|
||||||
const value = parseFloat(valueStr);
|
const value = parseFloat(valueStr);
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
return getMeasurement(def);
|
return getMeasurement(def);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
value: sign === "-" ? -value : value,
|
if (value === 0) {
|
||||||
unit: unit || "pt",
|
return 0;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const conv = dimConverters[unit];
|
||||||
|
if (conv) {
|
||||||
|
return conv(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRatio(data) {
|
function getRatio(data) {
|
||||||
@ -134,7 +147,7 @@ function getColor(data, def = [0, 0, 0]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getBBox(data) {
|
function getBBox(data) {
|
||||||
const def = getMeasurement("-1");
|
const def = -1;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return { x: def, y: def, width: def, height: def };
|
return { x: def, y: def, width: def, height: def };
|
||||||
}
|
}
|
||||||
@ -142,7 +155,7 @@ function getBBox(data) {
|
|||||||
.trim()
|
.trim()
|
||||||
.split(/\s*,\s*/)
|
.split(/\s*,\s*/)
|
||||||
.map(m => getMeasurement(m, "-1"));
|
.map(m => getMeasurement(m, "-1"));
|
||||||
if (bbox.length < 4 || bbox[2].value < 0 || bbox[3].value < 0) {
|
if (bbox.length < 4 || bbox[2] < 0 || bbox[3] < 0) {
|
||||||
return { x: def, y: def, width: def, height: def };
|
return { x: def, y: def, width: def, height: def };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ const $setSetAttributes = Symbol();
|
|||||||
const $setValue = Symbol();
|
const $setValue = Symbol();
|
||||||
const $text = Symbol();
|
const $text = Symbol();
|
||||||
const $toHTML = Symbol();
|
const $toHTML = Symbol();
|
||||||
|
const $toStyle = Symbol();
|
||||||
const $uid = Symbol("uid");
|
const $uid = Symbol("uid");
|
||||||
|
|
||||||
const _applyPrototype = Symbol();
|
const _applyPrototype = Symbol();
|
||||||
@ -259,6 +260,10 @@ class XFAObject {
|
|||||||
return dumped;
|
return dumped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$toStyle]() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
[$toHTML]() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -839,6 +844,7 @@ export {
|
|||||||
$setValue,
|
$setValue,
|
||||||
$text,
|
$text,
|
||||||
$toHTML,
|
$toHTML,
|
||||||
|
$toStyle,
|
||||||
$uid,
|
$uid,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
IntegerObject,
|
IntegerObject,
|
||||||
|
@ -36,9 +36,13 @@ class XfaLayer {
|
|||||||
}
|
}
|
||||||
const stack = [[root, -1, rootHtml]];
|
const stack = [[root, -1, rootHtml]];
|
||||||
|
|
||||||
parameters.div.appendChild(rootHtml);
|
const rootDiv = parameters.div;
|
||||||
|
rootDiv.appendChild(rootHtml);
|
||||||
const coeffs = parameters.viewport.transform.join(",");
|
const coeffs = parameters.viewport.transform.join(",");
|
||||||
parameters.div.style.transform = `matrix(${coeffs})`;
|
rootDiv.style.transform = `matrix(${coeffs})`;
|
||||||
|
|
||||||
|
// Set defaults.
|
||||||
|
rootDiv.setAttribute("class", "xfaLayer xfaFont");
|
||||||
|
|
||||||
while (stack.length > 0) {
|
while (stack.length > 0) {
|
||||||
const [parent, i, html] = stack[stack.length - 1];
|
const [parent, i, html] = stack[stack.length - 1];
|
||||||
|
@ -87,6 +87,7 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/writer_spec.js",
|
"pdfjs-test/unit/writer_spec.js",
|
||||||
"pdfjs-test/unit/xfa_formcalc_spec.js",
|
"pdfjs-test/unit/xfa_formcalc_spec.js",
|
||||||
"pdfjs-test/unit/xfa_parser_spec.js",
|
"pdfjs-test/unit/xfa_parser_spec.js",
|
||||||
|
"pdfjs-test/unit/xfa_tohtml_spec.js",
|
||||||
"pdfjs-test/unit/xml_spec.js",
|
"pdfjs-test/unit/xml_spec.js",
|
||||||
].map(function (moduleName) {
|
].map(function (moduleName) {
|
||||||
// eslint-disable-next-line no-unsanitized/method
|
// eslint-disable-next-line no-unsanitized/method
|
||||||
|
@ -81,9 +81,9 @@ describe("XFAParser", function () {
|
|||||||
};
|
};
|
||||||
const mediumAttributes = {
|
const mediumAttributes = {
|
||||||
id: "",
|
id: "",
|
||||||
long: { value: 0, unit: "pt" },
|
long: 0,
|
||||||
orientation: "portrait",
|
orientation: "portrait",
|
||||||
short: { value: 0, unit: "pt" },
|
short: 0,
|
||||||
stock: "",
|
stock: "",
|
||||||
trayIn: "auto",
|
trayIn: "auto",
|
||||||
trayOut: "auto",
|
trayOut: "auto",
|
||||||
@ -116,17 +116,17 @@ describe("XFAParser", function () {
|
|||||||
allowMacro: 0,
|
allowMacro: 0,
|
||||||
anchorType: "topLeft",
|
anchorType: "topLeft",
|
||||||
colSpan: 1,
|
colSpan: 1,
|
||||||
columnWidths: [{ value: 0, unit: "pt" }],
|
columnWidths: [0],
|
||||||
h: { value: 0, unit: "pt" },
|
h: 0,
|
||||||
hAlign: "left",
|
hAlign: "left",
|
||||||
id: "",
|
id: "",
|
||||||
layout: "position",
|
layout: "position",
|
||||||
locale: "",
|
locale: "",
|
||||||
maxH: { value: 0, unit: "pt" },
|
maxH: 0,
|
||||||
maxW: { value: 0, unit: "pt" },
|
maxW: 0,
|
||||||
mergeMode: "consumeData",
|
mergeMode: "consumeData",
|
||||||
minH: { value: 0, unit: "pt" },
|
minH: 0,
|
||||||
minW: { value: 0, unit: "pt" },
|
minW: 0,
|
||||||
name: "",
|
name: "",
|
||||||
presence: "visible",
|
presence: "visible",
|
||||||
relevant: [],
|
relevant: [],
|
||||||
@ -134,15 +134,15 @@ describe("XFAParser", function () {
|
|||||||
scope: "name",
|
scope: "name",
|
||||||
use: "",
|
use: "",
|
||||||
usehref: "",
|
usehref: "",
|
||||||
w: { value: 0, unit: "pt" },
|
w: 0,
|
||||||
x: { value: 0, unit: "pt" },
|
x: 0,
|
||||||
y: { value: 0, unit: "pt" },
|
y: 0,
|
||||||
proto: {
|
proto: {
|
||||||
area: {
|
area: {
|
||||||
...attributes,
|
...attributes,
|
||||||
colSpan: 1,
|
colSpan: 1,
|
||||||
x: { value: 0, unit: "pt" },
|
x: 0,
|
||||||
y: { value: -3.14, unit: "in" },
|
y: -226.08,
|
||||||
relevant: [
|
relevant: [
|
||||||
{ excluded: true, viewname: "foo" },
|
{ excluded: true, viewname: "foo" },
|
||||||
{ excluded: false, viewname: "bar" },
|
{ excluded: false, viewname: "bar" },
|
||||||
@ -162,19 +162,19 @@ describe("XFAParser", function () {
|
|||||||
{
|
{
|
||||||
...mediumAttributes,
|
...mediumAttributes,
|
||||||
imagingBBox: {
|
imagingBBox: {
|
||||||
x: { value: 1, unit: "pt" },
|
x: 1,
|
||||||
y: { value: 2, unit: "in" },
|
y: 144,
|
||||||
width: { value: 3.4, unit: "cm" },
|
width: 96.3779527559055,
|
||||||
height: { value: 5.67, unit: "px" },
|
height: 5.67,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...mediumAttributes,
|
...mediumAttributes,
|
||||||
imagingBBox: {
|
imagingBBox: {
|
||||||
x: { value: -1, unit: "pt" },
|
x: -1,
|
||||||
y: { value: -1, unit: "pt" },
|
y: -1,
|
||||||
width: { value: -1, unit: "pt" },
|
width: -1,
|
||||||
height: { value: -1, unit: "pt" },
|
height: -1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -288,7 +288,7 @@ describe("XFAParser", function () {
|
|||||||
let font = root.template.subform.field[0].font;
|
let font = root.template.subform.field[0].font;
|
||||||
expect(font.typeface).toEqual("Foo");
|
expect(font.typeface).toEqual("Foo");
|
||||||
expect(font.overline).toEqual(0);
|
expect(font.overline).toEqual(0);
|
||||||
expect(font.size).toEqual({ value: 123, unit: "pt" });
|
expect(font.size).toEqual(123);
|
||||||
expect(font.weight).toEqual("bold");
|
expect(font.weight).toEqual("bold");
|
||||||
expect(font.posture).toEqual("italic");
|
expect(font.posture).toEqual("italic");
|
||||||
expect(font.fill.color.value).toEqual({ r: 1, g: 2, b: 3 });
|
expect(font.fill.color.value).toEqual({ r: 1, g: 2, b: 3 });
|
||||||
@ -297,7 +297,7 @@ describe("XFAParser", function () {
|
|||||||
font = root.template.subform.field[1].font;
|
font = root.template.subform.field[1].font;
|
||||||
expect(font.typeface).toEqual("Foo");
|
expect(font.typeface).toEqual("Foo");
|
||||||
expect(font.overline).toEqual(0);
|
expect(font.overline).toEqual(0);
|
||||||
expect(font.size).toEqual({ value: 456, unit: "pt" });
|
expect(font.size).toEqual(456);
|
||||||
expect(font.weight).toEqual("bold");
|
expect(font.weight).toEqual("bold");
|
||||||
expect(font.posture).toEqual("normal");
|
expect(font.posture).toEqual("normal");
|
||||||
expect(font.fill.color.value).toEqual({ r: 4, g: 5, b: 6 });
|
expect(font.fill.color.value).toEqual({ r: 4, g: 5, b: 6 });
|
||||||
@ -387,7 +387,7 @@ describe("XFAParser", function () {
|
|||||||
|
|
||||||
expect(font.typeface).toEqual("helvetica");
|
expect(font.typeface).toEqual("helvetica");
|
||||||
expect(font.overline).toEqual(0);
|
expect(font.overline).toEqual(0);
|
||||||
expect(font.size).toEqual({ value: 31, unit: "pt" });
|
expect(font.size).toEqual(31);
|
||||||
expect(font.weight).toEqual("normal");
|
expect(font.weight).toEqual("normal");
|
||||||
expect(font.posture).toEqual("italic");
|
expect(font.posture).toEqual("italic");
|
||||||
expect(font.fill.color.value).toEqual({ r: 7, g: 8, b: 9 });
|
expect(font.fill.color.value).toEqual({ r: 7, g: 8, b: 9 });
|
||||||
@ -1005,10 +1005,7 @@ describe("XFAParser", function () {
|
|||||||
).toBe("myfont");
|
).toBe("myfont");
|
||||||
expect(
|
expect(
|
||||||
searchNode(form, form, "Id.LastName.font.size")[0][$text]()
|
searchNode(form, form, "Id.LastName.font.size")[0][$text]()
|
||||||
).toEqual({
|
).toEqual(123.4);
|
||||||
value: 123.4,
|
|
||||||
unit: "pt",
|
|
||||||
});
|
|
||||||
expect(
|
expect(
|
||||||
searchNode(form, form, "Id.LastName.assist.toolTip")[0][$dump]()
|
searchNode(form, form, "Id.LastName.assist.toolTip")[0][$dump]()
|
||||||
.$content
|
.$content
|
||||||
|
98
test/unit/xfa_tohtml_spec.js
Normal file
98
test/unit/xfa_tohtml_spec.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/* Copyright 2020 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { XFAFactory } from "../../src/core/xfa/factory.js";
|
||||||
|
|
||||||
|
describe("XFAFactory", function () {
|
||||||
|
describe("toHTML", function () {
|
||||||
|
it("should convert some basic properties to CSS", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="root" mergeMode="matchTemplate">
|
||||||
|
<pageSet>
|
||||||
|
<pageArea>
|
||||||
|
<contentArea x="123pt" w="456pt" h="789pt"/>
|
||||||
|
<medium stock="default" short="456pt" long="789pt"/>
|
||||||
|
<draw y="1pt" w="11pt" h="22pt" rotate="90" x="2pt">
|
||||||
|
<font size="7pt" typeface="Arial" baselineShift="2pt">
|
||||||
|
<fill>
|
||||||
|
<color value="12,23,34"/>
|
||||||
|
<solid/>
|
||||||
|
</fill>
|
||||||
|
</font>
|
||||||
|
<value/>
|
||||||
|
<margin topInset="1pt" bottomInset="2pt" leftInset="3pt" rightInset="4pt"/>
|
||||||
|
<para spaceAbove="1pt" spaceBelow="2pt" textIndent="3pt" marginLeft="4pt" marginRight="5pt"/>
|
||||||
|
</draw>
|
||||||
|
</pageArea>
|
||||||
|
</pageSet>
|
||||||
|
<subform name="first">
|
||||||
|
</subform>
|
||||||
|
<subform name="second">
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
|
expect(factory.numberPages).toEqual(2);
|
||||||
|
|
||||||
|
const page1 = factory.getPage(0);
|
||||||
|
expect(page1.attributes.style).toEqual({
|
||||||
|
height: "789px",
|
||||||
|
width: "456px",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(page1.children.length).toEqual(2);
|
||||||
|
const container = page1.children[0];
|
||||||
|
expect(container.attributes.class).toEqual("xfaContentarea");
|
||||||
|
expect(container.attributes.style).toEqual({
|
||||||
|
height: "789px",
|
||||||
|
width: "456px",
|
||||||
|
left: "123px",
|
||||||
|
top: "0px",
|
||||||
|
position: "absolute",
|
||||||
|
});
|
||||||
|
|
||||||
|
const draw = page1.children[1];
|
||||||
|
expect(draw.attributes.class).toEqual("xfaDraw xfaFont");
|
||||||
|
expect(draw.attributes.style).toEqual({
|
||||||
|
color: "#0c1722",
|
||||||
|
fontFamily: "Arial",
|
||||||
|
fontSize: "7px",
|
||||||
|
height: "22px",
|
||||||
|
left: "2px",
|
||||||
|
position: "absolute",
|
||||||
|
top: "1px",
|
||||||
|
transform: "rotate(-90deg)",
|
||||||
|
transformOrigin: "top left",
|
||||||
|
verticalAlign: "2px",
|
||||||
|
width: "11px",
|
||||||
|
});
|
||||||
|
|
||||||
|
// draw element must be on each page.
|
||||||
|
expect(draw.attributes.style).toEqual(
|
||||||
|
factory.getPage(1).children[1].attributes.style
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -20,3 +20,43 @@
|
|||||||
z-index: 200;
|
z-index: 200;
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xfaLayer * {
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
font-kerning: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
vertical-align: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xfaFont {
|
||||||
|
color: black;
|
||||||
|
font-weight: normal;
|
||||||
|
font-kerning: none;
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
@ -57,7 +57,6 @@ class XfaLayerBuilder {
|
|||||||
} else {
|
} else {
|
||||||
// Create an xfa layer div and render the form
|
// Create an xfa layer div and render the form
|
||||||
this.div = document.createElement("div");
|
this.div = document.createElement("div");
|
||||||
this.div.className = "xfaLayer";
|
|
||||||
this.pageDiv.appendChild(this.div);
|
this.pageDiv.appendChild(this.div);
|
||||||
parameters.div = this.div;
|
parameters.div = this.div;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user