| /* |
| * 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 org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.fop.area.AreaTreeHandler; |
| import org.apache.fop.area.AreaTreeModel; |
| import org.apache.fop.area.IDTracker; |
| import org.apache.fop.area.PageViewport; |
| import org.apache.fop.area.Resolvable; |
| import org.apache.fop.datatypes.Numeric; |
| 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.AbstractPageSequence; |
| |
| /** |
| * Abstract base class for a page sequence layout manager. |
| */ |
| public abstract class AbstractPageSequenceLayoutManager extends AbstractLayoutManager |
| implements TopLevelLayoutManager { |
| |
| private static Log log = LogFactory.getLog(AbstractPageSequenceLayoutManager.class); |
| |
| /** |
| * AreaTreeHandler which activates the PSLM and controls |
| * the rendering of its pages. |
| */ |
| protected AreaTreeHandler areaTreeHandler; |
| |
| /** ID tracker supplied by the AreaTreeHandler */ |
| protected IDTracker idTracker; |
| |
| /** page sequence formatting object being processed by this class */ |
| protected AbstractPageSequence pageSeq; |
| |
| /** Current page with page-viewport-area being filled by the PSLM. */ |
| protected Page curPage; |
| |
| /** the current page number */ |
| protected int currentPageNum = 0; |
| /** The stating page number */ |
| protected int startPageNum = 0; |
| |
| /** |
| * Constructor |
| * |
| * @param ath the area tree handler object |
| * @param pseq fo:page-sequence to process |
| */ |
| public AbstractPageSequenceLayoutManager(AreaTreeHandler ath, AbstractPageSequence pseq) { |
| super(pseq); |
| this.areaTreeHandler = ath; |
| this.idTracker = ath.getIDTracker(); |
| this.pageSeq = pseq; |
| } |
| |
| /** |
| * @return the LayoutManagerMaker object associated to the areaTreeHandler |
| */ |
| public LayoutManagerMaker getLayoutManagerMaker() { |
| return areaTreeHandler.getLayoutManagerMaker(); |
| } |
| |
| /** |
| * Provides access to the current page. |
| * @return the current Page |
| */ |
| public Page getCurrentPage() { |
| return curPage; |
| } |
| |
| /** |
| * Provides access for setting the current page. |
| * @param currentPage the new current Page |
| */ |
| protected void setCurrentPage(Page currentPage) { |
| this.curPage = currentPage; |
| } |
| |
| /** |
| * Provides access to the current page number |
| * @return the current page number |
| */ |
| protected int getCurrentPageNum() { |
| return currentPageNum; |
| } |
| |
| /** {@inheritDoc} */ |
| public void initialize() { |
| startPageNum = pageSeq.getStartingPageNumber(); |
| currentPageNum = startPageNum - 1; |
| } |
| |
| /** |
| * 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 = idTracker.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 = idTracker.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) { |
| idTracker.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 (!idTracker.alreadyResolvedID(id)) { |
| idTracker.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) { |
| idTracker.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 IDTracker 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); |
| idTracker.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; |
| } |
| } |
| |
| /** |
| * Creates and returns a new page. |
| * @param pageNumber the page number |
| * @param isBlank true if it's a blank page |
| * @return the newly created page |
| */ |
| protected abstract Page createPage(int pageNumber, boolean isBlank); |
| |
| /** |
| * Makes a new page |
| * |
| * @param bIsBlank whether this page is blank or not |
| * @param bIsLast whether this page is the last page or not |
| * @return a new page |
| */ |
| protected Page makeNewPage(boolean isBlank, boolean isLast) { |
| if (curPage != null) { |
| finishPage(); |
| } |
| |
| currentPageNum++; |
| |
| curPage = createPage(currentPageNum, isBlank); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("[" + curPage.getPageViewport().getPageNumberString() |
| + (isBlank ? "*" : "") + "]"); |
| } |
| |
| addIDToPage(pageSeq.getId()); |
| return curPage; |
| } |
| |
| /** |
| * Finishes a page in preparation for a new page. |
| */ |
| protected void finishPage() { |
| if (log.isTraceEnabled()) { |
| curPage.getPageViewport().dumpMarkers(); |
| } |
| |
| // Try to resolve any unresolved IDs for the current page. |
| // |
| idTracker.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; |
| } |
| |
| /** {@inheritDoc} */ |
| 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 an odd number of pages |
| curPage = makeNewPage(true, false); |
| } |
| } else if (forcePageCount == Constants.EN_ODD) { |
| if ((currentPageNum - startPageNum + 1) % 2 == 0) { // we have an even number of pages |
| curPage = makeNewPage(true, false); |
| } |
| } else if (forcePageCount == Constants.EN_END_ON_EVEN) { |
| if (currentPageNum % 2 != 0) { // we are now on an odd page |
| curPage = makeNewPage(true, false); |
| } |
| } else if (forcePageCount == Constants.EN_END_ON_ODD) { |
| if (currentPageNum % 2 == 0) { // we are now on an even page |
| curPage = makeNewPage(true, false); |
| } |
| } else if (forcePageCount == Constants.EN_NO_FORCE) { |
| // i hope: nothing special at all |
| } |
| |
| if (curPage != null) { |
| finishPage(); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void reset() { |
| throw new IllegalStateException(); |
| } |
| |
| } |