]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-3164: basic-link not navigating to corresponding footnote by João André Gonçalves
authorSimon Steiner <ssteiner@apache.org>
Mon, 15 Jan 2024 14:05:00 +0000 (14:05 +0000)
committerSimon Steiner <ssteiner@apache.org>
Mon, 15 Jan 2024 14:05:00 +0000 (14:05 +0000)
fop-core/src/main/java/org/apache/fop/area/Area.java
fop-core/src/main/java/org/apache/fop/area/inline/InlineParent.java
fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java
fop-core/src/main/java/org/apache/fop/layoutmgr/FootnoteBodyLayoutManager.java
fop-core/src/main/java/org/apache/fop/layoutmgr/LayoutManager.java
fop-core/src/main/java/org/apache/fop/layoutmgr/inline/FootnoteLayoutManager.java
fop-core/src/main/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java
fop-core/src/main/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java
fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
fop-core/src/test/java/org/apache/fop/area/inline/InlineParentTestCase.java [new file with mode: 0644]
fop/test/layoutengine/standard-testcases/footnote_basic_link.xml [new file with mode: 0644]

index 87f645dd289a490e63e72ee7475fe691c98db9c2..aab0828fb0fa916e161ce3f3b7ca628827f2c077 100644 (file)
@@ -135,6 +135,8 @@ public class Area extends AreaTreeObject implements Serializable {
      */
     private List<ChangeBar> changeBarList;
 
+    private boolean fromFootnote;
+
     /**
      * Returns the active change bar list.
      *
@@ -540,4 +542,12 @@ public class Area extends AreaTreeObject implements Serializable {
             ipd = effectiveIPD;
         }
     }
+
+    public boolean isFromFootnote() {
+        return fromFootnote;
+    }
+
+    public void setFromFootnote(boolean fromFootnote) {
+        this.fromFootnote = fromFootnote;
+    }
 }
index f69c3da739f3715edc5bf61d6e2c0fbcc13ebf33..477ce0a8462d98b36f58ac26685abd1d4fe5bbd6 100644 (file)
@@ -71,8 +71,16 @@ public class InlineParent extends InlineArea {
         }
         updateLevel(childArea.getBidiLevel());
         int childOffset = childArea.getVirtualOffset();
-        minChildOffset = Math.min(minChildOffset, childOffset);
-        maxAfterEdge = Math.max(maxAfterEdge, childOffset + childArea.getVirtualBPD());
+
+        // do not offset if the childArea comes from a footnote
+        // or if the parent itself comes from a footnote
+        if (!(childArea.isFromFootnote() || isFromFootnote())) {
+            minChildOffset = Math.min(minChildOffset, childOffset);
+            maxAfterEdge = Math.max(maxAfterEdge, childOffset + childArea.getVirtualBPD());
+        } else {
+            minChildOffset = Math.min(minChildOffset, childArea.getBlockProgressionOffset());
+            maxAfterEdge = minChildOffset + childArea.getVirtualBPD();
+        }
     }
 
     @Override
@@ -148,5 +156,14 @@ public class InlineParent extends InlineArea {
         }
     }
 
+    @Override
+    public void setFromFootnote(boolean fromFootnote) {
+        super.setFromFootnote(fromFootnote);
 
+        // set all the children to avoid offsetting any of them
+        // otherwise we would offset the parent by offsetting the child
+        for (InlineArea area : inlines) {
+            area.setFromFootnote(fromFootnote);
+        }
+    }
 }
index ca5d1c507dff37b654eb597e702d9b7e1329a408..a84a243b09cd8973f9e7492d3ddb8d8b9f616e83 100644 (file)
@@ -51,6 +51,8 @@ public abstract class AbstractBaseLayoutManager
      */
     private static final Log LOG = LogFactory.getLog(AbstractBaseLayoutManager.class);
 
+    private boolean fromFootnote;
+
     /**
      * Abstract base layout manager.
      */
@@ -294,4 +296,12 @@ public abstract class AbstractBaseLayoutManager
     public void recreateChildrenLMs() {
 
     }
+
+    public boolean isFromFootnote() {
+        return fromFootnote || (getParent() != null && getParent().isFromFootnote());
+    }
+
+    public void setFromFootnote(boolean fromFootnote) {
+        this.fromFootnote = fromFootnote;
+    }
 }
index 78156d37d2bdbf9a77a1929cdb815680ff70c4ac..e20826ca3b4c306bc9adec6ca3b5a903b7ea2fb6 100644 (file)
@@ -85,6 +85,7 @@ public class FootnoteBodyLayoutManager extends BlockStackingLayoutManager {
             lc.setFlags(LayoutContext.LAST_AREA,
                     (layoutContext.isLastArea() && childLM == lastLM));
             // Add the line areas to Area
+            childLM.setFromFootnote(true);
             childLM.addAreas(childPosIter, lc);
         }
     }
@@ -93,6 +94,7 @@ public class FootnoteBodyLayoutManager extends BlockStackingLayoutManager {
     @Override
     public void addChildArea(Area childArea) {
         childArea.setAreaClass(Area.CLASS_FOOTNOTE);
+        childArea.setFromFootnote(true);
         parentLayoutManager.addChildArea(childArea);
     }
 
index 8d1e4001cef6a00815e1d83c10ca377991cfe46d..97449762b40ce9557da3733a39b32792e6c7bbb0 100644 (file)
@@ -271,4 +271,8 @@ public interface LayoutManager extends PercentBaseContext {
      */
     List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
             Position positionAtIPDChange, LayoutManager restartAtLM);
+
+    boolean isFromFootnote();
+
+    void setFromFootnote(boolean fromFootnote);
 }
index 50b88b5b9d0a4cdaf97f168c1e315a4ce492d695..06e742434d67576af7bf7351a1fc8d15575653b7 100644 (file)
@@ -155,6 +155,7 @@ public class FootnoteLayoutManager extends InlineStackingLayoutManager {
         PositionIterator childPosIter = new PositionIterator(positionList.listIterator());
         LayoutManager childLM;
         while ((childLM = childPosIter.getNextChildLM()) != null) {
+            childLM.setFromFootnote(true);
             childLM.addAreas(childPosIter, childContext);
             childContext.setLeadingSpace(childContext.getTrailingSpace());
             childContext.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
index 85f545e5e5cc55b7c9d63fabf7d60eb87b207206..52418d816c7885afef15f2ca97e502f55a8eb80d 100644 (file)
@@ -540,6 +540,7 @@ public class InlineLayoutManager extends InlineStackingLayoutManager {
         // Not sure if lastPos can legally be null or if that masks a different problem.
         // But it seems to fix bug 38053.
         setTraits(areaCreated, lastPos == null || !isLast(lastPos));
+        getCurrentArea().setFromFootnote(isFromFootnote());
         parentLayoutManager.addChildArea(getCurrentArea());
 
         registerMarkers(
index 7a8ac8627c8ad1a2758cc6ac9479b304f081ec5b..a0acd1cd3ed03a80bf1d120d8b38ac028ebe4fce 100644 (file)
@@ -187,6 +187,8 @@ public abstract class InlineStackingLayoutManager extends AbstractLayoutManager
                 if (level >= 0) {
                     ls.setBidiLevel(level);
                 }
+                ls.setFromFootnote(isFromFootnote());
+
                 parentArea.addChildArea(ls);
             }
         }
index f44bd7bc0b68ed3b5009aa5c79fffa62a3f56eb6..e524b87ac0031766836164e9a3d1098d2267c433 100644 (file)
@@ -333,6 +333,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
         textArea.setTextLetterSpaceAdjust(letterSpaceDim);
         textArea.setTextWordSpaceAdjust(wordSpaceDim - spaceCharIPD
                 - 2 * textArea.getTextLetterSpaceAdjust());
+        textArea.setFromFootnote(isFromFootnote());
         if (context.getIPDAdjust() != 0) {
             // add information about space width
             textArea.setSpaceDifference(wordSpaceIPD.getOpt() - spaceCharIPD
diff --git a/fop-core/src/test/java/org/apache/fop/area/inline/InlineParentTestCase.java b/fop-core/src/test/java/org/apache/fop/area/inline/InlineParentTestCase.java
new file mode 100644 (file)
index 0000000..f14e05b
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.area.inline;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class InlineParentTestCase {
+
+    private static final int BLOCK_PROG_OFFSET = -12;
+
+    private static final int BPD = 25;
+
+    @Test
+    public void testAddChildAreaMixedFromFootnote() {
+        InlineParent parent = new InlineParent();
+        InlineParent firstChild = createChildInlineParent(BLOCK_PROG_OFFSET, BPD, false);
+        InlineParent secondChild = createChildInlineParent(3 * BLOCK_PROG_OFFSET, 3 * BPD, false);
+        InlineParent thirdChild = createChildInlineParent(2 * BLOCK_PROG_OFFSET, 2 * BPD, true);
+        InlineParent forthChild = createChildInlineParent(0, 0, true);
+
+        assertEquals("Default Values must be zero", 0, parent.minChildOffset);
+        assertEquals("Default Values must be zero", 0, parent.getVirtualBPD());
+
+        assertAddChildArea(parent, firstChild, -12, 0);
+        assertAddChildArea(parent, secondChild, -36, 0);
+        assertAddChildArea(parent, thirdChild, -36, -36);
+        assertAddChildArea(parent, forthChild, -36, -36);
+    }
+
+    @Test
+    public void testAddChildAreaNotFromFootnote() {
+        InlineParent parent = new InlineParent();
+        InlineParent firstChild = createChildInlineParent(BLOCK_PROG_OFFSET, BPD, false);
+        InlineParent secondChild = createChildInlineParent(3 * BLOCK_PROG_OFFSET, 3 * BPD, false);
+        InlineParent thirdChild = createChildInlineParent(2 * BLOCK_PROG_OFFSET, 2 * BPD, false);
+        InlineParent forthChild = createChildInlineParent(0, 0, false);
+
+        assertEquals("Default Values must be zero", 0, parent.minChildOffset);
+        assertEquals("Default Values must be zero", 0, parent.getVirtualBPD());
+
+        assertAddChildArea(parent, firstChild, -12, 0);
+        assertAddChildArea(parent, secondChild, -36, 0);
+        assertAddChildArea(parent, thirdChild, -36, 0);
+        assertAddChildArea(parent, forthChild, -36, 0);
+    }
+
+    @Test
+    public void testAddChildAreaFromFootnote() {
+        InlineParent parent = new InlineParent();
+        InlineParent firstChild = createChildInlineParent(BLOCK_PROG_OFFSET, BPD, true);
+        InlineParent secondChild = createChildInlineParent(3 * BLOCK_PROG_OFFSET, 3 * BPD, true);
+        InlineParent thirdChild = createChildInlineParent(2 * BLOCK_PROG_OFFSET, 2 * BPD, true);
+        InlineParent forthChild = createChildInlineParent(0, 0, true);
+
+        assertEquals("Default Values must be zero", 0, parent.minChildOffset);
+        assertEquals("Default Values must be zero", 0, parent.getVirtualBPD());
+
+        assertAddChildArea(parent, firstChild, -12, -12);
+        assertAddChildArea(parent, secondChild, -36, -36);
+        assertAddChildArea(parent, thirdChild, -36, -36);
+        assertAddChildArea(parent, forthChild, -36, -36);
+    }
+
+    private void assertAddChildArea(InlineParent parent, InlineParent child,
+                                    int minChildOffset, int maxAfterEdge) {
+        parent.addChildArea(child);
+
+        // the virtualBPD is the subtraction of the maxAfterEdge with the minChildOffset
+        // by adding the minChildOffset to the virtualBPD we get the maxAfterEdge alone
+        int parentMaxAfterEdge = parent.getVirtualBPD() + parent.minChildOffset;
+
+        if (!child.isFromFootnote()) {
+            assertEquals("Must be set to the min of the current minChildOffset and the "
+                            + "sum of the child's virtualOffset with the current value of the minChildOffset",
+                    minChildOffset, parent.minChildOffset);
+            assertEquals("Must be set to the max of the current maxAfterEdge and the "
+                            + "result of the sum of the child's virtualOffset with child's the virtualBPD",
+                    maxAfterEdge, parentMaxAfterEdge);
+        } else {
+            assertEquals("Must be set to the min of the current minChildOffset and the "
+                            + "sum of the child's blockProgressionOffset with the current value "
+                            + "of the minChildOffset",
+                    minChildOffset, parent.minChildOffset);
+            assertEquals("Must be the result of the sum of the maxAfterEdge "
+                    + "with the child's virtualBPD", maxAfterEdge, parentMaxAfterEdge);
+        }
+    }
+
+    private InlineParent createChildInlineParent(int bpo, int bpd, boolean footnote) {
+        InlineParent child = new InlineParent();
+        child.setBlockProgressionOffset(bpo);
+        child.setBPD(bpd);
+        child.setFromFootnote(footnote);
+
+        return child;
+    }
+}
diff --git a/fop/test/layoutengine/standard-testcases/footnote_basic_link.xml b/fop/test/layoutengine/standard-testcases/footnote_basic_link.xml
new file mode 100644 (file)
index 0000000..65ca278
--- /dev/null
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<!-- $Id$ -->
+<testcase>
+  <info>
+    <p>
+      This test checks footnotes.
+    </p>
+  </info>
+  <fo>
+    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:svg="http://www.w3.org/2000/svg">
+      <fo:layout-master-set>
+        <fo:simple-page-master master-name="normal" page-width="5in" page-height="3in">
+          <fo:region-body/>
+        </fo:simple-page-master>
+      </fo:layout-master-set>
+      <fo:page-sequence master-reference="normal" white-space-collapse="true">
+        <fo:flow flow-name="xsl-region-body">
+          <fo:block>
+            <!-- Mid-Link Footnote -->
+            <fo:block>
+              <fo:basic-link external-destination="https://www.google.com/">
+                <fo:inline>This is a link with a footnote</fo:inline>
+                <fo:footnote>
+                  <fo:inline>
+                    <fo:basic-link id="fn-ref-2" internal-destination="fn-2" color="blue">
+                      <fo:inline baseline-shift="super" font-size="80%">1</fo:inline>
+                    </fo:basic-link>
+                  </fo:inline>
+                  <fo:footnote-body>
+                    <fo:block color="black" text-decoration="none">
+                      <fo:table table-layout="fixed" width="100%">
+                        <fo:table-column column-width="proportional-column-width(5)"/>
+                        <fo:table-column column-width="proportional-column-width(95)"/>
+                        <fo:table-body>
+                          <fo:table-row>
+                            <fo:table-cell>
+                              <fo:block>
+                                <fo:basic-link id="fn-2" internal-destination="fn-ref-2" color="blue">1</fo:basic-link>
+                              </fo:block>
+                            </fo:table-cell>
+                            <fo:table-cell>
+                              <fo:block>Note text</fo:block>
+                            </fo:table-cell>
+                          </fo:table-row>
+                        </fo:table-body>
+                      </fo:table>
+                    </fo:block>
+                  </fo:footnote-body>
+                </fo:footnote>
+                <fo:inline> inside</fo:inline>
+              </fo:basic-link>
+            </fo:block>
+          </fo:block>
+        </fo:flow>
+      </fo:page-sequence>
+    </fo:root>
+  </fo>
+  <checks>
+    <!-- block with footnote -->
+    <eval expected="This is a link with a footnote   1 inside" xpath="//inlineparent[1]"/>
+    <eval expected="  1" xpath="//inlineparent[2]"/>
+    <!-- the footnote -->
+    <eval expected="4552" xpath="//inlineparent[1]/@offset"/>
+    <eval expected="11100" xpath="//inlineparent[1]/@bpd"/>
+    <eval expected="11100" xpath="//inlineparent[1]/@bpda"/>
+
+    <eval expected="0" xpath="//inlineparent[2]/@offset"/>
+    <eval expected="11100" xpath="//inlineparent[2]/@bpd"/>
+    <eval expected="11100" xpath="//inlineparent[2]/@bpda"/>
+
+    <eval expected="15652" xpath="//lineArea/@bpd"/>
+    <eval expected="18952" xpath="//lineArea/@bpda"/>
+  </checks>
+</testcase>