]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Added support for multiple multi-switch appearing on the same page.
authorVincent Hennebert <vhennebert@apache.org>
Tue, 8 Apr 2014 19:30:37 +0000 (19:30 +0000)
committerVincent Hennebert <vhennebert@apache.org>
Tue, 8 Apr 2014 19:30:37 +0000 (19:30 +0000)
Modified MultiSwitchLM to have its standard behaviour by default.
Moved BestFitLayoutUtils into MultiSwitchLM.
Code clean-up and javadoc.

Patch by Seifeddine Dridi, applied with some modifications.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_WhitespaceManagement@1585822 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/fop/fo/flow/MultiSwitch.java
src/java/org/apache/fop/layoutmgr/BestFitLayoutUtils.java [deleted file]
src/java/org/apache/fop/layoutmgr/BestFitPenalty.java [deleted file]
src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java
src/java/org/apache/fop/layoutmgr/MultiSwitchLayoutManager.java
src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java
src/java/org/apache/fop/layoutmgr/WhitespaceManagementPenalty.java [new file with mode: 0644]
test/layoutengine/standard-testcases/multi-switch_best-fit_multiple_dynamic_contents.xml [new file with mode: 0644]

index 6c4333cff24cfe2e75ff9cf69cca7b145ec7cbc5..f681f1695199dc1bc77c9a3ab07b84d277e192ac 100644 (file)
@@ -38,9 +38,7 @@ public class MultiSwitch extends FObj {
     //     private CommonAccessibility commonAccessibility;
     // End of property values
 
-    private FONode currentlyVisibleMultiCase;
     private String autoToggle;
-    private String fittingStrategy;
 
     /**
      * Base constructor
@@ -97,18 +95,6 @@ public class MultiSwitch extends FObj {
         return FO_MULTI_SWITCH;
     }
 
-    public void setCurrentlyVisibleNode(FONode node) {
-        currentlyVisibleMultiCase = node;
-    }
-
-    public FONode getCurrentlyVisibleNode() {
-        return currentlyVisibleMultiCase;
-    }
-
-    public String getFittingStrategy() {
-        return fittingStrategy;
-    }
-
     public String getAutoToggle() {
         return autoToggle;
     }
diff --git a/src/java/org/apache/fop/layoutmgr/BestFitLayoutUtils.java b/src/java/org/apache/fop/layoutmgr/BestFitLayoutUtils.java
deleted file mode 100644 (file)
index 148a83e..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.fop.layoutmgr;
-
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.fop.layoutmgr.BestFitPenalty.Variant;
-
-/**
- * Utility class used in {@link MultiSwitchLayoutManager}
- * to handle  the <i>best-fit</i> property value if specified in {@link org.apache.fop.fo.flow.MultiSwitch}
- */
-public final class BestFitLayoutUtils {
-
-    private BestFitLayoutUtils() { }
-
-    static class BestFitPosition extends Position {
-
-        private List<ListElement> knuthList;
-
-        public BestFitPosition(LayoutManager lm) {
-            super(lm);
-        }
-
-        public List<Position> getPositionList() {
-            List<Position> positions = new LinkedList<Position>();
-            if (knuthList != null && !knuthList.isEmpty()) {
-                SpaceResolver.performConditionalsNotification(knuthList, 0, knuthList.size() - 1, -1);
-                for (ListElement el : knuthList) {
-                    if (el.getPosition() != null) {
-                        positions.add(el.getPosition());
-                    }
-                }
-            }
-            return positions;
-        }
-
-        public void setKnuthList(List<ListElement> knuthList) {
-            this.knuthList = knuthList;
-        }
-
-    }
-
-    public static List<ListElement> getKnuthList(LayoutManager lm,
-            List<List<ListElement>> childrenLists) {
-
-        List<ListElement> knuthList = new LinkedList<ListElement>();
-
-        BestFitPenalty bestFitPenalty = new BestFitPenalty(new BestFitPosition(lm));
-        for (List<ListElement> childList : childrenLists) {
-            // TODO Doing space resolution here is not correct.
-            // Space elements will simply be nulled.
-            SpaceResolver.resolveElementList(childList);
-            int contentLength = ElementListUtils.calcContentLength(childList);
-            bestFitPenalty.addVariant(new Variant(childList, contentLength));
-        }
-        // TODO Adding two enclosing boxes is definitely a dirty hack.
-        // The first box forces the breaking algorithm to consider the penalty
-        // in case there are no elements preceding it
-        // and the last box prevents the glue and penalty from getting deleted
-        // when they are at the end of the Knuth list.
-        knuthList.add(new KnuthBox(0, new Position(lm), false));
-        knuthList.add(bestFitPenalty);
-        knuthList.add(new KnuthBox(0, new Position(lm), false));
-        return knuthList;
-    }
-
-    public static List<Position> getPositionList(LayoutManager lm, PositionIterator posIter) {
-
-        // "unwrap" the NonLeafPositions stored in parentIter
-        // and put them in a new list;
-        LinkedList<Position> positionList = new LinkedList<Position>();
-        while (posIter.hasNext()) {
-            Position pos = posIter.next();
-            if (pos instanceof BestFitPosition) {
-                positionList.addAll(((BestFitPosition) pos).getPositionList());
-            } else {
-                positionList.add(pos);
-            }
-        }
-        return positionList;
-    }
-
-}
diff --git a/src/java/org/apache/fop/layoutmgr/BestFitPenalty.java b/src/java/org/apache/fop/layoutmgr/BestFitPenalty.java
deleted file mode 100644 (file)
index 692cc19..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.layoutmgr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.fop.layoutmgr.BestFitLayoutUtils.BestFitPosition;
-
-/**
- * A type of penalty used to specify a set of alternatives for the layout engine
- * to choose from. The chosen alternative must have an occupied size
- * less than the remaining BPD on the active page.
- */
-public class BestFitPenalty extends KnuthPenalty {
-
-    public static class Variant {
-
-        public final List<ListElement> knuthList;
-        public final int width;
-        public int penaltyIndex;
-
-        public Variant(List<ListElement> knuthList, int width) {
-            this.knuthList = knuthList;
-            this.width = width;
-            this.penaltyIndex = -1;
-        }
-
-        public KnuthElement toPenalty() {
-            return new KnuthPenalty(width, 0, false, null, false);
-        }
-
-    }
-
-    private final BestFitPosition bestFitPosition;
-    private final List<Variant> variantList;
-
-    public BestFitPenalty(BestFitPosition pos) {
-        super(0, 0, false, pos, false);
-        this.bestFitPosition = pos;
-        variantList = new ArrayList<Variant>();
-    }
-
-    public void addVariant(Variant variant) {
-        variantList.add(variant);
-    }
-
-    public void setActiveVariant(Variant bestVariant) {
-        bestFitPosition.setKnuthList(bestVariant.knuthList);
-    }
-
-    public List<Variant> getVariants() {
-        return variantList;
-    }
-
-    @Override
-    public String toString() {
-        String str = super.toString();
-        StringBuffer buffer = new StringBuffer(64);
-        buffer.append(" number of variants = " + variantList.size());
-        return str + buffer.toString();
-    }
-
-}
index 2ffb48675e9c8dedcc6c2665ad6337e250b8e49f..e92a4e681add3956844d98bd16fbe848e88cfe32 100644 (file)
@@ -451,18 +451,8 @@ public class LayoutManagerMapping implements LayoutManagerMaker {
 
         @Override
         public void make(FONode node, List lms) {
-            MultiSwitch multiSwitch = (MultiSwitch) node;
-            MultiSwitchLayoutManager mslm = new MultiSwitchLayoutManager(multiSwitch);
-            FONode multiCase = multiSwitch.getCurrentlyVisibleNode();
-            if (multiCase != null) {
-                FONodeIterator childIter = multiCase.getChildNodes();
-                while (childIter.hasNext()) {
-                    FONode child = (FONode) childIter.next();
-                    makeLayoutManagers(child, lms);
-                }
-            } else {
-                lms.add(mslm);
-            }
+            MultiSwitchLayoutManager mslm = new MultiSwitchLayoutManager((MultiSwitch) node);
+            lms.add(mslm);
         }
     }
 
index a2b0e90b04b2f3bdf837ff9f82812fca887b8fe6..7c810754adcf297a50c5f05b2acd45a462323953 100644 (file)
 
 package org.apache.fop.layoutmgr;
 
-import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
 import org.apache.fop.area.Area;
 import org.apache.fop.fo.FObj;
+import org.apache.fop.fo.flow.MultiSwitch;
 
 public class MultiSwitchLayoutManager extends BlockStackingLayoutManager {
 
+    static class WhitespaceManagementPosition extends Position {
+
+        private List<ListElement> knuthList;
+
+        public WhitespaceManagementPosition(LayoutManager lm) {
+            super(lm);
+        }
+
+        public List<Position> getPositionList() {
+            List<Position> positions = new LinkedList<Position>();
+            if (knuthList != null && !knuthList.isEmpty()) {
+                SpaceResolver.performConditionalsNotification(knuthList, 0, knuthList.size() - 1, -1);
+                for (ListElement el : knuthList) {
+                    if (el.getPosition() != null) {
+                        positions.add(el.getPosition());
+                    }
+                }
+            }
+            return positions;
+        }
+
+        public void setKnuthList(List<ListElement> knuthList) {
+            this.knuthList = knuthList;
+        }
+
+        public List<ListElement> getKnuthList() {
+            return knuthList;
+        }
+
+    }
+
+    private interface KnuthElementsGenerator {
+        List<ListElement> getKnuthElement(LayoutContext context, int alignment);
+    }
+
+    private class DefaultKnuthListGenerator implements KnuthElementsGenerator {
+
+        public List<ListElement> getKnuthElement(LayoutContext context, int alignment) {
+
+            List<ListElement> knuthList = new LinkedList<ListElement>();
+            LayoutManager childLM;
+            while ((childLM = getChildLM()) != null) {
+                if (!childLM.isFinished()) {
+                    LayoutContext childLC = makeChildLayoutContext(context);
+                    List childElements = childLM.getNextKnuthElements(childLC, alignment);
+                    if (childElements != null) {
+                        List<ListElement> newList = new LinkedList<ListElement>();
+                        wrapPositionElements(childElements, newList);
+                        knuthList.addAll(newList);
+                    }
+                }
+            }
+            return knuthList;
+        }
+
+    }
+
+    private class WhitespaceManagement implements KnuthElementsGenerator {
+
+        public List<ListElement> getKnuthElement(LayoutContext context, int alignment) {
+
+            MultiSwitchLayoutManager mslm = MultiSwitchLayoutManager.this;
+            List<ListElement> knuthList = new LinkedList<ListElement>();
+            WhitespaceManagementPenalty penalty = new WhitespaceManagementPenalty(
+                    new WhitespaceManagementPosition(mslm));
+            LayoutManager childLM;
+            while ((childLM = getChildLM()) != null) {
+                if (!childLM.isFinished()) {
+                    LayoutContext childLC = makeChildLayoutContext(context);
+                    List childElements = childLM.getNextKnuthElements(childLC, alignment);
+                    if (childElements != null) {
+                        List<ListElement> newList = new LinkedList<ListElement>();
+                        wrapPositionElements(childElements, newList);
+                        // TODO Doing space resolution here is wrong.
+                        SpaceResolver.resolveElementList(newList);
+                        int contentLength = ElementListUtils.calcContentLength(newList);
+                        penalty.addVariant(penalty.new Variant(newList, contentLength));
+                    }
+                }
+            }
+            // Prevent the penalty from being ignored if it is at the beginning of the content
+            knuthList.add(new KnuthBox(0, new Position(mslm), false));
+            knuthList.add(penalty);
+            // Prevent the penalty from being ignored if it is at the end of the content
+            knuthList.add(new KnuthBox(0, new Position(mslm), false));
+            return knuthList;
+        }
+
+    }
+
+    private KnuthElementsGenerator knuthGen;
+
     public MultiSwitchLayoutManager(FObj node) {
         super(node);
+        MultiSwitch multiSwitchNode = (MultiSwitch) node;
+        if (multiSwitchNode.getAutoToggle().equals("best-fit")) {
+            knuthGen = new WhitespaceManagement();
+        } else {
+            knuthGen = new DefaultKnuthListGenerator();
+        }
     }
 
     @Override
     public List<ListElement> getNextKnuthElements(LayoutContext context, int alignment) {
-
         referenceIPD = context.getRefIPD();
-        List<List<ListElement>> childrenLists = new ArrayList<List<ListElement>>();
-        LayoutManager childLM;
-        while ((childLM = getChildLM()) != null) {
-            if (!childLM.isFinished()) {
-                LayoutContext childLC = makeChildLayoutContext(context);
-                List childElements = childLM.getNextKnuthElements(childLC, alignment);
-                if (childElements != null) {
-                    List<ListElement> newList = new LinkedList<ListElement>();
-                    wrapPositionElements(childElements, newList);
-                    childrenLists.add(newList);
-                }
-            }
-        }
+        List<ListElement> knuthList = knuthGen.getKnuthElement(context, alignment);
         setFinished(true);
-        return BestFitLayoutUtils.getKnuthList(this, childrenLists);
+        return knuthList;
     }
 
     @Override
@@ -63,11 +148,16 @@ public class MultiSwitchLayoutManager extends BlockStackingLayoutManager {
 
     @Override
     public void addAreas(PositionIterator posIter, LayoutContext context) {
-
-        List<Position> positionList = BestFitLayoutUtils.getPositionList(this, posIter);
-        PositionIterator newPosIter = new PositionIterator(
-                positionList.listIterator());
-
+        LinkedList<Position> positionList = new LinkedList<Position>();
+        while (posIter.hasNext()) {
+            Position pos = posIter.next();
+            if (pos instanceof WhitespaceManagementPosition) {
+                positionList.addAll(((WhitespaceManagementPosition) pos).getPositionList());
+            } else {
+                positionList.add(pos);
+            }
+        }
+        PositionIterator newPosIter = new PositionIterator(positionList.listIterator());
         AreaAdditionUtil.addAreas(this, newPosIter, context);
         flush();
     }
index 8c587bcc68aaf3a35f623087c5a270996d867382..64757854e60d6a11f262fa54ff35852f450681ba 100644 (file)
@@ -30,7 +30,7 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition;
-import org.apache.fop.layoutmgr.BestFitPenalty.Variant;
+import org.apache.fop.layoutmgr.WhitespaceManagementPenalty.Variant;
 import org.apache.fop.traits.MinOptMax;
 import org.apache.fop.util.ListUtil;
 
@@ -153,10 +153,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         /** Index of the last inserted element of the last inserted footnote. */
         public int footnoteElementIndex;
 
-        /** Current active variant attached to this node */
-        public final Variant variant;
-        /** Pending variant to be assigned to all descending nodes */
-        public Variant pendingVariant;
+        /**
+         * Pending variants of dynamic contents that were evaluated WRT this node.
+         * When computing page difference for a break element, the total width of these variants
+         * will be added to 'actualWidth'.
+         */
+        private final List<Variant> pendingVariants = new ArrayList<Variant>();
+        private int totalVariantsWidth;
 
         public KnuthPageNode(int position,
                              int line, int fitness,
@@ -164,7 +167,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
                              int insertedFootnotes, int totalFootnotes,
                              int footnoteListIndex, int footnoteElementIndex,
                              double adjustRatio, int availableShrink, int availableStretch,
-                             int difference, double totalDemerits, KnuthNode previous, Variant variant) {
+                             int difference, double totalDemerits, KnuthNode previous) {
             super(position, line, fitness,
                   totalWidth, totalStretch, totalShrink,
                   adjustRatio, availableShrink, availableStretch,
@@ -173,7 +176,11 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
             this.insertedFootnotes = insertedFootnotes;
             this.footnoteListIndex = footnoteListIndex;
             this.footnoteElementIndex = footnoteElementIndex;
-            this.variant = variant;
+        }
+
+        public void addVariant(Variant variant) {
+            pendingVariants.add(variant);
+            totalVariantsWidth += variant.width;
         }
 
     }
@@ -188,7 +195,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         private final int[] bestTotalFootnotesLength = new int[4];
         private final int[] bestFootnoteListIndex = new int[4];
         private final int[] bestFootnoteElementIndex = new int[4];
-        private final Variant[] bestVariant = new Variant[4];
 
         @Override
         public void addRecord(double demerits, KnuthNode node, double adjust,
@@ -201,7 +207,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
             bestTotalFootnotesLength[fitness] = totalFootnotesLength;
             bestFootnoteListIndex[fitness] = footnoteListIndex;
             bestFootnoteElementIndex[fitness] = footnoteElementIndex;
-            bestVariant[fitness] = ((KnuthPageNode) node).pendingVariant;
         }
 
         public int getInsertedFootnotesLength(int fitness) {
@@ -220,9 +225,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
             return bestFootnoteElementIndex[fitness];
         }
 
-        public Variant getVariant(int fitness) {
-            return bestVariant[fitness];
-        }
 
     }
 
@@ -316,8 +318,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
                                  insertedFootnotesLength, totalFootnotesLength,
                                  footnoteListIndex, footnoteElementIndex,
                                  adjustRatio, availableShrink, availableStretch,
-                                 difference, totalDemerits, previous,
-                                 (previous != null) ? ((KnuthPageNode)previous).pendingVariant : null);
+                                 difference, totalDemerits, previous);
     }
 
     /** {@inheritDoc} */
@@ -332,8 +333,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
                                  ((BestPageRecords) best).getFootnoteElementIndex(fitness),
                                  best.getAdjust(fitness), best.getAvailableShrink(fitness),
                                  best.getAvailableStretch(fitness), best.getDifference(fitness),
-                                 best.getDemerits(fitness), best.getNode(fitness),
-                                 ((BestPageRecords) best).getVariant(fitness));
+                                 best.getDemerits(fitness), best.getNode(fitness));
     }
 
     /**
@@ -527,15 +527,12 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         int actualWidth = totalWidth - pageNode.totalWidth;
         int footnoteSplit;
         boolean canDeferOldFN;
-        if (element.isPenalty()) {
-            if (element instanceof BestFitPenalty) {
-                actualWidth += handleBestFitPenalty(pageNode, (BestFitPenalty) element, elementIndex);
-            } else {
-                actualWidth += element.getWidth();
-                if (pageNode.pendingVariant != null) {
-                    actualWidth += ((KnuthPageNode) activeNode).pendingVariant.width;
-                }
-            }
+        actualWidth += pageNode.totalVariantsWidth;
+        if (element instanceof WhitespaceManagementPenalty) {
+            actualWidth += handleWhitespaceManagementPenalty(pageNode,
+                    (WhitespaceManagementPenalty) element, elementIndex);
+        } else if (element.isPenalty()) {
+            actualWidth += element.getWidth();
         }
         if (footnotesPending) {
             // compute the total length of the footnotes not yet inserted
@@ -595,13 +592,18 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         }
     }
 
-    private int handleBestFitPenalty(KnuthPageNode activeNode, BestFitPenalty penalty, int elementIndex) {
+    /**
+     * Evaluates the variants corresponding to the given penalty until one that
+     * leads to an acceptable adjustment ratio is found. That variant will
+     * be added to the list of pending variants in the given active node.
+     */
+    private int handleWhitespaceManagementPenalty(KnuthPageNode activeNode,
+            WhitespaceManagementPenalty penalty, int elementIndex) {
         for (Variant var : penalty.getVariants()) {
             int difference = computeDifference(activeNode, var.toPenalty(), elementIndex);
             double r = computeAdjustmentRatio(activeNode, difference);
             if (r >= -1.0) {
-                var.penaltyIndex = elementIndex;
-                activeNode.pendingVariant = var;
+                activeNode.addVariant(var);
                 return var.width;
             }
         }
@@ -1022,9 +1024,14 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
         //      ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth;
         // Check if the given node has an attached variant of a dynamic content
         KnuthPageNode pageNode = (KnuthPageNode) bestActiveNode;
-        if (pageNode.variant != null) {
-            BestFitPenalty penalty = (BestFitPenalty) par.get(pageNode.variant.penaltyIndex);
-            penalty.setActiveVariant(pageNode.variant);
+        KnuthPageNode previousPageNode = ((KnuthPageNode) pageNode.previous);
+        for (Variant var : previousPageNode.pendingVariants) {
+            WhitespaceManagementPenalty penalty = var.getBestFitPenalty();
+            int penaltyIndex = this.par.indexOf(penalty);
+            // Make sure penalty is inside the range of the current page node
+            if (penaltyIndex <= pageNode.position) {
+                penalty.setActiveVariant(var);
+            }
         }
         int difference = bestActiveNode.difference;
         if (difference + bestActiveNode.availableShrink < 0) {
diff --git a/src/java/org/apache/fop/layoutmgr/WhitespaceManagementPenalty.java b/src/java/org/apache/fop/layoutmgr/WhitespaceManagementPenalty.java
new file mode 100644 (file)
index 0000000..967df2d
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.layoutmgr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.layoutmgr.MultiSwitchLayoutManager.WhitespaceManagementPosition;
+
+/**
+ * A special penalty used to specify content having multiple variants. At most
+ * only one variant will be inserted into the final document. If none of the
+ * variants fit into the remaining space on the current page, the dynamic
+ * content will be completely ignored.
+ */
+public class WhitespaceManagementPenalty extends KnuthPenalty {
+
+    public class Variant {
+
+        public final List<ListElement> knuthList;
+        public final int width;
+
+        public Variant(List<ListElement> knuthList, int width) {
+            this.knuthList = knuthList;
+            this.width = width;
+        }
+
+        public KnuthElement toPenalty() {
+            return new KnuthPenalty(width, 0, false, null, false);
+        }
+
+        public WhitespaceManagementPenalty getBestFitPenalty() {
+            return WhitespaceManagementPenalty.this;
+        }
+
+    }
+
+    private final WhitespaceManagementPosition whitespaceManagementPosition;
+    private final List<Variant> variantList;
+
+    public WhitespaceManagementPenalty(WhitespaceManagementPosition pos) {
+        super(0, 0, false, pos, false);
+        this.whitespaceManagementPosition = pos;
+        variantList = new ArrayList<Variant>();
+    }
+
+    public void addVariant(Variant variant) {
+        variantList.add(variant);
+    }
+
+    public void setActiveVariant(Variant bestVariant) {
+        whitespaceManagementPosition.setKnuthList(bestVariant.knuthList);
+    }
+
+    public boolean isActivated() {
+        return whitespaceManagementPosition.getKnuthList() != null;
+    }
+
+    public List<Variant> getVariants() {
+        return variantList;
+    }
+
+    @Override
+    public String toString() {
+        String str = super.toString();
+        StringBuffer buffer = new StringBuffer(64);
+        buffer.append(" number of variants = " + variantList.size());
+        return str + buffer.toString();
+    }
+
+}
diff --git a/test/layoutengine/standard-testcases/multi-switch_best-fit_multiple_dynamic_contents.xml b/test/layoutengine/standard-testcases/multi-switch_best-fit_multiple_dynamic_contents.xml
new file mode 100644 (file)
index 0000000..df4c59f
--- /dev/null
@@ -0,0 +1,98 @@
+<?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.
+-->
+<testcase>
+  <info>
+    <p>
+      Test that multiple whitespace managment elements on the same page are handled nicely.
+    </p>
+  </info>
+  <fo>
+    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" 
+      xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" font-size="8pt" line-height="10pt">
+      <fo:layout-master-set>
+        <fo:simple-page-master master-name="page"
+          page-height="70pt" page-width="220pt" margin="10pt">
+          <fo:region-body background-color="#F0F0F0"/>
+        </fo:simple-page-master>
+      </fo:layout-master-set>
+      <fo:page-sequence master-reference="page">
+        <fo:flow flow-name="xsl-region-body">
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block>MS 1 Variant 1</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block>MS 2 Variant 1</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block>MS 3 Variant 1</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+        </fo:flow>
+      </fo:page-sequence>
+
+      <fo:page-sequence master-reference="page">
+        <fo:flow flow-name="xsl-region-body">
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block line-height="70pt">MS 1 Variant 1</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block>MS 2 Variant 1</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block line-height="50pt">MS 3 Variant 1</fo:block>
+            </fo:multi-case>
+            <fo:multi-case>
+              <fo:block>MS 3 Variant 2</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+          <fo:multi-switch fox:auto-toggle="best-fit">
+            <fo:multi-case>
+              <fo:block>MS 4 Variant 1</fo:block>
+            </fo:multi-case>
+          </fo:multi-switch>
+        </fo:flow>
+      </fo:page-sequence>
+
+    </fo:root>
+  </fo>
+  <checks>
+    <!-- 1. 3 multi-switch on the same page -->
+    <eval expected="1"        xpath="count(//pageSequence[1]/pageViewport)"/>
+    <eval expected="3"        xpath="count(//pageSequence[1]/pageViewport[1]//flow/block)"/>
+    <eval expected="MS 1 Variant 1" xpath="//pageSequence[1]/pageViewport[1]//flow/block[1]"/>
+    <eval expected="MS 2 Variant 1" xpath="//pageSequence[1]/pageViewport[1]//flow/block[2]"/>
+    <eval expected="MS 3 Variant 1" xpath="//pageSequence[1]/pageViewport[1]//flow/block[3]"/>
+
+    <!-- 2. 4 multi-switch, one cannot fit, one uses the 2nd variant  -->
+    <eval expected="1"        xpath="count(//pageSequence[2]/pageViewport)"/>
+    <eval expected="3"        xpath="count(//pageSequence[2]/pageViewport[1]//flow/block)"/>
+    <eval expected="MS 2 Variant 1" xpath="//pageSequence[2]/pageViewport[1]//flow/block[1]"/>
+    <eval expected="MS 3 Variant 2" xpath="//pageSequence[2]/pageViewport[1]//flow/block[2]"/>
+    <eval expected="MS 4 Variant 1" xpath="//pageSequence[2]/pageViewport[1]//flow/block[3]"/>
+  </checks>
+</testcase>