/*
* 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: PageBreaker.java 635508 2008-03-10 10:06:37Z jeremias $ */
package org.apache.fop.layoutmgr;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.fop.area.Block;
import org.apache.fop.area.Footnote;
import org.apache.fop.area.PageViewport;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.pagination.PageSequence;
import org.apache.fop.fo.pagination.Region;
import org.apache.fop.fo.pagination.RegionBody;
import org.apache.fop.fo.pagination.StaticContent;
import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener;
import org.apache.fop.traits.MinOptMax;
/**
* Handles the breaking of pages in an fo:flow
*/
public class PageBreaker extends AbstractBreaker {
private PageSequenceLayoutManager pslm;
private boolean firstPart = true;
private boolean pageBreakHandled;
private boolean needColumnBalancing;
private PageProvider pageProvider;
private Block separatorArea;
/**
* The FlowLayoutManager object, which processes
* the single fo:flow of the fo:page-sequence
*/
private FlowLayoutManager childFLM = null;
private StaticContentLayoutManager footnoteSeparatorLM = null;
public PageBreaker(PageSequenceLayoutManager pslm) {
this.pslm = pslm;
this.pageProvider = pslm.getPageProvider();
this.childFLM = pslm.getLayoutManagerMaker().makeFlowLayoutManager(
pslm, pslm.getPageSequence().getMainFlow());
}
/** {@inheritDoc} */
protected void updateLayoutContext(LayoutContext context) {
int flowIPD = pslm.getCurrentPV().getCurrentSpan().getColumnWidth();
context.setRefIPD(flowIPD);
}
/** {@inheritDoc} */
protected LayoutManager getTopLevelLM() {
return pslm;
}
/** {@inheritDoc} */
protected PageProvider getPageProvider() {
return pslm.getPageProvider();
}
/**
* {@inheritDoc}
*/
protected PageBreakingLayoutListener getLayoutListener() {
return new PageBreakingLayoutListener() {
public void notifyOverflow(int part, FObj obj) {
Page p = pageProvider.getPage(
false, part, PageProvider.RELTO_CURRENT_ELEMENT_LIST);
RegionBody body = (RegionBody)p.getSimplePageMaster().getRegion(
Region.FO_REGION_BODY);
String err = FONode.decorateWithContextInfo(
"Content of the region-body on page "
+ p.getPageViewport().getPageNumberString()
+ " overflows the available area in block-progression dimension.",
obj);
if (body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW) {
throw new RuntimeException(err);
} else {
log.warn(err);
}
}
};
}
/** {@inheritDoc} */
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) {
AbstractBreaker.log.debug(
"Column balancing necessary for the next element list!!!");
}
return nextSequenceStartsOn;
}
/** {@inheritDoc} */
protected int getNextBlockList(LayoutContext childLC,
int nextSequenceStartsOn) {
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;
pageProvider.setStartOfNextElementList(pslm.getCurrentPageNum(),
pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
return super.getNextBlockList(childLC, nextSequenceStartsOn);
}
/** {@inheritDoc} */
protected LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
LinkedList contentList = null;
while (!childFLM.isFinished() && contentList == null) {
contentList = childFLM.getNextKnuthElements(context, alignment);
}
// scan contentList, searching for footnotes
boolean bFootnotesPresent = false;
if (contentList != null) {
ListIterator contentListIterator = contentList.listIterator();
while (contentListIterator.hasNext()) {
ListElement element = (ListElement) contentListIterator.next();
if (element instanceof KnuthBlockBox
&& ((KnuthBlockBox) element).hasAnchors()) {
// element represents a line with footnote citations
bFootnotesPresent = true;
LayoutContext footnoteContext = new LayoutContext(context);
footnoteContext.setStackLimitBP(context.getStackLimitBP());
footnoteContext.setRefIPD(pslm.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
// in the box representing the line containing their references
while (footnoteBodyIterator.hasNext()) {
FootnoteBodyLayoutManager fblm
= (FootnoteBodyLayoutManager) footnoteBodyIterator.next();
fblm.setParent(childFLM);
fblm.initialize();
((KnuthBlockBox) element).addElementList(
fblm.getNextKnuthElements(footnoteContext, alignment));
}
}
}
}
if (bFootnotesPresent) {
// handle the footnote separator
StaticContent footnoteSeparator;
footnoteSeparator = pslm.getPageSequence().getStaticContent("xsl-footnote-separator");
if (footnoteSeparator != null) {
// the footnote separator can contain page-dependent content such as
// page numbers or retrieve markers, so its areas cannot simply be
// obtained now and repeated in each page;
// we need to know in advance the separator bpd: the actual separator
// could be different from page to page, but its bpd would likely be
// always the same
// create a Block area that will contain the separator areas
separatorArea = new Block();
separatorArea.setIPD(pslm.getCurrentPV()
.getRegionReference(Constants.FO_REGION_BODY).getIPD());
// create a StaticContentLM for the footnote separator
footnoteSeparatorLM = (StaticContentLayoutManager)
pslm.getLayoutManagerMaker().makeStaticContentLayoutManager(
pslm, footnoteSeparator, separatorArea);
footnoteSeparatorLM.doLayout();
footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD());
}
}
return contentList;
}
/**
* @return current display alignment
*/
protected int getCurrentDisplayAlign() {
return pslm.getCurrentPage().getSimplePageMaster().getRegion(
Constants.FO_REGION_BODY).getDisplayAlign();
}
/**
* @return whether or not this flow has more page break opportunities
*/
protected boolean hasMoreContent() {
return !childFLM.isFinished();
}
/**
* Adds an area to the flow layout manager
* @param posIter the position iterator
* @param context the layout context
*/
protected void addAreas(PositionIterator posIter, LayoutContext context) {
if (footnoteSeparatorLM != null) {
StaticContent footnoteSeparator = pslm.getPageSequence().getStaticContent(
"xsl-footnote-separator");
// create a Block area that will contain the separator areas
separatorArea = new Block();
separatorArea.setIPD(
pslm.getCurrentPV().getRegionReference(Constants.FO_REGION_BODY).getIPD());
// create a StaticContentLM for the footnote separator
footnoteSeparatorLM = (StaticContentLayoutManager)
pslm.getLayoutManagerMaker().makeStaticContentLayoutManager(
pslm, footnoteSeparator, separatorArea);
footnoteSeparatorLM.doLayout();
}
childFLM.addAreas(posIter, context);
}
/**
* Performs phase 3 operation
*
* @param alg page breaking algorithm
* @param partCount part count
* @param originalList the block sequence original list
* @param effectiveList the block sequence effective list
*/
protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
BlockSequence originalList, BlockSequence effectiveList) {
if (needColumnBalancing) {
doPhase3WithColumnBalancing(alg, partCount, originalList, effectiveList);
} else {
if (!hasMoreContent() && pslm.getPageSequence().hasPagePositionLast()) {
//last part is reached and we have a "last page" condition
doPhase3WithLastPage(alg, partCount, originalList, effectiveList);
} else {
//Directly add areas after finding the breaks
addAreas(alg, partCount, originalList, effectiveList);
}
}
}
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(Constants.EN_PAGE);
}
} else {
newStartPos = 0;
}
AbstractBreaker.log.debug("Last page handling now!!!");
AbstractBreaker.log.debug("===================================================");
AbstractBreaker.log.debug("Restarting at " + restartPoint
+ ", new start position: " + newStartPos);
pageBreakHandled = true;
//Update so the available BPD is reported correctly
int currentPageNum = pslm.getCurrentPageNum();
pageProvider.setStartOfNextElementList(currentPageNum,
pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
pageProvider.setLastPageIndex(currentPageNum);
//Restart last page
PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
getTopLevelLM(),
getPageProvider(), getLayoutListener(),
alg.getAlignment(), alg.getAlignmentLast(),
footnoteSeparatorLength,
isPartOverflowRecoveryActivated(), false, 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 <= pslm.getCurrentPV().getBodyRegion().getColumnCount();
if (replaceLastPage) {
//Replace last page
pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum));
//Make sure we only add the areas we haven't added already
effectiveList.ignoreAtStart = newStartPos;
addAreas(algRestart, iOptPageCount, originalList, effectiveList);
} else {
effectiveList.ignoreAtStart = newStartPos;
addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList);
//Add blank last page
pageProvider.setLastPageIndex(currentPageNum + 1);
pslm.setCurrentPage(pslm.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(Constants.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(pslm.getCurrentPageNum(),
pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
//Restart last page
PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
getTopLevelLM(),
getPageProvider(), getLayoutListener(),
alignment, Constants.EN_START, footnoteSeparatorLength,
isPartOverflowRecoveryActivated(),
pslm.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 > pslm.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) {
AbstractBreaker.log.debug("startPart() breakClass=" + breakClass);
if (pslm.getCurrentPage() == null) {
throw new IllegalStateException("curPage must not be null");
}
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 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);
}
pageProvider.setStartOfNextElementList(pslm.getCurrentPageNum(),
pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
}
pageBreakHandled = false;
// add static areas and resolve any new id areas
// finish page and add to area tree
firstPart = false;
}
/** {@inheritDoc} */
protected void handleEmptyContent() {
pslm.getCurrentPV().getPage().fakeNonEmpty();
}
protected void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp) {
// add footnote areas
if (pbp.footnoteFirstListIndex < pbp.footnoteLastListIndex
|| pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex) {
// call addAreas() for each FootnoteBodyLM
for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) {
LinkedList elementList = alg.getFootnoteList(i);
int firstIndex = (i == pbp.footnoteFirstListIndex
? pbp.footnoteFirstElementIndex : 0);
int lastIndex = (i == pbp.footnoteLastListIndex
? pbp.footnoteLastElementIndex : elementList.size() - 1);
SpaceResolver.performConditionalsNotification(elementList,
firstIndex, lastIndex, -1);
LayoutContext childLC = new LayoutContext(0);
AreaAdditionUtil.addAreas(null,
new KnuthPossPosIter(elementList, firstIndex, lastIndex + 1),
childLC);
}
// set the offset from the top margin
Footnote parentArea = (Footnote) pslm.getCurrentPV().getBodyRegion().getFootnote();
int topOffset = (int) pslm.getCurrentPV().getBodyRegion().getBPD() - parentArea.getBPD();
if (separatorArea != null) {
topOffset -= separatorArea.getBPD();
}
parentArea.setTop(topOffset);
parentArea.setSeparator(separatorArea);
}
pslm.getCurrentPV().getCurrentSpan().notifyFlowsFinished();
}
/**
* @return the current child flow layout manager
*/
protected LayoutManager getCurrentChildLM() {
return childFLM;
}
/** {@inheritDoc} */
protected void observeElementList(List elementList) {
ElementListObserver.observe(elementList, "breaker",
((PageSequence)pslm.getFObj()).getId());
}
/**
* Depending on the kind of break condition, move to next column
* or page. May need to make an empty page if next page would
* not have the desired "handedness".
* @param breakVal - value of break-before or break-after trait.
*/
private void handleBreakTrait(int breakVal) {
Page curPage = pslm.getCurrentPage();
if (breakVal == Constants.EN_ALL) {
//break due to span change in multi-column layout
curPage.getPageViewport().createSpan(true);
return;
} else if (breakVal == Constants.EN_NONE) {
curPage.getPageViewport().createSpan(false);
return;
} else if (breakVal == Constants.EN_COLUMN
|| breakVal <= 0
|| breakVal == Constants.EN_AUTO) {
PageViewport pv = curPage.getPageViewport();
//Check if previous page was spanned
boolean forceNewPageWithSpan = false;
RegionBody rb = (RegionBody)curPage.getSimplePageMaster().getRegion(
Constants.FO_REGION_BODY);
if (breakVal < 0
&& rb.getColumnCount() > 1
&& pv.getCurrentSpan().getColumnCount() == 1) {
forceNewPageWithSpan = true;
}
if (forceNewPageWithSpan) {
curPage = pslm.makeNewPage(false, false);
curPage.getPageViewport().createSpan(true);
} else if (pv.getCurrentSpan().hasMoreFlows()) {
pv.getCurrentSpan().moveToNextFlow();
} else {
curPage = pslm.makeNewPage(false, false);
}
return;
}
log.debug("handling break-before after page " + pslm.getCurrentPageNum()
+ " breakVal=" + breakVal);
if (needBlankPageBeforeNew(breakVal)) {
curPage = pslm.makeNewPage(true, false);
}
if (needNewPage(breakVal)) {
curPage = pslm.makeNewPage(false, false);
}
}
/**
* Check if a blank page is needed to accomodate
* desired even or odd page number.
* @param breakVal - value of break-before or break-after trait.
*/
private boolean needBlankPageBeforeNew(int breakVal) {
if (breakVal == Constants.EN_PAGE || (pslm.getCurrentPage().getPageViewport().getPage().isEmpty())) {
// any page is OK or we already have an empty page
return false;
} else {
/* IF we are on the kind of page we need, we'll need a new page. */
if (pslm.getCurrentPageNum() % 2 == 0) { // even page
return (breakVal == Constants.EN_EVEN_PAGE);
} else { // odd page
return (breakVal == Constants.EN_ODD_PAGE);
}
}
}
/**
* See if need to generate a new page
* @param breakVal - value of break-before or break-after trait.
*/
private boolean needNewPage(int breakVal) {
if (pslm.getCurrentPage().getPageViewport().getPage().isEmpty()) {
if (breakVal == Constants.EN_PAGE) {
return false;
} else if (pslm.getCurrentPageNum() % 2 == 0) { // even page
return (breakVal == Constants.EN_ODD_PAGE);
} else { // odd page
return (breakVal == Constants.EN_EVEN_PAGE);
}
} else {
return true;
}
}
}