Skip to content

Commit

Permalink
XFA - Fix layout issues
Browse files Browse the repository at this point in the history
  - PR mozilla#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.
  • Loading branch information
calixteman committed Jun 16, 2021
1 parent f9a0568 commit d2fdc03
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 215 deletions.
13 changes: 3 additions & 10 deletions src/core/xfa/html_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,16 +337,9 @@ function fixDimensions(node) {
}
}

if (node.layout === "position") {
// Acrobat doesn't take into account min, max values
// for containers with positioned layout (which makes sense).
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);
}
if (node.layout === "table") {
if (node.w === "" && Array.isArray(node.columnWidths)) {
node.w = node.columnWidths.reduce((a, x) => a + x, 0);
}
}
}
Expand Down
206 changes: 167 additions & 39 deletions src/core/xfa/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
* 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";

// Subform and ExclGroup have a layout so they share these functions.
Expand Down Expand Up @@ -146,59 +152,181 @@ function addHTML(node, html, bbox) {

function getAvailableSpace(node) {
const availableSpace = node[$extra].availableSpace;
const marginH = node.margin
const marginV = node.margin
? node.margin.topInset + node.margin.bottomInset
: 0;
const marginH = node.margin
? node.margin.leftInset + node.margin.rightInset
: 0;

switch (node.layout) {
case "lr-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,
};
if (node[$extra].attempt === 0) {
return {
width: availableSpace.width - marginH - node[$extra].currentWidth,
height: availableSpace.height - marginV - node[$extra].prevHeight,
};
}
return {
width: availableSpace.width - marginH,
height: availableSpace.height - marginV - node[$extra].height,
};
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 };
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":
default:
return availableSpace;
}
}

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;
}
// Overflow must stay in the container.
return { width: Infinity, height: Infinity };
case "table":
case "tb":
if (node[$extra].attempt === 0) {
return {
width: availableSpace.width,
height: availableSpace.height - marginH - node[$extra].height,
};
if (attempt !== 1 && node.h !== "" && !node[$isSplittable]()) {
const [, , , h] = getTransformedBBox(node);
if (Math.round(h - space.height) > 1) {
return false;
}
}
// Overflow must stay in the container.
return { width: Infinity, height: Infinity };
return true;
case "position":
default:
if (node[$extra].attempt === 0) {
return availableSpace;
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;
}
// Overflow must stay in the container.
return { width: Infinity, height: Infinity };

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, flushHTML, getAvailableSpace };
export { addHTML, checkDimensions, flushHTML, getAvailableSpace };
Loading

0 comments on commit d2fdc03

Please sign in to comment.