]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Initial support for page-position="last". Feedback requested!
authorJeremias Maerki <jeremias@apache.org>
Thu, 23 Mar 2006 14:45:17 +0000 (14:45 +0000)
committerJeremias Maerki <jeremias@apache.org>
Thu, 23 Mar 2006 14:45:17 +0000 (14:45 +0000)
See also: http://wiki.apache.org/xmlgraphics-fop/PagePositionLast

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

13 files changed:
src/java/org/apache/fop/fo/pagination/ConditionalPageMasterReference.java
src/java/org/apache/fop/fo/pagination/PageSequence.java
src/java/org/apache/fop/fo/pagination/PageSequenceMaster.java
src/java/org/apache/fop/fo/pagination/RepeatablePageMasterAlternatives.java
src/java/org/apache/fop/fo/pagination/RepeatablePageMasterReference.java
src/java/org/apache/fop/fo/pagination/SinglePageMasterReference.java
src/java/org/apache/fop/fo/pagination/SubSequenceSpecifier.java
src/java/org/apache/fop/layoutmgr/AbstractBreaker.java
src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java
src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
src/java/org/apache/fop/render/rtf/RTFHandler.java
status.xml
test/layoutengine/standard-testcases/page-position_last_1.xml [new file with mode: 0644]

index d688b0fab6563c26a6610b2abddf9e53370537e8..1ac7867514c06c3e4e1b778b87026beae251e23b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-2006 The Apache Software Foundation.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -90,28 +90,32 @@ public class ConditionalPageMasterReference extends FObj {
      * matches.
      * @param isOddPage True if page number odd
      * @param isFirstPage True if page is first page
+     * @param isLastPage True if page is last page
      * @param isBlankPage True if page is blank
      * @return True if the conditions for this reference are met
      */
     protected boolean isValid(boolean isOddPage,
                               boolean isFirstPage,
+                              boolean isLastPage,
                               boolean isBlankPage) {
         // page-position
         if (isFirstPage) {
             if (pagePosition == EN_REST) {
                 return false;
             } else if (pagePosition == EN_LAST) {
-                // ?? how can one know at this point?
-                getLogger().debug("LAST PagePosition NYI");
+                return false;
+            }
+        } else if (isLastPage) {
+            if (pagePosition == EN_REST) {
+                return false;
+            } else if (pagePosition == EN_FIRST) {
                 return false;
             }
         } else {
             if (pagePosition == EN_FIRST) {
                 return false;
             } else if (pagePosition == EN_LAST) {
-                // ?? how can one know at this point?
-                getLogger().debug("LAST PagePosition NYI");
-                // potentially valid, don't return
+                return false;
             }
         }
 
@@ -139,21 +143,22 @@ public class ConditionalPageMasterReference extends FObj {
         return true;
     }
 
-    /**
-     * Returns the "master-reference" property.
-     */
+    /** @return the "master-reference" property. */
     public String getMasterReference() {
         return masterReference;
     }
     
+    /** @return the page-position property value */
+    public int getPagePosition() {
+        return this.pagePosition;
+    }
+    
     /** @see org.apache.fop.fo.FONode#getLocalName() */
     public String getLocalName() {
         return "conditional-page-master-reference";
     }
     
-    /**
-     * @see org.apache.fop.fo.FObj#getNameId()
-     */
+    /** @see org.apache.fop.fo.FObj#getNameId() */
     public int getNameId() {
         return FO_CONDITIONAL_PAGE_MASTER_REFERENCE;
     }
index 010bf9699b2d012253b4799e8d1c83a2ea12756e..b79f194d155927ce8f8e97756c913101de483a68 100644 (file)
@@ -436,15 +436,18 @@ public class PageSequence extends FObj {
     /**
      * Public accessor for determining the next page master to use within this page sequence.
      * @param page the page number of the page to be created
-     * @param bIsFirstPage indicator whether this page is the first page of the
+     * @param isFirstPage indicator whether this page is the first page of the
      *      page sequence
-     * @param bIsBlank indicator whether the page will be blank
+     * @param isLastPage indicator whether this page is the last page of the
+     *      page sequence
+     * @param isBlank indicator whether the page will be blank
      * @return the SimplePageMaster to use for this page
      * @throws FOPException if there's a problem determining the page master
      */
     public SimplePageMaster getNextSimplePageMaster(int page, 
-            boolean bIsFirstPage,  
-            boolean bIsBlank) throws FOPException {
+            boolean isFirstPage,  
+            boolean isLastPage,  
+            boolean isBlank) throws FOPException {
 
         if (pageSequenceMaster == null) {
             return simplePageMaster;
@@ -453,11 +456,12 @@ public class PageSequence extends FObj {
         if (getLogger().isDebugEnabled()) {
             getLogger().debug("getNextSimplePageMaster(page=" + page
                     + " isOdd=" + isOddPage 
-                    + " isFirst=" + bIsFirstPage 
-                    + " isBlank=" + bIsBlank + ")");
+                    + " isFirst=" + isFirstPage 
+                    + " isLast=" + isLastPage 
+                    + " isBlank=" + isBlank + ")");
         }
         return pageSequenceMaster.getNextSimplePageMaster(isOddPage, 
-            bIsFirstPage, bIsBlank);
+            isFirstPage, isLastPage, isBlank);
     }
 
     /**
@@ -472,6 +476,15 @@ public class PageSequence extends FObj {
         }
     }
 
+    /** @return true if the page-sequence has a page-master with page-position="last" */
+    public boolean hasPagePositionLast() {
+        if (pageSequenceMaster == null) {
+            return false;
+        } else {
+            return pageSequenceMaster.hasPagePositionLast();
+        }
+    }
+    
     /**
      * Retrieves the string representation of a page number applicable
      * for this page sequence
index 1f1b012c67c37db05ede8b0e947ebd36aa9388a6..a55e6920cb46ac9d061b370436eafa9e1510db00 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-2006 The Apache Software Foundation.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -159,16 +159,27 @@ public class PageSequenceMaster extends FObj {
         return (currentSubSequence != null);
     }
     
+    /** @return true if the page-sequence-master has a page-master with page-position="last" */
+    public boolean hasPagePositionLast() {
+        if (currentSubSequence != null) {
+            return currentSubSequence.hasPagePositionLast();
+        } else {
+            return false;
+        }
+    }
+    
     /**
      * Returns the next simple-page-master.
      * @param isOddPage True if the next page number is odd
      * @param isFirstPage True if the next page is the first
+     * @param isLastPage True if the next page is the last
      * @param isBlankPage True if the next page is blank
      * @return the requested page master
      * @throws FOPException if there's a problem determining the next page master
      */
     public SimplePageMaster getNextSimplePageMaster(boolean isOddPage,
                                                     boolean isFirstPage,
+                                                    boolean isLastPage,
                                                     boolean isBlankPage)
                                                       throws FOPException {
         if (currentSubSequence == null) {
@@ -179,7 +190,7 @@ public class PageSequenceMaster extends FObj {
             }
         }
         String pageMasterName = currentSubSequence
-            .getNextPageMasterName(isOddPage, isFirstPage, isBlankPage);
+            .getNextPageMasterName(isOddPage, isFirstPage, isLastPage, isBlankPage);
         boolean canRecover = true;
         while (pageMasterName == null) {
             SubSequenceSpecifier nextSubSequence = getNextSubSequence();
@@ -198,7 +209,7 @@ public class PageSequenceMaster extends FObj {
                 currentSubSequence = nextSubSequence;
             }
             pageMasterName = currentSubSequence
-                .getNextPageMasterName(isOddPage, isFirstPage, isBlankPage);
+                .getNextPageMasterName(isOddPage, isFirstPage, isLastPage, isBlankPage);
         }
         SimplePageMaster pageMaster = this.layoutMasterSet
             .getSimplePageMaster(pageMasterName);
index 6ff2f6a4b318184ba31b706930e2267c412fb35f..5a08c287329e6d0205a6a2535dd49209b25b38b7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-2006 The Apache Software Foundation.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ public class RepeatablePageMasterAlternatives extends FObj
     private int numberConsumed = 0;
 
     private List conditionalPageMasterRefs;
+    private boolean hasPagePositionLast = false;
 
     /**
      * @see org.apache.fop.fo.FONode#FONode(FONode)
@@ -93,9 +94,9 @@ public class RepeatablePageMasterAlternatives extends FObj
      */
     protected void validateChildNode(Locator loc, String nsURI, String localName) 
         throws ValidationException {
-        if (!(FO_URI.equals(nsURI) &&
-            localName.equals("conditional-page-master-reference"))) {
-                invalidChildError(loc, nsURI, localName);
+        if (!(FO_URI.equals(nsURI)
+                && localName.equals("conditional-page-master-reference"))) {
+            invalidChildError(loc, nsURI, localName);
         }
     }
 
@@ -121,6 +122,7 @@ public class RepeatablePageMasterAlternatives extends FObj
      */
     public String getNextPageMasterName(boolean isOddPage,
                                         boolean isFirstPage,
+                                        boolean isLastPage,
                                         boolean isBlankPage) {
         if (getMaximumRepeats() != INFINITE) {
             if (numberConsumed < getMaximumRepeats()) {
@@ -128,12 +130,14 @@ public class RepeatablePageMasterAlternatives extends FObj
             } else {
                 return null;
             }
+        } else {
+            numberConsumed++;
         }
 
         for (int i = 0; i < conditionalPageMasterRefs.size(); i++) {
-            ConditionalPageMasterReference cpmr =
-                (ConditionalPageMasterReference)conditionalPageMasterRefs.get(i);
-            if (cpmr.isValid(isOddPage, isFirstPage, isBlankPage)) {
+            ConditionalPageMasterReference cpmr
+                (ConditionalPageMasterReference)conditionalPageMasterRefs.get(i);
+            if (cpmr.isValid(isOddPage, isFirstPage, isLastPage, isBlankPage)) {
                 return cpmr.getMasterReference();
             }
         }
@@ -147,6 +151,9 @@ public class RepeatablePageMasterAlternatives extends FObj
      */
     public void addConditionalPageMasterReference(ConditionalPageMasterReference cpmr) {
         this.conditionalPageMasterRefs.add(cpmr);
+        if (cpmr.getPagePosition() == EN_LAST) {
+            this.hasPagePositionLast = true;
+        }
     }
 
     /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier#reset() */
@@ -164,6 +171,11 @@ public class RepeatablePageMasterAlternatives extends FObj
         }
     }
     
+    /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier#hasPagePositionLast() */
+    public boolean hasPagePositionLast() {
+        return this.hasPagePositionLast;
+    }
+    
     /** @see org.apache.fop.fo.FONode#getLocalName() */
     public String getLocalName() {
         return "repeatable-page-master-alternatives";
@@ -173,4 +185,5 @@ public class RepeatablePageMasterAlternatives extends FObj
     public int getNameId() {
         return FO_REPEATABLE_PAGE_MASTER_ALTERNATIVES;
     }
+
 }
index 1acdf532efb522ac11929e033581af9c044b001c..811b30d54fbe2ac754f30e444d735f4fcd7d8275 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-2006 The Apache Software Foundation.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -91,6 +91,7 @@ public class RepeatablePageMasterReference extends FObj
      */
     public String getNextPageMasterName(boolean isOddPage,
                                         boolean isFirstPage,
+                                        boolean isLastPage,
                                         boolean isEmptyPage) {
         if (getMaximumRepeats() != INFINITE) {
             if (numberConsumed < getMaximumRepeats()) {
@@ -133,6 +134,11 @@ public class RepeatablePageMasterReference extends FObj
         }
     }
     
+    /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier#hasPagePositionLast() */
+    public boolean hasPagePositionLast() {
+        return false;
+    }
+
     /** @see org.apache.fop.fo.FONode#getLocalName() */
     public String getLocalName() {
         return "repeatable-page-master-reference";
@@ -142,4 +148,5 @@ public class RepeatablePageMasterReference extends FObj
     public int getNameId() {
         return FO_REPEATABLE_PAGE_MASTER_REFERENCE;
     }
+
 }
index 75b2ac899d19a95030ff4529400ba806550c9b7d..cea4a20d7a13a8310a6a387620c58dc92e8e11a4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-2006 The Apache Software Foundation.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -83,6 +83,7 @@ public class SinglePageMasterReference extends FObj
     /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier */
     public String getNextPageMasterName(boolean isOddPage,
                                         boolean isFirstPage,
+                                        boolean isLastPage,
                                         boolean isEmptyPage) {
         if (this.state == FIRST) {
             this.state = DONE;
@@ -109,6 +110,11 @@ public class SinglePageMasterReference extends FObj
         }
     }
     
+    /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier#hasPagePositionLast() */
+    public boolean hasPagePositionLast() {
+        return false;
+    }
+
     /** @see org.apache.fop.fo.FONode#getLocalName() */
     public String getLocalName() {
         return "single-page-master-reference";
index 39609bb051e4487b9894077168a999930ab6fb53..2d0138f32a2c5a13ee5b78564c68ca9a8d90e19f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-2006 The Apache Software Foundation.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,12 +30,14 @@ public interface SubSequenceSpecifier {
      * Returns the name of the next page master.
      * @param isOddPage True if the next page number is odd
      * @param isFirstPage True if the next page is the first
+     * @param isLastPage True if the next page is the last
      * @param isBlankPage True if the next page is blank
      * @return the page master name
      * @throws FOPException if there's a problem determining the next page master
      */
     String getNextPageMasterName(boolean isOddPage,
                                  boolean isFirstPage,
+                                 boolean isLastPage,
                                  boolean isBlankPage)
                                     throws FOPException;
 
@@ -50,6 +52,9 @@ public interface SubSequenceSpecifier {
      * @return true if there is a previous item, false if the current one was the first one.
      */
     boolean goToPrevious();
+
+    /** @return true if the subsequence has a page master for page-position "last" */
+    boolean hasPagePositionLast();
     
 }
 
index 732a0133a753654504a6c7dc57b3fc46984af6e3..eea83f1ab5ad6ebc61433e24631c3c5f0a400df0 100644 (file)
@@ -318,13 +318,26 @@ public abstract class AbstractBreaker {
      */
     protected void addAreas(PageBreakingAlgorithm alg, int partCount, 
             BlockSequence originalList, BlockSequence effectiveList) {
+        addAreas(alg, 0, partCount, originalList, effectiveList);
+    }
+    
+    /**
+     * Phase 3 of Knuth algorithm: Adds the areas 
+     * @param alg PageBreakingAlgorithm instance which determined the breaks
+     * @param startPart index of the first part (page) to be rendered
+     * @param partCount number of parts (pages) to be rendered
+     * @param originalList original Knuth element list
+     * @param effectiveList effective Knuth element list (after adjustments)
+     */
+    protected void addAreas(PageBreakingAlgorithm alg, int startPart, int partCount, 
+            BlockSequence originalList, BlockSequence effectiveList) {
         LayoutContext childLC;
         // add areas
         ListIterator effectiveListIterator = effectiveList.listIterator();
         int startElementIndex = 0;
         int endElementIndex = 0;
         int lastBreak = -1;
-        for (int p = 0; p < partCount; p++) {
+        for (int p = startPart; p < startPart + partCount; p++) {
             PageBreakPosition pbp = (PageBreakPosition) alg.getPageBreaks().get(p);
 
             //Check the last break position for forced breaks
@@ -426,7 +439,8 @@ public abstract class AbstractBreaker {
 
                 /* *** *** non-standard extension *** *** */
                 if (displayAlign == Constants.EN_X_FILL) {
-                    int averageLineLength = optimizeLineLength(effectiveList, startElementIndex, endElementIndex);
+                    int averageLineLength = optimizeLineLength(effectiveList, 
+                            startElementIndex, endElementIndex);
                     if (averageLineLength != 0) {
                         childLC.setStackLimit(new MinOptMax(averageLineLength));
                     }
index 4109836e03b144d9f4a309e6bcb54559c7ac6ae4..0e7a554b8a804d439b21ac6fa7bb973a38381f41 100644 (file)
@@ -991,4 +991,15 @@ public abstract class BreakingAlgorithm {
             bestActiveNode = bestActiveNode.previous;
         }
     }
+    
+    /** @return the alignment for normal lines/parts */
+    public int getAlignment() {
+        return this.alignment;
+    }
+
+    /** @return the alignment for the last line/part */
+    public int getAlignmentLast() {
+        return this.alignmentLast;
+    }
+
 }
index b94b2623c5891fe08662b44c0f9eb13010bc0bfd..a410e1838cf10bab252c762e409c51fc6cc9a092 100644 (file)
@@ -154,6 +154,9 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager {
         finishPage();
     }
         
+    /**
+     * Finished the page-sequence and notifies everyone about it.
+     */
     public void finishPageSequence() {
         pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
                 (currentPageNum - startPageNum) + 1);
@@ -318,62 +321,132 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager {
         protected void doPhase3(PageBreakingAlgorithm alg, int partCount, 
                 BlockSequence originalList, BlockSequence effectiveList) {
             if (needColumnBalancing) {
-                AbstractBreaker.log.debug("Column balancing now!!!");
-                AbstractBreaker.log.debug("===================================================");
-                int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
-                if (restartPoint > 0) {
-                    addAreas(alg, restartPoint, originalList, effectiveList);
-                }
-                
-                int newStartPos;
-                if (restartPoint > 0) {
-                    PageBreakPosition pbp = (PageBreakPosition)
-                            alg.getPageBreaks().get(restartPoint - 1);
-                    newStartPos = pbp.getLeafPos();
+                doPhase3WithColumnBalancing(alg, partCount, originalList, effectiveList);
+            } else {
+                if (!hasMoreContent() && pageSeq.hasPagePositionLast()) {
+                    //last part is reached and we have a "last page" condition
+                    doPhase3WithLastPage(alg, partCount, originalList, effectiveList);
                 } else {
-                    newStartPos = 0;
+                    //Directly add areas after finding the breaks
+                    addAreas(alg, partCount, originalList, effectiveList);
                 }
-                AbstractBreaker.log.debug("Restarting at " + restartPoint 
-                        + ", new start position: " + newStartPos);
+            }
+        }
 
+        private void doPhase3WithLastPage(PageBreakingAlgorithm alg, int partCount, 
+                BlockSequence originalList, BlockSequence effectiveList) {
+            int newStartPos;
+            int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
+            if (restartPoint > 0) {
+                //Add definitive areas before last page
+                addAreas(alg, restartPoint, originalList, effectiveList);
+                //Get page break from which we restart
+                PageBreakPosition pbp = (PageBreakPosition)
+                        alg.getPageBreaks().get(restartPoint - 1);
+                newStartPos = pbp.getLeafPos();
                 //Handle page break right here to avoid any side-effects
                 if (newStartPos > 0) {
                     handleBreakTrait(EN_PAGE);
                 }
-                pageBreakHandled = true;
-                //Update so the available BPD is reported correctly
-                pageProvider.setStartOfNextElementList(currentPageNum, 
-                        getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
+            } else {
+                newStartPos = 0;
+            }
+            AbstractBreaker.log.debug("Last page handling now!!!");
+            AbstractBreaker.log.debug("===================================================");
+            AbstractBreaker.log.debug("Restarting at " + restartPoint 
+                    + ", new start position: " + newStartPos);
 
-                //Restart last page
-                PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
-                        getTopLevelLM(),
-                        getPageProvider(),
-                        alignment, Constants.EN_START, footnoteSeparatorLength,
-                        isPartOverflowRecoveryActivated(),
-                        getCurrentPV().getBodyRegion().getColumnCount());
-                //alg.setConstantLineWidth(flowBPD);
-                int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
-                            newStartPos,
-                            1, true, BreakingAlgorithm.ALL_BREAKS);
-                AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
-                        + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
-                if (iOptPageCount > getCurrentPV().getBodyRegion().getColumnCount()) {
-                    AbstractBreaker.log.warn(
-                            "Breaking algorithm produced more columns than are available.");
-                    /* reenable when everything works
-                    throw new IllegalStateException(
-                            "Breaking algorithm must not produce more columns than available.");
-                    */
-                }
+            pageBreakHandled = true;
+            //Update so the available BPD is reported correctly
+            pageProvider.setStartOfNextElementList(currentPageNum, 
+                    getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
+            pageProvider.setLastPageIndex(currentPageNum);
+
+            //Restart last page
+            PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
+                    getTopLevelLM(),
+                    getPageProvider(),
+                    alg.getAlignment(), alg.getAlignmentLast(), 
+                    footnoteSeparatorLength,
+                    isPartOverflowRecoveryActivated(), false);
+            //alg.setConstantLineWidth(flowBPD);
+            int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
+                        newStartPos,
+                        1, true, BreakingAlgorithm.ALL_BREAKS);
+            AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
+                    + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
+            boolean replaceLastPage 
+                    = iOptPageCount <= getCurrentPV().getBodyRegion().getColumnCount(); 
+            if (replaceLastPage) {
+
+                //Replace last page
+                pslm.curPage = pageProvider.getPage(false, currentPageNum);
                 //Make sure we only add the areas we haven't added already
                 effectiveList.ignoreAtStart = newStartPos;
                 addAreas(algRestart, iOptPageCount, originalList, effectiveList);
-                AbstractBreaker.log.debug("===================================================");
             } else {
-                //Directly add areas after finding the breaks
-                addAreas(alg, partCount, originalList, effectiveList);
+                effectiveList.ignoreAtStart = newStartPos;
+                addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList);
+                //Add blank last page
+                pageProvider.setLastPageIndex(currentPageNum + 1);
+                pslm.curPage = makeNewPage(true, true);
+            }
+            AbstractBreaker.log.debug("===================================================");
+        }
+
+        private void doPhase3WithColumnBalancing(PageBreakingAlgorithm alg, int partCount, 
+                BlockSequence originalList, BlockSequence effectiveList) {
+            AbstractBreaker.log.debug("Column balancing now!!!");
+            AbstractBreaker.log.debug("===================================================");
+            int newStartPos;
+            int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
+            if (restartPoint > 0) {
+                //Add definitive areas
+                addAreas(alg, restartPoint, originalList, effectiveList);
+                //Get page break from which we restart
+                PageBreakPosition pbp = (PageBreakPosition)
+                        alg.getPageBreaks().get(restartPoint - 1);
+                newStartPos = pbp.getLeafPos();
+                //Handle page break right here to avoid any side-effects
+                if (newStartPos > 0) {
+                    handleBreakTrait(EN_PAGE);
+                }
+            } else {
+                newStartPos = 0;
+            }
+            AbstractBreaker.log.debug("Restarting at " + restartPoint 
+                    + ", new start position: " + newStartPos);
+
+            pageBreakHandled = true;
+            //Update so the available BPD is reported correctly
+            pageProvider.setStartOfNextElementList(currentPageNum, 
+                    getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
+
+            //Restart last page
+            PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
+                    getTopLevelLM(),
+                    getPageProvider(),
+                    alignment, Constants.EN_START, footnoteSeparatorLength,
+                    isPartOverflowRecoveryActivated(),
+                    getCurrentPV().getBodyRegion().getColumnCount());
+            //alg.setConstantLineWidth(flowBPD);
+            int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
+                        newStartPos,
+                        1, true, BreakingAlgorithm.ALL_BREAKS);
+            AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
+                    + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
+            if (iOptPageCount > getCurrentPV().getBodyRegion().getColumnCount()) {
+                AbstractBreaker.log.warn(
+                        "Breaking algorithm produced more columns than are available.");
+                /* reenable when everything works
+                throw new IllegalStateException(
+                        "Breaking algorithm must not produce more columns than available.");
+                */
             }
+            //Make sure we only add the areas we haven't added already
+            effectiveList.ignoreAtStart = newStartPos;
+            addAreas(algRestart, iOptPageCount, originalList, effectiveList);
+            AbstractBreaker.log.debug("===================================================");
         }
         
         protected void startPart(BlockSequence list, int breakClass) {
@@ -728,6 +801,9 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager {
         private int startColumnOfCurrentElementList;
         private List cachedPages = new java.util.ArrayList();
         
+        private int lastPageIndex = -1;
+        private int indexOfCachedLastPage = -1;
+        
         //Cache to optimize getAvailableBPD() calls
         private int lastRequestedIndex = -1;
         private int lastReportedBPD = -1;
@@ -756,6 +832,15 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager {
             this.lastReportedBPD = -1;
         }
         
+        /**
+         * Sets the index of the last page. This is done as soon as the position of the last page
+         * is known or assumed.
+         * @param index the index relative to the first page in the page-sequence
+         */
+        public void setLastPageIndex(int index) {
+            this.lastPageIndex = index;
+        }
+        
         /**
          * Returns the available BPD for the part/page indicated by the index parameter.
          * The index is the part/page relative to the start of the current element list.
@@ -823,60 +908,79 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager {
 
         /**
          * Returns a Page.
-         * @param bIsBlank true if this page is supposed to be blank.
+         * @param isBlank true if this page is supposed to be blank.
          * @param index Index of the page (see relativeTo)
          * @param relativeTo Defines which value the index parameter should be evaluated relative 
          * to. (One of PageProvider.RELTO_*)
          * @return the requested Page
          */
-        public Page getPage(boolean bIsBlank, int index, int relativeTo) {
+        public Page getPage(boolean isBlank, int index, int relativeTo) {
             if (relativeTo == RELTO_PAGE_SEQUENCE) {
-                return getPage(bIsBlank, index);
+                return getPage(isBlank, index);
             } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
                 int effIndex = startPageOfCurrentElementList + index;
                 effIndex += startPageOfPageSequence - 1;
-                return getPage(bIsBlank, effIndex);
+                return getPage(isBlank, effIndex);
             } else {
                 throw new IllegalArgumentException(
                         "Illegal value for relativeTo: " + relativeTo);
             }
         }
         
-        private Page getPage(boolean bIsBlank, int index) {
+        private Page getPage(boolean isBlank, int index) {
+            boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
             if (log.isTraceEnabled()) {
-                log.trace("getPage(" + index + " " + bIsBlank);
+                log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank") 
+                        + (isLastPage ? " <LAST>" : "") + ")");
             }
             int intIndex = index - startPageOfPageSequence;
             if (log.isTraceEnabled()) {
-                if (bIsBlank) {
+                if (isBlank) {
                     log.trace("blank page requested: " + index);
                 }
+                if (isLastPage) {
+                    log.trace("last page requested: " + index);
+                }
             }
             while (intIndex >= cachedPages.size()) {
                 if (log.isTraceEnabled()) {
                     log.trace("Caching " + index);
                 }
-                cacheNextPage(index, bIsBlank);
+                cacheNextPage(index, isBlank, isLastPage);
             }
             Page page = (Page)cachedPages.get(intIndex);
-            if (page.getPageViewport().isBlank() != bIsBlank) {
+            boolean replace = false;
+            if (page.getPageViewport().isBlank() != isBlank) {
                 log.debug("blank condition doesn't match. Replacing PageViewport.");
-                while (intIndex < cachedPages.size()) {
-                    this.cachedPages.remove(cachedPages.size() - 1);
-                    if (!pageSeq.goToPreviousSimplePageMaster()) {
-                        log.warn("goToPreviousSimplePageMaster() on the first page called!");
-                    }
-                }
-                cacheNextPage(index, bIsBlank);
+                replace = true;
+            }
+            if ((isLastPage && indexOfCachedLastPage != intIndex)
+                    || (!isLastPage && indexOfCachedLastPage >= 0)) {
+                log.debug("last page condition doesn't match. Replacing PageViewport.");
+                replace = true;
+                indexOfCachedLastPage = (isLastPage ? intIndex : -1);
+            }
+            if (replace) {
+                disardCacheStartingWith(intIndex);
+                page = cacheNextPage(index, isBlank, isLastPage);
             }
             return page;
         }
+
+        private void disardCacheStartingWith(int index) {
+            while (index < cachedPages.size()) {
+                this.cachedPages.remove(cachedPages.size() - 1);
+                if (!pageSeq.goToPreviousSimplePageMaster()) {
+                    log.warn("goToPreviousSimplePageMaster() on the first page called!");
+                }
+            }
+        }
         
-        private void cacheNextPage(int index, boolean bIsBlank) {
+        private Page cacheNextPage(int index, boolean isBlank, boolean isLastPage) {
             try {
                 String pageNumberString = pageSeq.makeFormattedPageNumber(index);
                 SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
-                        index, (startPageOfPageSequence == index), bIsBlank);
+                        index, (startPageOfPageSequence == index), isLastPage, isBlank);
                     
                 Region body = spm.getRegion(FO_REGION_BODY);
                 if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) {
@@ -887,8 +991,9 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager {
                         + spm.getMasterName() + "'.  FOP presently "
                         + "does not support this.");
                 }
-                Page page = new Page(spm, index, pageNumberString, bIsBlank);
+                Page page = new Page(spm, index, pageNumberString, isBlank);
                 cachedPages.add(page);
+                return page;
             } catch (FOPException e) {
                 //TODO Maybe improve. It'll mean to propagate this exception up several
                 //methods calls.
index 41cc3dba849f67ad929a95e7b8186cdf0bb28c42..6c99ab4fe191079b1c35a426ac30e043b4b50ccd 100644 (file)
@@ -186,7 +186,7 @@ public class RTFHandler extends FOEventHandler {
                     log.warn("Using default simple-page-master from page-sequence-master...");
                     PageSequenceMaster master 
                         = pageSeq.getRoot().getLayoutMasterSet().getPageSequenceMaster(reference);
-                    this.pagemaster = master.getNextSimplePageMaster(false, false, false);
+                    this.pagemaster = master.getNextSimplePageMaster(false, false, false, false);
                 }
             }
 
index 65b0221dfd759ec2ccdf0e3cba347a9430fc15ce..964f7d56e546e5b2dcb67c26e37e05bc108e5237 100644 (file)
@@ -27,6 +27,9 @@
 
   <changes>
     <release version="FOP Trunk">
+      <action context="Code" dev="JM" type="add">
+        Initial support for page-position="last" added.
+      </action>
       <action context="Code" dev="JM" type="add">
         Reenabled loading of user-supplied hyphenation patterns that was available in
         FOP 0.20.5. (See "hyphenation-base" option in the user configuration)
diff --git a/test/layoutengine/standard-testcases/page-position_last_1.xml b/test/layoutengine/standard-testcases/page-position_last_1.xml
new file mode 100644 (file)
index 0000000..bb83c28
--- /dev/null
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2006 The Apache Software Foundation
+
+  Licensed 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 page-position="last".
+    </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="5in">
+          <fo:region-body margin-bottom="1in" column-count="2" column-gap="10pt" background-color="lightgray"/>
+          <fo:region-after extent="1in" display-align="after"/>
+        </fo:simple-page-master>
+        <fo:simple-page-master master-name="last" page-width="5in" page-height="5in">
+          <fo:region-body margin-bottom="3in" column-count="2" column-gap="10pt" background-color="rgb(250,250,240)"/>
+          <fo:region-after extent="1in" display-align="after"/>
+        </fo:simple-page-master>
+        <fo:page-sequence-master master-name="master">
+          <fo:repeatable-page-master-alternatives>
+            <fo:conditional-page-master-reference master-reference="last" page-position="last"/>
+            <fo:conditional-page-master-reference master-reference="normal" page-position="any"/>
+          </fo:repeatable-page-master-alternatives>
+        </fo:page-sequence-master>
+      </fo:layout-master-set>
+      <fo:page-sequence master-reference="master" id="replace-last">
+        <fo:static-content flow-name="xsl-region-after">
+          <fo:block text-align="end">page <fo:page-number/> of <fo:page-number-citation ref-id="eof"/></fo:block>
+        </fo:static-content>
+        <fo:flow flow-name="xsl-region-body">
+          <fo:block-container id="boxA0" height="1.5in" background-color="yellow" text-align="center" display-align="center" break-after="page">
+            <fo:block>box0</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA1" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box1</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA2" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box2</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA3" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box3</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA4" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box4</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA5" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box5</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA6" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box6</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA7" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box7</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA8" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box8</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA9" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box9</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxA10" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box10</fo:block>
+          </fo:block-container>
+          <fo:block id="eof"/>
+        </fo:flow>
+      </fo:page-sequence>
+      <fo:page-sequence master-reference="master" id="blank-last">
+        <fo:static-content flow-name="xsl-region-after">
+          <fo:block text-align="end">page <fo:page-number/> of <fo:page-number-citation ref-id="eof"/></fo:block>
+        </fo:static-content>
+        <fo:flow flow-name="xsl-region-body">
+          <fo:block-container id="boxB1" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box1</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB2" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box2</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB3" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box3</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB4" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box4</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB5" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box5</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB6" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box6</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB7" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box7</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB8" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box8</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB9" height="1.5in" background-color="yellow" text-align="center" display-align="center">
+            <fo:block>box9</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB10" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box10</fo:block>
+          </fo:block-container>
+          <fo:block-container id="boxB11" height="1.5in" background-color="orange" text-align="center" display-align="center">
+            <fo:block>box11</fo:block>
+          </fo:block-container>
+        </fo:flow>
+      </fo:page-sequence>
+    </fo:root>
+  </fo>
+  <checks>
+    <eval expected="4" xpath="count(//pageSequence[1]/pageViewport)"/>
+    <eval expected="1" xpath="//block[@prod-id='boxA0']/ancestor::pageViewport/@nr"/>
+    <eval expected="2" xpath="//block[@prod-id='boxA1']/ancestor::pageViewport/@nr"/>
+    <eval expected="2" xpath="//block[@prod-id='boxA2']/ancestor::pageViewport/@nr"/>
+    <eval expected="2" xpath="//block[@prod-id='boxA3']/ancestor::pageViewport/@nr"/>
+    <eval expected="2" xpath="//block[@prod-id='boxA4']/ancestor::pageViewport/@nr"/>
+    <eval expected="3" xpath="//block[@prod-id='boxA5']/ancestor::pageViewport/@nr"/>
+    <eval expected="3" xpath="//block[@prod-id='boxA6']/ancestor::pageViewport/@nr"/>
+    <eval expected="3" xpath="//block[@prod-id='boxA7']/ancestor::pageViewport/@nr"/>
+    <eval expected="3" xpath="//block[@prod-id='boxA8']/ancestor::pageViewport/@nr"/>
+    <eval expected="4" xpath="//block[@prod-id='boxA9']/ancestor::pageViewport/@nr"/>
+    <eval expected="4" xpath="//block[@prod-id='boxA10']/ancestor::pageViewport/@nr"/>
+    <eval expected="288000" xpath="//pageViewport[@nr='1']/descendant::regionBody/@bpd"/>
+    <eval expected="288000" xpath="//pageViewport[@nr='2']/descendant::regionBody/@bpd"/>
+    <eval expected="288000" xpath="//pageViewport[@nr='3']/descendant::regionBody/@bpd"/>
+    <eval expected="144000" xpath="//pageViewport[@nr='4']/descendant::regionBody/@bpd"/>
+    <eval expected="normal" xpath="//pageViewport[@nr='1']/@simple-page-master-name"/>
+    <eval expected="normal" xpath="//pageViewport[@nr='2']/@simple-page-master-name"/>
+    <eval expected="normal" xpath="//pageViewport[@nr='3']/@simple-page-master-name"/>
+    <eval expected="last" xpath="//pageViewport[@nr='4']/@simple-page-master-name"/>
+
+    <eval expected="4" xpath="count(//pageSequence[2]/pageViewport)"/>
+    <eval expected="5" xpath="//block[@prod-id='boxB1']/ancestor::pageViewport/@nr"/>
+    <eval expected="5" xpath="//block[@prod-id='boxB2']/ancestor::pageViewport/@nr"/>
+    <eval expected="5" xpath="//block[@prod-id='boxB3']/ancestor::pageViewport/@nr"/>
+    <eval expected="5" xpath="//block[@prod-id='boxB4']/ancestor::pageViewport/@nr"/>
+    <eval expected="6" xpath="//block[@prod-id='boxB5']/ancestor::pageViewport/@nr"/>
+    <eval expected="6" xpath="//block[@prod-id='boxB6']/ancestor::pageViewport/@nr"/>
+    <eval expected="6" xpath="//block[@prod-id='boxB7']/ancestor::pageViewport/@nr"/>
+    <eval expected="6" xpath="//block[@prod-id='boxB8']/ancestor::pageViewport/@nr"/>
+    <eval expected="7" xpath="//block[@prod-id='boxB9']/ancestor::pageViewport/@nr"/>
+    <eval expected="7" xpath="//block[@prod-id='boxB10']/ancestor::pageViewport/@nr"/>
+    <eval expected="7" xpath="//block[@prod-id='boxB11']/ancestor::pageViewport/@nr"/>
+    <eval expected="288000" xpath="//pageViewport[@nr='5']/descendant::regionBody/@bpd"/>
+    <eval expected="288000" xpath="//pageViewport[@nr='6']/descendant::regionBody/@bpd"/>
+    <eval expected="288000" xpath="//pageViewport[@nr='7']/descendant::regionBody/@bpd"/>
+    <eval expected="144000" xpath="//pageViewport[@nr='8']/descendant::regionBody/@bpd"/>
+    <eval expected="normal" xpath="//pageViewport[@nr='5']/@simple-page-master-name"/>
+    <eval expected="normal" xpath="//pageViewport[@nr='6']/@simple-page-master-name"/>
+    <eval expected="normal" xpath="//pageViewport[@nr='7']/@simple-page-master-name"/>
+    <eval expected="last" xpath="//pageViewport[@nr='8']/@simple-page-master-name"/>
+  </checks>
+</testcase>