/* * 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.fo.Constants; import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.SimplePageMaster; /** *

This class delivers Page instances. It also caches them as necessary. *

*

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. *

*/ public class PageProvider implements Constants { 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 boolean spanAllForCurrentElementList; 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; /** * 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; /** * Main constructor. * @param ath the area tree handler * @param ps The page-sequence the provider operates on */ public PageProvider(AreaTreeHandler ath, PageSequence ps) { this.areaTreeHandler = ath; this.pageSeq = 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. * @param spanAll true if the current element list is for a column-spanning section */ public void setStartOfNextElementList(int startPage, int startColumn, boolean spanAll) { if (log.isDebugEnabled()) { log.debug("start of the next element list is:" + " page=" + startPage + " col=" + startColumn + (spanAll ? ", column-spanning" : "")); } this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1; this.startColumnOfCurrentElementList = startColumn; this.spanAllForCurrentElementList = spanAll; //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; } private static class Column { final Page page; final int pageIndex; final int colIndex; final int columnCount; Column(Page page, int pageIndex, int colIndex, int columnCount) { this.page = page; this.pageIndex = pageIndex; this.colIndex = colIndex; this.columnCount = columnCount; } } private Column getColumn(int index) { int columnCount = 0; int colIndex = startColumnOfCurrentElementList + index; int pageIndex = -1; Page page; do { colIndex -= columnCount; pageIndex++; page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST); columnCount = page.getPageViewport().getCurrentSpan().getColumnCount(); } while (colIndex >= columnCount); return new Column(page, pageIndex, colIndex, columnCount); } /** * Compares the IPD of the given part with the following one. * * @param index index of the current part * @return a negative integer, zero or a positive integer as the current IPD is less * than, equal to or greater than the IPD of the following part */ public int compareIPDs(int index) { Column column = getColumn(index); if (column.colIndex + 1 < column.columnCount) { // Next part is a column on same page => same IPD return 0; } else { Page nextPage = getPage(false, column.pageIndex + 1, RELTO_CURRENT_ELEMENT_LIST); return column.page.getPageViewport().getBodyRegion().getIPD() - nextPage.getPageViewport().getBodyRegion().getIPD(); } } /** * Checks if a break at the passed index would start a new page * @param index the index of the element before the break * @return {@code true} if the break starts a new page */ boolean startPage(int index) { return getColumn(index).colIndex == 0; } /** * Checks if a break at the passed index would end a page * @param index the index of the element before the break * @return {@code true} if the break ends a page */ boolean endPage(int index) { Column column = getColumn(index); return column.colIndex == column.columnCount - 1; } /** * Obtain the applicable column-count for the element at the * passed index * @param index the index of the element * @return the number of columns */ int getColumnCount(int index) { return getColumn(index).columnCount; } /** * Returns the part index (0= 0) && (index == lastPageIndex); if (log.isTraceEnabled()) { log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank") + (isLastPage ? " " : "") + ")"); } int intIndex = index - startPageOfPageSequence; if (log.isTraceEnabled()) { if (isBlank) { log.trace("blank page requested: " + index); } if (isLastPage) { log.trace("last page requested: " + index); } } if (intIndex > cachedPages.size()) { throw new UnsupportedOperationException("Cannot handle holes in page cache"); } else if (intIndex == cachedPages.size()) { if (log.isTraceEnabled()) { log.trace("Caching " + index); } cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList); } 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) { discardCacheStartingWith(intIndex); page = cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList); } return page; } private void discardCacheStartingWith(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, boolean spanAll) { String pageNumberString = pageSeq.makeFormattedPageNumber(index); boolean isFirstPage = (startPageOfPageSequence == index); SimplePageMaster spm = pageSeq.getNextSimplePageMaster( index, isFirstPage, isLastPage, isBlank); Page page = new Page(spm, index, pageNumberString, isBlank, spanAll); //Set unique key obtained from the AreaTreeHandler page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey()); page.getPageViewport().setForeignAttributes(spm.getForeignAttributes()); page.getPageViewport().setWritingModeTraits(pageSeq); cachedPages.add(page); return page; } /** * Indicates whether the column/page at the given index is on the first page of the page sequence. * * @return {@code true} if the given part is on the first page of the sequence */ boolean isOnFirstPage(int partIndex) { Column column = getColumn(partIndex); return startPageOfCurrentElementList + column.pageIndex == startPageOfPageSequence; } }