From 56a75f8b265b1ca87350382f10d4891f0a7c810a Mon Sep 17 00:00:00 2001
From: calixteman <cdenizet@mozilla.com>
Date: Mon, 21 Jun 2021 16:02:33 +0200
Subject: [PATCH] Revert "Revert "XFA - Fix the way to select page on
 breaking"" - and fix the error which caused the backout: add an $extra
 property when creating html. - switch to next content area when breaking on
 page area.

---
 src/core/xfa/template.js          | 133 ++++++++++++++++++++----------
 src/core/xfa/xfa_object.js        |   4 +
 test/pdfs/xfa_bug1716838.pdf.link |   1 +
 test/test_manifest.json           |   8 ++
 test/unit/xfa_tohtml_spec.js      |   4 +
 5 files changed, 106 insertions(+), 44 deletions(-)
 create mode 100644 test/pdfs/xfa_bug1716838.pdf.link

diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js
index 93d635929..5eb7ed21e 100644
--- a/src/core/xfa/template.js
+++ b/src/core/xfa/template.js
@@ -19,6 +19,7 @@ import {
   $appendChild,
   $childrenToHTML,
   $clean,
+  $cleanPage,
   $content,
   $extra,
   $finalize,
@@ -37,6 +38,7 @@ import {
   $isCDATAXml,
   $isSplittable,
   $isTransparent,
+  $isUsable,
   $namespaceId,
   $nodeName,
   $onChild,
@@ -2398,8 +2400,10 @@ class Field extends XFAObject {
 
     const caption = this.caption ? this.caption[$toHTML]().html : null;
     if (!caption) {
-      // Even if no caption this class will help to center the ui.
-      ui.attributes.class.push("xfaLeft");
+      if (ui.attributes.class) {
+        // Even if no caption this class will help to center the ui.
+        ui.attributes.class.push("xfaLeft");
+      }
       return HTMLResult.success(createWrapper(this, html), bbox);
     }
 
@@ -2633,13 +2637,8 @@ class Font extends XFAObject {
     // TODO: fontHorizontalScale
     // TODO: fontVerticalScale
 
-    if (this.kerningMode !== "none") {
-      style.fontKerning = "normal";
-    }
-
-    if (this.letterSpacing) {
-      style.letterSpacing = measureToString(this.letterSpacing);
-    }
+    style.fontKerning = this.kerningMode === "none" ? "none" : "normal";
+    style.letterSpacing = measureToString(this.letterSpacing);
 
     if (this.lineThrough !== 0) {
       style.textDecoration = "line-through";
@@ -2659,9 +2658,7 @@ class Font extends XFAObject {
 
     // TODO: overlinePeriod
 
-    if (this.posture !== "normal") {
-      style.fontStyle = this.posture;
-    }
+    style.fontStyle = this.posture;
 
     const fontSize = measureToString(0.99 * this.size);
     if (fontSize !== "10px") {
@@ -2679,9 +2676,7 @@ class Font extends XFAObject {
 
     // TODO: underlinePeriod
 
-    if (this.weight !== "normal") {
-      style.fontWeight = this.weight;
-    }
+    style.fontWeight = this.weight;
 
     return style;
   }
@@ -3286,24 +3281,39 @@ class PageArea extends XFAObject {
     this.subform = new XFAObjectArray();
   }
 
+  [$isUsable]() {
+    if (!this[$extra]) {
+      this[$extra] = {
+        numberOfUse: 0,
+      };
+      return true;
+    }
+    return (
+      !this.occur ||
+      this.occur.max === -1 ||
+      this[$extra].numberOfUse < this.occur.max
+    );
+  }
+
+  [$cleanPage]() {
+    delete this[$extra];
+  }
+
   [$getNextPage]() {
     if (!this[$extra]) {
       this[$extra] = {
-        numberOfUse: 1,
+        numberOfUse: 0,
       };
     }
+
     const parent = this[$getParent]();
     if (parent.relation === "orderedOccurrence") {
-      if (
-        this.occur &&
-        (this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max)
-      ) {
+      if (this[$isUsable]()) {
         this[$extra].numberOfUse += 1;
         return this;
       }
     }
 
-    delete this[$extra];
     return parent[$getNextPage]();
   }
 
@@ -3389,43 +3399,57 @@ class PageSet extends XFAObject {
     this.pageSet = new XFAObjectArray();
   }
 
+  [$cleanPage]() {
+    for (const page of this.pageArea.children) {
+      page[$cleanPage]();
+    }
+    for (const page of this.pageSet.children) {
+      page[$cleanPage]();
+    }
+  }
+
+  [$isUsable]() {
+    return (
+      !this.occur ||
+      this.occur.max === -1 ||
+      this[$extra].numberOfUse < this.occur.max
+    );
+  }
+
   [$getNextPage]() {
     if (!this[$extra]) {
       this[$extra] = {
         numberOfUse: 1,
-        currentIndex: -1,
+        pageIndex: -1,
+        pageSetIndex: -1,
       };
     }
 
     if (this.relation === "orderedOccurrence") {
-      if (this[$extra].currentIndex + 1 < this.pageArea.children.length) {
-        this[$extra].currentIndex += 1;
-        return this.pageArea.children[this[$extra].currentIndex];
+      if (this[$extra].pageIndex + 1 < this.pageArea.children.length) {
+        this[$extra].pageIndex += 1;
+        const pageArea = this.pageArea.children[this[$extra].pageIndex];
+        return pageArea[$getNextPage]();
       }
 
-      if (this[$extra].currentIndex + 1 < this.pageSet.children.length) {
-        this[$extra].currentIndex += 1;
-        return this.pageSet.children[this[$extra].currentIndex];
+      if (this[$extra].pageSetIndex + 1 < this.pageSet.children.length) {
+        this[$extra].pageSetIndex += 1;
+        return this.pageSet.children[this[$extra].pageSetIndex][$getNextPage]();
       }
 
-      if (
-        this.occur &&
-        (this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max)
-      ) {
+      if (this[$isUsable]()) {
         this[$extra].numberOfUse += 1;
-        this[$extra].currentIndex = 0;
-        if (this.pageArea.children.length > 0) {
-          return this.pageArea.children[0];
-        }
-        return this.pageSet.children[0][$getNextPage]();
+        this[$extra].pageIndex = -1;
+        this[$extra].pageSetIndex = -1;
+        return this[$getNextPage]();
       }
 
-      delete this[$extra];
       const parent = this[$getParent]();
       if (parent instanceof PageSet) {
         return parent[$getNextPage]();
       }
 
+      this[$cleanPage]();
       return this[$getNextPage]();
     }
     const pageNumber = this[$getTemplateRoot]()[$extra].pageNumber;
@@ -4495,6 +4519,8 @@ class Template extends XFAObject {
     };
 
     const root = this.subform.children[0];
+    root.pageSet[$cleanPage]();
+
     const pageAreas = root.pageSet.pageArea.children;
     const mainHtml = {
       name: "div",
@@ -4541,10 +4567,15 @@ class Template extends XFAObject {
       pageArea = pageAreas[0];
     }
 
+    pageArea[$extra] = {
+      numberOfUse: 1,
+    };
+
     const pageAreaParent = pageArea[$getParent]();
     pageAreaParent[$extra] = {
       numberOfUse: 1,
-      currentIndex: pageAreaParent.pageArea.children.indexOf(pageArea),
+      pageIndex: pageAreaParent.pageArea.children.indexOf(pageArea),
+      pageSetIndex: 0,
     };
 
     let targetPageArea;
@@ -4645,19 +4676,26 @@ class Template extends XFAObject {
           }
 
           if (node.targetType === "pageArea") {
+            if (!(target instanceof PageArea)) {
+              target = null;
+            }
+
             if (startNew) {
+              targetPageArea = target || pageArea;
               flush(i);
               i = Infinity;
-            } else if (target === pageArea || !(target instanceof PageArea)) {
-              // Just ignore the break and do layout again.
-              i--;
-            } else {
-              // We must stop the contentAreas filling and go to the next page.
+            } else if (target && target !== pageArea) {
               targetPageArea = target;
               flush(i);
               i = Infinity;
+            } else {
+              i--;
             }
           } else if (node.targetType === "contentArea") {
+            if (!(target instanceof ContentArea)) {
+              target = null;
+            }
+
             const index = contentAreas.findIndex(e => e === target);
             if (index !== -1) {
               flush(i);
@@ -4711,6 +4749,13 @@ class Template extends XFAObject {
       }
 
       this[$extra].pageNumber += 1;
+      if (targetPageArea) {
+        if (targetPageArea[$isUsable]()) {
+          targetPageArea[$extra].numberOfUse += 1;
+        } else {
+          targetPageArea = null;
+        }
+      }
       pageArea = targetPageArea || pageArea[$getNextPage]();
     }
   }
diff --git a/src/core/xfa/xfa_object.js b/src/core/xfa/xfa_object.js
index 07e1700b4..49ca4d3e7 100644
--- a/src/core/xfa/xfa_object.js
+++ b/src/core/xfa/xfa_object.js
@@ -25,6 +25,7 @@ const $addHTML = Symbol();
 const $appendChild = Symbol();
 const $childrenToHTML = Symbol();
 const $clean = Symbol();
+const $cleanPage = Symbol();
 const $cleanup = Symbol();
 const $clone = Symbol();
 const $consumed = Symbol();
@@ -59,6 +60,7 @@ const $isDataValue = Symbol();
 const $isDescendent = Symbol();
 const $isSplittable = Symbol();
 const $isTransparent = Symbol();
+const $isUsable = Symbol();
 const $lastAttribute = Symbol();
 const $namespaceId = Symbol("namespaceId");
 const $nodeName = Symbol("nodeName");
@@ -978,6 +980,7 @@ export {
   $appendChild,
   $childrenToHTML,
   $clean,
+  $cleanPage,
   $cleanup,
   $clone,
   $consumed,
@@ -1012,6 +1015,7 @@ export {
   $isDescendent,
   $isSplittable,
   $isTransparent,
+  $isUsable,
   $namespaceId,
   $nodeName,
   $nsAttributes,
diff --git a/test/pdfs/xfa_bug1716838.pdf.link b/test/pdfs/xfa_bug1716838.pdf.link
new file mode 100644
index 000000000..e348452cb
--- /dev/null
+++ b/test/pdfs/xfa_bug1716838.pdf.link
@@ -0,0 +1 @@
+https://bugzilla.mozilla.org/attachment.cgi?id=9227473
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 3afe94ec3..f203d4b8a 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -962,6 +962,14 @@
        "enableXfa": true,
        "type": "eq"
     },
+    {  "id": "xfa_bug1716838",
+       "file": "pdfs/xfa_bug1716838.pdf",
+       "md5": "564ecff67be690b43c2a144ae5967034",
+       "link": true,
+       "rounds": 1,
+       "enableXfa": true,
+       "type": "eq"
+    },
     {  "id": "xfa_candidate_petitions",
        "file": "pdfs/xfa_candidate_petitions.pdf",
        "md5": "0db96a00667f8f58f94cf81022e69341",
diff --git a/test/unit/xfa_tohtml_spec.js b/test/unit/xfa_tohtml_spec.js
index 3f77e5c5b..42767e362 100644
--- a/test/unit/xfa_tohtml_spec.js
+++ b/test/unit/xfa_tohtml_spec.js
@@ -118,6 +118,10 @@ describe("XFAFactory", function () {
       expect(draw.attributes.style).toEqual({
         color: "#0c1722",
         fontFamily: '"FooBar"',
+        fontKerning: "none",
+        letterSpacing: "0px",
+        fontStyle: "normal",
+        fontWeight: "normal",
         fontSize: "6.93px",
         margin: "1px 4px 2px 3px",
         verticalAlign: "2px",