You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PageProvider.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr;
  19. import java.util.List;
  20. import org.apache.commons.logging.Log;
  21. import org.apache.commons.logging.LogFactory;
  22. import org.apache.fop.area.AreaTreeHandler;
  23. import org.apache.fop.fo.Constants;
  24. import org.apache.fop.fo.pagination.PageSequence;
  25. import org.apache.fop.fo.pagination.SimplePageMaster;
  26. /**
  27. * <p>This class delivers Page instances. It also caches them as necessary.
  28. * </p>
  29. * <p>Additional functionality makes sure that surplus instances that are requested by the
  30. * page breaker are properly discarded, especially in situations where hard breaks cause
  31. * blank pages. The reason for that: The page breaker sometimes needs to preallocate
  32. * additional pages since it doesn't know exactly until the end how many pages it really needs.
  33. * </p>
  34. */
  35. public class PageProvider implements Constants {
  36. private Log log = LogFactory.getLog(PageProvider.class);
  37. /** Indices are evaluated relative to the first page in the page-sequence. */
  38. public static final int RELTO_PAGE_SEQUENCE = 0;
  39. /** Indices are evaluated relative to the first page in the current element list. */
  40. public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
  41. private int startPageOfPageSequence;
  42. private int startPageOfCurrentElementList;
  43. private int startColumnOfCurrentElementList;
  44. private boolean spanAllForCurrentElementList;
  45. private List<Page> cachedPages = new java.util.ArrayList<Page>();
  46. private int lastPageIndex = -1;
  47. private int indexOfCachedLastPage = -1;
  48. //Cache to optimize getAvailableBPD() calls
  49. private int lastRequestedIndex = -1;
  50. private int lastReportedBPD = -1;
  51. /**
  52. * AreaTreeHandler which activates the PSLM and controls
  53. * the rendering of its pages.
  54. */
  55. private AreaTreeHandler areaTreeHandler;
  56. /**
  57. * fo:page-sequence formatting object being
  58. * processed by this class
  59. */
  60. private PageSequence pageSeq;
  61. /**
  62. * Main constructor.
  63. * @param ath the area tree handler
  64. * @param ps The page-sequence the provider operates on
  65. */
  66. public PageProvider(AreaTreeHandler ath, PageSequence ps) {
  67. this.areaTreeHandler = ath;
  68. this.pageSeq = ps;
  69. this.startPageOfPageSequence = ps.getStartingPageNumber();
  70. }
  71. /**
  72. * The page breaker notifies the provider about the page number an element list starts
  73. * on so it can later retrieve PageViewports relative to this first page.
  74. * @param startPage the number of the first page for the element list.
  75. * @param startColumn the starting column number for the element list.
  76. * @param spanAll true if the current element list is for a column-spanning section
  77. */
  78. public void setStartOfNextElementList(int startPage, int startColumn, boolean spanAll) {
  79. if (log.isDebugEnabled()) {
  80. log.debug("start of the next element list is:"
  81. + " page=" + startPage + " col=" + startColumn
  82. + (spanAll ? ", column-spanning" : ""));
  83. }
  84. this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1;
  85. this.startColumnOfCurrentElementList = startColumn;
  86. this.spanAllForCurrentElementList = spanAll;
  87. //Reset Cache
  88. this.lastRequestedIndex = -1;
  89. this.lastReportedBPD = -1;
  90. }
  91. /**
  92. * Sets the index of the last page. This is done as soon as the position of the last page
  93. * is known or assumed.
  94. * @param index the index relative to the first page in the page-sequence
  95. */
  96. public void setLastPageIndex(int index) {
  97. this.lastPageIndex = index;
  98. }
  99. /**
  100. * Returns the available BPD for the part/page indicated by the index parameter.
  101. * The index is the part/page relative to the start of the current element list.
  102. * This method takes multiple columns into account.
  103. * @param index zero-based index of the requested part/page
  104. * @return the available BPD
  105. */
  106. public int getAvailableBPD(int index) {
  107. //Special optimization: There may be many equal calls by the BreakingAlgorithm
  108. if (this.lastRequestedIndex == index) {
  109. if (log.isTraceEnabled()) {
  110. log.trace("getAvailableBPD(" + index + ") -> (cached) " + lastReportedBPD);
  111. }
  112. return this.lastReportedBPD;
  113. }
  114. int c = index;
  115. int pageIndex = 0;
  116. int colIndex = startColumnOfCurrentElementList;
  117. Page page = getPage(
  118. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  119. while (c > 0) {
  120. colIndex++;
  121. if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
  122. colIndex = 0;
  123. pageIndex++;
  124. page = getPage(
  125. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  126. }
  127. c--;
  128. }
  129. this.lastRequestedIndex = index;
  130. this.lastReportedBPD = page.getPageViewport().getBodyRegion().getRemainingBPD();
  131. if (log.isTraceEnabled()) {
  132. log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD);
  133. }
  134. return this.lastReportedBPD;
  135. }
  136. private static class Column {
  137. final Page page;
  138. final int pageIndex;
  139. final int colIndex;
  140. final int columnCount;
  141. Column(Page page, int pageIndex, int colIndex, int columnCount) {
  142. this.page = page;
  143. this.pageIndex = pageIndex;
  144. this.colIndex = colIndex;
  145. this.columnCount = columnCount;
  146. }
  147. }
  148. private Column getColumn(int index) {
  149. int columnCount = 0;
  150. int colIndex = startColumnOfCurrentElementList + index;
  151. int pageIndex = -1;
  152. Page page;
  153. do {
  154. colIndex -= columnCount;
  155. pageIndex++;
  156. page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  157. columnCount = page.getPageViewport().getCurrentSpan().getColumnCount();
  158. } while (colIndex >= columnCount);
  159. return new Column(page, pageIndex, colIndex, columnCount);
  160. }
  161. /**
  162. * Compares the IPD of the given part with the following one.
  163. *
  164. * @param index index of the current part
  165. * @return a negative integer, zero or a positive integer as the current IPD is less
  166. * than, equal to or greater than the IPD of the following part
  167. */
  168. public int compareIPDs(int index) {
  169. Column column = getColumn(index);
  170. if (column.colIndex + 1 < column.columnCount) {
  171. // Next part is a column on same page => same IPD
  172. return 0;
  173. } else {
  174. Page nextPage = getPage(false, column.pageIndex + 1, RELTO_CURRENT_ELEMENT_LIST);
  175. return column.page.getPageViewport().getBodyRegion().getIPD()
  176. - nextPage.getPageViewport().getBodyRegion().getIPD();
  177. }
  178. }
  179. /**
  180. * Checks if a break at the passed index would start a new page
  181. * @param index the index of the element before the break
  182. * @return {@code true} if the break starts a new page
  183. */
  184. boolean startPage(int index) {
  185. return getColumn(index).colIndex == 0;
  186. }
  187. /**
  188. * Checks if a break at the passed index would end a page
  189. * @param index the index of the element before the break
  190. * @return {@code true} if the break ends a page
  191. */
  192. boolean endPage(int index) {
  193. Column column = getColumn(index);
  194. return column.colIndex == column.columnCount - 1;
  195. }
  196. /**
  197. * Obtain the applicable column-count for the element at the
  198. * passed index
  199. * @param index the index of the element
  200. * @return the number of columns
  201. */
  202. int getColumnCount(int index) {
  203. return getColumn(index).columnCount;
  204. }
  205. /**
  206. * Returns the part index (0<x<partCount) which denotes the first part on the last page
  207. * generated by the current element list.
  208. * @param partCount Number of parts determined by the breaking algorithm
  209. * @return the requested part index
  210. */
  211. public int getStartingPartIndexForLastPage(int partCount) {
  212. int lastPartIndex = partCount - 1;
  213. return lastPartIndex - getColumn(lastPartIndex).colIndex;
  214. }
  215. Page getPageFromColumnIndex(int columnIndex) {
  216. return getColumn(columnIndex).page;
  217. }
  218. /**
  219. * Returns a Page.
  220. * @param isBlank true if this page is supposed to be blank.
  221. * @param index Index of the page (see relativeTo)
  222. * @param relativeTo Defines which value the index parameter should be evaluated relative
  223. * to. (One of PageProvider.RELTO_*)
  224. * @return the requested Page
  225. */
  226. public Page getPage(boolean isBlank, int index, int relativeTo) {
  227. if (relativeTo == RELTO_PAGE_SEQUENCE) {
  228. return getPage(isBlank, index);
  229. } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
  230. int effIndex = startPageOfCurrentElementList + index;
  231. effIndex += startPageOfPageSequence - 1;
  232. return getPage(isBlank, effIndex);
  233. } else {
  234. throw new IllegalArgumentException(
  235. "Illegal value for relativeTo: " + relativeTo);
  236. }
  237. }
  238. /**
  239. * Returns a Page.
  240. * @param isBlank true if the Page should be a blank one
  241. * @param index the Page's index
  242. * @return a Page instance
  243. */
  244. protected Page getPage(boolean isBlank, int index) {
  245. boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
  246. if (log.isTraceEnabled()) {
  247. log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank")
  248. + (isLastPage ? " <LAST>" : "") + ")");
  249. }
  250. int intIndex = index - startPageOfPageSequence;
  251. if (log.isTraceEnabled()) {
  252. if (isBlank) {
  253. log.trace("blank page requested: " + index);
  254. }
  255. if (isLastPage) {
  256. log.trace("last page requested: " + index);
  257. }
  258. }
  259. if (intIndex > cachedPages.size()) {
  260. throw new UnsupportedOperationException("Cannot handle holes in page cache");
  261. } else if (intIndex == cachedPages.size()) {
  262. if (log.isTraceEnabled()) {
  263. log.trace("Caching " + index);
  264. }
  265. cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList);
  266. }
  267. Page page = cachedPages.get(intIndex);
  268. boolean replace = false;
  269. if (page.getPageViewport().isBlank() != isBlank) {
  270. log.debug("blank condition doesn't match. Replacing PageViewport.");
  271. replace = true;
  272. }
  273. if ((isLastPage && indexOfCachedLastPage != intIndex)
  274. || (!isLastPage && indexOfCachedLastPage >= 0)) {
  275. log.debug("last page condition doesn't match. Replacing PageViewport.");
  276. replace = true;
  277. indexOfCachedLastPage = (isLastPage ? intIndex : -1);
  278. }
  279. if (replace) {
  280. discardCacheStartingWith(intIndex);
  281. page = cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList);
  282. }
  283. return page;
  284. }
  285. private void discardCacheStartingWith(int index) {
  286. while (index < cachedPages.size()) {
  287. this.cachedPages.remove(cachedPages.size() - 1);
  288. if (!pageSeq.goToPreviousSimplePageMaster()) {
  289. log.warn("goToPreviousSimplePageMaster() on the first page called!");
  290. }
  291. }
  292. }
  293. private Page cacheNextPage(int index, boolean isBlank, boolean isLastPage, boolean spanAll) {
  294. String pageNumberString = pageSeq.makeFormattedPageNumber(index);
  295. boolean isFirstPage = (startPageOfPageSequence == index);
  296. SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
  297. index, isFirstPage, isLastPage, isBlank);
  298. Page page = new Page(spm, index, pageNumberString, isBlank, spanAll);
  299. //Set unique key obtained from the AreaTreeHandler
  300. page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
  301. page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
  302. page.getPageViewport().setWritingModeTraits(pageSeq);
  303. cachedPages.add(page);
  304. return page;
  305. }
  306. }