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.

PageSequenceLayoutManager.java 48KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  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 org.apache.commons.logging.Log;
  20. import org.apache.commons.logging.LogFactory;
  21. import org.apache.fop.apps.FOPException;
  22. import org.apache.fop.datatypes.Numeric;
  23. import org.apache.fop.area.AreaTreeHandler;
  24. import org.apache.fop.area.AreaTreeModel;
  25. import org.apache.fop.area.Block;
  26. import org.apache.fop.area.Footnote;
  27. import org.apache.fop.area.PageViewport;
  28. import org.apache.fop.area.LineArea;
  29. import org.apache.fop.area.Resolvable;
  30. import org.apache.fop.fo.Constants;
  31. import org.apache.fop.fo.flow.Marker;
  32. import org.apache.fop.fo.flow.RetrieveMarker;
  33. import org.apache.fop.fo.pagination.Flow;
  34. import org.apache.fop.fo.pagination.PageSequence;
  35. import org.apache.fop.fo.pagination.Region;
  36. import org.apache.fop.fo.pagination.RegionBody;
  37. import org.apache.fop.fo.pagination.SideRegion;
  38. import org.apache.fop.fo.pagination.SimplePageMaster;
  39. import org.apache.fop.fo.pagination.StaticContent;
  40. import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
  41. import org.apache.fop.traits.MinOptMax;
  42. import java.util.LinkedList;
  43. import java.util.List;
  44. import java.util.ListIterator;
  45. /**
  46. * LayoutManager for a PageSequence. This class is instantiated by
  47. * area.AreaTreeHandler for each fo:page-sequence found in the
  48. * input document.
  49. */
  50. public class PageSequenceLayoutManager extends AbstractLayoutManager {
  51. private static Log log = LogFactory.getLog(PageSequenceLayoutManager.class);
  52. /**
  53. * AreaTreeHandler which activates the PSLM and controls
  54. * the rendering of its pages.
  55. */
  56. private AreaTreeHandler areaTreeHandler;
  57. /**
  58. * fo:page-sequence formatting object being
  59. * processed by this class
  60. */
  61. private PageSequence pageSeq;
  62. private PageProvider pageProvider;
  63. /**
  64. * Current page with page-viewport-area being filled by
  65. * the PSLM.
  66. */
  67. private Page curPage = null;
  68. /**
  69. * The FlowLayoutManager object, which processes
  70. * the single fo:flow of the fo:page-sequence
  71. */
  72. private FlowLayoutManager childFLM = null;
  73. private int startPageNum = 0;
  74. private int currentPageNum = 0;
  75. private Block separatorArea = null;
  76. /**
  77. * Constructor
  78. *
  79. * @param ath the area tree handler object
  80. * @param pseq fo:page-sequence to process
  81. */
  82. public PageSequenceLayoutManager(AreaTreeHandler ath, PageSequence pseq) {
  83. super(pseq);
  84. this.areaTreeHandler = ath;
  85. this.pageSeq = pseq;
  86. this.pageProvider = new PageProvider(this.pageSeq);
  87. }
  88. /**
  89. * @see org.apache.fop.layoutmgr.LayoutManager
  90. * @return the LayoutManagerMaker object
  91. */
  92. public LayoutManagerMaker getLayoutManagerMaker() {
  93. return areaTreeHandler.getLayoutManagerMaker();
  94. }
  95. /** @return the PageProvider applicable to this page-sequence. */
  96. public PageProvider getPageProvider() {
  97. return this.pageProvider;
  98. }
  99. /**
  100. * Activate the layout of this page sequence.
  101. * PageViewports corresponding to each page generated by this
  102. * page sequence will be created and sent to the AreaTreeModel
  103. * for rendering.
  104. */
  105. public void activateLayout() {
  106. startPageNum = pageSeq.getStartingPageNumber();
  107. currentPageNum = startPageNum - 1;
  108. LineArea title = null;
  109. if (pageSeq.getTitleFO() != null) {
  110. try {
  111. ContentLayoutManager clm = getLayoutManagerMaker().
  112. makeContentLayoutManager(this, pageSeq.getTitleFO());
  113. title = (LineArea) clm.getParentArea(null);
  114. } catch (IllegalStateException e) {
  115. // empty title; do nothing
  116. }
  117. }
  118. areaTreeHandler.getAreaTreeModel().startPageSequence(title);
  119. log.debug("Starting layout");
  120. curPage = makeNewPage(false, false);
  121. Flow mainFlow = pageSeq.getMainFlow();
  122. childFLM = getLayoutManagerMaker().
  123. makeFlowLayoutManager(this, mainFlow);
  124. PageBreaker breaker = new PageBreaker(this);
  125. int flowBPD = (int)getCurrentPV().getBodyRegion().getRemainingBPD();
  126. breaker.doLayout(flowBPD);
  127. finishPage();
  128. }
  129. /**
  130. * Finished the page-sequence and notifies everyone about it.
  131. */
  132. public void finishPageSequence() {
  133. if (!pageSeq.getId().equals("")) {
  134. areaTreeHandler.signalIDProcessed(pageSeq.getId());
  135. }
  136. pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
  137. (currentPageNum - startPageNum) + 1);
  138. areaTreeHandler.notifyPageSequenceFinished(pageSeq,
  139. (currentPageNum - startPageNum) + 1);
  140. pageSeq.releasePageSequence();
  141. log.debug("Ending layout");
  142. }
  143. private class PageBreaker extends AbstractBreaker {
  144. private PageSequenceLayoutManager pslm;
  145. private boolean firstPart = true;
  146. private boolean pageBreakHandled;
  147. private boolean needColumnBalancing;
  148. private StaticContentLayoutManager footnoteSeparatorLM = null;
  149. public PageBreaker(PageSequenceLayoutManager pslm) {
  150. this.pslm = pslm;
  151. }
  152. /** @see org.apache.fop.layoutmgr.AbstractBreaker */
  153. protected void updateLayoutContext(LayoutContext context) {
  154. int flowIPD = getCurrentPV().getCurrentSpan().getColumnWidth();
  155. context.setRefIPD(flowIPD);
  156. }
  157. protected LayoutManager getTopLevelLM() {
  158. return null; // unneeded for PSLM
  159. }
  160. /** @see org.apache.fop.layoutmgr.AbstractBreaker#getPageProvider() */
  161. protected PageSequenceLayoutManager.PageProvider getPageProvider() {
  162. return pageProvider;
  163. }
  164. /** @see org.apache.fop.layoutmgr.AbstractBreaker */
  165. protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) {
  166. needColumnBalancing = false;
  167. if (childLC.getNextSpan() != Constants.NOT_SET) {
  168. //Next block list will have a different span.
  169. nextSequenceStartsOn = childLC.getNextSpan();
  170. needColumnBalancing = (childLC.getNextSpan() == Constants.EN_ALL);
  171. }
  172. if (needColumnBalancing) {
  173. AbstractBreaker.log.debug(
  174. "Column balancing necessary for the next element list!!!");
  175. }
  176. return nextSequenceStartsOn;
  177. }
  178. /** @see org.apache.fop.layoutmgr.AbstractBreaker */
  179. protected int getNextBlockList(LayoutContext childLC,
  180. int nextSequenceStartsOn,
  181. List blockLists) {
  182. if (!firstPart) {
  183. // if this is the first page that will be created by
  184. // the current BlockSequence, it could have a break
  185. // condition that must be satisfied;
  186. // otherwise, we may simply need a new page
  187. handleBreakTrait(nextSequenceStartsOn);
  188. }
  189. firstPart = false;
  190. pageBreakHandled = true;
  191. pageProvider.setStartOfNextElementList(currentPageNum,
  192. getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
  193. return super.getNextBlockList(childLC, nextSequenceStartsOn, blockLists);
  194. }
  195. /** @see org.apache.fop.layoutmgr.AbstractBreaker */
  196. protected LinkedList getNextKnuthElements(LayoutContext context, int alignment) {
  197. LinkedList contentList = null;
  198. while (!childFLM.isFinished() && contentList == null) {
  199. contentList = childFLM.getNextKnuthElements(context, alignment);
  200. }
  201. // scan contentList, searching for footnotes
  202. boolean bFootnotesPresent = false;
  203. if (contentList != null) {
  204. ListIterator contentListIterator = contentList.listIterator();
  205. while (contentListIterator.hasNext()) {
  206. ListElement element = (ListElement) contentListIterator.next();
  207. if (element instanceof KnuthBlockBox
  208. && ((KnuthBlockBox) element).hasAnchors()) {
  209. // element represents a line with footnote citations
  210. bFootnotesPresent = true;
  211. LayoutContext footnoteContext = new LayoutContext(context);
  212. footnoteContext.setStackLimit(context.getStackLimit());
  213. footnoteContext.setRefIPD(getCurrentPV()
  214. .getRegionReference(Constants.FO_REGION_BODY).getIPD());
  215. LinkedList footnoteBodyLMs = ((KnuthBlockBox) element).getFootnoteBodyLMs();
  216. ListIterator footnoteBodyIterator = footnoteBodyLMs.listIterator();
  217. // store the lists of elements representing the footnote bodies
  218. // in the box representing the line containing their references
  219. while (footnoteBodyIterator.hasNext()) {
  220. FootnoteBodyLayoutManager fblm
  221. = (FootnoteBodyLayoutManager) footnoteBodyIterator.next();
  222. fblm.setParent(childFLM);
  223. fblm.initialize();
  224. ((KnuthBlockBox) element).addElementList(
  225. fblm.getNextKnuthElements(footnoteContext, alignment));
  226. }
  227. }
  228. }
  229. }
  230. // handle the footnote separator
  231. StaticContent footnoteSeparator;
  232. if (bFootnotesPresent
  233. && (footnoteSeparator = pageSeq.getStaticContent(
  234. "xsl-footnote-separator")) != null) {
  235. // the footnote separator can contain page-dependent content such as
  236. // page numbers or retrieve markers, so its areas cannot simply be
  237. // obtained now and repeated in each page;
  238. // we need to know in advance the separator bpd: the actual separator
  239. // could be different from page to page, but its bpd would likely be
  240. // always the same
  241. // create a Block area that will contain the separator areas
  242. separatorArea = new Block();
  243. separatorArea.setIPD(pslm.getCurrentPV()
  244. .getRegionReference(Constants.FO_REGION_BODY).getIPD());
  245. // create a StaticContentLM for the footnote separator
  246. footnoteSeparatorLM = (StaticContentLayoutManager)
  247. getLayoutManagerMaker().makeStaticContentLayoutManager(
  248. pslm, footnoteSeparator, separatorArea);
  249. footnoteSeparatorLM.doLayout();
  250. footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD());
  251. }
  252. return contentList;
  253. }
  254. protected int getCurrentDisplayAlign() {
  255. return curPage.getSimplePageMaster().getRegion(
  256. Constants.FO_REGION_BODY).getDisplayAlign();
  257. }
  258. protected boolean hasMoreContent() {
  259. return !childFLM.isFinished();
  260. }
  261. protected void addAreas(PositionIterator posIter, LayoutContext context) {
  262. if (footnoteSeparatorLM != null) {
  263. StaticContent footnoteSeparator = pageSeq.getStaticContent(
  264. "xsl-footnote-separator");
  265. // create a Block area that will contain the separator areas
  266. separatorArea = new Block();
  267. separatorArea.setIPD(
  268. getCurrentPV().getRegionReference(Constants.FO_REGION_BODY).getIPD());
  269. // create a StaticContentLM for the footnote separator
  270. footnoteSeparatorLM = (StaticContentLayoutManager)
  271. getLayoutManagerMaker().makeStaticContentLayoutManager(
  272. pslm, footnoteSeparator, separatorArea);
  273. footnoteSeparatorLM.doLayout();
  274. }
  275. childFLM.addAreas(posIter, context);
  276. }
  277. protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
  278. BlockSequence originalList, BlockSequence effectiveList) {
  279. if (needColumnBalancing) {
  280. doPhase3WithColumnBalancing(alg, partCount, originalList, effectiveList);
  281. } else {
  282. if (!hasMoreContent() && pageSeq.hasPagePositionLast()) {
  283. //last part is reached and we have a "last page" condition
  284. doPhase3WithLastPage(alg, partCount, originalList, effectiveList);
  285. } else {
  286. //Directly add areas after finding the breaks
  287. addAreas(alg, partCount, originalList, effectiveList);
  288. }
  289. }
  290. }
  291. private void doPhase3WithLastPage(PageBreakingAlgorithm alg, int partCount,
  292. BlockSequence originalList, BlockSequence effectiveList) {
  293. int newStartPos;
  294. int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
  295. if (restartPoint > 0) {
  296. //Add definitive areas before last page
  297. addAreas(alg, restartPoint, originalList, effectiveList);
  298. //Get page break from which we restart
  299. PageBreakPosition pbp = (PageBreakPosition)
  300. alg.getPageBreaks().get(restartPoint - 1);
  301. newStartPos = pbp.getLeafPos();
  302. //Handle page break right here to avoid any side-effects
  303. if (newStartPos > 0) {
  304. handleBreakTrait(EN_PAGE);
  305. }
  306. } else {
  307. newStartPos = 0;
  308. }
  309. AbstractBreaker.log.debug("Last page handling now!!!");
  310. AbstractBreaker.log.debug("===================================================");
  311. AbstractBreaker.log.debug("Restarting at " + restartPoint
  312. + ", new start position: " + newStartPos);
  313. pageBreakHandled = true;
  314. //Update so the available BPD is reported correctly
  315. pageProvider.setStartOfNextElementList(currentPageNum,
  316. getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
  317. pageProvider.setLastPageIndex(currentPageNum);
  318. //Restart last page
  319. PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
  320. getTopLevelLM(),
  321. getPageProvider(),
  322. alg.getAlignment(), alg.getAlignmentLast(),
  323. footnoteSeparatorLength,
  324. isPartOverflowRecoveryActivated(), false, false);
  325. //alg.setConstantLineWidth(flowBPD);
  326. int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
  327. newStartPos,
  328. 1, true, BreakingAlgorithm.ALL_BREAKS);
  329. AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
  330. + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
  331. boolean replaceLastPage
  332. = iOptPageCount <= getCurrentPV().getBodyRegion().getColumnCount();
  333. if (replaceLastPage) {
  334. //Replace last page
  335. pslm.curPage = pageProvider.getPage(false, currentPageNum);
  336. //Make sure we only add the areas we haven't added already
  337. effectiveList.ignoreAtStart = newStartPos;
  338. addAreas(algRestart, iOptPageCount, originalList, effectiveList);
  339. } else {
  340. effectiveList.ignoreAtStart = newStartPos;
  341. addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList);
  342. //Add blank last page
  343. pageProvider.setLastPageIndex(currentPageNum + 1);
  344. pslm.curPage = makeNewPage(true, true);
  345. }
  346. AbstractBreaker.log.debug("===================================================");
  347. }
  348. private void doPhase3WithColumnBalancing(PageBreakingAlgorithm alg, int partCount,
  349. BlockSequence originalList, BlockSequence effectiveList) {
  350. AbstractBreaker.log.debug("Column balancing now!!!");
  351. AbstractBreaker.log.debug("===================================================");
  352. int newStartPos;
  353. int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount);
  354. if (restartPoint > 0) {
  355. //Add definitive areas
  356. addAreas(alg, restartPoint, originalList, effectiveList);
  357. //Get page break from which we restart
  358. PageBreakPosition pbp = (PageBreakPosition)
  359. alg.getPageBreaks().get(restartPoint - 1);
  360. newStartPos = pbp.getLeafPos();
  361. //Handle page break right here to avoid any side-effects
  362. if (newStartPos > 0) {
  363. handleBreakTrait(EN_PAGE);
  364. }
  365. } else {
  366. newStartPos = 0;
  367. }
  368. AbstractBreaker.log.debug("Restarting at " + restartPoint
  369. + ", new start position: " + newStartPos);
  370. pageBreakHandled = true;
  371. //Update so the available BPD is reported correctly
  372. pageProvider.setStartOfNextElementList(currentPageNum,
  373. getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
  374. //Restart last page
  375. PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
  376. getTopLevelLM(),
  377. getPageProvider(),
  378. alignment, Constants.EN_START, footnoteSeparatorLength,
  379. isPartOverflowRecoveryActivated(),
  380. getCurrentPV().getBodyRegion().getColumnCount());
  381. //alg.setConstantLineWidth(flowBPD);
  382. int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
  383. newStartPos,
  384. 1, true, BreakingAlgorithm.ALL_BREAKS);
  385. AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount
  386. + " pageBreaks.size()= " + algRestart.getPageBreaks().size());
  387. if (iOptPageCount > getCurrentPV().getBodyRegion().getColumnCount()) {
  388. AbstractBreaker.log.warn(
  389. "Breaking algorithm produced more columns than are available.");
  390. /* reenable when everything works
  391. throw new IllegalStateException(
  392. "Breaking algorithm must not produce more columns than available.");
  393. */
  394. }
  395. //Make sure we only add the areas we haven't added already
  396. effectiveList.ignoreAtStart = newStartPos;
  397. addAreas(algRestart, iOptPageCount, originalList, effectiveList);
  398. AbstractBreaker.log.debug("===================================================");
  399. }
  400. protected void startPart(BlockSequence list, int breakClass) {
  401. AbstractBreaker.log.debug("startPart() breakClass=" + breakClass);
  402. if (curPage == null) {
  403. throw new IllegalStateException("curPage must not be null");
  404. }
  405. if (!pageBreakHandled) {
  406. //firstPart is necessary because we need the first page before we start the
  407. //algorithm so we have a BPD and IPD. This may subject to change later when we
  408. //start handling more complex cases.
  409. if (!firstPart) {
  410. // if this is the first page that will be created by
  411. // the current BlockSequence, it could have a break
  412. // condition that must be satisfied;
  413. // otherwise, we may simply need a new page
  414. handleBreakTrait(breakClass);
  415. }
  416. pageProvider.setStartOfNextElementList(currentPageNum,
  417. getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
  418. }
  419. pageBreakHandled = false;
  420. // add static areas and resolve any new id areas
  421. // finish page and add to area tree
  422. firstPart = false;
  423. }
  424. /** @see org.apache.fop.layoutmgr.AbstractBreaker#handleEmptyContent() */
  425. protected void handleEmptyContent() {
  426. getCurrentPV().getPage().fakeNonEmpty();
  427. }
  428. protected void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp) {
  429. // add footnote areas
  430. if (pbp.footnoteFirstListIndex < pbp.footnoteLastListIndex
  431. || pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex) {
  432. // call addAreas() for each FootnoteBodyLM
  433. for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) {
  434. LinkedList elementList = alg.getFootnoteList(i);
  435. int firstIndex = (i == pbp.footnoteFirstListIndex
  436. ? pbp.footnoteFirstElementIndex : 0);
  437. int lastIndex = (i == pbp.footnoteLastListIndex
  438. ? pbp.footnoteLastElementIndex : elementList.size() - 1);
  439. SpaceResolver.performConditionalsNotification(elementList,
  440. firstIndex, lastIndex, -1);
  441. LayoutContext childLC = new LayoutContext(0);
  442. AreaAdditionUtil.addAreas(null,
  443. new KnuthPossPosIter(elementList, firstIndex, lastIndex + 1),
  444. childLC);
  445. }
  446. // set the offset from the top margin
  447. Footnote parentArea = (Footnote) getCurrentPV().getBodyRegion().getFootnote();
  448. int topOffset = (int) getCurrentPV().getBodyRegion().getBPD() - parentArea.getBPD();
  449. if (separatorArea != null) {
  450. topOffset -= separatorArea.getBPD();
  451. }
  452. parentArea.setTop(topOffset);
  453. parentArea.setSeparator(separatorArea);
  454. }
  455. getCurrentPV().getCurrentSpan().notifyFlowsFinished();
  456. }
  457. protected LayoutManager getCurrentChildLM() {
  458. return childFLM;
  459. }
  460. /** @see org.apache.fop.layoutmgr.AbstractBreaker#observeElementList(java.util.List) */
  461. protected void observeElementList(List elementList) {
  462. ElementListObserver.observe(elementList, "breaker",
  463. ((PageSequence)pslm.getFObj()).getId());
  464. }
  465. }
  466. /**
  467. * Provides access to the current page.
  468. * @return the current Page
  469. */
  470. public Page getCurrentPage() {
  471. return curPage;
  472. }
  473. /**
  474. * Provides access to the current page viewport.
  475. * @return the current PageViewport
  476. *//*
  477. public PageViewport getCurrentPageViewport() {
  478. return curPage.getPageViewport();
  479. }*/
  480. /**
  481. * Provides access to this object
  482. * @return this PageSequenceLayoutManager instance
  483. */
  484. public PageSequenceLayoutManager getPSLM() {
  485. return this;
  486. }
  487. /**
  488. * This returns the first PageViewport that contains an id trait
  489. * matching the idref argument, or null if no such PV exists.
  490. *
  491. * @param idref the idref trait needing to be resolved
  492. * @return the first PageViewport that contains the ID trait
  493. */
  494. public PageViewport getFirstPVWithID(String idref) {
  495. List list = areaTreeHandler.getPageViewportsContainingID(idref);
  496. if (list != null && list.size() > 0) {
  497. return (PageViewport) list.get(0);
  498. }
  499. return null;
  500. }
  501. /**
  502. * This returns the last PageViewport that contains an id trait
  503. * matching the idref argument, or null if no such PV exists.
  504. *
  505. * @param idref the idref trait needing to be resolved
  506. * @return the last PageViewport that contains the ID trait
  507. */
  508. public PageViewport getLastPVWithID(String idref) {
  509. List list = areaTreeHandler.getPageViewportsContainingID(idref);
  510. if (list != null && list.size() > 0) {
  511. return (PageViewport) list.get(list.size() - 1);
  512. }
  513. return null;
  514. }
  515. /**
  516. * Add an ID reference to the current page.
  517. * When adding areas the area adds its ID reference.
  518. * For the page layout manager it adds the id reference
  519. * with the current page to the area tree.
  520. *
  521. * @param id the ID reference to add
  522. */
  523. public void addIDToPage(String id) {
  524. if (id != null && id.length() > 0) {
  525. areaTreeHandler.associateIDWithPageViewport(id, curPage.getPageViewport());
  526. }
  527. }
  528. /**
  529. * Add an id reference of the layout manager in the AreaTreeHandler,
  530. * if the id hasn't been resolved yet
  531. * @param id the id to track
  532. * @return a boolean indicating if the id has already been resolved
  533. * TODO Maybe give this a better name
  534. */
  535. public boolean associateLayoutManagerID(String id) {
  536. if (log.isDebugEnabled()) {
  537. log.debug("associateLayoutManagerID(" + id + ")");
  538. }
  539. if (!areaTreeHandler.alreadyResolvedID(id)) {
  540. areaTreeHandler.signalPendingID(id);
  541. return false;
  542. } else {
  543. return true;
  544. }
  545. }
  546. /**
  547. * Notify the areaTreeHandler that the LayoutManagers containing
  548. * idrefs have finished creating areas
  549. * @param id the id for which layout has finished
  550. */
  551. public void notifyEndOfLayout(String id) {
  552. areaTreeHandler.signalIDProcessed(id);
  553. }
  554. /**
  555. * Identify an unresolved area (one needing an idref to be
  556. * resolved, e.g. the internal-destination of an fo:basic-link)
  557. * for both the AreaTreeHandler and PageViewport object.
  558. *
  559. * The AreaTreeHandler keeps a document-wide list of idref's
  560. * and the PV's needing them to be resolved. It uses this to
  561. * send notifications to the PV's when an id has been resolved.
  562. *
  563. * The PageViewport keeps lists of id's needing resolving, along
  564. * with the child areas (page-number-citation, basic-link, etc.)
  565. * of the PV needing their resolution.
  566. *
  567. * @param id the ID reference to add
  568. * @param res the resolvable object that needs resolving
  569. */
  570. public void addUnresolvedArea(String id, Resolvable res) {
  571. curPage.getPageViewport().addUnresolvedIDRef(id, res);
  572. areaTreeHandler.addUnresolvedIDRef(id, curPage.getPageViewport());
  573. }
  574. /**
  575. * Bind the RetrieveMarker to the corresponding Marker subtree.
  576. * If the boundary is page then it will only check the
  577. * current page. For page-sequence and document it will
  578. * lookup preceding pages from the area tree and try to find
  579. * a marker.
  580. * If we retrieve a marker from a preceding page,
  581. * then the containing page does not have a qualifying area,
  582. * and all qualifying areas have ended.
  583. * Therefore we use last-ending-within-page (Constants.EN_LEWP)
  584. * as the position.
  585. *
  586. * @param rm the RetrieveMarker instance whose properties are to
  587. * used to find the matching Marker.
  588. * @return a bound RetrieveMarker instance, or null if no Marker
  589. * could be found.
  590. */
  591. public RetrieveMarker resolveRetrieveMarker(RetrieveMarker rm) {
  592. AreaTreeModel areaTreeModel = areaTreeHandler.getAreaTreeModel();
  593. String name = rm.getRetrieveClassName();
  594. int pos = rm.getRetrievePosition();
  595. int boundary = rm.getRetrieveBoundary();
  596. // get marker from the current markers on area tree
  597. Marker mark = (Marker)getCurrentPV().getMarker(name, pos);
  598. if (mark == null && boundary != EN_PAGE) {
  599. // go back over pages until mark found
  600. // if document boundary then keep going
  601. boolean doc = boundary == EN_DOCUMENT;
  602. int seq = areaTreeModel.getPageSequenceCount();
  603. int page = areaTreeModel.getPageCount(seq) - 1;
  604. while (page < 0 && doc && seq > 1) {
  605. seq--;
  606. page = areaTreeModel.getPageCount(seq) - 1;
  607. }
  608. while (page >= 0) {
  609. PageViewport pv = areaTreeModel.getPage(seq, page);
  610. mark = (Marker)pv.getMarker(name, Constants.EN_LEWP);
  611. if (mark != null) {
  612. break;
  613. }
  614. page--;
  615. if (page < 0 && doc && seq > 1) {
  616. seq--;
  617. page = areaTreeModel.getPageCount(seq) - 1;
  618. }
  619. }
  620. }
  621. if (mark == null) {
  622. log.debug("found no marker with name: " + name);
  623. return null;
  624. } else {
  625. rm.bindMarker(mark);
  626. return rm;
  627. }
  628. }
  629. private Page makeNewPage(boolean bIsBlank, boolean bIsLast) {
  630. if (curPage != null) {
  631. finishPage();
  632. }
  633. currentPageNum++;
  634. curPage = pageProvider.getPage(bIsBlank,
  635. currentPageNum, PageProvider.RELTO_PAGE_SEQUENCE);
  636. if (log.isDebugEnabled()) {
  637. log.debug("[" + curPage.getPageViewport().getPageNumberString()
  638. + (bIsBlank ? "*" : "") + "]");
  639. }
  640. addIDToPage(pageSeq.getId());
  641. return curPage;
  642. }
  643. private void layoutSideRegion(int regionID) {
  644. SideRegion reg = (SideRegion)curPage.getSimplePageMaster().getRegion(regionID);
  645. if (reg == null) {
  646. return;
  647. }
  648. StaticContent sc = pageSeq.getStaticContent(reg.getRegionName());
  649. if (sc == null) {
  650. return;
  651. }
  652. StaticContentLayoutManager lm = (StaticContentLayoutManager)
  653. getLayoutManagerMaker().makeStaticContentLayoutManager(
  654. this, sc, reg);
  655. lm.doLayout();
  656. }
  657. private void finishPage() {
  658. curPage.getPageViewport().dumpMarkers();
  659. // Layout side regions
  660. layoutSideRegion(FO_REGION_BEFORE);
  661. layoutSideRegion(FO_REGION_AFTER);
  662. layoutSideRegion(FO_REGION_START);
  663. layoutSideRegion(FO_REGION_END);
  664. // Try to resolve any unresolved IDs for the current page.
  665. //
  666. areaTreeHandler.tryIDResolution(curPage.getPageViewport());
  667. // Queue for ID resolution and rendering
  668. areaTreeHandler.getAreaTreeModel().addPage(curPage.getPageViewport());
  669. if (log.isDebugEnabled()) {
  670. log.debug("page finished: " + curPage.getPageViewport().getPageNumberString()
  671. + ", current num: " + currentPageNum);
  672. }
  673. curPage = null;
  674. }
  675. /**
  676. * Depending on the kind of break condition, move to next column
  677. * or page. May need to make an empty page if next page would
  678. * not have the desired "handedness".
  679. * @param breakVal - value of break-before or break-after trait.
  680. */
  681. private void handleBreakTrait(int breakVal) {
  682. if (breakVal == Constants.EN_ALL) {
  683. //break due to span change in multi-column layout
  684. curPage.getPageViewport().createSpan(true);
  685. return;
  686. } else if (breakVal == Constants.EN_NONE) {
  687. curPage.getPageViewport().createSpan(false);
  688. return;
  689. } else if (breakVal == Constants.EN_COLUMN || breakVal <= 0) {
  690. PageViewport pv = curPage.getPageViewport();
  691. //Check if previous page was spanned
  692. boolean forceNewPageWithSpan = false;
  693. RegionBody rb = (RegionBody)curPage.getSimplePageMaster().getRegion(
  694. Constants.FO_REGION_BODY);
  695. if (breakVal < 0
  696. && rb.getColumnCount() > 1
  697. && pv.getCurrentSpan().getColumnCount() == 1) {
  698. forceNewPageWithSpan = true;
  699. }
  700. if (forceNewPageWithSpan) {
  701. curPage = makeNewPage(false, false);
  702. curPage.getPageViewport().createSpan(true);
  703. } else if (pv.getCurrentSpan().hasMoreFlows()) {
  704. pv.getCurrentSpan().moveToNextFlow();
  705. } else {
  706. curPage = makeNewPage(false, false);
  707. }
  708. return;
  709. }
  710. log.debug("handling break-before after page " + currentPageNum
  711. + " breakVal=" + breakVal);
  712. if (needBlankPageBeforeNew(breakVal)) {
  713. curPage = makeNewPage(true, false);
  714. }
  715. if (needNewPage(breakVal)) {
  716. curPage = makeNewPage(false, false);
  717. }
  718. }
  719. /**
  720. * Check if a blank page is needed to accomodate
  721. * desired even or odd page number.
  722. * @param breakVal - value of break-before or break-after trait.
  723. */
  724. private boolean needBlankPageBeforeNew(int breakVal) {
  725. if (breakVal == Constants.EN_PAGE || (curPage.getPageViewport().getPage().isEmpty())) {
  726. // any page is OK or we already have an empty page
  727. return false;
  728. } else {
  729. /* IF we are on the kind of page we need, we'll need a new page. */
  730. if (currentPageNum % 2 == 0) { // even page
  731. return (breakVal == Constants.EN_EVEN_PAGE);
  732. } else { // odd page
  733. return (breakVal == Constants.EN_ODD_PAGE);
  734. }
  735. }
  736. }
  737. /**
  738. * See if need to generate a new page
  739. * @param breakVal - value of break-before or break-after trait.
  740. */
  741. private boolean needNewPage(int breakVal) {
  742. if (curPage.getPageViewport().getPage().isEmpty()) {
  743. if (breakVal == Constants.EN_PAGE) {
  744. return false;
  745. } else if (currentPageNum % 2 == 0) { // even page
  746. return (breakVal == Constants.EN_ODD_PAGE);
  747. } else { // odd page
  748. return (breakVal == Constants.EN_EVEN_PAGE);
  749. }
  750. } else {
  751. return true;
  752. }
  753. }
  754. /**
  755. * <p>This class delivers Page instances. It also caches them as necessary.
  756. * </p>
  757. * <p>Additional functionality makes sure that surplus instances that are requested by the
  758. * page breaker are properly discarded, especially in situations where hard breaks cause
  759. * blank pages. The reason for that: The page breaker sometimes needs to preallocate
  760. * additional pages since it doesn't know exactly until the end how many pages it really needs.
  761. * </p>
  762. */
  763. public class PageProvider {
  764. private Log log = LogFactory.getLog(PageProvider.class);
  765. /** Indices are evaluated relative to the first page in the page-sequence. */
  766. public static final int RELTO_PAGE_SEQUENCE = 0;
  767. /** Indices are evaluated relative to the first page in the current element list. */
  768. public static final int RELTO_CURRENT_ELEMENT_LIST = 1;
  769. private int startPageOfPageSequence;
  770. private int startPageOfCurrentElementList;
  771. private int startColumnOfCurrentElementList;
  772. private List cachedPages = new java.util.ArrayList();
  773. private int lastPageIndex = -1;
  774. private int indexOfCachedLastPage = -1;
  775. //Cache to optimize getAvailableBPD() calls
  776. private int lastRequestedIndex = -1;
  777. private int lastReportedBPD = -1;
  778. /**
  779. * Main constructor.
  780. * @param ps The page-sequence the provider operates on
  781. */
  782. public PageProvider(PageSequence ps) {
  783. this.startPageOfPageSequence = ps.getStartingPageNumber();
  784. }
  785. /**
  786. * The page breaker notifies the provider about the page number an element list starts
  787. * on so it can later retrieve PageViewports relative to this first page.
  788. * @param startPage the number of the first page for the element list.
  789. * @param startColumn the starting column number for the element list.
  790. */
  791. public void setStartOfNextElementList(int startPage, int startColumn) {
  792. log.debug("start of the next element list is:"
  793. + " page=" + startPage + " col=" + startColumn);
  794. this.startPageOfCurrentElementList = startPage - startPageOfPageSequence + 1;
  795. this.startColumnOfCurrentElementList = startColumn;
  796. //Reset Cache
  797. this.lastRequestedIndex = -1;
  798. this.lastReportedBPD = -1;
  799. }
  800. /**
  801. * Sets the index of the last page. This is done as soon as the position of the last page
  802. * is known or assumed.
  803. * @param index the index relative to the first page in the page-sequence
  804. */
  805. public void setLastPageIndex(int index) {
  806. this.lastPageIndex = index;
  807. }
  808. /**
  809. * Returns the available BPD for the part/page indicated by the index parameter.
  810. * The index is the part/page relative to the start of the current element list.
  811. * This method takes multiple columns into account.
  812. * @param index zero-based index of the requested part/page
  813. * @return the available BPD
  814. */
  815. public int getAvailableBPD(int index) {
  816. //Special optimization: There may be many equal calls by the BreakingAlgorithm
  817. if (this.lastRequestedIndex == index) {
  818. if (log.isTraceEnabled()) {
  819. log.trace("getAvailableBPD(" + index + ") -> (cached) " + lastReportedBPD);
  820. }
  821. return this.lastReportedBPD;
  822. }
  823. int c = index;
  824. int pageIndex = 0;
  825. int colIndex = startColumnOfCurrentElementList;
  826. Page page = getPage(
  827. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  828. while (c > 0) {
  829. colIndex++;
  830. if (colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount()) {
  831. colIndex = 0;
  832. pageIndex++;
  833. page = getPage(
  834. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  835. }
  836. c--;
  837. }
  838. this.lastRequestedIndex = index;
  839. this.lastReportedBPD = page.getPageViewport().getBodyRegion().getRemainingBPD();
  840. if (log.isTraceEnabled()) {
  841. log.trace("getAvailableBPD(" + index + ") -> " + lastReportedBPD);
  842. }
  843. return this.lastReportedBPD;
  844. }
  845. /**
  846. * Returns the part index (0<x<partCount) which denotes the first part on the last page
  847. * generated by the current element list.
  848. * @param partCount Number of parts determined by the breaking algorithm
  849. * @return the requested part index
  850. */
  851. public int getStartingPartIndexForLastPage(int partCount) {
  852. int result = 0;
  853. int idx = 0;
  854. int pageIndex = 0;
  855. int colIndex = startColumnOfCurrentElementList;
  856. Page page = getPage(
  857. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  858. while (idx < partCount) {
  859. if ((colIndex >= page.getPageViewport().getCurrentSpan().getColumnCount())) {
  860. colIndex = 0;
  861. pageIndex++;
  862. page = getPage(
  863. false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
  864. result = idx;
  865. }
  866. colIndex++;
  867. idx++;
  868. }
  869. return result;
  870. }
  871. /**
  872. * Returns a Page.
  873. * @param isBlank true if this page is supposed to be blank.
  874. * @param index Index of the page (see relativeTo)
  875. * @param relativeTo Defines which value the index parameter should be evaluated relative
  876. * to. (One of PageProvider.RELTO_*)
  877. * @return the requested Page
  878. */
  879. public Page getPage(boolean isBlank, int index, int relativeTo) {
  880. if (relativeTo == RELTO_PAGE_SEQUENCE) {
  881. return getPage(isBlank, index);
  882. } else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
  883. int effIndex = startPageOfCurrentElementList + index;
  884. effIndex += startPageOfPageSequence - 1;
  885. return getPage(isBlank, effIndex);
  886. } else {
  887. throw new IllegalArgumentException(
  888. "Illegal value for relativeTo: " + relativeTo);
  889. }
  890. }
  891. private Page getPage(boolean isBlank, int index) {
  892. boolean isLastPage = (lastPageIndex >= 0) && (index == lastPageIndex);
  893. if (log.isTraceEnabled()) {
  894. log.trace("getPage(" + index + " " + (isBlank ? "blank" : "non-blank")
  895. + (isLastPage ? " <LAST>" : "") + ")");
  896. }
  897. int intIndex = index - startPageOfPageSequence;
  898. if (log.isTraceEnabled()) {
  899. if (isBlank) {
  900. log.trace("blank page requested: " + index);
  901. }
  902. if (isLastPage) {
  903. log.trace("last page requested: " + index);
  904. }
  905. }
  906. while (intIndex >= cachedPages.size()) {
  907. if (log.isTraceEnabled()) {
  908. log.trace("Caching " + index);
  909. }
  910. cacheNextPage(index, isBlank, isLastPage);
  911. }
  912. Page page = (Page)cachedPages.get(intIndex);
  913. boolean replace = false;
  914. if (page.getPageViewport().isBlank() != isBlank) {
  915. log.debug("blank condition doesn't match. Replacing PageViewport.");
  916. replace = true;
  917. }
  918. if ((isLastPage && indexOfCachedLastPage != intIndex)
  919. || (!isLastPage && indexOfCachedLastPage >= 0)) {
  920. log.debug("last page condition doesn't match. Replacing PageViewport.");
  921. replace = true;
  922. indexOfCachedLastPage = (isLastPage ? intIndex : -1);
  923. }
  924. if (replace) {
  925. disardCacheStartingWith(intIndex);
  926. page = cacheNextPage(index, isBlank, isLastPage);
  927. }
  928. return page;
  929. }
  930. private void disardCacheStartingWith(int index) {
  931. while (index < cachedPages.size()) {
  932. this.cachedPages.remove(cachedPages.size() - 1);
  933. if (!pageSeq.goToPreviousSimplePageMaster()) {
  934. log.warn("goToPreviousSimplePageMaster() on the first page called!");
  935. }
  936. }
  937. }
  938. private Page cacheNextPage(int index, boolean isBlank, boolean isLastPage) {
  939. try {
  940. String pageNumberString = pageSeq.makeFormattedPageNumber(index);
  941. SimplePageMaster spm = pageSeq.getNextSimplePageMaster(
  942. index, (startPageOfPageSequence == index), isLastPage, isBlank);
  943. Region body = spm.getRegion(FO_REGION_BODY);
  944. if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) {
  945. // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to
  946. // any region), but we don't support it yet.
  947. throw new FOPException("Flow '" + pageSeq.getMainFlow().getFlowName()
  948. + "' does not map to the region-body in page-master '"
  949. + spm.getMasterName() + "'. FOP presently "
  950. + "does not support this.");
  951. }
  952. Page page = new Page(spm, index, pageNumberString, isBlank);
  953. //Set unique key obtained from the AreaTreeHandler
  954. page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
  955. page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
  956. cachedPages.add(page);
  957. return page;
  958. } catch (FOPException e) {
  959. //TODO Maybe improve. It'll mean to propagate this exception up several
  960. //methods calls.
  961. throw new IllegalStateException(e.getMessage());
  962. }
  963. }
  964. }
  965. /**
  966. * Act upon the force-page-count trait,
  967. * in relation to the initial-page-number trait of the following page-sequence.
  968. * @param nextPageSeqInitialPageNumber initial-page-number trait of next page-sequence
  969. */
  970. public void doForcePageCount(Numeric nextPageSeqInitialPageNumber) {
  971. int forcePageCount = pageSeq.getForcePageCount();
  972. // xsl-spec version 1.0 (15.oct 2001)
  973. // auto | even | odd | end-on-even | end-on-odd | no-force | inherit
  974. // auto:
  975. // Force the last page in this page-sequence to be an odd-page
  976. // if the initial-page-number of the next page-sequence is even.
  977. // Force it to be an even-page
  978. // if the initial-page-number of the next page-sequence is odd.
  979. // If there is no next page-sequence
  980. // or if the value of its initial-page-number is "auto" do not force any page.
  981. // if force-page-count is auto then set the value of forcePageCount
  982. // depending on the initial-page-number of the next page-sequence
  983. if (nextPageSeqInitialPageNumber != null && forcePageCount == Constants.EN_AUTO) {
  984. if (nextPageSeqInitialPageNumber.getEnum() != 0) {
  985. // auto | auto-odd | auto-even
  986. int nextPageSeqPageNumberType = nextPageSeqInitialPageNumber.getEnum();
  987. if (nextPageSeqPageNumberType == Constants.EN_AUTO_ODD) {
  988. forcePageCount = Constants.EN_END_ON_EVEN;
  989. } else if (nextPageSeqPageNumberType == Constants.EN_AUTO_EVEN) {
  990. forcePageCount = Constants.EN_END_ON_ODD;
  991. } else { // auto
  992. forcePageCount = Constants.EN_NO_FORCE;
  993. }
  994. } else { // <integer> for explicit page number
  995. int nextPageSeqPageStart = nextPageSeqInitialPageNumber.getValue();
  996. // spec rule
  997. nextPageSeqPageStart = (nextPageSeqPageStart > 0) ? nextPageSeqPageStart : 1;
  998. if (nextPageSeqPageStart % 2 == 0) { // explicit even startnumber
  999. forcePageCount = Constants.EN_END_ON_ODD;
  1000. } else { // explicit odd startnumber
  1001. forcePageCount = Constants.EN_END_ON_EVEN;
  1002. }
  1003. }
  1004. }
  1005. if (forcePageCount == Constants.EN_EVEN) {
  1006. if ((currentPageNum - startPageNum + 1) % 2 != 0) { // we have a odd number of pages
  1007. curPage = makeNewPage(true, false);
  1008. }
  1009. } else if (forcePageCount == Constants.EN_ODD) {
  1010. if ((currentPageNum - startPageNum + 1) % 2 == 0) { // we have a even number of pages
  1011. curPage = makeNewPage(true, false);
  1012. }
  1013. } else if (forcePageCount == Constants.EN_END_ON_EVEN) {
  1014. if (currentPageNum % 2 != 0) { // we are now on a odd page
  1015. curPage = makeNewPage(true, false);
  1016. }
  1017. } else if (forcePageCount == Constants.EN_END_ON_ODD) {
  1018. if (currentPageNum % 2 == 0) { // we are now on a even page
  1019. curPage = makeNewPage(true, false);
  1020. }
  1021. } else if (forcePageCount == Constants.EN_NO_FORCE) {
  1022. // i hope: nothing special at all
  1023. }
  1024. if (curPage != null) {
  1025. finishPage();
  1026. }
  1027. }
  1028. }