From 1f6345b6c2e2d4b1aede1fff5db48bb9f4ff526d Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 4 Jun 2021 20:10:35 +0200 Subject: [PATCH] XFA - Display rectangle, line and arc --- src/core/xfa/template.js | 184 ++++++++++++++++++++++++++-- src/display/xfa_layer.js | 8 +- test/pdfs/xfa_fish_licence.pdf.link | 1 + test/pdfs/xfa_imm1295e.pdf.link | 1 + test/test_manifest.json | 16 +++ 5 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 test/pdfs/xfa_fish_licence.pdf.link create mode 100644 test/pdfs/xfa_imm1295e.pdf.link diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index a9eebffc3..54cc73774 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -80,6 +80,7 @@ import { stringToBytes, Util, warn } from "../../shared/util.js"; import { searchNode } from "./som.js"; const TEMPLATE_NS_ID = NamespaceIds.template.id; +const SVG_NS = "http://www.w3.org/2000/svg"; // In case of lr-tb (and rl-tb) layouts, we try: // - to put the container at the end of a line @@ -171,6 +172,10 @@ const NOSPACE = 1; const VALID = 2; function checkDimensions(node, space) { 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) { const area = getRoot(node)[$extra].currentContentArea; if (x + w > area.w) { @@ -227,6 +232,73 @@ class Arc extends XFAObject { this.edge = null; this.fill = null; } + + [$toHTML]() { + const edge = this.edge ? this.edge : new Edge({}); + const edgeStyle = edge[$toStyle](); + const style = Object.create(null); + if (this.fill) { + Object.assign(style, this.fill[$toStyle]()); + } else { + style.fill = "transparent"; + } + style.strokeWidth = measureToString(Math.round(edge.thickness)); + style.stroke = edgeStyle.color; + let arc; + const attributes = { + xmlns: SVG_NS, + style: { + position: "absolute", + width: "100%", + height: "100%", + }, + }; + + if (this.startAngle === 0 && this.sweepAngle === 360) { + arc = { + name: "ellipse", + attributes: { + xmlns: SVG_NS, + cx: "50%", + cy: "50%", + rx: "50%", + ry: "50%", + style, + }, + }; + } else { + const startAngle = (this.startAngle * Math.PI) / 180; + const sweepAngle = (this.sweepAngle * Math.PI) / 180; + const largeArc = this.sweepAngle - this.startAngle > 180 ? 1 : 0; + const [x1, y1, x2, y2] = [ + 50 * (1 + Math.cos(startAngle)), + 50 * (1 - Math.sin(startAngle)), + 50 * (1 + Math.cos(sweepAngle)), + 50 * (1 - Math.sin(sweepAngle)), + ]; + + arc = { + name: "path", + attributes: { + xmlns: SVG_NS, + d: `M ${x1} ${y1} A 50 50 0 ${largeArc} 0 ${x2} ${y2}`, + vectorEffect: "non-scaling-stroke", + style, + }, + }; + + Object.assign(attributes, { + viewBox: "0 0 100 100", + preserveAspectRatio: "none", + }); + } + + return HTMLResult.success({ + name: "svg", + children: [arc], + attributes, + }); + } } class Area extends XFAObject { @@ -1170,7 +1242,7 @@ class Corner extends XFAObject { // 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); + style.radius = measureToString(this.join === "square" ? 0 : this.radius); return style; } } @@ -1412,12 +1484,7 @@ class Draw extends XFAObject { } [$toHTML](availableSpace) { - if ( - this.presence === "hidden" || - this.presence === "inactive" || - this.h === 0 || - this.w === 0 - ) { + if (this.presence === "hidden" || this.presence === "inactive") { return HTMLResult.EMPTY; } @@ -1485,7 +1552,7 @@ class Draw extends XFAObject { } html.children.push(value); - if (value.attributes.class.includes("xfaRich")) { + if (value.attributes.class && value.attributes.class.includes("xfaRich")) { if (this.h === "") { style.height = "auto"; } @@ -2379,6 +2446,9 @@ class Fill extends XFAObject { if (parent instanceof Border) { propName = "background"; } + if (parent instanceof Rectangle) { + propName = "fill"; + } const style = Object.create(null); for (const name of Object.getOwnPropertyNames(this)) { if (name === "extras" || name === "color") { @@ -2853,6 +2923,57 @@ class Line extends XFAObject { this.usehref = attributes.usehref || ""; this.edge = null; } + + [$toHTML]() { + const parent = this[$getParent]()[$getParent](); + const edge = this.edge ? this.edge : new Edge({}); + const edgeStyle = edge[$toStyle](); + const style = Object.create(null); + style.strokeWidth = measureToString(Math.round(edge.thickness)); + style.stroke = edgeStyle.color; + let x1, y1, x2, y2; + let width = "100%"; + let height = "100%"; + + if (parent.w <= edge.thickness) { + [x1, y1, x2, y2] = ["50%", 0, "50%", "100%"]; + width = style.strokeWidth; + } else if (parent.h <= edge.thickness) { + [x1, y1, x2, y2] = [0, "50%", "100%", "50%"]; + height = style.strokeWidth; + } else { + if (this.slope === "\\") { + [x1, y1, x2, y2] = [0, 0, "100%", "100%"]; + } else { + [x1, y1, x2, y2] = [0, "100%", "100%", 0]; + } + } + + const line = { + name: "line", + attributes: { + xmlns: SVG_NS, + x1, + y1, + x2, + y2, + style, + }, + }; + + return HTMLResult.success({ + name: "svg", + children: [line], + attributes: { + xmlns: SVG_NS, + width, + height, + style: { + position: "absolute", + }, + }, + }); + } } class Linear extends XFAObject { @@ -3631,6 +3752,53 @@ class Rectangle extends XFAObject { this.edge = new XFAObjectArray(4); this.fill = null; } + + [$toHTML]() { + const edge = this.edge.children.length + ? this.edge.children[0] + : new Edge({}); + const edgeStyle = edge[$toStyle](); + const style = Object.create(null); + if (this.fill) { + Object.assign(style, this.fill[$toStyle]()); + } else { + style.fill = "transparent"; + } + style.strokeWidth = measureToString(2 * edge.thickness); + style.stroke = edgeStyle.color; + + const corner = this.corner.children.length + ? this.corner.children[0] + : new Corner({}); + const cornerStyle = corner[$toStyle](); + + const rect = { + name: "rect", + attributes: { + xmlns: SVG_NS, + width: "100%", + height: "100%", + x: 0, + y: 0, + rx: cornerStyle.radius, + ry: cornerStyle.radius, + style, + }, + }; + + return HTMLResult.success({ + name: "svg", + children: [rect], + attributes: { + xmlns: SVG_NS, + style: { + position: "absolute", + }, + width: "100%", + height: "100%", + }, + }); + } } class RefElement extends StringObject { diff --git a/src/display/xfa_layer.js b/src/display/xfa_layer.js index e129ca049..7f2ba8650 100644 --- a/src/display/xfa_layer.js +++ b/src/display/xfa_layer.js @@ -163,7 +163,13 @@ class XfaLayer { continue; } - const childHtml = document.createElement(name); + let childHtml; + if (child?.attributes?.xmlns) { + childHtml = document.createElementNS(child.attributes.xmlns, name); + } else { + childHtml = document.createElement(name); + } + html.appendChild(childHtml); if (child.attributes) { this.setAttributes(childHtml, child, storage, intent); diff --git a/test/pdfs/xfa_fish_licence.pdf.link b/test/pdfs/xfa_fish_licence.pdf.link new file mode 100644 index 000000000..e15e253ac --- /dev/null +++ b/test/pdfs/xfa_fish_licence.pdf.link @@ -0,0 +1 @@ +https://web.archive.org/web/20210609130056/https://inspection.canada.ca/DAM/DAM-food-aliments/STAGING/text-texte/c5704_re_1357758804123_eng.pdf diff --git a/test/pdfs/xfa_imm1295e.pdf.link b/test/pdfs/xfa_imm1295e.pdf.link new file mode 100644 index 000000000..dbdfd3cda --- /dev/null +++ b/test/pdfs/xfa_imm1295e.pdf.link @@ -0,0 +1 @@ +https://web.archive.org/web/20210506174920/https://www.canada.ca/content/dam/ircc/migration/ircc/english/pdf/kits/forms/imm1295e.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 4eb7c0cbf..711270d45 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -930,6 +930,14 @@ "link": true, "type": "load" }, + { "id": "xfa_fish_licence", + "file": "pdfs/xfa_fish_licence.pdf", + "md5": "9b993128bbd7f4217098fd44116ebec2", + "link": true, + "rounds": 1, + "enableXfa": true, + "type": "eq" + }, { "id": "issue10272", "file": "pdfs/issue10272.pdf", "md5": "bf3b2f74c6878d38a70dc0825f1b9a02", @@ -4201,6 +4209,14 @@ "type": "eq", "about": "A CIDFontType0 font with a CFF font that isn't actually CID." }, + { "id": "xfa_imm1295e", + "file": "pdfs/xfa_imm1295e.pdf", + "md5": "b995232fda9bb7fa57856ff7f4cda046", + "rounds": 1, + "link": true, + "enableXfa": true, + "type": "eq" + }, { "id": "scorecard_reduced", "file": "pdfs/scorecard_reduced.pdf", "md5": "aa8ed0827092c963eea64adb718a3806",