Package org.apache.fop.layoutmgr

Source Code of org.apache.fop.layoutmgr.PageSequenceLayoutManager$PageViewportProvider

/*
* Copyright 1999-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id: PageSequenceLayoutManager.java 332594 2005-11-11 16:39:21Z jeremias $ */

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;
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.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 PageViewportProvider pvProvider;
   
    /**
     * Current page-viewport-area being filled by
     * the PSLM.
     */
    private PageViewport curPV = 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.pvProvider = new PageViewportProvider(this.pageSeq);
    }

    /**
     * @see org.apache.fop.layoutmgr.LayoutManager
     * @return the LayoutManagerMaker object
     */
    public LayoutManagerMaker getLayoutManagerMaker() {
        return areaTreeHandler.getLayoutManagerMaker();
    }

    /** @return the PageViewportProvider applicable to this page-sequence. */
    public PageViewportProvider getPageViewportProvider() {
        return this.pvProvider;
    }
   
    /**
     * 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) {
            ContentLayoutManager clm = new ContentLayoutManager(pageSeq
                    .getTitleFO(), this);
            title = (LineArea) clm.getParentArea(null);
        }

        areaTreeHandler.getAreaTreeModel().startPageSequence(title);
        log.debug("Starting layout");

        curPV = makeNewPage(false, false);

        addIDToPage(pageSeq.getId());
        Flow mainFlow = pageSeq.getMainFlow();
        childFLM = getLayoutManagerMaker().
            makeFlowLayoutManager(this, mainFlow);

        PageBreaker breaker = new PageBreaker(this);
        int flowBPD = (int) curPV.getBodyRegion().getRemainingBPD();
        breaker.doLayout(flowBPD);
       
        finishPage();
        pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
                (currentPageNum - startPageNum) + 1);
        areaTreeHandler.notifyPageSequenceFinished(pageSeq,
                (currentPageNum - startPageNum) + 1);
        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 = curPV.getCurrentSpan().getColumnWidth();
            context.setRefIPD(flowIPD);
        }
       
        protected LayoutManager getTopLevelLM() {
            return null// unneeded for PSLM
        }
       
        /** @see org.apache.fop.layoutmgr.AbstractBreaker#getPageViewportProvider() */
        protected PageSequenceLayoutManager.PageViewportProvider getPageViewportProvider() {
            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) {
                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;
            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;
           
            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.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 curPV.getSPM().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(curPV.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) {
                AbstractBreaker.log.debug("Column balancing now!!!");
                AbstractBreaker.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;
                }
                AbstractBreaker.log.debug("Restarting at " + restartPoint
                        + ", new start position: " + newStartPos);

                //Handle page break right here to avoid any side-effects
                handleBreakTrait(EN_PAGE);
                pageBreakHandled = true;
                //Update so the available BPD is reported correctly
                pvProvider.setStartOfNextElementList(currentPageNum,
                        curPV.getCurrentSpan().getCurrentFlowIndex());

                //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, BreakingAlgorithm.ALL_BREAKS);
                AbstractBreaker.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);
                AbstractBreaker.log.debug("===================================================");
            } else {
                //Directly add areas after finding the breaks
                addAreas(alg, partCount, originalList, effectiveList);
            }
        }
       
        protected void startPart(BlockSequence list, int breakClass) {
            AbstractBreaker.log.debug("startPart() breakClass=" + breakClass);
            if (curPV == null) {
                throw new IllegalStateException("curPV 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);
                }
                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;
        }
       
        /** @see org.apache.fop.layoutmgr.AbstractBreaker#handleEmptyContent() */
        protected void handleEmptyContent() {
            curPV.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) curPV.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 PageViewport
     */
    public PageViewport getCurrentPV() {
        return curPV;
    }

    /**
     * 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;
    }

    /**
     * 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, curPV);
        }
    }

    /**
     * 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) {
        curPV.addUnresolvedIDRef(id, res);
        areaTreeHandler.addUnresolvedIDRef(id, curPV);
    }

    /**
     * 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)curPV.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 PageViewport makeNewPage(boolean bIsBlank, boolean bIsLast) {
        if (curPV != null) {
            finishPage();
        }

        currentPageNum++;

        curPV = pvProvider.getPageViewport(bIsBlank,
                currentPageNum, PageViewportProvider.RELTO_PAGE_SEQUENCE);

        if (log.isDebugEnabled()) {
            log.debug("[" + curPV.getPageNumberString() + (bIsBlank ? "*" : "") + "]");
        }
        return curPV;
    }

    private void layoutSideRegion(int regionID) {
        SideRegion reg = (SideRegion)curPV.getSPM().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() {
        curPV.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(curPV);
        // Queue for ID resolution and rendering
        areaTreeHandler.getAreaTreeModel().addPage(curPV);
        if (log.isDebugEnabled()) {
            log.debug("page finished: " + curPV.getPageNumberString()
                    + ", current num: " + currentPageNum);
        }
        curPV = 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
            curPV.createSpan(true);
            return;
        } else if (breakVal == Constants.EN_NONE) {
            curPV.createSpan(false);
            return;
        } else if (breakVal == Constants.EN_COLUMN || breakVal <= 0) {
            if (curPV.getCurrentSpan().hasMoreFlows()) {
                curPV.getCurrentSpan().moveToNextFlow();
            } else {
                curPV = makeNewPage(false, false);
            }
            return;
        }
        log.debug("handling break-before after page " + currentPageNum
            + " breakVal=" + breakVal);
        if (needBlankPageBeforeNew(breakVal)) {
            curPV = makeNewPage(true, false);
        }
        if (needNewPage(breakVal)) {
            curPV = 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 || (curPV.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 (curPV.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 PageViewport 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 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. */
        public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
       
        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
         */
        public PageViewportProvider(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;
        }
       
        /**
         * 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;
            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<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;
            PageViewport pv = getPageViewport(
                    false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
            while (idx < partCount) {
                if ((idx < partCount - 1) && (colIndex >= 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.
         * @param index Index of the page (see relativeTo)
         * @param relativeTo Defines which value the index parameter should be evaluated relative
         * to. (One of PageViewportProvider.RELTO_*)
         * @return the requested PageViewport
         */
        public PageViewport getPageViewport(boolean bIsBlank, int index, int relativeTo) {
            if (relativeTo == RELTO_PAGE_SEQUENCE) {
                return getPageViewport(bIsBlank, index);
            } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
                int effIndex = startPageOfCurrentElementList + index;
                effIndex += startPageOfPageSequence - 1;
                return getPageViewport(bIsBlank, effIndex);
            } else {
                throw new IllegalArgumentException(
                        "Illegal value for relativeTo: " + relativeTo);
            }
        }
       
        private PageViewport getPageViewport(boolean bIsBlank, int index) {
            if (log.isTraceEnabled()) {
                log.trace("getPageViewport(" + index + " " + bIsBlank);
            }
            int intIndex = index - startPageOfPageSequence;
            if (log.isTraceEnabled()) {
                if (bIsBlank) {
                    log.trace("blank page requested: " + index);
                }
            }
            while (intIndex >= cachedPageViewports.size()) {
                if (log.isTraceEnabled()) {
                    log.trace("Caching " + index);
                }
                cacheNextPageViewport(index, bIsBlank);
            }
            PageViewport pv = (PageViewport)cachedPageViewports.get(intIndex);
            if (pv.isBlank() != bIsBlank) {
                log.debug("blank condition doesn't match. Replacing PageViewport.");
                while (intIndex < cachedPageViewports.size()) {
                    this.cachedPageViewports.remove(cachedPageViewports.size() - 1);
                    if (!pageSeq.goToPreviousSimplePageMaster()) {
                        log.warn("goToPreviousSimplePageMaster() on the first page called!");
                    }
                }
                cacheNextPageViewport(index, bIsBlank);
            }
            return pv;
        }
       
        private void cacheNextPageViewport(int index, boolean bIsBlank) {
            try {
                String pageNumberString = pageSeq.makeFormattedPageNumber(index);
                SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
                        index, (startPageOfPageSequence == index), bIsBlank);
                   
                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.");
                }
                PageViewport pv = new PageViewport(spm, pageNumberString, bIsBlank);
                cachedPageViewports.add(pv);
            } catch (FOPException e) {
                //TODO Maybe improve. It'll mean to propagate this exception up several
                //methods calls.
                throw new IllegalStateException(e.getMessage());
            }
        }
       
    }
}
TOP

Related Classes of org.apache.fop.layoutmgr.PageSequenceLayoutManager$PageViewportProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.