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.

AbstractLayoutManager.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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.ArrayList;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import java.util.Map;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.xmlgraphics.util.QName;
  26. import org.apache.fop.area.Area;
  27. import org.apache.fop.area.AreaTreeObject;
  28. import org.apache.fop.area.PageViewport;
  29. import org.apache.fop.fo.Constants;
  30. import org.apache.fop.fo.FONode;
  31. import org.apache.fop.fo.FObj;
  32. import org.apache.fop.fo.flow.Marker;
  33. import org.apache.fop.fo.flow.RetrieveMarker;
  34. /**
  35. * The base class for most LayoutManagers.
  36. */
  37. public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager implements Constants {
  38. /** logging instance */
  39. private static Log log = LogFactory.getLog(AbstractLayoutManager.class);
  40. /** Parent LayoutManager for this LayoutManager */
  41. protected LayoutManager parentLayoutManager;
  42. /** List of child LayoutManagers */
  43. protected List<LayoutManager> childLMs;
  44. /** Iterator for child LayoutManagers */
  45. protected ListIterator fobjIter;
  46. /** Marker map for markers related to this LayoutManager */
  47. private Map<String, Marker> markers;
  48. /** True if this LayoutManager has handled all of its content. */
  49. private boolean isFinished;
  50. /** child LM during getNextKnuthElement phase */
  51. protected LayoutManager curChildLM;
  52. /** child LM iterator during getNextKnuthElement phase */
  53. protected ListIterator<LayoutManager> childLMiter;
  54. private int lastGeneratedPosition = -1;
  55. private int smallestPosNumberChecked = Integer.MAX_VALUE;
  56. private boolean preserveChildrenAtEndOfLayout;
  57. /**
  58. * Abstract layout manager.
  59. */
  60. public AbstractLayoutManager() {
  61. }
  62. /**
  63. * Abstract layout manager.
  64. *
  65. * @param fo the formatting object for this layout manager
  66. */
  67. public AbstractLayoutManager(FObj fo) {
  68. super(fo);
  69. markers = fo.getMarkers();
  70. fobjIter = fo.getChildNodes();
  71. childLMiter = new LMiter(this);
  72. }
  73. /** {@inheritDoc} */
  74. public void setParent(LayoutManager lm) {
  75. this.parentLayoutManager = lm;
  76. }
  77. /** {@inheritDoc} */
  78. public LayoutManager getParent() {
  79. return this.parentLayoutManager;
  80. }
  81. /** {@inheritDoc} */
  82. public void initialize() {
  83. // Empty
  84. }
  85. /**
  86. * Return currently active child LayoutManager or null if
  87. * all children have finished layout.
  88. * Note: child must implement LayoutManager! If it doesn't, skip it
  89. * and print a warning.
  90. * @return the current child LayoutManager
  91. */
  92. protected LayoutManager getChildLM() {
  93. if (curChildLM != null && !curChildLM.isFinished()) {
  94. return curChildLM;
  95. }
  96. if (childLMiter.hasNext()) {
  97. curChildLM = childLMiter.next();
  98. curChildLM.initialize();
  99. return curChildLM;
  100. }
  101. return null;
  102. }
  103. /**
  104. * Set currently active child layout manager.
  105. * @param childLM the child layout manager
  106. */
  107. protected void setCurrentChildLM(LayoutManager childLM) {
  108. curChildLM = childLM;
  109. childLMiter = new LMiter(this);
  110. do {
  111. curChildLM = childLMiter.next();
  112. } while (curChildLM != childLM);
  113. }
  114. /**
  115. * Return indication if getChildLM will return another LM.
  116. * @return true if another child LM is still available
  117. */
  118. protected boolean hasNextChildLM() {
  119. return childLMiter.hasNext();
  120. }
  121. /**
  122. * Tell whether this LayoutManager has handled all of its content.
  123. * @return True if there are no more break possibilities,
  124. * ie. the last one returned represents the end of the content.
  125. */
  126. public boolean isFinished() {
  127. return isFinished;
  128. }
  129. /**
  130. * Set the flag indicating the LayoutManager has handled all of its content.
  131. * @param fin the flag value to be set
  132. */
  133. public void setFinished(boolean fin) {
  134. isFinished = fin;
  135. }
  136. /** {@inheritDoc} */
  137. public void addAreas(PositionIterator posIter, LayoutContext context) {
  138. }
  139. /** {@inheritDoc} */
  140. public List getNextKnuthElements(LayoutContext context, int alignment) {
  141. log.warn("null implementation of getNextKnuthElements() called!");
  142. setFinished(true);
  143. return null;
  144. }
  145. /** {@inheritDoc} */
  146. public List getChangedKnuthElements(List oldList, int alignment) {
  147. log.warn("null implementation of getChangeKnuthElement() called!");
  148. return null;
  149. }
  150. /**
  151. * Return an Area which can contain the passed childArea. The childArea
  152. * may not yet have any content, but it has essential traits set.
  153. * In general, if the LayoutManager already has an Area it simply returns
  154. * it. Otherwise, it makes a new Area of the appropriate class.
  155. * It gets a parent area for its area by calling its parent LM.
  156. * Finally, based on the dimensions of the parent area, it initializes
  157. * its own area. This includes setting the content IPD and the maximum
  158. * BPD.
  159. * @param childArea the child area for which the parent area is wanted
  160. * @return the parent area for the given child
  161. */
  162. public Area getParentArea(Area childArea) {
  163. return null;
  164. }
  165. /**
  166. * Add a child area to the current area. If this causes the maximum
  167. * dimension of the current area to be exceeded, the parent LM is called
  168. * to add it.
  169. * @param childArea the child area to be added
  170. */
  171. public void addChildArea(Area childArea) {
  172. }
  173. /**
  174. * Create the LM instances for the children of the
  175. * formatting object being handled by this LM.
  176. * @param size the requested number of child LMs
  177. * @return the list with the preloaded child LMs
  178. */
  179. protected List<LayoutManager> createChildLMs(int size) {
  180. if (fobjIter == null) {
  181. return null;
  182. }
  183. List<LayoutManager> newLMs = new ArrayList<LayoutManager>(size);
  184. while (fobjIter.hasNext() && newLMs.size() < size) {
  185. Object theobj = fobjIter.next();
  186. if (theobj instanceof FONode) {
  187. FONode foNode = (FONode) theobj;
  188. if (foNode instanceof RetrieveMarker) {
  189. foNode = getPSLM().resolveRetrieveMarker(
  190. (RetrieveMarker) foNode);
  191. }
  192. if (foNode != null) {
  193. getPSLM().getLayoutManagerMaker()
  194. .makeLayoutManagers(foNode, newLMs);
  195. }
  196. }
  197. }
  198. return newLMs;
  199. }
  200. /** {@inheritDoc} */
  201. public PageSequenceLayoutManager getPSLM() {
  202. return parentLayoutManager.getPSLM();
  203. }
  204. /**
  205. * @see PageSequenceLayoutManager#getCurrentPage()
  206. * @return the {@link Page} instance corresponding to the current page
  207. */
  208. public Page getCurrentPage() {
  209. return getPSLM().getCurrentPage();
  210. }
  211. /** @return the current page viewport */
  212. public PageViewport getCurrentPV() {
  213. return getPSLM().getCurrentPage().getPageViewport();
  214. }
  215. /** {@inheritDoc} */
  216. public boolean createNextChildLMs(int pos) {
  217. List<LayoutManager> newLMs = createChildLMs(pos + 1 - childLMs.size());
  218. addChildLMs(newLMs);
  219. return pos < childLMs.size();
  220. }
  221. /** {@inheritDoc} */
  222. public List<LayoutManager> getChildLMs() {
  223. if (childLMs == null) {
  224. childLMs = new java.util.ArrayList<LayoutManager>(10);
  225. }
  226. return childLMs;
  227. }
  228. /** {@inheritDoc} */
  229. public void addChildLM(LayoutManager lm) {
  230. if (lm == null) {
  231. return;
  232. }
  233. lm.setParent(this);
  234. if (childLMs == null) {
  235. childLMs = new java.util.ArrayList<LayoutManager>(10);
  236. }
  237. childLMs.add(lm);
  238. if (log.isTraceEnabled()) {
  239. log.trace(this.getClass().getName()
  240. + ": Adding child LM " + lm.getClass().getName());
  241. }
  242. }
  243. /** {@inheritDoc} */
  244. public void addChildLMs(List newLMs) {
  245. if (newLMs == null || newLMs.size() == 0) {
  246. return;
  247. }
  248. for (LayoutManager newLM : (Iterable<LayoutManager>) newLMs) {
  249. addChildLM(newLM);
  250. }
  251. }
  252. /**
  253. * Adds a Position to the Position participating in the first|last determination by assigning
  254. * it a unique position index.
  255. * @param pos the Position
  256. * @return the same Position but with a position index
  257. */
  258. public Position notifyPos(Position pos) {
  259. if (pos.getIndex() >= 0) {
  260. throw new IllegalStateException("Position already got its index");
  261. }
  262. pos.setIndex(++lastGeneratedPosition);
  263. return pos;
  264. }
  265. private void verifyNonNullPosition(Position pos) {
  266. if (pos == null || pos.getIndex() < 0) {
  267. throw new IllegalArgumentException(
  268. "Only non-null Positions with an index can be checked");
  269. }
  270. }
  271. /**
  272. * Indicates whether the given Position is the first area-generating Position of this LM.
  273. * @param pos the Position (must be one with a position index)
  274. * @return True if it is the first Position
  275. */
  276. public boolean isFirst(Position pos) {
  277. //log.trace("isFirst() smallestPosNumberChecked=" + smallestPosNumberChecked + " " + pos);
  278. verifyNonNullPosition(pos);
  279. if (pos.getIndex() == this.smallestPosNumberChecked) {
  280. return true;
  281. } else if (pos.getIndex() < this.smallestPosNumberChecked) {
  282. this.smallestPosNumberChecked = pos.getIndex();
  283. return true;
  284. } else {
  285. return false;
  286. }
  287. }
  288. /**
  289. * Indicates whether the given Position is the last area-generating Position of this LM.
  290. * @param pos the Position (must be one with a position index)
  291. * @return True if it is the last Position
  292. */
  293. public boolean isLast(Position pos) {
  294. verifyNonNullPosition(pos);
  295. return (pos.getIndex() == this.lastGeneratedPosition
  296. && isFinished());
  297. }
  298. public boolean hasLineAreaDescendant() {
  299. if (childLMs == null || childLMs.isEmpty()) {
  300. return false;
  301. } else {
  302. for (LayoutManager childLM : childLMs) {
  303. if (childLM.hasLineAreaDescendant()) {
  304. return true;
  305. }
  306. }
  307. }
  308. return false;
  309. }
  310. public int getBaselineOffset() {
  311. if (childLMs != null) {
  312. for (LayoutManager childLM : childLMs) {
  313. if (childLM.hasLineAreaDescendant()) {
  314. return childLM.getBaselineOffset();
  315. }
  316. }
  317. }
  318. throw newNoLineAreaDescendantException();
  319. }
  320. protected IllegalStateException newNoLineAreaDescendantException() {
  321. return new IllegalStateException("getBaselineOffset called on an object that has no line-area descendant");
  322. }
  323. /**
  324. * Transfers foreign attributes from the formatting object to the area.
  325. * @param targetArea the area to set the attributes on
  326. */
  327. protected void transferForeignAttributes(AreaTreeObject targetArea) {
  328. Map<QName, String> atts = fobj.getForeignAttributes();
  329. targetArea.setForeignAttributes(atts);
  330. }
  331. /**
  332. * Transfers extension attachments from the formatting object to the area.
  333. * @param targetArea the area to set the extensions on
  334. */
  335. protected void transferExtensionAttachments(AreaTreeObject targetArea) {
  336. if (fobj.hasExtensionAttachments()) {
  337. targetArea.setExtensionAttachments(fobj.getExtensionAttachments());
  338. }
  339. }
  340. /**
  341. * Transfers extensions (foreign attributes and extension attachments) from
  342. * the formatting object to the area.
  343. * @param targetArea the area to set the extensions on
  344. */
  345. protected void transferExtensions(AreaTreeObject targetArea) {
  346. transferForeignAttributes(targetArea);
  347. transferExtensionAttachments(targetArea);
  348. }
  349. /**
  350. * Registers the FO's markers on the current PageViewport, and if applicable on the parent TableLM.
  351. *
  352. * @param isStarting boolean indicating whether the markers qualify as 'starting'
  353. * @param isFirst boolean indicating whether the markers qualify as 'first'
  354. * @param isLast boolean indicating whether the markers qualify as 'last'
  355. */
  356. protected void registerMarkers(boolean isStarting, boolean isFirst, boolean isLast) {
  357. if (this.markers != null) {
  358. getCurrentPV().registerMarkers(
  359. this.markers,
  360. isStarting,
  361. isFirst,
  362. isLast);
  363. possiblyRegisterMarkersForTables(markers, isStarting, isFirst, isLast);
  364. }
  365. }
  366. /**
  367. * Registers the FO's id on the current PageViewport
  368. */
  369. protected void addId() {
  370. if (fobj != null) {
  371. getPSLM().addIDToPage(fobj.getId());
  372. }
  373. }
  374. /**
  375. * Notifies the {@link PageSequenceLayoutManager} that layout
  376. * for this LM has ended.
  377. */
  378. protected void notifyEndOfLayout() {
  379. if (fobj != null) {
  380. getPSLM().notifyEndOfLayout(fobj.getId());
  381. }
  382. }
  383. /**
  384. * Checks to see if the incoming {@link Position}
  385. * is the last one for this LM, and if so, calls
  386. * {@link #notifyEndOfLayout()} and cleans up.
  387. *
  388. * @param pos the {@link Position} to check
  389. */
  390. protected void checkEndOfLayout(Position pos) {
  391. if (pos != null
  392. && pos.getLM() == this
  393. && this.isLast(pos)) {
  394. notifyEndOfLayout();
  395. if (!preserveChildrenAtEndOfLayout) {
  396. // References to the child LMs are no longer needed
  397. childLMs = null;
  398. curChildLM = null;
  399. childLMiter = null;
  400. }
  401. /* markers that qualify have been transferred to the page
  402. */
  403. markers = null;
  404. /* References to the FO's children can be released if the
  405. * LM is a descendant of the FlowLM. For static-content
  406. * the FO may still be needed on following pages.
  407. */
  408. LayoutManager lm = this.parentLayoutManager;
  409. while (!(lm instanceof FlowLayoutManager
  410. || lm instanceof PageSequenceLayoutManager)) {
  411. lm = lm.getParent();
  412. }
  413. if (lm instanceof FlowLayoutManager && !preserveChildrenAtEndOfLayout) {
  414. fobj.clearChildNodes();
  415. fobjIter = null;
  416. }
  417. }
  418. }
  419. /*
  420. * Preserves the children LMs at the end of layout. This is necessary if the layout is expected to be
  421. * repeated, as when using retrieve-table-markers.
  422. */
  423. public void preserveChildrenAtEndOfLayout() {
  424. preserveChildrenAtEndOfLayout = true;
  425. }
  426. /** {@inheritDoc} */
  427. @Override
  428. public String toString() {
  429. return (super.toString() + (fobj != null ? "{fobj = " + fobj.toString() + "}" : ""));
  430. }
  431. /** {@inheritDoc} */
  432. @Override
  433. public void reset() {
  434. isFinished = false;
  435. curChildLM = null;
  436. childLMiter = new LMiter(this);
  437. /* Reset all the children LM that have been created so far. */
  438. for (LayoutManager childLM : getChildLMs()) {
  439. childLM.reset();
  440. }
  441. if (fobj != null) {
  442. markers = fobj.getMarkers();
  443. }
  444. lastGeneratedPosition = -1;
  445. }
  446. public void recreateChildrenLMs() {
  447. childLMs = new ArrayList();
  448. isFinished = false;
  449. if (fobj == null) {
  450. return;
  451. }
  452. fobjIter = fobj.getChildNodes();
  453. int position = 0;
  454. while (createNextChildLMs(position++)) {
  455. //
  456. }
  457. childLMiter = new LMiter(this);
  458. for (LMiter iter = new LMiter(this); iter.hasNext();) {
  459. AbstractBaseLayoutManager alm = (AbstractBaseLayoutManager) iter.next();
  460. alm.initialize();
  461. alm.recreateChildrenLMs();
  462. alm.preserveChildrenAtEndOfLayout();
  463. }
  464. curChildLM = getChildLM();
  465. }
  466. protected void possiblyRegisterMarkersForTables(Map<String, Marker> markers, boolean isStarting,
  467. boolean isFirst, boolean isLast) {
  468. LayoutManager lm = this.parentLayoutManager;
  469. if (lm instanceof FlowLayoutManager || lm instanceof PageSequenceLayoutManager
  470. || !(lm instanceof AbstractLayoutManager)) {
  471. return;
  472. }
  473. ((AbstractLayoutManager) lm).possiblyRegisterMarkersForTables(markers, isStarting, isFirst, isLast);
  474. }
  475. public boolean handlingFloat() {
  476. if (parentLayoutManager != null && parentLayoutManager instanceof AbstractLayoutManager) {
  477. return ((AbstractLayoutManager) parentLayoutManager).handlingFloat();
  478. }
  479. return false;
  480. }
  481. }