From ef9732a759aa15d5155c5a6469a3ab161b17ab31 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Wed, 29 Jun 2005 13:15:43 +0000 Subject: [PATCH] Bugfix: IPD for footnote region now correct in multi-column layout. Next step at multi-column layout: - A multi-column section that needs column balancing is normally rendered until the next-to-last page. The rest of the element are re-broken by a special balancing page breaker. - Multiple spans supported in area tree and through break handling. - There are still problems with footnotes and column balancing. - Main layout loop changed to render an element list right after it's broken. The block lists are not collected anymore and then rendered. Bugfix: PageViewportProvider had a one-off (when accessing through "relative to current element list") git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@202368 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fop/layoutmgr/AbstractBreaker.java | 121 ++++++---- .../fop/layoutmgr/PageBreakingAlgorithm.java | 6 +- .../layoutmgr/PageSequenceLayoutManager.java | 224 ++++++++++++++++-- 3 files changed, 277 insertions(+), 74 deletions(-) diff --git a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java index 7b5a2eb5a..5b14e5dab 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java @@ -149,10 +149,22 @@ public abstract class AbstractBreaker { protected abstract void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp); + /** + * Creates the top-level LayoutContext for the breaker operation. + * @return the top-level LayoutContext + */ protected LayoutContext createLayoutContext() { return new LayoutContext(0); } + /** + * Used to update the LayoutContext in subclasses prior to starting a new element list. + * @param context the LayoutContext to update + */ + protected void updateLayoutContext(LayoutContext context) { + //nop + } + public void doLayout(int flowBPD) { LayoutContext childLC = createLayoutContext(); childLC.setStackLimit(new MinOptMax(flowBPD)); @@ -175,53 +187,56 @@ public abstract class AbstractBreaker { //*** Phase 1: Get Knuth elements *** int nextSequenceStartsOn = Constants.EN_ANY; while (hasMoreContent()) { + blockLists.clear(); + nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn, blockLists); - } - //*** Phase 2: Alignment and breaking *** - log.debug("PLM> blockLists.size() = " + blockLists.size()); - for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) { - blockList = (BlockSequence) blockLists.get(blockListIndex); - - //debug code start - if (log.isDebugEnabled()) { - log.debug(" blockListIndex = " + blockListIndex); - String pagina = (blockList.startOn == Constants.EN_ANY) ? "any page" - : (blockList.startOn == Constants.EN_ODD_PAGE) ? "odd page" - : "even page"; - log.debug(" sequence starts on " + pagina); - } - ElementListObserver.observe(blockList, "breaker", null); - //debug code end - - log.debug("PLM> start of algorithm (" + this.getClass().getName() - + "), flow BPD =" + flowBPD); - PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(), - getPageViewportProvider(), - alignment, alignmentLast, footnoteSeparatorLength, - isPartOverflowRecoveryActivated()); - int iOptPageCount; - - BlockSequence effectiveList; - if (alignment == Constants.EN_JUSTIFY) { - /* justification */ - effectiveList = justifyBoxes(blockList, alg, flowBPD); - } else { - /* no justification */ - effectiveList = blockList; - } + //*** Phase 2: Alignment and breaking *** + log.debug("PLM> blockLists.size() = " + blockLists.size()); + for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) { + blockList = (BlockSequence) blockLists.get(blockListIndex); + + //debug code start + if (log.isDebugEnabled()) { + log.debug(" blockListIndex = " + blockListIndex); + String pagina = (blockList.startOn == Constants.EN_ANY) ? "any page" + : (blockList.startOn == Constants.EN_ODD_PAGE) ? "odd page" + : "even page"; + log.debug(" sequence starts on " + pagina); + } + ElementListObserver.observe(blockList, "breaker", null); + //debug code end + + log.debug("PLM> start of algorithm (" + this.getClass().getName() + + "), flow BPD =" + flowBPD); + PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(), + getPageViewportProvider(), + alignment, alignmentLast, footnoteSeparatorLength, + isPartOverflowRecoveryActivated()); + int iOptPageCount; + + BlockSequence effectiveList; + if (alignment == Constants.EN_JUSTIFY) { + /* justification */ + effectiveList = justifyBoxes(blockList, alg, flowBPD); + } else { + /* no justification */ + effectiveList = blockList; + } - //iOptPageCount = alg.firstFit(effectiveList, flowBPD, 1, true); - alg.setConstantLineWidth(flowBPD); - iOptPageCount = alg.findBreakingPoints(effectiveList, /*flowBPD,*/ - 1, true, true); - log.debug("PLM> iOptPageCount= " + iOptPageCount - + " pageBreaks.size()= " + alg.getPageBreaks().size()); + //iOptPageCount = alg.firstFit(effectiveList, flowBPD, 1, true); + alg.setConstantLineWidth(flowBPD); + iOptPageCount = alg.findBreakingPoints(effectiveList, /*flowBPD,*/ + 1, true, true); + log.debug("PLM> iOptPageCount= " + iOptPageCount + + " pageBreaks.size()= " + alg.getPageBreaks().size()); - - //*** Phase 3: Add areas *** - doPhase3(alg, iOptPageCount, blockList, effectiveList); + + //*** Phase 3: Add areas *** + doPhase3(alg, iOptPageCount, blockList, effectiveList); + } } + } /** @@ -356,6 +371,16 @@ public abstract class AbstractBreaker { } } + /** + * Handles span changes reported through the LayoutContext. + * Only used by the PSLM and called by getNextBlockList(). + * @param childLC the LayoutContext + * @param nextSequenceStartsOn previous value for break handling + * @return effective value for break handling + */ + protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) { + return nextSequenceStartsOn; + } /** * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty. * @param childLC LayoutContext to use @@ -363,7 +388,13 @@ public abstract class AbstractBreaker { * @param blockLists list of block lists (sequences) * @return the page on which the next content should appear after a hard break */ - private int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn, List blockLists) { + protected int getNextBlockList(LayoutContext childLC, + int nextSequenceStartsOn, + List blockLists) { + updateLayoutContext(childLC); + //Make sure the span change signal is reset + childLC.signalSpanChange(Constants.NOT_SET); + LinkedList returnedList; BlockSequence blockList; if ((returnedList = getNextKnuthElements(childLC, alignment)) != null) { @@ -371,6 +402,10 @@ public abstract class AbstractBreaker { return nextSequenceStartsOn; } blockList = new BlockSequence(nextSequenceStartsOn); + + //Only implemented by the PSLM + nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn); + if (((KnuthElement) returnedList.getLast()).isPenalty() && ((KnuthPenalty) returnedList.getLast()).getP() == -KnuthElement.INFINITE) { KnuthPenalty breakPenalty = (KnuthPenalty) returnedList diff --git a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java index b96d2ad78..a303630e7 100644 --- a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java +++ b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.ListIterator; -import org.apache.fop.area.PageViewport; import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition; import org.apache.fop.traits.MinOptMax; @@ -752,10 +751,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { protected int getLineWidth(int line) { int bpd; if (pvProvider != null) { - PageViewport pv = pvProvider.getPageViewport( - false, line, - PageSequenceLayoutManager.PageViewportProvider.RELTO_CURRENT_ELEMENT_LIST); - bpd = pv.getBodyRegion().getBPD(); + bpd = pvProvider.getAvailableBPD(line); } else { bpd = super.getLineWidth(line); } diff --git a/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java b/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java index 30f2ebbbd..c64a30788 100644 --- a/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java @@ -18,6 +18,8 @@ package org.apache.fop.layoutmgr; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.fop.apps.FOPException; import org.apache.fop.area.AreaTreeHandler; @@ -52,6 +54,8 @@ import java.util.ListIterator; */ public class PageSequenceLayoutManager extends AbstractLayoutManager { + private Log log = LogFactory.getLog(PageSequenceLayoutManager.class); + /** * AreaTreeHandler which activates the PSLM and controls * the rendering of its pages. @@ -137,7 +141,7 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { makeFlowLayoutManager(this, mainFlow); PageBreaker breaker = new PageBreaker(this); - int flowBPD = (int) curPV.getBodyRegion().getBPD(); + int flowBPD = (int) curPV.getBodyRegion().getRemainingBPD(); breaker.doLayout(flowBPD); finishPage(); @@ -150,6 +154,8 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { private PageSequenceLayoutManager pslm; private boolean firstPart = true; + private boolean pageBreakHandled; + private boolean needColumnBalancing; private StaticContentLayoutManager footnoteSeparatorLM = null; @@ -157,11 +163,10 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { this.pslm = pslm; } - protected LayoutContext createLayoutContext() { - LayoutContext lc = new LayoutContext(0); + /** @see org.apache.fop.layoutmgr.AbstractBreaker */ + protected void updateLayoutContext(LayoutContext context) { int flowIPD = curPV.getCurrentSpan().getColumnWidth(); - lc.setRefIPD(flowIPD); - return lc; + context.setRefIPD(flowIPD); } protected LayoutManager getTopLevelLM() { @@ -173,6 +178,39 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { return pvProvider; } + /** @see org.apache.fop.layoutmgr.AbstractBreaker */ + protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) { + needColumnBalancing = false; + if (childLC.getNextSpan() != Constants.NOT_SET) { + //Next block list will have a different span. + nextSequenceStartsOn = childLC.getNextSpan(); + needColumnBalancing = (childLC.getNextSpan() == Constants.EN_ALL); + } + if (needColumnBalancing) { + log.debug("Column balancing necessary for the next element list!!!"); + } + return nextSequenceStartsOn; + } + + /** @see org.apache.fop.layoutmgr.AbstractBreaker */ + protected int getNextBlockList(LayoutContext childLC, + int nextSequenceStartsOn, + List blockLists) { + if (!firstPart) { + // if this is the first page that will be created by + // the current BlockSequence, it could have a break + // condition that must be satisfied; + // otherwise, we may simply need a new page + handleBreakTrait(nextSequenceStartsOn); + } + firstPart = false; + pageBreakHandled = true; + pvProvider.setStartOfNextElementList(currentPageNum, + curPV.getCurrentSpan().getCurrentFlowIndex()); + return super.getNextBlockList(childLC, nextSequenceStartsOn, blockLists); + } + + /** @see org.apache.fop.layoutmgr.AbstractBreaker */ protected LinkedList getNextKnuthElements(LayoutContext context, int alignment) { LinkedList contentList = null; @@ -190,6 +228,9 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { && ((KnuthBlockBox) element).hasAnchors()) { // element represents a line with footnote citations bFootnotesPresent = true; + LayoutContext footnoteContext = new LayoutContext(context); + footnoteContext.setRefIPD(getCurrentPV() + .getRegionReference(Constants.FO_REGION_BODY).getIPD()); LinkedList footnoteBodyLMs = ((KnuthBlockBox) element).getFootnoteBodyLMs(); ListIterator footnoteBodyIterator = footnoteBodyLMs.listIterator(); // store the lists of elements representing the footnote bodies @@ -199,7 +240,7 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { = (FootnoteBodyLayoutManager) footnoteBodyIterator.next(); fblm.setParent(childFLM); ((KnuthBlockBox) element).addElementList( - fblm.getNextKnuthElements(context, alignment)); + fblm.getNextKnuthElements(footnoteContext, alignment)); } } } @@ -219,7 +260,8 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { // create a Block area that will contain the separator areas separatorArea = new Block(); - separatorArea.setIPD(context.getRefIPD()); + separatorArea.setIPD(pslm.getCurrentPV() + .getRegionReference(Constants.FO_REGION_BODY).getIPD()); // create a StaticContentLM for the footnote separator footnoteSeparatorLM = (StaticContentLayoutManager) getLayoutManagerMaker().makeStaticContentLayoutManager( @@ -245,7 +287,7 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { "xsl-footnote-separator"); // create a Block area that will contain the separator areas separatorArea = new Block(); - separatorArea.setIPD(curPV.getCurrentSpan().getColumnWidth()); + separatorArea.setIPD(curPV.getRegionReference(Constants.FO_REGION_BODY).getIPD()); // create a StaticContentLM for the footnote separator footnoteSeparatorLM = (StaticContentLayoutManager) getLayoutManagerMaker().makeStaticContentLayoutManager( @@ -258,30 +300,74 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { protected void doPhase3(PageBreakingAlgorithm alg, int partCount, BlockSequence originalList, BlockSequence effectiveList) { - //Directly add areas after finding the breaks - addAreas(alg, partCount, originalList, effectiveList); + if (needColumnBalancing) { + log.debug("Column balancing now!!!"); + log.debug("==================================================="); + int restartPoint = pvProvider.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(); + } else { + newStartPos = 0; + } + log.debug("Restarting at " + restartPoint + ", new start position: " + newStartPos); + + //Restart last page + PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm( + getTopLevelLM(), + getPageViewportProvider(), + alignment, Constants.EN_START, footnoteSeparatorLength, + isPartOverflowRecoveryActivated(), + getCurrentPV().getBodyRegion().getColumnCount()); + //alg.setConstantLineWidth(flowBPD); + int iOptPageCount = algRestart.findBreakingPoints(effectiveList, + newStartPos, + 1, true, true); + log.debug("restart: iOptPageCount= " + iOptPageCount + + " pageBreaks.size()= " + algRestart.getPageBreaks().size()); + if (iOptPageCount > getCurrentPV().getBodyRegion().getColumnCount()) { + /* 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); + log.debug("==================================================="); + } else { + //Directly add areas after finding the breaks + addAreas(alg, partCount, originalList, effectiveList); + } } protected void startPart(BlockSequence list, int breakClass) { + log.info("startPart() breakClass=" + breakClass); if (curPV == null) { throw new IllegalStateException("curPV must not be null"); - } else { + } + if (!pageBreakHandled) { + //firstPart is necessary because we need the first page before we start the //algorithm so we have a BPD and IPD. This may subject to change later when we //start handling more complex cases. if (!firstPart) { - if (curPV.getCurrentSpan().hasMoreFlows()) { - curPV.getCurrentSpan().moveToNextFlow(); - } else { - // if this is the first page that will be created by - // the current BlockSequence, it could have a break - // condition that must be satisfied; - // otherwise, we may simply need a new page - handleBreakTrait(breakClass); - } + // if this is the first page that will be created by + // the current BlockSequence, it could have a break + // condition that must be satisfied; + // otherwise, we may simply need a new page + handleBreakTrait(breakClass); } - pvProvider.setStartPageOfNextElementList(currentPageNum); + pvProvider.setStartOfNextElementList(currentPageNum, + curPV.getCurrentSpan().getCurrentFlowIndex()); } + pageBreakHandled = false; // add static areas and resolve any new id areas // finish page and add to area tree firstPart = false; @@ -319,6 +405,7 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { parentArea.setTop(topOffset); parentArea.setSeparator(separatorArea); } + getCurrentPV().getCurrentSpan().notifyFlowsFinished(); } protected LayoutManager getCurrentChildLM() { @@ -510,7 +597,14 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { * @param breakVal - value of break-before or break-after trait. */ private void handleBreakTrait(int breakVal) { - if (breakVal == Constants.EN_COLUMN) { + if (breakVal == Constants.EN_ALL) { + //break due to span change in multi-column layout + curPV.createSpan(true); + return; + } else if (breakVal == Constants.EN_NONE) { + curPV.createSpan(false); + return; + } else if (breakVal == Constants.EN_COLUMN || breakVal == -1) { if (curPV.getCurrentSpan().hasMoreFlows()) { curPV.getCurrentSpan().moveToNextFlow(); } else { @@ -577,6 +671,8 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { */ public class PageViewportProvider { + private Log log = LogFactory.getLog(PageViewportProvider.class); + /** Indices are evaluated relative to the first page in the page-sequence. */ public static final int RELTO_PAGE_SEQUENCE = 0; /** Indices are evaluated relative to the first page in the current element list. */ @@ -584,8 +680,13 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { private int startPageOfPageSequence; private int startPageOfCurrentElementList; + private int startColumnOfCurrentElementList; private List cachedPageViewports = new java.util.ArrayList(); + //Cache to optimize getAvailableBPD() calls + private int lastRequestedIndex = -1; + private int lastReportedBPD = -1; + /** * Main constructor. * @param ps The page-sequence the provider operates on @@ -598,12 +699,83 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { * The page breaker notifies the provider about the page number an element list starts * on so it can later retrieve PageViewports relative to this first page. * @param startPage the number of the first page for the element list. + * @param startColumn the starting column number for the element list. + */ + public void setStartOfNextElementList(int startPage, int startColumn) { + log.debug("start of the next element list is:" + + " page=" + startPage + " col=" + startColumn); + this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1; + this.startColumnOfCurrentElementList = startColumn; + //Reset Cache + this.lastRequestedIndex = -1; + this.lastReportedBPD = -1; + } + + /** + * 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. + * This method takes multiple columns into account. + * @param index zero-based index of the requested part/page + * @return the available BPD */ - public void setStartPageOfNextElementList(int startPage) { - log.debug("start page of the next element list is: " + startPage); - this.startPageOfCurrentElementList = startPage; + public int getAvailableBPD(int index) { + //Special optimization: There may be many equal calls by the BreakingAlgorithm + if (this.lastRequestedIndex == index) { + if (log.isTraceEnabled()) { + log.trace("getAvailableBPD(" + index + ") -> (cached) " + lastReportedBPD); + } + return this.lastReportedBPD; + } + int c = index; + int pageIndex = 0; + int colIndex = startColumnOfCurrentElementList; + PageViewport pv = getPageViewport( + false, pageIndex, RELTO_CURRENT_ELEMENT_LIST); + while (c > 0) { + colIndex++; + if (colIndex >= pv.getCurrentSpan().getColumnCount()) { + colIndex = 0; + pageIndex++; + pv = getPageViewport( + false, pageIndex, RELTO_CURRENT_ELEMENT_LIST); + } + c--; + } + this.lastRequestedIndex = index; + this.lastReportedBPD = pv.getBodyRegion().getRemainingBPD(); + if (log.isTraceEnabled()) { + log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD); + } + return this.lastReportedBPD; } + /** + * Returns the part index (0= pv.getCurrentSpan().getColumnCount())) { + colIndex = 0; + pageIndex++; + pv = getPageViewport( + false, pageIndex, RELTO_CURRENT_ELEMENT_LIST); + result = idx; + } + colIndex++; + idx++; + } + return result; + } + /** * Returns a PageViewport. * @param bIsBlank true if this page is supposed to be blank. @@ -617,7 +789,7 @@ public class PageSequenceLayoutManager extends AbstractLayoutManager { return getPageViewport(bIsBlank, index); } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) { int effIndex = startPageOfCurrentElementList + index; - effIndex += startPageOfPageSequence; + effIndex += startPageOfPageSequence - 1; return getPageViewport(bIsBlank, effIndex); } else { throw new IllegalArgumentException( -- 2.39.5