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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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.area.BodyRegion;
  24. import org.apache.fop.area.PageViewport;
  25. import org.apache.fop.fo.Constants;
  26. import org.apache.fop.fo.pagination.PageProductionException;
  27. import org.apache.fop.fo.pagination.PageSequence;
  28. import org.apache.fop.fo.pagination.Region;
  29. import org.apache.fop.fo.pagination.RegionBody;
  30. import org.apache.fop.fo.pagination.SimplePageMaster;
  31. /**
  32. * <p>This class delivers Page instances. It also caches them as necessary.
  33. * </p>
  34. * <p>Additional functionality makes sure that surplus instances that are requested by the
  35. * page breaker are properly discarded, especially in situations where hard breaks cause
  36. * blank pages. The reason for that: The page breaker sometimes needs to preallocate
  37. * additional pages since it doesn't know exactly until the end how many pages it really needs.
  38. * </p>
  39. */
  40. public class PageProvider implements Constants {
  41. private Log log = LogFactory.getLog(PageProvider.class);
  42. /** Indices are evaluated relative to the first page in the page-sequence. */
  43. public static final int RELTO_PAGE_SEQUENCE = 0;
  44. /** Indices are evaluated relative to the first page in the current element list. */
  45. public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
  46. private int startPageOfPageSequence;
  47. private int startPageOfCurrentElementList;
  48. private int startColumnOfCurrentElementList;
  49. private boolean spanAllForCurrentElementList;
  50. private List<Page> cachedPages = new java.util.ArrayList<Page>();
  51. private int lastPageIndex = -1;
  52. private int indexOfCachedLastPage = -1;
  53. //Cache to optimize getAvailableBPD() calls
  54. private int lastRequestedIndex = -1;
  55. private int lastReportedBPD = -1;
  56. /**
  57. * AreaTreeHandler which activates the PSLM and controls
  58. * the rendering of its pages.
  59. */
  60. private AreaTreeHandler areaTreeHandler;
  61. /**
  62. * fo:page-sequence formatting object being
  63. * processed by this class
  64. */
  65. private PageSequence pageSeq;
  66. protected boolean skipPagePositionOnly;
  67. /**
  68. * Main constructor.
  69. * @param ath the area tree handler
  70. * @param ps The page-sequence the provider operates on
  71. */
  72. public PageProvider(AreaTreeHandler ath, PageSequence ps) {
  73. this.areaTreeHandler = ath;
  74. this.pageSeq = ps;
  75. this.startPageOfPageSequence = ps.getStartingPageNumber();
  76. }
  77. public void initialize() {
  78. cachedPages.clear();
  79. }
  80. /**
  81. * The page breaker notifies the provider about the page number an element list starts
  82. * on so it can later retrieve PageViewports relative to this first page.
  83. * @param startPage the number of the first page for the element list.
  84. * @param startColumn the starting column number for the element list.
  85. * @param spanAll true if the current element list is for a column-spanning section
  86. */
  87. public void setStartOfNextElementList(int startPage, int startColumn, boolean spanAll) {
  88. if (log.isDebugEnabled()) {
  89. log.debug("start of the next element list is:"
  90. + " page=" + startPage + " col=" + startColumn
  91. + (spanAll ? ", column-spanning" : ""));
  92. }
  93. this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1;
  94. this.startColumnOfCurrentElementList = startColumn;
  95. this.spanAllForCurrentElementList = spanAll;
  96. //Reset Cache
  97. this.lastRequestedIndex = -1;
  98. this.lastReportedBPD = -1;
  99. }
  100. /**
  101. * Sets the index of the last page. This is done as soon as the position of the last page
  102. * is known or assumed.
  103. * @param index the index relative to the first page in the page-sequence
  104. */
  105. public void setLastPageIndex(int index) {
  106. this.lastPageIndex = index;
  107. }
  108. /**
  109. * Returns the available BPD for the part/page indicated by the index parameter.
  110. * The index is the part/page relative to the start of the current element list.
  111. * This method takes multiple columns into account.
  112. * @param index zero-based index of the requested part/page
  113. * @return the available BPD
  114. */
  115. public int getAvailableBPD(int index) {
  116. //Special optimization: There may be many equal calls by the BreakingAlgorithm
  117. if (this.lastRequestedIndex == index) {
  118. if (log.isTraceEnabled()) {
  119. log.trace("getAvailableBPD(" + index + ") -> (cached) " + lastReportedBPD);
  120. }
  121. return this.lastReportedBPD;
  122. }
  123. int pageIndexTmp = index;
  124. int pageIndex = 0;
  125. int colIndex = startColumnOfCurrentElementList;
  126. Page page = getPage(
  127. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  128. while (pageIndexTmp > 0) {
  129. colIndex++;
  130. if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
  131. colIndex = 0;
  132. pageIndex++;
  133. page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  134. BodyRegion br = page.getPageViewport().getBodyRegion();
  135. if (!pageSeq.getMainFlow().getFlowName().equals(br.getRegionName())) {
  136. pageIndexTmp++;
  137. }
  138. }
  139. pageIndexTmp--;
  140. }
  141. this.lastRequestedIndex = index;
  142. this.lastReportedBPD = page.getPageViewport().getBodyRegion().getRemainingBPD();
  143. if (log.isTraceEnabled()) {
  144. log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD);
  145. }
  146. return this.lastReportedBPD;
  147. }
  148. private static class Column {
  149. final Page page;
  150. final int pageIndex;
  151. final int colIndex;
  152. final int columnCount;
  153. Column(Page page, int pageIndex, int colIndex, int columnCount) {
  154. this.page = page;
  155. this.pageIndex = pageIndex;
  156. this.colIndex = colIndex;
  157. this.columnCount = columnCount;
  158. }
  159. }
  160. private Column getColumn(int index) {
  161. int columnCount = 0;
  162. int colIndex = startColumnOfCurrentElementList + index;
  163. int pageIndex = -1;
  164. Page page;
  165. do {
  166. colIndex -= columnCount;
  167. pageIndex++;
  168. page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  169. if (page.getPageViewport().getPage() != null) {
  170. columnCount = page.getPageViewport().getCurrentSpan().getColumnCount();
  171. }
  172. } while (colIndex >= columnCount);
  173. return new Column(page, pageIndex, colIndex, columnCount);
  174. }
  175. /**
  176. * Compares the IPD of the given part with the following one.
  177. *
  178. * @param index index of the current part
  179. * @return a negative integer, zero or a positive integer as the current IPD is less
  180. * than, equal to or greater than the IPD of the following part
  181. */
  182. public int compareIPDs(int index) {
  183. Column column = getColumn(index);
  184. if (column.colIndex + 1 < column.columnCount) {
  185. // Next part is a column on same page => same IPD
  186. return 0;
  187. } else {
  188. Page nextPage = getPage(false, column.pageIndex + 1, RELTO_CURRENT_ELEMENT_LIST);
  189. return column.page.getPageViewport().getBodyRegion().getColumnIPD()
  190. - nextPage.getPageViewport().getBodyRegion().getColumnIPD();
  191. }
  192. }
  193. /**
  194. * Checks if a break at the passed index would start a new page
  195. * @param index the index of the element before the break
  196. * @return {@code true} if the break starts a new page
  197. */
  198. boolean startPage(int index) {
  199. return getColumn(index).colIndex == 0;
  200. }
  201. /**
  202. * Checks if a break at the passed index would end a page
  203. * @param index the index of the element before the break
  204. * @return {@code true} if the break ends a page
  205. */
  206. boolean endPage(int index) {
  207. Column column = getColumn(index);
  208. return column.colIndex == column.columnCount - 1;
  209. }
  210. /**
  211. * Obtain the applicable column-count for the element at the
  212. * passed index
  213. * @param index the index of the element
  214. * @return the number of columns
  215. */
  216. int getColumnCount(int index) {
  217. return getColumn(index).columnCount;
  218. }
  219. /**
  220. * Returns the part index (0&lt;x&lt;partCount) which denotes the first part on the last page
  221. * generated by the current element list.
  222. * @param partCount Number of parts determined by the breaking algorithm
  223. * @return the requested part index
  224. */
  225. public int getStartingPartIndexForLastPage(int partCount) {
  226. int lastPartIndex = partCount - 1;
  227. return lastPartIndex - getColumn(lastPartIndex).colIndex;
  228. }
  229. Page getPageFromColumnIndex(int columnIndex) {
  230. return getColumn(columnIndex).page;
  231. }
  232. /**
  233. * Returns a Page.
  234. * @param isBlank true if this page is supposed to be blank.
  235. * @param index Index of the page (see relativeTo)
  236. * @param relativeTo Defines which value the index parameter should be evaluated relative
  237. * to. (One of PageProvider.RELTO_*)
  238. * @return the requested Page
  239. */
  240. public Page getPage(boolean isBlank, int index, int relativeTo) {
  241. if (relativeTo == RELTO_PAGE_SEQUENCE) {
  242. return getPage(isBlank, index);
  243. } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
  244. int effIndex = startPageOfCurrentElementList + index;
  245. effIndex += startPageOfPageSequence - 1;
  246. return getPage(isBlank, effIndex);
  247. } else {
  248. throw new IllegalArgumentException(
  249. "Illegal value for relativeTo: " + relativeTo);
  250. }
  251. }
  252. /**
  253. * Returns a Page.
  254. * @param isBlank true if the Page should be a blank one
  255. * @param index the Page's index
  256. * @return a Page instance
  257. */
  258. protected Page getPage(boolean isBlank, int index) {
  259. boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
  260. if (log.isTraceEnabled()) {
  261. log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank")
  262. + (isLastPage ? " <LAST>" : "") + ")");
  263. }
  264. int intIndex = index - startPageOfPageSequence;
  265. if (log.isTraceEnabled()) {
  266. if (isBlank) {
  267. log.trace("blank page requested: " + index);
  268. }
  269. if (isLastPage) {
  270. log.trace("last page requested: " + index);
  271. }
  272. }
  273. if (intIndex > cachedPages.size()) {
  274. throw new UnsupportedOperationException("Cannot handle holes in page cache");
  275. } else if (intIndex == cachedPages.size()) {
  276. if (log.isTraceEnabled()) {
  277. log.trace("Caching " + index);
  278. }
  279. cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList);
  280. }
  281. Page page = cachedPages.get(intIndex);
  282. boolean replace = false;
  283. if (page.getPageViewport().isBlank() != isBlank) {
  284. log.debug("blank condition doesn't match. Replacing PageViewport.");
  285. replace = true;
  286. }
  287. if (page.getPageViewport().getPage() != null
  288. && page.getPageViewport().getCurrentSpan().getColumnCount() == 1
  289. && !this.spanAllForCurrentElementList) {
  290. RegionBody rb = (RegionBody)page.getSimplePageMaster().getRegion(Region.FO_REGION_BODY);
  291. int colCount = rb.getColumnCount();
  292. if (colCount > 1) {
  293. log.debug("Span doesn't match. Replacing PageViewport.");
  294. replace = true;
  295. }
  296. }
  297. if ((isLastPage && indexOfCachedLastPage != intIndex)
  298. || (!isLastPage && indexOfCachedLastPage >= 0)) {
  299. log.debug("last page condition doesn't match. Replacing PageViewport.");
  300. replace = true;
  301. indexOfCachedLastPage = (isLastPage ? intIndex : -1);
  302. }
  303. if (replace) {
  304. discardCacheStartingWith(intIndex);
  305. PageViewport oldPageVP = page.getPageViewport();
  306. page = cacheNextPage(index, isBlank, isLastPage, this.spanAllForCurrentElementList);
  307. PageViewport newPageVP = page.getPageViewport();
  308. newPageVP.replace(oldPageVP);
  309. this.areaTreeHandler.getIDTracker().replacePageViewPort(oldPageVP, newPageVP);
  310. }
  311. return page;
  312. }
  313. protected void discardCacheStartingWith(int index) {
  314. while (index < cachedPages.size()) {
  315. this.cachedPages.remove(cachedPages.size() - 1);
  316. if (!pageSeq.goToPreviousSimplePageMaster()) {
  317. log.warn("goToPreviousSimplePageMaster() on the first page called!");
  318. }
  319. }
  320. }
  321. private Page cacheNextPage(int index, boolean isBlank, boolean isLastPage, boolean spanAll) {
  322. String pageNumberString = pageSeq.makeFormattedPageNumber(index);
  323. boolean isFirstPage = (startPageOfPageSequence == index);
  324. SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
  325. index, isFirstPage, isLastPage, isBlank);
  326. boolean isPagePositionOnly = pageSeq.hasPagePositionOnly() && !skipPagePositionOnly;
  327. if (isPagePositionOnly) {
  328. spm = pageSeq.getNextSimplePageMaster(index, isFirstPage, true, isBlank);
  329. }
  330. Page page = new Page(spm, index, pageNumberString, isBlank, spanAll, isPagePositionOnly);
  331. //Set unique key obtained from the AreaTreeHandler
  332. page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
  333. page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
  334. page.getPageViewport().setWritingModeTraits(pageSeq);
  335. cachedPages.add(page);
  336. if (isLastPage) {
  337. pageSeq.getRoot().setLastSeq(pageSeq);
  338. } else if (!isFirstPage) {
  339. pageSeq.getRoot().setLastSeq(null);
  340. }
  341. return page;
  342. }
  343. public int getIndexOfCachedLastPage() {
  344. return indexOfCachedLastPage;
  345. }
  346. public int getLastPageIndex() {
  347. return lastPageIndex;
  348. }
  349. public int getLastPageIPD() {
  350. int index = this.cachedPages.size();
  351. boolean isFirstPage = (startPageOfPageSequence == index);
  352. SimplePageMaster spm = pageSeq.getLastSimplePageMaster(index, isFirstPage, false);
  353. Page page = new Page(spm, index, "", false, false, false);
  354. if (pageSeq.getRoot().getLastSeq() != null && pageSeq.getRoot().getLastSeq() != pageSeq) {
  355. return -1;
  356. }
  357. return page.getPageViewport().getBodyRegion().getColumnIPD();
  358. }
  359. public int getCurrentIPD() {
  360. if (startPageOfCurrentElementList == 0) {
  361. return -1;
  362. }
  363. Page page = getPageFromColumnIndex(startColumnOfCurrentElementList);
  364. return page.getPageViewport().getBodyRegion().getColumnIPD();
  365. }
  366. public int getNextIPD() {
  367. pageSeq.setOnlyTryInfinite(true);
  368. try {
  369. int oldSize = cachedPages.size();
  370. Page page = getPageFromColumnIndex(startColumnOfCurrentElementList + 1);
  371. if (oldSize != cachedPages.size()) {
  372. cachedPages.remove(cachedPages.size() - 1);
  373. }
  374. return page.getPageViewport().getBodyRegion().getColumnIPD();
  375. } catch (PageProductionException e) {
  376. return getCurrentIPD();
  377. } finally {
  378. pageSeq.setOnlyTryInfinite(false);
  379. }
  380. }
  381. public int getCurrentColumnCount() {
  382. Page page = getPageFromColumnIndex(startColumnOfCurrentElementList);
  383. return page.getPageViewport().getCurrentSpan().getColumnCount();
  384. }
  385. /**
  386. * Indicates whether the column/page at the given index is on the first page of the page sequence.
  387. *
  388. * @return {@code true} if the given part is on the first page of the sequence
  389. */
  390. boolean isOnFirstPage(int partIndex) {
  391. Column column = getColumn(partIndex);
  392. return startPageOfCurrentElementList + column.pageIndex == startPageOfPageSequence;
  393. }
  394. }