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.

FlowLayoutManager.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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.LinkedList;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import java.util.Stack;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.fop.area.Area;
  26. import org.apache.fop.area.BlockParent;
  27. import org.apache.fop.fo.pagination.Flow;
  28. import org.apache.fop.util.ListUtil;
  29. /**
  30. * LayoutManager for an fo:flow object.
  31. * Its parent LM is the PageSequenceLayoutManager.
  32. * This LM is responsible for getting columns of the appropriate size
  33. * and filling them with block-level areas generated by its children.
  34. * TODO Reintroduce emergency counter (generate error to avoid endless loop)
  35. */
  36. public class FlowLayoutManager extends BlockStackingLayoutManager
  37. implements BlockLevelLayoutManager {
  38. /**
  39. * logging instance
  40. */
  41. private static Log log = LogFactory.getLog(FlowLayoutManager.class);
  42. /** Array of areas currently being filled stored by area class */
  43. private BlockParent[] currentAreas = new BlockParent[Area.CLASS_MAX];
  44. /**
  45. * This is the top level layout manager.
  46. * It is created by the PageSequence FO.
  47. * @param pslm parent PageSequenceLayoutManager object
  48. * @param node Flow object
  49. */
  50. public FlowLayoutManager(PageSequenceLayoutManager pslm, Flow node) {
  51. super(node);
  52. setGeneratesBlockArea(true);
  53. setParent(pslm);
  54. }
  55. /** {@inheritDoc} */
  56. @Override
  57. public List getNextKnuthElements(LayoutContext context, int alignment) {
  58. return getNextKnuthElements(context, alignment, null, null);
  59. }
  60. /**
  61. * Get a sequence of KnuthElements representing the content
  62. * of the node assigned to the LM.
  63. * @param context the LayoutContext used to store layout information
  64. * @param alignment the desired text alignment
  65. * @param restartPosition {@link Position} to restart from
  66. * @param restartLM {@link LayoutManager} to restart from
  67. * @return the list of KnuthElements
  68. * @see LayoutManager#getNextKnuthElements(LayoutContext,int)
  69. */
  70. List getNextKnuthElements(LayoutContext context, int alignment,
  71. Position restartPosition, LayoutManager restartLM) {
  72. List<ListElement> elements = new LinkedList<ListElement>();
  73. boolean isRestart = (restartPosition != null);
  74. // always reset in case of restart (exception: see below)
  75. boolean doReset = isRestart;
  76. LayoutManager currentChildLM;
  77. Stack<LayoutManager> lmStack = new Stack<LayoutManager>();
  78. if (isRestart) {
  79. currentChildLM = restartPosition.getLM();
  80. if (currentChildLM == null) {
  81. throw new IllegalStateException("Cannot find layout manager to restart from");
  82. }
  83. if (restartLM != null && restartLM.getParent() == this) {
  84. currentChildLM = restartLM;
  85. } else {
  86. while (currentChildLM.getParent() != this) {
  87. lmStack.push(currentChildLM);
  88. currentChildLM = currentChildLM.getParent();
  89. }
  90. doReset = false;
  91. }
  92. setCurrentChildLM(currentChildLM);
  93. } else {
  94. currentChildLM = getChildLM();
  95. }
  96. while (currentChildLM != null) {
  97. if (!isRestart || doReset) {
  98. if (doReset) {
  99. currentChildLM.reset(); // TODO won't work with forced breaks
  100. }
  101. if (addChildElements(elements, currentChildLM, context, alignment,
  102. null, null, null) != null) {
  103. return elements;
  104. }
  105. } else {
  106. if (addChildElements(elements, currentChildLM, context, alignment, lmStack,
  107. restartPosition, restartLM) != null) {
  108. return elements;
  109. }
  110. // restarted; force reset as of next child
  111. doReset = true;
  112. }
  113. currentChildLM = getChildLM();
  114. }
  115. SpaceResolver.resolveElementList(elements);
  116. setFinished(true);
  117. assert !elements.isEmpty();
  118. return elements;
  119. }
  120. private List<ListElement> addChildElements(List<ListElement> elements,
  121. LayoutManager childLM, LayoutContext context, int alignment,
  122. Stack<LayoutManager> lmStack, Position position, LayoutManager restartAtLM) {
  123. if (handleSpanChange(childLM, context)) {
  124. SpaceResolver.resolveElementList(elements);
  125. return elements;
  126. }
  127. LayoutContext childLC = makeChildLayoutContext(context);
  128. List<ListElement> childElements
  129. = getNextChildElements(childLM, context, childLC, alignment, lmStack,
  130. position, restartAtLM);
  131. if (elements.isEmpty()) {
  132. context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
  133. }
  134. if (!elements.isEmpty()
  135. && !ElementListUtils.startsWithForcedBreak(childElements)) {
  136. addInBetweenBreak(elements, context, childLC);
  137. }
  138. context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
  139. elements.addAll(childElements);
  140. if (ElementListUtils.endsWithForcedBreak(elements)) {
  141. // a descendant of this flow has break-before or break-after
  142. if (childLM.isFinished() && !hasNextChildLM()) {
  143. setFinished(true);
  144. }
  145. SpaceResolver.resolveElementList(elements);
  146. return elements;
  147. }
  148. return null;
  149. }
  150. private boolean handleSpanChange(LayoutManager childLM, LayoutContext context) {
  151. int span = EN_NONE;
  152. int disableColumnBalancing = EN_FALSE;
  153. if (childLM instanceof BlockLayoutManager) {
  154. span = ((BlockLayoutManager)childLM).getBlockFO().getSpan();
  155. disableColumnBalancing = ((BlockLayoutManager) childLM).getBlockFO()
  156. .getDisableColumnBalancing();
  157. } else if (childLM instanceof BlockContainerLayoutManager) {
  158. span = ((BlockContainerLayoutManager)childLM).getBlockContainerFO().getSpan();
  159. disableColumnBalancing = ((BlockContainerLayoutManager) childLM).getBlockContainerFO()
  160. .getDisableColumnBalancing();
  161. }
  162. int currentSpan = context.getCurrentSpan();
  163. if (currentSpan != span) {
  164. if (span == EN_ALL) {
  165. context.setDisableColumnBalancing(disableColumnBalancing);
  166. }
  167. log.debug("span change from " + currentSpan + " to " + span);
  168. context.signalSpanChange(span);
  169. return true;
  170. } else {
  171. return false;
  172. }
  173. }
  174. /**
  175. * Overridden to take into account the current page-master's
  176. * writing-mode
  177. * {@inheritDoc}
  178. */
  179. @Override
  180. protected LayoutContext makeChildLayoutContext(LayoutContext context) {
  181. LayoutContext childLC = LayoutContext.newInstance();
  182. childLC.setStackLimitBP(context.getStackLimitBP());
  183. childLC.setRefIPD(context.getRefIPD());
  184. childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode());
  185. return childLC;
  186. }
  187. /**
  188. * Overridden to wrap the child positions before returning the list
  189. * {@inheritDoc}
  190. */
  191. @Override
  192. protected List<ListElement> getNextChildElements(LayoutManager childLM, LayoutContext context,
  193. LayoutContext childLC, int alignment, Stack<LayoutManager> lmStack,
  194. Position restartPosition, LayoutManager restartLM) {
  195. List<ListElement> childElements;
  196. if (lmStack == null) {
  197. childElements = childLM.getNextKnuthElements(childLC, alignment);
  198. } else {
  199. childElements = childLM.getNextKnuthElements(childLC, alignment,
  200. lmStack, restartPosition, restartLM);
  201. }
  202. assert !childElements.isEmpty();
  203. // "wrap" the Position inside each element
  204. List tempList = childElements;
  205. childElements = new LinkedList<ListElement>();
  206. wrapPositionElements(tempList, childElements);
  207. return childElements;
  208. }
  209. /** {@inheritDoc} */
  210. @Override
  211. public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
  212. log.debug(" FLM.negotiateBPDAdjustment> " + adj);
  213. if (lastElement.getPosition() instanceof NonLeafPosition) {
  214. // this element was not created by this FlowLM
  215. NonLeafPosition savedPos = (NonLeafPosition)lastElement.getPosition();
  216. lastElement.setPosition(savedPos.getPosition());
  217. int returnValue = ((BlockLevelLayoutManager)lastElement.getLayoutManager())
  218. .negotiateBPDAdjustment(adj, lastElement);
  219. lastElement.setPosition(savedPos);
  220. log.debug(" FLM.negotiateBPDAdjustment> result " + returnValue);
  221. return returnValue;
  222. } else {
  223. return 0;
  224. }
  225. }
  226. /** {@inheritDoc} */
  227. @Override
  228. public void discardSpace(KnuthGlue spaceGlue) {
  229. log.debug(" FLM.discardSpace> ");
  230. if (spaceGlue.getPosition() instanceof NonLeafPosition) {
  231. // this element was not created by this FlowLM
  232. NonLeafPosition savedPos = (NonLeafPosition)spaceGlue.getPosition();
  233. spaceGlue.setPosition(savedPos.getPosition());
  234. ((BlockLevelLayoutManager) spaceGlue.getLayoutManager()).discardSpace(spaceGlue);
  235. spaceGlue.setPosition(savedPos);
  236. }
  237. }
  238. /** {@inheritDoc} */
  239. @Override
  240. public Keep getKeepTogether() {
  241. return Keep.KEEP_AUTO;
  242. }
  243. /** {@inheritDoc} */
  244. @Override
  245. public Keep getKeepWithNext() {
  246. return Keep.KEEP_AUTO;
  247. }
  248. /** {@inheritDoc} */
  249. @Override
  250. public Keep getKeepWithPrevious() {
  251. return Keep.KEEP_AUTO;
  252. }
  253. /** {@inheritDoc} */
  254. @Override
  255. public List<KnuthElement> getChangedKnuthElements(List oldList, int alignment) {
  256. ListIterator<KnuthElement> oldListIterator = oldList.listIterator();
  257. KnuthElement returnedElement;
  258. List<KnuthElement> returnedList = new LinkedList<KnuthElement>();
  259. List<KnuthElement> returnList = new LinkedList<KnuthElement>();
  260. KnuthElement prevElement = null;
  261. KnuthElement currElement = null;
  262. int fromIndex = 0;
  263. // "unwrap" the Positions stored in the elements
  264. KnuthElement oldElement;
  265. while (oldListIterator.hasNext()) {
  266. oldElement = oldListIterator.next();
  267. if (oldElement.getPosition() instanceof NonLeafPosition) {
  268. // oldElement was created by a descendant of this FlowLM
  269. oldElement.setPosition((oldElement.getPosition()).getPosition());
  270. } else {
  271. // thisElement was created by this FlowLM, remove it
  272. oldListIterator.remove();
  273. }
  274. }
  275. // reset the iterator
  276. oldListIterator = oldList.listIterator();
  277. while (oldListIterator.hasNext()) {
  278. currElement = oldListIterator.next();
  279. if (prevElement != null
  280. && prevElement.getLayoutManager() != currElement.getLayoutManager()) {
  281. // prevElement is the last element generated by the same LM
  282. BlockLevelLayoutManager prevLM = (BlockLevelLayoutManager)
  283. prevElement.getLayoutManager();
  284. BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
  285. currElement.getLayoutManager();
  286. returnedList.addAll(prevLM.getChangedKnuthElements(
  287. oldList.subList(fromIndex, oldListIterator.previousIndex()), alignment));
  288. fromIndex = oldListIterator.previousIndex();
  289. // there is another block after this one
  290. if (prevLM.mustKeepWithNext()
  291. || currLM.mustKeepWithPrevious()) {
  292. // add an infinite penalty to forbid a break between blocks
  293. returnedList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
  294. new Position(this), false));
  295. } else if (!ListUtil.getLast(returnedList).isGlue()) {
  296. // add a null penalty to allow a break between blocks
  297. returnedList.add(new KnuthPenalty(0, 0, false, new Position(this), false));
  298. }
  299. }
  300. prevElement = currElement;
  301. }
  302. if (currElement != null) {
  303. BlockLevelLayoutManager currLM = (BlockLevelLayoutManager)
  304. currElement.getLayoutManager();
  305. returnedList.addAll(currLM.getChangedKnuthElements(
  306. oldList.subList(fromIndex, oldList.size()), alignment));
  307. }
  308. // "wrap" the Position stored in each element of returnedList
  309. // and add elements to returnList
  310. ListIterator<KnuthElement> listIter = returnedList.listIterator();
  311. while (listIter.hasNext()) {
  312. returnedElement = listIter.next();
  313. if (returnedElement.getLayoutManager() != this) {
  314. returnedElement.setPosition(
  315. new NonLeafPosition(this, returnedElement.getPosition()));
  316. }
  317. returnList.add(returnedElement);
  318. }
  319. return returnList;
  320. }
  321. /** {@inheritDoc} */
  322. @Override
  323. public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
  324. AreaAdditionUtil.addAreas(this, parentIter, layoutContext);
  325. flush();
  326. }
  327. /**
  328. * Add child area to a the correct container, depending on its
  329. * area class. A Flow can fill at most one area container of any class
  330. * at any one time. The actual work is done by BlockStackingLM.
  331. *
  332. * @param childArea the area to add
  333. */
  334. @Override
  335. public void addChildArea(Area childArea) {
  336. getParentArea(childArea);
  337. addChildToArea(childArea,
  338. this.currentAreas[childArea.getAreaClass()]);
  339. }
  340. /** {@inheritDoc} */
  341. @Override
  342. public Area getParentArea(Area childArea) {
  343. BlockParent parentArea = null;
  344. int aclass = childArea.getAreaClass();
  345. if (aclass == Area.CLASS_NORMAL) {
  346. parentArea = getCurrentPV().getCurrentFlow();
  347. } else if (aclass == Area.CLASS_BEFORE_FLOAT) {
  348. parentArea = getCurrentPV().getBodyRegion().getBeforeFloat();
  349. } else if (aclass == Area.CLASS_FOOTNOTE) {
  350. parentArea = getCurrentPV().getBodyRegion().getFootnote();
  351. } else {
  352. throw new IllegalStateException("(internal error) Invalid "
  353. + "area class (" + aclass + ") requested.");
  354. }
  355. this.currentAreas[aclass] = parentArea;
  356. setCurrentArea(parentArea);
  357. return parentArea;
  358. }
  359. /**
  360. * Returns the IPD of the content area
  361. * @return the IPD of the content area
  362. */
  363. @Override
  364. public int getContentAreaIPD() {
  365. return getCurrentPV().getCurrentSpan().getColumnWidth();
  366. }
  367. /**
  368. * Returns the BPD of the content area
  369. * @return the BPD of the content area
  370. */
  371. @Override
  372. public int getContentAreaBPD() {
  373. return getCurrentPV().getBodyRegion().getBPD();
  374. }
  375. /** {@inheritDoc} */
  376. @Override
  377. public boolean isRestartable() {
  378. return true;
  379. }
  380. }