123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- /*
- * 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.BodyRegion;
- import org.apache.fop.area.PageViewport;
- import org.apache.fop.fo.Constants;
- 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.SimplePageMaster;
-
- /**
- * <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 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<Page> cachedPages = new java.util.ArrayList<Page>();
-
- 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;
-
- protected boolean skipPagePositionOnly;
-
- /**
- * 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();
- }
-
- public void initialize() {
- cachedPages.clear();
- }
-
- /**
- * 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 pageIndexTmp = index;
- int pageIndex = 0;
- int colIndex = startColumnOfCurrentElementList;
- Page page = getPage(
- false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
- while (pageIndexTmp > 0) {
- colIndex++;
- if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
- colIndex = 0;
- pageIndex++;
- page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
- BodyRegion br = page.getPageViewport().getBodyRegion();
- if (!pageSeq.getMainFlow().getFlowName().equals(br.getRegionName())) {
- pageIndexTmp++;
- }
- }
- pageIndexTmp--;
- }
- 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().getColumnIPD()
- - nextPage.getPageViewport().getBodyRegion().getColumnIPD();
- }
- }
-
- /**
- * 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<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 lastPartIndex = partCount - 1;
- return lastPartIndex - getColumn(lastPartIndex).colIndex;
- }
-
- Page getPageFromColumnIndex(int columnIndex) {
- return getColumn(columnIndex).page;
- }
-
- /**
- * 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);
- }
- }
-
- /**
- * Returns a Page.
- * @param isBlank true if the Page should be a blank one
- * @param index the Page's index
- * @return a Page instance
- */
- protected 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);
- }
- }
- 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 (page.getPageViewport().getCurrentSpan().getColumnCount() == 1
- && !this.spanAllForCurrentElementList) {
- RegionBody rb = (RegionBody)page.getSimplePageMaster().getRegion(Region.FO_REGION_BODY);
- int colCount = rb.getColumnCount();
- if (colCount > 1) {
- log.debug("Span 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);
- PageViewport oldPageVP = page.getPageViewport();
- page = cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList);
- PageViewport newPageVP = page.getPageViewport();
- newPageVP.replace(oldPageVP);
- this.areaTreeHandler.getIDTracker().replacePageViewPort(oldPageVP, newPageVP);
- }
- return page;
- }
-
- protected 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);
- boolean isPagePositionOnly = pageSeq.hasPagePositionOnly() && !skipPagePositionOnly;
- if (isPagePositionOnly) {
- spm = pageSeq.getNextSimplePageMaster(index, isFirstPage, true, isBlank);
- }
- Page page = new Page(spm, index, pageNumberString, isBlank, spanAll, isPagePositionOnly);
- //Set unique key obtained from the AreaTreeHandler
- page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
- page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
- page.getPageViewport().setWritingModeTraits(pageSeq);
- cachedPages.add(page);
- if (isLastPage) {
- pageSeq.getRoot().setLastSeq(pageSeq);
- } else if (!isFirstPage) {
- pageSeq.getRoot().setLastSeq(null);
- }
- return page;
- }
-
- public int getIndexOfCachedLastPage() {
- return indexOfCachedLastPage;
- }
-
- public int getLastPageIndex() {
- return lastPageIndex;
- }
-
- public int getLastPageIPD() {
- int index = this.cachedPages.size();
- boolean isFirstPage = (startPageOfPageSequence == index);
- SimplePageMaster spm = pageSeq.getLastSimplePageMaster(index, isFirstPage, false);
- Page page = new Page(spm, index, "", false, false, false);
- if (pageSeq.getRoot().getLastSeq() != null && pageSeq.getRoot().getLastSeq() != pageSeq) {
- return -1;
- }
- return page.getPageViewport().getBodyRegion().getColumnIPD();
- }
-
- public int getCurrentIPD() {
- return getPageFromColumnIndex(startColumnOfCurrentElementList).getPageViewport().getBodyRegion()
- .getColumnIPD();
- }
-
- /**
- * 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;
- }
-
- }
|