XFA - Fix layout issues

- PR #13554 is buggy, so this patch aims to fix bugs.
  - check if a component fits into its parent in taking into account the parent layout.
  - introduce method isSplittable for template nodes to know if a component can be splitted in case of overflow.
This commit is contained in:
Calixte Denizet 2021-06-16 16:02:41 +02:00
parent 326226df45
commit df08b1548b
7 changed files with 337 additions and 232 deletions

View File

@ -102,24 +102,12 @@ const converters = {
style.width = measureToString(width); style.width = measureToString(width);
} else { } else {
style.width = "auto"; style.width = "auto";
if (node.maxW > 0) {
style.maxWidth = measureToString(node.maxW);
}
if (parent.layout === "position") {
style.minWidth = measureToString(node.minW);
}
} }
if (height !== "") { if (height !== "") {
style.height = measureToString(height); style.height = measureToString(height);
} else { } else {
style.height = "auto"; style.height = "auto";
if (node.maxH > 0) {
style.maxHeight = measureToString(node.maxH);
}
if (parent.layout === "position") {
style.minHeight = measureToString(node.minH);
}
} }
}, },
position(node, style) { position(node, style) {
@ -188,6 +176,20 @@ const converters = {
}, },
}; };
function setMinMaxDimensions(node, style) {
const parent = node[$getParent]();
if (parent.layout === "position") {
style.minWidth = measureToString(node.minW);
if (node.maxW) {
style.maxWidth = measureToString(node.maxW);
}
style.minHeight = measureToString(node.minH);
if (node.maxH) {
style.maxHeight = measureToString(node.maxH);
}
}
}
function layoutText(text, xfaFont, fonts, width) { function layoutText(text, xfaFont, fonts, width) {
const measure = new TextMeasure(xfaFont, fonts); const measure = new TextMeasure(xfaFont, fonts);
if (typeof text === "string") { if (typeof text === "string") {
@ -283,16 +285,9 @@ function fixDimensions(node) {
} }
} }
if (node.layout === "position") { if (node.layout === "table") {
// Acrobat doesn't take into account min, max values if (node.w === "" && Array.isArray(node.columnWidths)) {
// for containers with positioned layout (which makes sense). node.w = node.columnWidths.reduce((a, x) => a + x, 0);
node.minW = node.minH = 0;
node.maxW = node.maxH = Infinity;
} else {
if (node.layout === "table") {
if (node.w === "" && Array.isArray(node.columnWidths)) {
node.w = node.columnWidths.reduce((a, x) => a + x, 0);
}
} }
} }
} }
@ -471,5 +466,6 @@ export {
layoutClass, layoutClass,
layoutText, layoutText,
measureToString, measureToString,
setMinMaxDimensions,
toStyle, toStyle,
}; };

View File

@ -13,7 +13,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { $extra, $flushHTML } from "./xfa_object.js"; import {
$extra,
$flushHTML,
$getParent,
$getTemplateRoot,
$isSplittable,
} from "./xfa_object.js";
import { measureToString } from "./html_utils.js"; import { measureToString } from "./html_utils.js";
// Subform and ExclGroup have a layout so they share these functions. // Subform and ExclGroup have a layout so they share these functions.
@ -146,59 +152,181 @@ function addHTML(node, html, bbox) {
function getAvailableSpace(node) { function getAvailableSpace(node) {
const availableSpace = node[$extra].availableSpace; const availableSpace = node[$extra].availableSpace;
const marginH = node.margin const marginV = node.margin
? node.margin.topInset + node.margin.bottomInset ? node.margin.topInset + node.margin.bottomInset
: 0; : 0;
const marginH = node.margin
? node.margin.leftInset + node.margin.rightInset
: 0;
switch (node.layout) { switch (node.layout) {
case "lr-tb": case "lr-tb":
case "rl-tb": case "rl-tb":
switch (node[$extra].attempt) {
case 0:
return {
width: availableSpace.width - node[$extra].currentWidth,
height: availableSpace.height - marginH - node[$extra].prevHeight,
};
case 1:
return {
width: availableSpace.width,
height: availableSpace.height - marginH - node[$extra].height,
};
default:
// Overflow must stay in the container.
return {
width: Infinity,
height: Infinity,
};
}
case "rl-row":
case "row":
if (node[$extra].attempt === 0) {
const width = node[$extra].columnWidths
.slice(node[$extra].currentColumn)
.reduce((a, x) => a + x);
return { width, height: availableSpace.height - marginH };
}
// Overflow must stay in the container.
return { width: Infinity, height: Infinity };
case "table":
case "tb":
if (node[$extra].attempt === 0) { if (node[$extra].attempt === 0) {
return { return {
width: availableSpace.width, width: availableSpace.width - marginH - node[$extra].currentWidth,
height: availableSpace.height - marginH - node[$extra].height, height: availableSpace.height - marginV - node[$extra].prevHeight,
}; };
} }
// Overflow must stay in the container. return {
return { width: Infinity, height: Infinity }; width: availableSpace.width - marginH,
height: availableSpace.height - marginV - node[$extra].height,
};
case "rl-row":
case "row":
const width = node[$extra].columnWidths
.slice(node[$extra].currentColumn)
.reduce((a, x) => a + x);
return { width, height: availableSpace.height - marginH };
case "table":
case "tb":
return {
width: availableSpace.width - marginH,
height: availableSpace.height - marginV - node[$extra].height,
};
case "position": case "position":
default: default:
if (node[$extra].attempt === 0) { return availableSpace;
return availableSpace;
}
// Overflow must stay in the container.
return { width: Infinity, height: Infinity };
} }
} }
export { addHTML, flushHTML, getAvailableSpace }; function getTransformedBBox(node) {
// Take into account rotation and anchor the get the
// real bounding box.
let w = node.w === "" ? NaN : node.w;
let h = node.h === "" ? NaN : node.h;
let [centerX, centerY] = [0, 0];
switch (node.anchorType || "") {
case "bottomCenter":
[centerX, centerY] = [w / 2, h];
break;
case "bottomLeft":
[centerX, centerY] = [0, h];
break;
case "bottomRight":
[centerX, centerY] = [w, h];
break;
case "middleCenter":
[centerX, centerY] = [w / 2, h / 2];
break;
case "middleLeft":
[centerX, centerY] = [0, h / 2];
break;
case "middleRight":
[centerX, centerY] = [w, h / 2];
break;
case "topCenter":
[centerX, centerY] = [w / 2, 0];
break;
case "topRight":
[centerX, centerY] = [w, 0];
break;
}
let x;
let y;
switch (node.rotate || 0) {
case 0:
[x, y] = [-centerX, -centerY];
break;
case 90:
[x, y] = [-centerY, centerX];
[w, h] = [h, -w];
break;
case 180:
[x, y] = [centerX, centerY];
[w, h] = [-w, -h];
break;
case 270:
[x, y] = [centerY, -centerX];
[w, h] = [-h, w];
break;
}
return [
node.x + x + Math.min(0, w),
node.y + y + Math.min(0, h),
Math.abs(w),
Math.abs(h),
];
}
/**
* Returning true means that the node will be layed out
* else the layout will go to its next step (changing of line
* in case of lr-tb or changing content area...).
*/
function checkDimensions(node, space) {
if (node.w === 0 || node.h === 0) {
return true;
}
if (space.width <= 0 || space.height <= 0) {
return false;
}
const parent = node[$getParent]();
const attempt = (node[$extra] && node[$extra].attempt) || 0;
switch (parent.layout) {
case "lr-tb":
case "rl-tb":
switch (attempt) {
case 0: {
let w, h;
if (node.w !== "" || node.h !== "") {
[, , w, h] = getTransformedBBox(node);
}
if (node.h !== "" && Math.round(h - space.height) > 1) {
return false;
}
if (node.w !== "") {
return Math.round(w - space.width) <= 1;
}
return node.minW <= space.width;
}
case 1: {
if (node.h !== "" && !node[$isSplittable]()) {
const [, , , h] = getTransformedBBox(node);
if (Math.round(h - space.height) > 1) {
return false;
}
}
return true;
}
default:
return true;
}
case "table":
case "tb":
if (attempt !== 1 && node.h !== "" && !node[$isSplittable]()) {
const [, , , h] = getTransformedBBox(node);
if (Math.round(h - space.height) > 1) {
return false;
}
}
return true;
case "position":
const [x, y, w, h] = getTransformedBBox(node);
const isWidthOk = node.w === "" || Math.round(w + x - space.width) <= 1;
const isHeightOk = node.h === "" || Math.round(h + y - space.height) <= 1;
if (isWidthOk && isHeightOk) {
return true;
}
const area = node[$getTemplateRoot]()[$extra].currentContentArea;
if (isWidthOk) {
return h + y > area.h;
}
return w + x > area.w;
case "rl-row":
case "row":
default:
// No layout, so accept everything.
return true;
}
}
export { addHTML, checkDimensions, flushHTML, getAvailableSpace };

View File

@ -30,10 +30,12 @@ import {
$getNextPage, $getNextPage,
$getParent, $getParent,
$getSubformParent, $getSubformParent,
$getTemplateRoot,
$hasItem, $hasItem,
$hasSettableValue, $hasSettableValue,
$ids, $ids,
$isCDATAXml, $isCDATAXml,
$isSplittable,
$isTransparent, $isTransparent,
$namespaceId, $namespaceId,
$nodeName, $nodeName,
@ -55,7 +57,12 @@ import {
XFAObjectArray, XFAObjectArray,
} from "./xfa_object.js"; } from "./xfa_object.js";
import { $buildXFAObject, NamespaceIds } from "./namespaces.js"; import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
import { addHTML, flushHTML, getAvailableSpace } from "./layout.js"; import {
addHTML,
checkDimensions,
flushHTML,
getAvailableSpace,
} from "./layout.js";
import { import {
computeBbox, computeBbox,
createWrapper, createWrapper,
@ -65,6 +72,7 @@ import {
layoutClass, layoutClass,
layoutText, layoutText,
measureToString, measureToString,
setMinMaxDimensions,
toStyle, toStyle,
} from "./html_utils.js"; } from "./html_utils.js";
import { import {
@ -107,14 +115,6 @@ function _setValue(templateNode, value) {
templateNode.value[$setValue](value); templateNode.value[$setValue](value);
} }
function getRoot(node) {
let parent = node[$getParent]();
while (!(parent instanceof Template)) {
parent = parent[$getParent]();
}
return parent;
}
function* getContainedChildren(node) { function* getContainedChildren(node) {
for (const child of node[$getChildren]()) { for (const child of node[$getChildren]()) {
if (child instanceof SubformSet) { if (child instanceof SubformSet) {
@ -136,90 +136,6 @@ function valueToHtml(value) {
}); });
} }
function getTransformedBBox(node) {
// Take into account rotation and anchor the get the
// real bounding box.
let w = node.w === "" ? NaN : node.w;
let h = node.h === "" ? NaN : node.h;
let [centerX, centerY] = [0, 0];
switch (node.anchorType || "") {
case "bottomCenter":
[centerX, centerY] = [w / 2, h];
break;
case "bottomLeft":
[centerX, centerY] = [0, h];
break;
case "bottomRight":
[centerX, centerY] = [w, h];
break;
case "middleCenter":
[centerX, centerY] = [w / 2, h / 2];
break;
case "middleLeft":
[centerX, centerY] = [0, h / 2];
break;
case "middleRight":
[centerX, centerY] = [w, h / 2];
break;
case "topCenter":
[centerX, centerY] = [w / 2, 0];
break;
case "topRight":
[centerX, centerY] = [w, 0];
break;
}
let x;
let y;
switch (node.rotate || 0) {
case 0:
[x, y] = [-centerX, -centerY];
break;
case 90:
[x, y] = [-centerY, centerX];
[w, h] = [h, -w];
break;
case 180:
[x, y] = [centerX, centerY];
[w, h] = [-w, -h];
break;
case 270:
[x, y] = [centerY, -centerX];
[w, h] = [-h, w];
break;
}
return [
node.x + x + Math.min(0, w),
node.y + y + Math.min(0, h),
Math.abs(w),
Math.abs(h),
];
}
const NOTHING = 0;
const NOSPACE = 1;
const VALID = 2;
function checkDimensions(node, space) {
if (node[$getParent]().layout === "position") {
return VALID;
}
const [x, y, w, h] = getTransformedBBox(node);
if (node.w === 0 || node.h === 0) {
return VALID;
}
if (node.w !== "" && Math.round(x + w - space.width) > 1) {
return NOSPACE;
}
if (node.h !== "" && Math.round(y + h - space.height) > 1) {
return NOSPACE;
}
return VALID;
}
class AppearanceFilter extends StringObject { class AppearanceFilter extends StringObject {
constructor(attributes) { constructor(attributes) {
super(TEMPLATE_NS_ID, "appearanceFilter"); super(TEMPLATE_NS_ID, "appearanceFilter");
@ -1525,7 +1441,7 @@ class Draw extends XFAObject {
if ((this.w === "" || this.h === "") && this.value) { if ((this.w === "" || this.h === "") && this.value) {
const maxWidth = this.w === "" ? availableSpace.width : this.w; const maxWidth = this.w === "" ? availableSpace.width : this.w;
const fonts = getRoot(this)[$fonts]; const fonts = this[$getTemplateRoot]()[$fonts];
let font = this.font; let font = this.font;
if (!font) { if (!font) {
let parent = this[$getParent](); let parent = this[$getParent]();
@ -1571,13 +1487,8 @@ class Draw extends XFAObject {
} }
} }
switch (checkDimensions(this, availableSpace)) { if (!checkDimensions(this, availableSpace)) {
case NOTHING: return HTMLResult.FAILURE;
return HTMLResult.EMPTY;
case NOSPACE:
return HTMLResult.FAILURE;
default:
break;
} }
const style = toStyle( const style = toStyle(
@ -1593,6 +1504,8 @@ class Draw extends XFAObject {
"margin" "margin"
); );
setMinMaxDimensions(this, style);
const classNames = ["xfaDraw"]; const classNames = ["xfaDraw"];
if (this.font) { if (this.font) {
classNames.push("xfaFont"); classNames.push("xfaFont");
@ -2062,6 +1975,27 @@ class ExclGroup extends XFAObject {
} }
} }
[$isSplittable]() {
// We cannot cache the result here because the contentArea
// can change.
const root = this[$getTemplateRoot]();
const contentArea = root[$extra].currentContentArea;
if (contentArea && Math.max(this.minH, this.h || 0) >= contentArea.h) {
return true;
}
if (this.layout === "position") {
return false;
}
const parentLayout = this[$getParent]().layout;
if (parentLayout && parentLayout.includes("row")) {
return false;
}
return true;
}
[$flushHTML]() { [$flushHTML]() {
return flushHTML(this); return flushHTML(this);
} }
@ -2106,13 +2040,8 @@ class ExclGroup extends XFAObject {
currentWidth: 0, currentWidth: 0,
}); });
switch (checkDimensions(this, availableSpace)) { if (!checkDimensions(this, availableSpace)) {
case NOTHING: return HTMLResult.FAILURE;
return HTMLResult.EMPTY;
case NOSPACE:
return HTMLResult.FAILURE;
default:
break;
} }
availableSpace = { availableSpace = {
@ -2172,7 +2101,7 @@ class ExclGroup extends XFAObject {
} }
} }
failure = this[$extra].attempt === 2; failure = this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT;
} else { } else {
const result = this[$childrenToHTML]({ const result = this[$childrenToHTML]({
filter, filter,
@ -2185,12 +2114,16 @@ class ExclGroup extends XFAObject {
} }
if (failure) { if (failure) {
if (this.layout === "position") { if (this[$isSplittable]()) {
delete this[$extra]; delete this[$extra];
} }
return HTMLResult.FAILURE; return HTMLResult.FAILURE;
} }
if (children.length === 0) {
return HTMLResult.EMPTY;
}
let marginH = 0; let marginH = 0;
let marginV = 0; let marginV = 0;
if (this.margin) { if (this.margin) {
@ -2198,11 +2131,15 @@ class ExclGroup extends XFAObject {
marginV = this.margin.topInset + this.margin.bottomInset; marginV = this.margin.topInset + this.margin.bottomInset;
} }
const width = Math.max(this[$extra].width + marginH, this.w || 0);
const height = Math.max(this[$extra].height + marginV, this.h || 0);
const bbox = [this.x, this.y, width, height];
if (this.w === "") { if (this.w === "") {
style.width = measureToString(this[$extra].width + marginH); style.width = measureToString(width);
} }
if (this.h === "") { if (this.h === "") {
style.height = measureToString(this[$extra].height + marginV); style.height = measureToString(height);
} }
const html = { const html = {
@ -2211,16 +2148,6 @@ class ExclGroup extends XFAObject {
children, children,
}; };
let bbox;
if (this.w !== "" && this.h !== "") {
bbox = [this.x, this.y, this.w, this.h];
} else {
const width = this.w === "" ? marginH + this[$extra].width : this.w;
const height = this.h === "" ? marginV + this[$extra].height : this.h;
bbox = [this.x, this.y, width, height];
}
delete this[$extra]; delete this[$extra];
return HTMLResult.success(createWrapper(this, html), bbox); return HTMLResult.success(createWrapper(this, html), bbox);
@ -2372,13 +2299,8 @@ class Field extends XFAObject {
fixDimensions(this); fixDimensions(this);
switch (checkDimensions(this, availableSpace)) { if (!checkDimensions(this, availableSpace)) {
case NOTHING: return HTMLResult.FAILURE;
return HTMLResult.EMPTY;
case NOSPACE:
return HTMLResult.FAILURE;
default:
break;
} }
const style = toStyle( const style = toStyle(
@ -2393,6 +2315,8 @@ class Field extends XFAObject {
"hAlign" "hAlign"
); );
setMinMaxDimensions(this, style);
const classNames = ["xfaField"]; const classNames = ["xfaField"];
// If no font, font properties are inherited. // If no font, font properties are inherited.
if (this.font) { if (this.font) {
@ -3374,7 +3298,7 @@ class PageArea extends XFAObject {
} }
[$getAvailableSpace]() { [$getAvailableSpace]() {
return { width: Infinity, height: Infinity }; return this[$extra].space || { width: 0, height: 0 };
} }
[$toHTML]() { [$toHTML]() {
@ -3392,10 +3316,18 @@ class PageArea extends XFAObject {
if (this.medium && this.medium.short && this.medium.long) { 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);
this[$extra].space = {
width: this.medium.short,
height: this.medium.long,
};
if (this.medium.orientation === "landscape") { if (this.medium.orientation === "landscape") {
const x = style.width; const x = style.width;
style.width = style.height; style.width = style.height;
style.height = x; style.height = x;
this[$extra].space = {
width: this.medium.long,
height: this.medium.short,
};
} }
} else { } else {
warn("XFA - No medium specified in pageArea: please file a bug."); warn("XFA - No medium specified in pageArea: please file a bug.");
@ -3486,7 +3418,7 @@ class PageSet extends XFAObject {
return this[$getNextPage](); return this[$getNextPage]();
} }
const pageNumber = getRoot(this)[$extra].pageNumber; const pageNumber = this[$getTemplateRoot]()[$extra].pageNumber;
const parity = pageNumber % 2 === 0 ? "even" : "odd"; const parity = pageNumber % 2 === 0 ? "even" : "odd";
const position = pageNumber === 0 ? "first" : "rest"; const position = pageNumber === 0 ? "first" : "rest";
@ -4175,6 +4107,36 @@ class Subform extends XFAObject {
return getAvailableSpace(this); return getAvailableSpace(this);
} }
[$isSplittable](x) {
// We cannot cache the result here because the contentArea
// can change.
const root = this[$getTemplateRoot]();
const contentArea = root[$extra].currentContentArea;
if (contentArea && Math.max(this.minH, this.h || 0) >= contentArea.h) {
return true;
}
if (this.layout === "position") {
return false;
}
if (this.keep && this.keep.intact !== "none") {
return false;
}
const parentLayout = this[$getParent]().layout;
if (parentLayout && parentLayout.includes("row")) {
return false;
}
if (this.overflow && this.overflow.target) {
const target = root[$searchNode](this.overflow.target, this);
return target && target[0] === contentArea;
}
return true;
}
[$toHTML](availableSpace) { [$toHTML](availableSpace) {
if (this.presence === "hidden" || this.presence === "inactive") { if (this.presence === "hidden" || this.presence === "inactive") {
return HTMLResult.EMPTY; return HTMLResult.EMPTY;
@ -4201,9 +4163,7 @@ class Subform extends XFAObject {
} }
if (this[$extra] && this[$extra].afterBreakAfter) { if (this[$extra] && this[$extra].afterBreakAfter) {
const result = this[$extra].afterBreakAfter; return HTMLResult.EMPTY;
delete this[$extra];
return result;
} }
// TODO: incomplete. // TODO: incomplete.
@ -4228,21 +4188,8 @@ class Subform extends XFAObject {
currentWidth: 0, currentWidth: 0,
}); });
switch (checkDimensions(this, availableSpace)) { if (!checkDimensions(this, availableSpace)) {
case NOTHING: return HTMLResult.FAILURE;
return HTMLResult.EMPTY;
case NOSPACE:
return HTMLResult.FAILURE;
default:
break;
}
let noBreakOnOverflow = false;
if (this.overflow && this.overflow.target) {
const root = getRoot(this);
const target = root[$searchNode](this.overflow.target, this);
noBreakOnOverflow =
target && target[0] === root[$extra].currentContentArea;
} }
const filter = new Set([ const filter = new Set([
@ -4285,6 +4232,8 @@ class Subform extends XFAObject {
attributes.xfaName = this.name; attributes.xfaName = this.name;
} }
const isSplittable = this[$isSplittable]();
// If the container overflows into itself we add an extra // If the container overflows into itself we add an extra
// layout step to accept finally the element which caused // layout step to accept finally the element which caused
// the overflow. // the overflow.
@ -4292,7 +4241,7 @@ class Subform extends XFAObject {
this.layout === "lr-tb" || this.layout === "rl-tb" this.layout === "lr-tb" || this.layout === "rl-tb"
? MAX_ATTEMPTS_FOR_LRTB_LAYOUT ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT
: 1; : 1;
maxRun += noBreakOnOverflow ? 1 : 0; maxRun += !isSplittable && this.layout !== "position" ? 1 : 0;
for (; this[$extra].attempt < maxRun; this[$extra].attempt++) { for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
const result = this[$childrenToHTML]({ const result = this[$childrenToHTML]({
filter, filter,
@ -4308,15 +4257,21 @@ class Subform extends XFAObject {
if (this[$extra].attempt === maxRun) { if (this[$extra].attempt === maxRun) {
if (this.overflow) { if (this.overflow) {
getRoot(this)[$extra].overflowNode = this.overflow; this[$getTemplateRoot]()[$extra].overflowNode = this.overflow;
} }
if (this.layout === "position") { if (!isSplittable) {
// Since a new try will happen in a new container with maybe
// new dimensions, we invalidate already layed out components.
delete this[$extra]; delete this[$extra];
} }
return HTMLResult.FAILURE; return HTMLResult.FAILURE;
} }
if (children.length === 0) {
return HTMLResult.EMPTY;
}
let marginH = 0; let marginH = 0;
let marginV = 0; let marginV = 0;
if (this.margin) { if (this.margin) {
@ -4324,11 +4279,15 @@ class Subform extends XFAObject {
marginV = this.margin.topInset + this.margin.bottomInset; marginV = this.margin.topInset + this.margin.bottomInset;
} }
const width = Math.max(this[$extra].width + marginH, this.w || 0);
const height = Math.max(this[$extra].height + marginV, this.h || 0);
const bbox = [this.x, this.y, width, height];
if (this.w === "") { if (this.w === "") {
style.width = measureToString(this[$extra].width + marginH); style.width = measureToString(width);
} }
if (this.h === "") { if (this.h === "") {
style.height = measureToString(this[$extra].height + marginV); style.height = measureToString(height);
} }
const html = { const html = {
@ -4337,16 +4296,6 @@ class Subform extends XFAObject {
children, children,
}; };
let bbox;
if (this.w !== "" && this.h !== "") {
bbox = [this.x, this.y, this.w, this.h];
} else {
const width = this.w === "" ? marginH + this[$extra].width : this.w;
const height = this.h === "" ? marginV + this[$extra].height : this.h;
bbox = [this.x, this.y, width, height];
}
const result = HTMLResult.success(createWrapper(this, html), bbox); const result = HTMLResult.success(createWrapper(this, html), bbox);
if (this.breakAfter.children.length >= 1) { if (this.breakAfter.children.length >= 1) {
@ -4605,12 +4554,12 @@ class Template extends XFAObject {
mainHtml.children.push(page); mainHtml.children.push(page);
if (leader) { if (leader) {
page.children.push(leader[$toHTML](page[$extra].space).html); page.children.push(leader[$toHTML](pageArea[$extra].space).html);
leader = null; leader = null;
} }
if (trailer) { if (trailer) {
page.children.push(trailer[$toHTML](page[$extra].space).html); page.children.push(trailer[$toHTML](pageArea[$extra].space).html);
trailer = null; trailer = null;
} }
@ -4646,7 +4595,10 @@ class Template extends XFAObject {
const html = root[$toHTML](space); const html = root[$toHTML](space);
if (html.success) { if (html.success) {
if (html.html) { if (html.html) {
hasSomething = true;
htmlContentAreas[i].children.push(html.html); htmlContentAreas[i].children.push(html.html);
} else if (!hasSomething) {
mainHtml.children.pop();
} }
return mainHtml; return mainHtml;
} }

View File

@ -47,7 +47,7 @@ const $getContainedChildren = Symbol();
const $getNextPage = Symbol(); const $getNextPage = Symbol();
const $getSubformParent = Symbol(); const $getSubformParent = Symbol();
const $getParent = Symbol(); const $getParent = Symbol();
const $pushGlyphs = Symbol(); const $getTemplateRoot = Symbol();
const $global = Symbol(); const $global = Symbol();
const $hasItem = Symbol(); const $hasItem = Symbol();
const $hasSettableValue = Symbol(); const $hasSettableValue = Symbol();
@ -57,6 +57,7 @@ const $insertAt = Symbol();
const $isCDATAXml = Symbol(); const $isCDATAXml = Symbol();
const $isDataValue = Symbol(); const $isDataValue = Symbol();
const $isDescendent = Symbol(); const $isDescendent = Symbol();
const $isSplittable = Symbol();
const $isTransparent = Symbol(); const $isTransparent = Symbol();
const $lastAttribute = Symbol(); const $lastAttribute = Symbol();
const $namespaceId = Symbol("namespaceId"); const $namespaceId = Symbol("namespaceId");
@ -65,6 +66,7 @@ const $nsAttributes = Symbol();
const $onChild = Symbol(); const $onChild = Symbol();
const $onChildCheck = Symbol(); const $onChildCheck = Symbol();
const $onText = Symbol(); const $onText = Symbol();
const $pushGlyphs = Symbol();
const $removeChild = Symbol(); const $removeChild = Symbol();
const $root = Symbol("root"); const $root = Symbol("root");
const $resolvePrototypes = Symbol(); const $resolvePrototypes = Symbol();
@ -162,6 +164,18 @@ class XFAObject {
} }
} }
[$getTemplateRoot]() {
let parent = this[$getParent]();
while (parent[$nodeName] !== "template") {
parent = parent[$getParent]();
}
return parent;
}
[$isSplittable]() {
return false;
}
[$appendChild](child) { [$appendChild](child) {
child[_parent] = this; child[_parent] = this;
this[_children].push(child); this[_children].push(child);
@ -985,6 +999,7 @@ export {
$getParent, $getParent,
$getRealChildrenByNameIt, $getRealChildrenByNameIt,
$getSubformParent, $getSubformParent,
$getTemplateRoot,
$global, $global,
$hasItem, $hasItem,
$hasSettableValue, $hasSettableValue,
@ -994,6 +1009,7 @@ export {
$isCDATAXml, $isCDATAXml,
$isDataValue, $isDataValue,
$isDescendent, $isDescendent,
$isSplittable,
$isTransparent, $isTransparent,
$namespaceId, $namespaceId,
$nodeName, $nodeName,

View File

@ -0,0 +1 @@
https://web.archive.org/web/20210509141453/https://www.sos.state.oh.us/globalassets/elections/directives/2020/dir2020-15_independentcandidatepetitions2020signedextended.pdf

View File

@ -946,6 +946,14 @@
"enableXfa": true, "enableXfa": true,
"type": "eq" "type": "eq"
}, },
{ "id": "xfa_candidate_petitions",
"file": "pdfs/xfa_candidate_petitions.pdf",
"md5": "0db96a00667f8f58f94cf81022e69341",
"link": true,
"rounds": 1,
"enableXfa": true,
"type": "eq"
},
{ "id": "xfa_annual_expense_report", { "id": "xfa_annual_expense_report",
"file": "pdfs/xfa_annual_expense_report.pdf", "file": "pdfs/xfa_annual_expense_report.pdf",
"md5": "06866e7a6bbc0346789208ef5f6e885c", "md5": "06866e7a6bbc0346789208ef5f6e885c",

View File

@ -60,6 +60,9 @@ describe("XFAFactory", function () {
</subform> </subform>
<subform name="second"> <subform name="second">
<breakBefore targetType="pageArea" startNew="1"/> <breakBefore targetType="pageArea" startNew="1"/>
<subform>
<draw w="1pt" h="1pt"><value><text>foo</text></value></draw>
</subform>
</subform> </subform>
</subform> </subform>
</template> </template>
@ -133,7 +136,7 @@ describe("XFAFactory", function () {
<subform name="root" mergeMode="matchTemplate"> <subform name="root" mergeMode="matchTemplate">
<pageSet> <pageSet>
<pageArea> <pageArea>
<contentArea x="123pt" w="456pt" h="789pt"/> <contentArea x="0pt" w="456pt" h="789pt"/>
<medium stock="default" short="456pt" long="789pt"/> <medium stock="default" short="456pt" long="789pt"/>
<field y="1pt" w="11pt" h="22pt" x="2pt"> <field y="1pt" w="11pt" h="22pt" x="2pt">
<ui> <ui>
@ -146,6 +149,7 @@ describe("XFAFactory", function () {
</pageArea> </pageArea>
</pageSet> </pageSet>
<subform name="first"> <subform name="first">
<draw w="1pt" h="1pt"><value><text>foo</text></value></draw>
</subform> </subform>
</subform> </subform>
</template> </template>