Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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.Region;
  26. import org.apache.fop.fo.pagination.SimplePageMaster;
  27. /**
  28. * <p>This class delivers Page instances. It also caches them as necessary.
  29. * </p>
  30. * <p>Additional functionality makes sure that surplus instances that are requested by the
  31. * page breaker are properly discarded, especially in situations where hard breaks cause
  32. * blank pages. The reason for that: The page breaker sometimes needs to preallocate
  33. * additional pages since it doesn't know exactly until the end how many pages it really needs.
  34. * </p>
  35. */
  36. public class PageProvider implements Constants {
  37. private Log log = LogFactory.getLog(PageProvider.class);
  38. /** Indices are evaluated relative to the first page in the page-sequence. */
  39. public static final int RELTO_PAGE_SEQUENCE = 0;
  40. /** Indices are evaluated relative to the first page in the current element list. */
  41. public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
  42. private int startPageOfPageSequence;
  43. private int startPageOfCurrentElementList;
  44. private int startColumnOfCurrentElementList;
  45. private List cachedPages = new java.util.ArrayList();
  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. */
  77. public void setStartOfNextElementList(int startPage, int startColumn) {
  78. log.debug("start of the next element list is:"
  79. + " page=" + startPage + " col=" + startColumn);
  80. this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1;
  81. this.startColumnOfCurrentElementList = startColumn;
  82. //Reset Cache
  83. this.lastRequestedIndex = -1;
  84. this.lastReportedBPD = -1;
  85. }
  86. /**
  87. * Sets the index of the last page. This is done as soon as the position of the last page
  88. * is known or assumed.
  89. * @param index the index relative to the first page in the page-sequence
  90. */
  91. public void setLastPageIndex(int index) {
  92. this.lastPageIndex = index;
  93. }
  94. /**
  95. * Returns the available BPD for the part/page indicated by the index parameter.
  96. * The index is the part/page relative to the start of the current element list.
  97. * This method takes multiple columns into account.
  98. * @param index zero-based index of the requested part/page
  99. * @return the available BPD
  100. */
  101. public int getAvailableBPD(int index) {
  102. //Special optimization: There may be many equal calls by the BreakingAlgorithm
  103. if (this.lastRequestedIndex == index) {
  104. if (log.isTraceEnabled()) {
  105. log.trace("getAvailableBPD(" + index + ") -> (cached) " + lastReportedBPD);
  106. }
  107. return this.lastReportedBPD;
  108. }
  109. int c = index;
  110. int pageIndex = 0;
  111. int colIndex = startColumnOfCurrentElementList;
  112. Page page = getPage(
  113. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  114. while (c > 0) {
  115. colIndex++;
  116. if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
  117. colIndex = 0;
  118. pageIndex++;
  119. page = getPage(
  120. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  121. }
  122. c--;
  123. }
  124. this.lastRequestedIndex = index;
  125. this.lastReportedBPD = page.getPageViewport().getBodyRegion().getRemainingBPD();
  126. if (log.isTraceEnabled()) {
  127. log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD);
  128. }
  129. return this.lastReportedBPD;
  130. }
  131. // Wish there were a more elegant way to do this in Java
  132. private int[] getColIndexAndColCount(int index) {
  133. int columnCount = 0;
  134. int colIndex = startColumnOfCurrentElementList + index;
  135. int pageIndex = -1;
  136. do {
  137. colIndex -= columnCount;
  138. pageIndex++;
  139. Page page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  140. columnCount = page.getPageViewport().getCurrentSpan().getColumnCount();
  141. } while (colIndex >= columnCount);
  142. return new int[] {colIndex, columnCount};
  143. }
  144. /**
  145. * Compares the IPD of the given part with the following one.
  146. *
  147. * @param index index of the current part
  148. * @return a negative integer, zero or a positive integer as the current IPD is less
  149. * than, equal to or greater than the IPD of the following part
  150. */
  151. public int compareIPDs(int index) {
  152. int columnCount = 0;
  153. int colIndex = startColumnOfCurrentElementList + index;
  154. int pageIndex = -1;
  155. Page page;
  156. do {
  157. colIndex -= columnCount;
  158. pageIndex++;
  159. page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  160. columnCount = page.getPageViewport().getCurrentSpan().getColumnCount();
  161. } while (colIndex >= columnCount);
  162. if (colIndex + 1 < columnCount) {
  163. // Next part is a column on same page => same IPD
  164. return 0;
  165. } else {
  166. Page nextPage = getPage(false, pageIndex + 1, RELTO_CURRENT_ELEMENT_LIST);
  167. return page.getPageViewport().getBodyRegion().getIPD()
  168. - nextPage.getPageViewport().getBodyRegion().getIPD();
  169. }
  170. }
  171. /**
  172. * Checks if a break at the passed index would start a new page
  173. * @param index the index of the element before the break
  174. * @return {@code true} if the break starts a new page
  175. */
  176. boolean startPage(int index) {
  177. return getColIndexAndColCount(index)[0] == 0;
  178. }
  179. /**
  180. * Checks if a break at the passed index would end a page
  181. * @param index the index of the element before the break
  182. * @return {@code true} if the break ends a page
  183. */
  184. boolean endPage(int index) {
  185. int[] colIndexAndColCount = getColIndexAndColCount(index);
  186. return colIndexAndColCount[0] == colIndexAndColCount[1] - 1;
  187. }
  188. /**
  189. * Obtain the applicable column-count for the element at the
  190. * passed index
  191. * @param index the index of the element
  192. * @return the number of columns
  193. */
  194. int getColumnCount(int index) {
  195. return getColIndexAndColCount(index)[1];
  196. }
  197. /**
  198. * Returns the part index (0<x<partCount) which denotes the first part on the last page
  199. * generated by the current element list.
  200. * @param partCount Number of parts determined by the breaking algorithm
  201. * @return the requested part index
  202. */
  203. public int getStartingPartIndexForLastPage(int partCount) {
  204. int result = 0;
  205. int idx = 0;
  206. int pageIndex = 0;
  207. int colIndex = startColumnOfCurrentElementList;
  208. Page page = getPage(
  209. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  210. while (idx < partCount) {
  211. if ((colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount())) {
  212. colIndex = 0;
  213. pageIndex++;
  214. page = getPage(
  215. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  216. result = idx;
  217. }
  218. colIndex++;
  219. idx++;
  220. }
  221. return result;
  222. }
  223. /**
  224. * Returns a Page.
  225. * @param isBlank true if this page is supposed to be blank.
  226. * @param index Index of the page (see relativeTo)
  227. * @param relativeTo Defines which value the index parameter should be evaluated relative
  228. * to. (One of PageProvider.RELTO_*)
  229. * @return the requested Page
  230. */
  231. public Page getPage(boolean isBlank, int index, int relativeTo) {
  232. if (relativeTo == RELTO_PAGE_SEQUENCE) {
  233. return getPage(isBlank, index);
  234. } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
  235. int effIndex = startPageOfCurrentElementList + index;
  236. effIndex += startPageOfPageSequence - 1;
  237. return getPage(isBlank, effIndex);
  238. } else {
  239. throw new IllegalArgumentException(
  240. "Illegal value for relativeTo: " + relativeTo);
  241. }
  242. }
  243. /**
  244. * Returns a Page.
  245. * @param isBlank true if the Page should be a blank one
  246. * @param index the Page's index
  247. * @return a Page instance
  248. */
  249. protected Page getPage(boolean isBlank, int index) {
  250. boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
  251. if (log.isTraceEnabled()) {
  252. log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank")
  253. + (isLastPage ? " <LAST>" : "") + ")");
  254. }
  255. int intIndex = index - startPageOfPageSequence;
  256. if (log.isTraceEnabled()) {
  257. if (isBlank) {
  258. log.trace("blank page requested: " + index);
  259. }
  260. if (isLastPage) {
  261. log.trace("last page requested: " + index);
  262. }
  263. }
  264. while (intIndex >= cachedPages.size()) {
  265. if (log.isTraceEnabled()) {
  266. log.trace("Caching " + index);
  267. }
  268. cacheNextPage(index, isBlank, isLastPage);
  269. }
  270. Page page = (Page)cachedPages.get(intIndex);
  271. boolean replace = false;
  272. if (page.getPageViewport().isBlank() != isBlank) {
  273. log.debug("blank condition doesn't match. Replacing PageViewport.");
  274. replace = true;
  275. }
  276. if ((isLastPage && indexOfCachedLastPage != intIndex)
  277. || (!isLastPage && indexOfCachedLastPage >= 0)) {
  278. log.debug("last page condition doesn't match. Replacing PageViewport.");
  279. replace = true;
  280. indexOfCachedLastPage = (isLastPage ? intIndex : -1);
  281. }
  282. if (replace) {
  283. discardCacheStartingWith(intIndex);
  284. page = cacheNextPage(index, isBlank, isLastPage);
  285. }
  286. return page;
  287. }
  288. private void discardCacheStartingWith(int index) {
  289. while (index < cachedPages.size()) {
  290. this.cachedPages.remove(cachedPages.size() - 1);
  291. if (!pageSeq.goToPreviousSimplePageMaster()) {
  292. log.warn("goToPreviousSimplePageMaster() on the first page called!");
  293. }
  294. }
  295. }
  296. private Page cacheNextPage(int index, boolean isBlank, boolean isLastPage) {
  297. String pageNumberString = pageSeq.makeFormattedPageNumber(index);
  298. boolean isFirstPage = (startPageOfPageSequence == index);
  299. SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
  300. index, isFirstPage, isLastPage, isBlank);
  301. Region body = spm.getRegion(FO_REGION_BODY);
  302. if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) {
  303. // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to
  304. // any region), but we don't support it yet.
  305. BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
  306. pageSeq.getUserAgent().getEventBroadcaster());
  307. eventProducer.flowNotMappingToRegionBody(this,
  308. pageSeq.getMainFlow().getFlowName(), spm.getMasterName(), spm.getLocator());
  309. }
  310. Page page = new Page(spm, index, pageNumberString, isBlank);
  311. //Set unique key obtained from the AreaTreeHandler
  312. page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
  313. page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
  314. cachedPages.add(page);
  315. return page;
  316. }
  317. }