123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146 |
- /*
- * 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 org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.fop.apps.FOPException;
- import org.apache.fop.datatypes.Numeric;
-
- import org.apache.fop.area.AreaTreeHandler;
- import org.apache.fop.area.AreaTreeModel;
- import org.apache.fop.area.Block;
- import org.apache.fop.area.Footnote;
- import org.apache.fop.area.PageViewport;
- import org.apache.fop.area.LineArea;
- import org.apache.fop.area.Resolvable;
-
- import org.apache.fop.fo.Constants;
- import org.apache.fop.fo.flow.Marker;
- import org.apache.fop.fo.flow.RetrieveMarker;
-
- import org.apache.fop.fo.pagination.Flow;
- 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.SideRegion;
- import org.apache.fop.fo.pagination.SimplePageMaster;
- import org.apache.fop.fo.pagination.StaticContent;
- import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
-
- import org.apache.fop.traits.MinOptMax;
-
- import java.util.LinkedList;
- import java.util.List;
- import java.util.ListIterator;
-
- /**
- * LayoutManager for a PageSequence. This class is instantiated by
- * area.AreaTreeHandler for each fo:page-sequence found in the
- * input document.
- */
- public class PageSequenceLayoutManager extends AbstractLayoutManager {
-
- private static Log log = LogFactory.getLog(PageSequenceLayoutManager.class);
-
- /**
- * AreaTreeHandler which activates the PSLM and controls
- * the rendering of its pages.
- */
- private AreaTreeHandler areaTreeHandler;
-
- /**
- * fo:page-sequence formatting object being
- * processed by this class
- */
- private PageSequence pageSeq;
-
- private PageProvider pageProvider;
-
- /**
- * Current page with page-viewport-area being filled by
- * the PSLM.
- */
- private Page curPage = null;
-
- /**
- * The FlowLayoutManager object, which processes
- * the single fo:flow of the fo:page-sequence
- */
- private FlowLayoutManager childFLM = null;
-
- private int startPageNum = 0;
- private int currentPageNum = 0;
-
- private Block separatorArea = null;
-
- /**
- * Constructor
- *
- * @param ath the area tree handler object
- * @param pseq fo:page-sequence to process
- */
- public PageSequenceLayoutManager(AreaTreeHandler ath, PageSequence pseq) {
- super(pseq);
- this.areaTreeHandler = ath;
- this.pageSeq = pseq;
- this.pageProvider = new PageProvider(this.pageSeq);
- }
-
- /**
- * @see org.apache.fop.layoutmgr.LayoutManager
- * @return the LayoutManagerMaker object
- */
- public LayoutManagerMaker getLayoutManagerMaker() {
- return areaTreeHandler.getLayoutManagerMaker();
- }
-
- /** @return the PageProvider applicable to this page-sequence. */
- public PageProvider getPageProvider() {
- return this.pageProvider;
- }
-
- /**
- * Activate the layout of this page sequence.
- * PageViewports corresponding to each page generated by this
- * page sequence will be created and sent to the AreaTreeModel
- * for rendering.
- */
- public void activateLayout() {
- startPageNum = pageSeq.getStartingPageNumber();
- currentPageNum = startPageNum - 1;
-
- LineArea title = null;
-
- if (pageSeq.getTitleFO() != null) {
- try {
- ContentLayoutManager clm = getLayoutManagerMaker().
- makeContentLayoutManager(this, pageSeq.getTitleFO());
- title = (LineArea) clm.getParentArea(null);
- } catch (IllegalStateException e) {
- // empty title; do nothing
- }
- }
-
- areaTreeHandler.getAreaTreeModel().startPageSequence(title);
- log.debug("Starting layout");
-
- curPage = makeNewPage(false, false);
-
-
- Flow mainFlow = pageSeq.getMainFlow();
- childFLM = getLayoutManagerMaker().
- makeFlowLayoutManager(this, mainFlow);
-
- PageBreaker breaker = new PageBreaker(this);
- int flowBPD = (int)getCurrentPV().getBodyRegion().getRemainingBPD();
- breaker.doLayout(flowBPD);
-
- finishPage();
- }
-
- /**
- * Finished the page-sequence and notifies everyone about it.
- */
- public void finishPageSequence() {
- if (!pageSeq.getId().equals("")) {
- areaTreeHandler.signalIDProcessed(pageSeq.getId());
- }
-
- pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
- (currentPageNum - startPageNum) + 1);
- areaTreeHandler.notifyPageSequenceFinished(pageSeq,
- (currentPageNum - startPageNum) + 1);
- pageSeq.releasePageSequence();
- log.debug("Ending layout");
- }
-
-
- private class PageBreaker extends AbstractBreaker {
-
- private PageSequenceLayoutManager pslm;
- private boolean firstPart = true;
- private boolean pageBreakHandled;
- private boolean needColumnBalancing;
-
- private StaticContentLayoutManager footnoteSeparatorLM = null;
-
- public PageBreaker(PageSequenceLayoutManager pslm) {
- this.pslm = pslm;
- }
-
- /** @see org.apache.fop.layoutmgr.AbstractBreaker */
- protected void updateLayoutContext(LayoutContext context) {
- int flowIPD = getCurrentPV().getCurrentSpan().getColumnWidth();
- context.setRefIPD(flowIPD);
- }
-
- protected LayoutManager getTopLevelLM() {
- return null; // unneeded for PSLM
- }
-
- /** @see org.apache.fop.layoutmgr.AbstractBreaker#getPageProvider() */
- protected PageSequenceLayoutManager.PageProvider getPageProvider() {
- return pageProvider;
- }
-
- /** @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) {
- AbstractBreaker.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;
- pageProvider.setStartOfNextElementList(currentPageNum,
- getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
- return super.getNextBlockList(childLC, nextSequenceStartsOn, blockLists);
- }
-
- /** @see org.apache.fop.layoutmgr.AbstractBreaker */
- 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.setStackLimit(context.getStackLimit());
- 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
- // 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));
- }
- }
- }
- }
-
- // handle the footnote separator
- StaticContent footnoteSeparator;
- if (bFootnotesPresent
- && (footnoteSeparator = pageSeq.getStaticContent(
- "xsl-footnote-separator")) != 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)
- getLayoutManagerMaker().makeStaticContentLayoutManager(
- pslm, footnoteSeparator, separatorArea);
- footnoteSeparatorLM.doLayout();
-
- footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD());
- }
- return contentList;
- }
-
- protected int getCurrentDisplayAlign() {
- return curPage.getSimplePageMaster().getRegion(
- Constants.FO_REGION_BODY).getDisplayAlign();
- }
-
- protected boolean hasMoreContent() {
- return !childFLM.isFinished();
- }
-
- protected void addAreas(PositionIterator posIter, LayoutContext context) {
- if (footnoteSeparatorLM != null) {
- StaticContent footnoteSeparator = pageSeq.getStaticContent(
- "xsl-footnote-separator");
- // create a Block area that will contain the separator areas
- separatorArea = new Block();
- separatorArea.setIPD(
- getCurrentPV().getRegionReference(Constants.FO_REGION_BODY).getIPD());
- // create a StaticContentLM for the footnote separator
- footnoteSeparatorLM = (StaticContentLayoutManager)
- getLayoutManagerMaker().makeStaticContentLayoutManager(
- pslm, footnoteSeparator, separatorArea);
- footnoteSeparatorLM.doLayout();
- }
-
- childFLM.addAreas(posIter, context);
- }
-
- protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
- BlockSequence originalList, BlockSequence effectiveList) {
- if (needColumnBalancing) {
- 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 {
- //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(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
- 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, 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);
- } else {
- 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) {
- AbstractBreaker.log.debug("startPart() breakClass=" + breakClass);
- if (curPage == 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(currentPageNum,
- getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
- }
- pageBreakHandled = false;
- // add static areas and resolve any new id areas
- // finish page and add to area tree
- firstPart = false;
- }
-
- /** @see org.apache.fop.layoutmgr.AbstractBreaker#handleEmptyContent() */
- protected void handleEmptyContent() {
- 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) getCurrentPV().getBodyRegion().getFootnote();
- int topOffset = (int) getCurrentPV().getBodyRegion().getBPD() - parentArea.getBPD();
- if (separatorArea != null) {
- topOffset -= separatorArea.getBPD();
- }
- parentArea.setTop(topOffset);
- parentArea.setSeparator(separatorArea);
- }
- getCurrentPV().getCurrentSpan().notifyFlowsFinished();
- }
-
- protected LayoutManager getCurrentChildLM() {
- return childFLM;
- }
-
- /** @see org.apache.fop.layoutmgr.AbstractBreaker#observeElementList(java.util.List) */
- protected void observeElementList(List elementList) {
- ElementListObserver.observe(elementList, "breaker",
- ((PageSequence)pslm.getFObj()).getId());
- }
-
- }
-
- /**
- * Provides access to the current page.
- * @return the current Page
- */
- public Page getCurrentPage() {
- return curPage;
- }
-
- /**
- * Provides access to the current page viewport.
- * @return the current PageViewport
- *//*
- public PageViewport getCurrentPageViewport() {
- return curPage.getPageViewport();
- }*/
-
- /**
- * Provides access to this object
- * @return this PageSequenceLayoutManager instance
- */
- public PageSequenceLayoutManager getPSLM() {
- return this;
- }
-
- /**
- * This returns the first PageViewport that contains an id trait
- * matching the idref argument, or null if no such PV exists.
- *
- * @param idref the idref trait needing to be resolved
- * @return the first PageViewport that contains the ID trait
- */
- public PageViewport getFirstPVWithID(String idref) {
- List list = areaTreeHandler.getPageViewportsContainingID(idref);
- if (list != null && list.size() > 0) {
- return (PageViewport) list.get(0);
- }
- return null;
- }
-
- /**
- * This returns the last PageViewport that contains an id trait
- * matching the idref argument, or null if no such PV exists.
- *
- * @param idref the idref trait needing to be resolved
- * @return the last PageViewport that contains the ID trait
- */
- public PageViewport getLastPVWithID(String idref) {
- List list = areaTreeHandler.getPageViewportsContainingID(idref);
- if (list != null && list.size() > 0) {
- return (PageViewport) list.get(list.size() - 1);
- }
- return null;
- }
-
- /**
- * Add an ID reference to the current page.
- * When adding areas the area adds its ID reference.
- * For the page layout manager it adds the id reference
- * with the current page to the area tree.
- *
- * @param id the ID reference to add
- */
- public void addIDToPage(String id) {
- if (id != null && id.length() > 0) {
- areaTreeHandler.associateIDWithPageViewport(id, curPage.getPageViewport());
- }
- }
-
- /**
- * Add an id reference of the layout manager in the AreaTreeHandler,
- * if the id hasn't been resolved yet
- * @param id the id to track
- * @return a boolean indicating if the id has already been resolved
- * TODO Maybe give this a better name
- */
- public boolean associateLayoutManagerID(String id) {
- if (log.isDebugEnabled()) {
- log.debug("associateLayoutManagerID(" + id + ")");
- }
- if (!areaTreeHandler.alreadyResolvedID(id)) {
- areaTreeHandler.signalPendingID(id);
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Notify the areaTreeHandler that the LayoutManagers containing
- * idrefs have finished creating areas
- * @param id the id for which layout has finished
- */
- public void notifyEndOfLayout(String id) {
- areaTreeHandler.signalIDProcessed(id);
- }
-
- /**
- * Identify an unresolved area (one needing an idref to be
- * resolved, e.g. the internal-destination of an fo:basic-link)
- * for both the AreaTreeHandler and PageViewport object.
- *
- * The AreaTreeHandler keeps a document-wide list of idref's
- * and the PV's needing them to be resolved. It uses this to
- * send notifications to the PV's when an id has been resolved.
- *
- * The PageViewport keeps lists of id's needing resolving, along
- * with the child areas (page-number-citation, basic-link, etc.)
- * of the PV needing their resolution.
- *
- * @param id the ID reference to add
- * @param res the resolvable object that needs resolving
- */
- public void addUnresolvedArea(String id, Resolvable res) {
- curPage.getPageViewport().addUnresolvedIDRef(id, res);
- areaTreeHandler.addUnresolvedIDRef(id, curPage.getPageViewport());
- }
-
- /**
- * Bind the RetrieveMarker to the corresponding Marker subtree.
- * If the boundary is page then it will only check the
- * current page. For page-sequence and document it will
- * lookup preceding pages from the area tree and try to find
- * a marker.
- * If we retrieve a marker from a preceding page,
- * then the containing page does not have a qualifying area,
- * and all qualifying areas have ended.
- * Therefore we use last-ending-within-page (Constants.EN_LEWP)
- * as the position.
- *
- * @param rm the RetrieveMarker instance whose properties are to
- * used to find the matching Marker.
- * @return a bound RetrieveMarker instance, or null if no Marker
- * could be found.
- */
- public RetrieveMarker resolveRetrieveMarker(RetrieveMarker rm) {
- AreaTreeModel areaTreeModel = areaTreeHandler.getAreaTreeModel();
- String name = rm.getRetrieveClassName();
- int pos = rm.getRetrievePosition();
- int boundary = rm.getRetrieveBoundary();
-
- // get marker from the current markers on area tree
- Marker mark = (Marker)getCurrentPV().getMarker(name, pos);
- if (mark == null && boundary != EN_PAGE) {
- // go back over pages until mark found
- // if document boundary then keep going
- boolean doc = boundary == EN_DOCUMENT;
- int seq = areaTreeModel.getPageSequenceCount();
- int page = areaTreeModel.getPageCount(seq) - 1;
- while (page < 0 && doc && seq > 1) {
- seq--;
- page = areaTreeModel.getPageCount(seq) - 1;
- }
- while (page >= 0) {
- PageViewport pv = areaTreeModel.getPage(seq, page);
- mark = (Marker)pv.getMarker(name, Constants.EN_LEWP);
- if (mark != null) {
- break;
- }
- page--;
- if (page < 0 && doc && seq > 1) {
- seq--;
- page = areaTreeModel.getPageCount(seq) - 1;
- }
- }
- }
-
- if (mark == null) {
- log.debug("found no marker with name: " + name);
- return null;
- } else {
- rm.bindMarker(mark);
- return rm;
- }
- }
-
- private Page makeNewPage(boolean bIsBlank, boolean bIsLast) {
- if (curPage != null) {
- finishPage();
- }
-
- currentPageNum++;
-
- curPage = pageProvider.getPage(bIsBlank,
- currentPageNum, PageProvider.RELTO_PAGE_SEQUENCE);
-
- if (log.isDebugEnabled()) {
- log.debug("[" + curPage.getPageViewport().getPageNumberString()
- + (bIsBlank ? "*" : "") + "]");
- }
-
- addIDToPage(pageSeq.getId());
- return curPage;
- }
-
- private void layoutSideRegion(int regionID) {
- SideRegion reg = (SideRegion)curPage.getSimplePageMaster().getRegion(regionID);
- if (reg == null) {
- return;
- }
- StaticContent sc = pageSeq.getStaticContent(reg.getRegionName());
- if (sc == null) {
- return;
- }
-
- StaticContentLayoutManager lm = (StaticContentLayoutManager)
- getLayoutManagerMaker().makeStaticContentLayoutManager(
- this, sc, reg);
- lm.doLayout();
- }
-
- private void finishPage() {
- curPage.getPageViewport().dumpMarkers();
- // Layout side regions
- layoutSideRegion(FO_REGION_BEFORE);
- layoutSideRegion(FO_REGION_AFTER);
- layoutSideRegion(FO_REGION_START);
- layoutSideRegion(FO_REGION_END);
-
- // Try to resolve any unresolved IDs for the current page.
- //
- areaTreeHandler.tryIDResolution(curPage.getPageViewport());
- // Queue for ID resolution and rendering
- areaTreeHandler.getAreaTreeModel().addPage(curPage.getPageViewport());
- if (log.isDebugEnabled()) {
- log.debug("page finished: " + curPage.getPageViewport().getPageNumberString()
- + ", current num: " + currentPageNum);
- }
- curPage = null;
- }
-
- /**
- * 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) {
- 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) {
- 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 = makeNewPage(false, false);
- curPage.getPageViewport().createSpan(true);
- } else if (pv.getCurrentSpan().hasMoreFlows()) {
- pv.getCurrentSpan().moveToNextFlow();
- } else {
- curPage = makeNewPage(false, false);
- }
- return;
- }
- log.debug("handling break-before after page " + currentPageNum
- + " breakVal=" + breakVal);
- if (needBlankPageBeforeNew(breakVal)) {
- curPage = makeNewPage(true, false);
- }
- if (needNewPage(breakVal)) {
- curPage = 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 || (curPage.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 (currentPageNum % 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 (curPage.getPageViewport().getPage().isEmpty()) {
- if (breakVal == Constants.EN_PAGE) {
- return false;
- } else if (currentPageNum % 2 == 0) { // even page
- return (breakVal == Constants.EN_ODD_PAGE);
- } else { // odd page
- return (breakVal == Constants.EN_EVEN_PAGE);
- }
- } else {
- return true;
- }
- }
-
-
- /**
- * <p>This class delivers Page instances. It also caches them as necessary.
- * </p>
- * <p>Additional functionality makes sure that surplus instances that are requested by the
- * page breaker are properly discarded, especially in situations where hard breaks cause
- * blank pages. The reason for that: The page breaker sometimes needs to preallocate
- * additional pages since it doesn't know exactly until the end how many pages it really needs.
- * </p>
- */
- public class PageProvider {
-
- private Log log = LogFactory.getLog(PageProvider.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. */
- public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
-
- private int startPageOfPageSequence;
- private int startPageOfCurrentElementList;
- 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;
-
- /**
- * Main constructor.
- * @param ps The page-sequence the provider operates on
- */
- public PageProvider(PageSequence ps) {
- this.startPageOfPageSequence = ps.getStartingPageNumber();
- }
-
- /**
- * 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;
- }
-
- /**
- * 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.
- * This method takes multiple columns into account.
- * @param index zero-based index of the requested part/page
- * @return the available BPD
- */
- 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;
- Page page = getPage(
- false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
- while (c > 0) {
- colIndex++;
- if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
- colIndex = 0;
- pageIndex++;
- page = getPage(
- false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
- }
- c--;
- }
- this.lastRequestedIndex = index;
- this.lastReportedBPD = page.getPageViewport().getBodyRegion().getRemainingBPD();
- if (log.isTraceEnabled()) {
- log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD);
- }
- return this.lastReportedBPD;
- }
-
- /**
- * Returns the part index (0<x<partCount) which denotes the first part on the last page
- * generated by the current element list.
- * @param partCount Number of parts determined by the breaking algorithm
- * @return the requested part index
- */
- public int getStartingPartIndexForLastPage(int partCount) {
- int result = 0;
- int idx = 0;
- int pageIndex = 0;
- int colIndex = startColumnOfCurrentElementList;
- Page page = getPage(
- false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
- while (idx < partCount) {
- if ((colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount())) {
- colIndex = 0;
- pageIndex++;
- page = getPage(
- false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
- result = idx;
- }
- colIndex++;
- idx++;
- }
- return result;
- }
-
- /**
- * Returns a Page.
- * @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 isBlank, int index, int relativeTo) {
- if (relativeTo == RELTO_PAGE_SEQUENCE) {
- return getPage(isBlank, index);
- } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
- int effIndex = startPageOfCurrentElementList + index;
- effIndex += startPageOfPageSequence - 1;
- return getPage(isBlank, effIndex);
- } else {
- throw new IllegalArgumentException(
- "Illegal value for relativeTo: " + relativeTo);
- }
- }
-
- private Page getPage(boolean isBlank, int index) {
- boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
- if (log.isTraceEnabled()) {
- log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank")
- + (isLastPage ? " <LAST>" : "") + ")");
- }
- int intIndex = index - startPageOfPageSequence;
- if (log.isTraceEnabled()) {
- 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, isBlank, isLastPage);
- }
- Page page = (Page)cachedPages.get(intIndex);
- boolean replace = false;
- if (page.getPageViewport().isBlank() != isBlank) {
- log.debug("blank condition doesn't match. Replacing PageViewport.");
- 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 Page cacheNextPage(int index, boolean isBlank, boolean isLastPage) {
- try {
- String pageNumberString = pageSeq.makeFormattedPageNumber(index);
- SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
- index, (startPageOfPageSequence == index), isLastPage, isBlank);
-
- Region body = spm.getRegion(FO_REGION_BODY);
- if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) {
- // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to
- // any region), but we don't support it yet.
- throw new FOPException("Flow '" + pageSeq.getMainFlow().getFlowName()
- + "' does not map to the region-body in page-master '"
- + spm.getMasterName() + "'. FOP presently "
- + "does not support this.");
- }
- Page page = new Page(spm, index, pageNumberString, isBlank);
- //Set unique key obtained from the AreaTreeHandler
- page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
- page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
- cachedPages.add(page);
- return page;
- } catch (FOPException e) {
- //TODO Maybe improve. It'll mean to propagate this exception up several
- //methods calls.
- throw new IllegalStateException(e.getMessage());
- }
- }
-
- }
-
- /**
- * Act upon the force-page-count trait,
- * in relation to the initial-page-number trait of the following page-sequence.
- * @param nextPageSeqInitialPageNumber initial-page-number trait of next page-sequence
- */
- public void doForcePageCount(Numeric nextPageSeqInitialPageNumber) {
-
- int forcePageCount = pageSeq.getForcePageCount();
-
- // xsl-spec version 1.0 (15.oct 2001)
- // auto | even | odd | end-on-even | end-on-odd | no-force | inherit
- // auto:
- // Force the last page in this page-sequence to be an odd-page
- // if the initial-page-number of the next page-sequence is even.
- // Force it to be an even-page
- // if the initial-page-number of the next page-sequence is odd.
- // If there is no next page-sequence
- // or if the value of its initial-page-number is "auto" do not force any page.
-
-
- // if force-page-count is auto then set the value of forcePageCount
- // depending on the initial-page-number of the next page-sequence
- if (nextPageSeqInitialPageNumber != null && forcePageCount == Constants.EN_AUTO) {
- if (nextPageSeqInitialPageNumber.getEnum() != 0) {
- // auto | auto-odd | auto-even
- int nextPageSeqPageNumberType = nextPageSeqInitialPageNumber.getEnum();
- if (nextPageSeqPageNumberType == Constants.EN_AUTO_ODD) {
- forcePageCount = Constants.EN_END_ON_EVEN;
- } else if (nextPageSeqPageNumberType == Constants.EN_AUTO_EVEN) {
- forcePageCount = Constants.EN_END_ON_ODD;
- } else { // auto
- forcePageCount = Constants.EN_NO_FORCE;
- }
- } else { // <integer> for explicit page number
- int nextPageSeqPageStart = nextPageSeqInitialPageNumber.getValue();
- // spec rule
- nextPageSeqPageStart = (nextPageSeqPageStart > 0) ? nextPageSeqPageStart : 1;
- if (nextPageSeqPageStart % 2 == 0) { // explicit even startnumber
- forcePageCount = Constants.EN_END_ON_ODD;
- } else { // explicit odd startnumber
- forcePageCount = Constants.EN_END_ON_EVEN;
- }
- }
- }
-
- if (forcePageCount == Constants.EN_EVEN) {
- if ((currentPageNum - startPageNum + 1) % 2 != 0) { // we have a odd number of pages
- curPage = makeNewPage(true, false);
- }
- } else if (forcePageCount == Constants.EN_ODD) {
- if ((currentPageNum - startPageNum + 1) % 2 == 0) { // we have a even number of pages
- curPage = makeNewPage(true, false);
- }
- } else if (forcePageCount == Constants.EN_END_ON_EVEN) {
- if (currentPageNum % 2 != 0) { // we are now on a odd page
- curPage = makeNewPage(true, false);
- }
- } else if (forcePageCount == Constants.EN_END_ON_ODD) {
- if (currentPageNum % 2 == 0) { // we are now on a even page
- curPage = makeNewPage(true, false);
- }
- } else if (forcePageCount == Constants.EN_NO_FORCE) {
- // i hope: nothing special at all
- }
-
- if (curPage != null) {
- finishPage();
- }
- }
- }
|