123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599 |
- /*
- * 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.List;
- import java.util.ListIterator;
-
- import org.apache.fop.area.Block;
- import org.apache.fop.area.BodyRegion;
- import org.apache.fop.area.Footnote;
- import org.apache.fop.area.PageViewport;
- import org.apache.fop.fo.Constants;
- import org.apache.fop.fo.FObj;
- 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;
- private boolean spanAllActive;
-
- /**
- * The FlowLayoutManager object, which processes
- * the single fo:flow of the fo:page-sequence
- */
- private FlowLayoutManager childFLM = null;
-
- private StaticContentLayoutManager footnoteSeparatorLM = null;
-
- /**
- * Construct page breaker.
- * @param pslm the page sequence layout manager
- */
- 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();
- }
-
- /**
- * Starts the page breaking process.
- * @param flowBPD the constant available block-progression-dimension (used for every part)
- */
- void doLayout(int flowBPD) {
- doLayout(flowBPD, false);
- }
-
- /** {@inheritDoc} */
- protected PageBreakingLayoutListener createLayoutListener() {
- return new PageBreakingLayoutListener() {
-
- public void notifyOverflow(int part, int amount, FObj obj) {
- Page p = pageProvider.getPage(
- false, part, PageProvider.RELTO_CURRENT_ELEMENT_LIST);
- RegionBody body = (RegionBody)p.getSimplePageMaster().getRegion(
- Region.FO_REGION_BODY);
- BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
- body.getUserAgent().getEventBroadcaster());
-
- boolean canRecover = (body.getOverflow() != Constants.EN_ERROR_IF_OVERFLOW);
- boolean needClip = (body.getOverflow() == Constants.EN_HIDDEN
- || body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW);
- eventProducer.regionOverflow(this, body.getName(),
- p.getPageViewport().getPageNumberString(),
- amount, needClip, canRecover,
- body.getLocator());
- }
-
- };
- }
-
- /** {@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
- && childLC.getDisableColumnBalancing() == Constants.EN_FALSE;
-
- }
- if (needColumnBalancing) {
- AbstractBreaker.log.debug(
- "Column balancing necessary for the next element list!!!");
- }
- return nextSequenceStartsOn;
- }
-
- /** {@inheritDoc} */
- protected int getNextBlockList(LayoutContext childLC,
- int nextSequenceStartsOn) {
- return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null);
- }
-
- /** {@inheritDoc} */
- protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn,
- Position positionAtIPDChange, LayoutManager restartLM, List firstElements) {
- 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(), this.spanAllActive);
- return super.getNextBlockList(childLC, nextSequenceStartsOn, positionAtIPDChange,
- restartLM, firstElements);
- }
-
- private boolean containsFootnotes(List contentList, LayoutContext context) {
-
- boolean containsFootnotes = 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
- containsFootnotes = true;
- LayoutContext footnoteContext = new LayoutContext(context);
- footnoteContext.setStackLimitBP(context.getStackLimitBP());
- footnoteContext.setRefIPD(pslm.getCurrentPV()
- .getRegionReference(Constants.FO_REGION_BODY).getIPD());
- List 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));
- }
- }
- }
- }
- return containsFootnotes;
- }
-
- private void handleFootnoteSeparator() {
- 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
- = pslm.getLayoutManagerMaker().makeStaticContentLayoutManager(
- pslm, footnoteSeparator, separatorArea);
- footnoteSeparatorLM.doLayout();
-
- footnoteSeparatorLength = MinOptMax.getInstance(separatorArea.getBPD());
- }
- }
-
- /** {@inheritDoc} */
- protected List getNextKnuthElements(LayoutContext context, int alignment) {
- List contentList = null;
-
- while (!childFLM.isFinished() && contentList == null) {
- contentList = childFLM.getNextKnuthElements(context, alignment);
- }
-
- // scan contentList, searching for footnotes
- if (containsFootnotes(contentList, context)) {
- // handle the footnote separator
- handleFootnoteSeparator();
- }
- return contentList;
- }
-
- /** {@inheritDoc} */
- protected List getNextKnuthElements(LayoutContext context, int alignment,
- Position positionAtIPDChange, LayoutManager restartAtLM) {
- List contentList = null;
-
- do {
- contentList = childFLM.getNextKnuthElements(context, alignment, positionAtIPDChange,
- restartAtLM);
- } while (!childFLM.isFinished() && contentList == null);
-
- // scan contentList, searching for footnotes
- if (containsFootnotes(contentList, context)) {
- // handle the footnote separator
- handleFootnoteSeparator();
- }
- 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 = pslm.getLayoutManagerMaker().makeStaticContentLayoutManager(
- pslm, footnoteSeparator, separatorArea);
- footnoteSeparatorLM.doLayout();
- }
-
- childFLM.addAreas(posIter, context);
- }
-
- /**
- * {@inheritDoc}
- * This implementation checks whether to trigger column-balancing,
- * or whether to take into account a 'last-page' condition.
- */
- protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
- BlockSequence originalList, BlockSequence effectiveList) {
-
- if (needColumnBalancing) {
- //column balancing for the last part
- redoLayout(alg, partCount, originalList, effectiveList);
- return;
- }
-
- boolean lastPageMasterDefined = pslm.getPageSequence().hasPagePositionLast();
- if (!hasMoreContent()) {
- //last part is reached
- if (lastPageMasterDefined) {
- //last-page condition
- redoLayout(alg, partCount, originalList, effectiveList);
- return;
- }
- }
-
- //nothing special: just add the areas now
- addAreas(alg, partCount, originalList, effectiveList);
- }
-
- /**
- * Restart the algorithm at the break corresponding to the given partCount. Used to
- * re-do the part after the last break in case of either column-balancing or a last
- * page-master.
- */
- private void redoLayout(PageBreakingAlgorithm alg, int partCount,
- BlockSequence originalList, BlockSequence effectiveList) {
-
- int newStartPos = 0;
- int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
- if (restartPoint > 0) {
- //Add definitive areas for the parts before the
- //restarting point
- addAreas(alg, restartPoint, originalList, effectiveList);
- //Get page break from which we restart
- PageBreakPosition pbp = (PageBreakPosition)
- alg.getPageBreaks().get(restartPoint - 1);
- newStartPos = pbp.getLeafPos() + 1;
- //Handle page break right here to avoid any side-effects
- if (newStartPos > 0) {
- handleBreakTrait(Constants.EN_PAGE);
- }
- }
-
- 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(), this.spanAllActive);
-
- //Make sure we only add the areas we haven't added already
- effectiveList.ignoreAtStart = newStartPos;
-
- PageBreakingAlgorithm algRestart;
- if (needColumnBalancing) {
- AbstractBreaker.log.debug("Column balancing now!!!");
- AbstractBreaker.log.debug("===================================================");
-
- //Restart last page
- algRestart = new BalancingColumnBreakingAlgorithm(
- getTopLevelLM(), getPageProvider(), createLayoutListener(),
- alignment, Constants.EN_START, footnoteSeparatorLength,
- isPartOverflowRecoveryActivated(),
- pslm.getCurrentPV().getBodyRegion().getColumnCount());
- AbstractBreaker.log.debug("===================================================");
- } else {
- // Handle special page-master for last page
- BodyRegion currentBody = pageProvider.getPage(false, currentPageNum)
- .getPageViewport().getBodyRegion();
-
- setLastPageIndex(currentPageNum);
-
- BodyRegion lastBody = pageProvider.getPage(false, currentPageNum)
- .getPageViewport().getBodyRegion();
- lastBody.getMainReference().setSpans(currentBody.getMainReference().getSpans());
- AbstractBreaker.log.debug("Last page handling now!!!");
- AbstractBreaker.log.debug("===================================================");
- //Restart last page
- algRestart = new PageBreakingAlgorithm(
- getTopLevelLM(), getPageProvider(), createLayoutListener(),
- alg.getAlignment(), alg.getAlignmentLast(),
- footnoteSeparatorLength,
- isPartOverflowRecoveryActivated(), false, false);
- AbstractBreaker.log.debug("===================================================");
- }
-
- int optimalPageCount = algRestart.findBreakingPoints(effectiveList,
- newStartPos,
- 1, true, BreakingAlgorithm.ALL_BREAKS);
- AbstractBreaker.log.debug("restart: optimalPageCount= " + optimalPageCount
- + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
-
- boolean fitsOnePage
- = optimalPageCount <= pslm.getCurrentPV()
- .getBodyRegion().getMainReference().getCurrentSpan().getColumnCount();
-
- if (needColumnBalancing) {
- if (!fitsOnePage) {
- 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.");
- */
- }
- } else {
- if (fitsOnePage) {
- //Replace last page
- pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum));
- } else {
- //Last page-master cannot hold the content.
- //Add areas now...
- addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList);
- //...and add a blank last page
- setLastPageIndex(currentPageNum + 1);
- pslm.setCurrentPage(pslm.makeNewPage(true, true));
- return;
- }
- }
-
- addAreas(algRestart, optimalPageCount, originalList, effectiveList);
- }
-
- private void setLastPageIndex(int currentPageNum) {
- int lastPageIndex = pslm.getForcedLastPageNum(currentPageNum);
- pageProvider.setLastPageIndex(lastPageIndex);
- }
-
- /** {@inheritDoc} */
- protected void startPart(BlockSequence list, int breakClass) {
- AbstractBreaker.log.debug("startPart() breakClass=" + getBreakClassName(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(),
- this.spanAllActive);
- }
- 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();
- }
-
- /** {@inheritDoc} */
- 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++) {
- List 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 = pslm.getCurrentPV().getBodyRegion().getFootnote();
- int topOffset = pslm.getCurrentPV().getBodyRegion().getBPD() - parentArea.getBPD();
- if (separatorArea != null) {
- topOffset -= separatorArea.getBPD();
- }
- parentArea.setTop(topOffset);
- parentArea.setSeparator(separatorArea);
- }
- pslm.getCurrentPV().getCurrentSpan().notifyFlowsFinished();
- }
-
- /** {@inheritDoc} */
- protected LayoutManager getCurrentChildLM() {
- return childFLM;
- }
-
- /** {@inheritDoc} */
- protected void observeElementList(List elementList) {
- ElementListObserver.observe(elementList, "breaker",
- 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();
- switch (breakVal) {
- case Constants.EN_ALL:
- //break due to span change in multi-column layout
- curPage.getPageViewport().createSpan(true);
- this.spanAllActive = true;
- return;
- case Constants.EN_NONE:
- curPage.getPageViewport().createSpan(false);
- this.spanAllActive = false;
- return;
- case Constants.EN_COLUMN:
- case Constants.EN_AUTO:
- case Constants.EN_PAGE:
- case -1:
- PageViewport pv = curPage.getPageViewport();
-
- //Check if previous page was spanned
- boolean forceNewPageWithSpan = false;
- RegionBody rb = (RegionBody)curPage.getSimplePageMaster().getRegion(
- Constants.FO_REGION_BODY);
- forceNewPageWithSpan
- = (rb.getColumnCount() > 1
- && pv.getCurrentSpan().getColumnCount() == 1);
-
- if (forceNewPageWithSpan) {
- log.trace("Forcing new page with span");
- curPage = pslm.makeNewPage(false, false);
- curPage.getPageViewport().createSpan(true);
- } else if (pv.getCurrentSpan().hasMoreFlows()) {
- log.trace("Moving to next flow");
- pv.getCurrentSpan().moveToNextFlow();
- } else {
- log.trace("Making new page");
- /*curPage = */pslm.makeNewPage(false, false);
- }
- return;
- default:
- log.debug("handling break-before after page " + pslm.getCurrentPageNum()
- + " breakVal=" + getBreakClassName(breakVal));
- if (needBlankPageBeforeNew(breakVal)) {
- log.trace("Inserting blank page");
- /*curPage = */pslm.makeNewPage(true, false);
- }
- if (needNewPage(breakVal)) {
- log.trace("Making new page");
- /*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;
- }
- }
- }
|