001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: PageProvider.java 554094 2007-07-07 00:04:25Z adelmelle $ */
019:
020: package org.apache.fop.layoutmgr;
021:
022: import java.util.List;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.apache.fop.apps.FOPException;
027: import org.apache.fop.area.AreaTreeHandler;
028: import org.apache.fop.fo.Constants;
029: import org.apache.fop.fo.pagination.PageSequence;
030: import org.apache.fop.fo.pagination.Region;
031: import org.apache.fop.fo.pagination.SimplePageMaster;
032:
033: /**
034: * <p>This class delivers Page instances. It also caches them as necessary.
035: * </p>
036: * <p>Additional functionality makes sure that surplus instances that are requested by the
037: * page breaker are properly discarded, especially in situations where hard breaks cause
038: * blank pages. The reason for that: The page breaker sometimes needs to preallocate
039: * additional pages since it doesn't know exactly until the end how many pages it really needs.
040: * </p>
041: */
042: public class PageProvider implements Constants {
043:
044: private Log log = LogFactory.getLog(PageProvider.class);
045:
046: /** Indices are evaluated relative to the first page in the page-sequence. */
047: public static final int RELTO_PAGE_SEQUENCE = 0;
048: /** Indices are evaluated relative to the first page in the current element list. */
049: public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
050:
051: private int startPageOfPageSequence;
052: private int startPageOfCurrentElementList;
053: private int startColumnOfCurrentElementList;
054: private List cachedPages = new java.util.ArrayList();
055:
056: private int lastPageIndex = -1;
057: private int indexOfCachedLastPage = -1;
058:
059: //Cache to optimize getAvailableBPD() calls
060: private int lastRequestedIndex = -1;
061: private int lastReportedBPD = -1;
062:
063: /**
064: * AreaTreeHandler which activates the PSLM and controls
065: * the rendering of its pages.
066: */
067: private AreaTreeHandler areaTreeHandler;
068:
069: /**
070: * fo:page-sequence formatting object being
071: * processed by this class
072: */
073: private PageSequence pageSeq;
074:
075: /**
076: * Main constructor.
077: * @param ps The page-sequence the provider operates on
078: */
079: public PageProvider(AreaTreeHandler ath, PageSequence ps) {
080: this .areaTreeHandler = ath;
081: this .pageSeq = ps;
082: this .startPageOfPageSequence = ps.getStartingPageNumber();
083: }
084:
085: /**
086: * The page breaker notifies the provider about the page number an element list starts
087: * on so it can later retrieve PageViewports relative to this first page.
088: * @param startPage the number of the first page for the element list.
089: * @param startColumn the starting column number for the element list.
090: */
091: public void setStartOfNextElementList(int startPage, int startColumn) {
092: log.debug("start of the next element list is:" + " page="
093: + startPage + " col=" + startColumn);
094: this .startPageOfCurrentElementList = startPage
095: - startPageOfPageSequence + 1;
096: this .startColumnOfCurrentElementList = startColumn;
097: //Reset Cache
098: this .lastRequestedIndex = -1;
099: this .lastReportedBPD = -1;
100: }
101:
102: /**
103: * Sets the index of the last page. This is done as soon as the position of the last page
104: * is known or assumed.
105: * @param index the index relative to the first page in the page-sequence
106: */
107: public void setLastPageIndex(int index) {
108: this .lastPageIndex = index;
109: }
110:
111: /**
112: * Returns the available BPD for the part/page indicated by the index parameter.
113: * The index is the part/page relative to the start of the current element list.
114: * This method takes multiple columns into account.
115: * @param index zero-based index of the requested part/page
116: * @return the available BPD
117: */
118: public int getAvailableBPD(int index) {
119: //Special optimization: There may be many equal calls by the BreakingAlgorithm
120: if (this .lastRequestedIndex == index) {
121: if (log.isTraceEnabled()) {
122: log.trace("getAvailableBPD(" + index + ") -> (cached) "
123: + lastReportedBPD);
124: }
125: return this .lastReportedBPD;
126: }
127: int c = index;
128: int pageIndex = 0;
129: int colIndex = startColumnOfCurrentElementList;
130: Page page = getPage(false, pageIndex,
131: RELTO_CURRENT_ELEMENT_LIST);
132: while (c > 0) {
133: colIndex++;
134: if (colIndex >= page.getPageViewport().getCurrentSpan()
135: .getColumnCount()) {
136: colIndex = 0;
137: pageIndex++;
138: page = getPage(false, pageIndex,
139: RELTO_CURRENT_ELEMENT_LIST);
140: }
141: c--;
142: }
143: this .lastRequestedIndex = index;
144: this .lastReportedBPD = page.getPageViewport().getBodyRegion()
145: .getRemainingBPD();
146: if (log.isTraceEnabled()) {
147: log.trace("getAvailableBPD(" + index + ") -> "
148: + lastReportedBPD);
149: }
150: return this .lastReportedBPD;
151: }
152:
153: /**
154: * Returns the part index (0<x<partCount) which denotes the first part on the last page
155: * generated by the current element list.
156: * @param partCount Number of parts determined by the breaking algorithm
157: * @return the requested part index
158: */
159: public int getStartingPartIndexForLastPage(int partCount) {
160: int result = 0;
161: int idx = 0;
162: int pageIndex = 0;
163: int colIndex = startColumnOfCurrentElementList;
164: Page page = getPage(false, pageIndex,
165: RELTO_CURRENT_ELEMENT_LIST);
166: while (idx < partCount) {
167: if ((colIndex >= page.getPageViewport().getCurrentSpan()
168: .getColumnCount())) {
169: colIndex = 0;
170: pageIndex++;
171: page = getPage(false, pageIndex,
172: RELTO_CURRENT_ELEMENT_LIST);
173: result = idx;
174: }
175: colIndex++;
176: idx++;
177: }
178: return result;
179: }
180:
181: /**
182: * Returns a Page.
183: * @param isBlank true if this page is supposed to be blank.
184: * @param index Index of the page (see relativeTo)
185: * @param relativeTo Defines which value the index parameter should be evaluated relative
186: * to. (One of PageProvider.RELTO_*)
187: * @return the requested Page
188: */
189: public Page getPage(boolean isBlank, int index, int relativeTo) {
190: if (relativeTo == RELTO_PAGE_SEQUENCE) {
191: return getPage(isBlank, index);
192: } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
193: int effIndex = startPageOfCurrentElementList + index;
194: effIndex += startPageOfPageSequence - 1;
195: return getPage(isBlank, effIndex);
196: } else {
197: throw new IllegalArgumentException(
198: "Illegal value for relativeTo: " + relativeTo);
199: }
200: }
201:
202: /**
203: *
204: * @param isBlank
205: * @param index
206: * @return
207: */
208: protected Page getPage(boolean isBlank, int index) {
209: boolean isLastPage = (lastPageIndex >= 0)
210: && (index == lastPageIndex);
211: if (log.isTraceEnabled()) {
212: log.trace("getPage(" + index + " "
213: + (isBlank ? "blank" : "non-blank")
214: + (isLastPage ? " <LAST>" : "") + ")");
215: }
216: int intIndex = index - startPageOfPageSequence;
217: if (log.isTraceEnabled()) {
218: if (isBlank) {
219: log.trace("blank page requested: " + index);
220: }
221: if (isLastPage) {
222: log.trace("last page requested: " + index);
223: }
224: }
225: while (intIndex >= cachedPages.size()) {
226: if (log.isTraceEnabled()) {
227: log.trace("Caching " + index);
228: }
229: cacheNextPage(index, isBlank, isLastPage);
230: }
231: Page page = (Page) cachedPages.get(intIndex);
232: boolean replace = false;
233: if (page.getPageViewport().isBlank() != isBlank) {
234: log
235: .debug("blank condition doesn't match. Replacing PageViewport.");
236: replace = true;
237: }
238: if ((isLastPage && indexOfCachedLastPage != intIndex)
239: || (!isLastPage && indexOfCachedLastPage >= 0)) {
240: log
241: .debug("last page condition doesn't match. Replacing PageViewport.");
242: replace = true;
243: indexOfCachedLastPage = (isLastPage ? intIndex : -1);
244: }
245: if (replace) {
246: disardCacheStartingWith(intIndex);
247: page = cacheNextPage(index, isBlank, isLastPage);
248: }
249: return page;
250: }
251:
252: private void disardCacheStartingWith(int index) {
253: while (index < cachedPages.size()) {
254: this .cachedPages.remove(cachedPages.size() - 1);
255: if (!pageSeq.goToPreviousSimplePageMaster()) {
256: log
257: .warn("goToPreviousSimplePageMaster() on the first page called!");
258: }
259: }
260: }
261:
262: private Page cacheNextPage(int index, boolean isBlank,
263: boolean isLastPage) {
264: try {
265: String pageNumberString = pageSeq
266: .makeFormattedPageNumber(index);
267: SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
268: index, (startPageOfPageSequence == index),
269: isLastPage, isBlank);
270:
271: Region body = spm.getRegion(FO_REGION_BODY);
272: if (!pageSeq.getMainFlow().getFlowName().equals(
273: body.getRegionName())) {
274: // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to
275: // any region), but we don't support it yet.
276: throw new FOPException(
277: "Flow '"
278: + pageSeq.getMainFlow().getFlowName()
279: + "' does not map to the region-body in page-master '"
280: + spm.getMasterName()
281: + "'. FOP presently "
282: + "does not support this.");
283: }
284: Page page = new Page(spm, index, pageNumberString, isBlank);
285: //Set unique key obtained from the AreaTreeHandler
286: page.getPageViewport().setKey(
287: areaTreeHandler.generatePageViewportKey());
288: page.getPageViewport().setForeignAttributes(
289: spm.getForeignAttributes());
290: cachedPages.add(page);
291: return page;
292: } catch (FOPException e) {
293: //TODO Maybe improve. It'll mean to propagate this exception up several
294: //methods calls.
295: throw new IllegalStateException(e.getMessage());
296: }
297: }
298:
299: }
|