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.

AreaTreeHandler.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.area;
  18. // Java
  19. import java.io.OutputStream;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.HashMap;
  24. import java.util.Set;
  25. import java.util.HashSet;
  26. import java.util.Iterator;
  27. // XML
  28. import org.xml.sax.SAXException;
  29. // Apache
  30. import org.apache.fop.apps.FOPException;
  31. import org.apache.fop.apps.FOUserAgent;
  32. import org.apache.fop.area.extensions.BookmarkData;
  33. import org.apache.fop.fo.FOEventHandler;
  34. import org.apache.fop.fo.extensions.Outline;
  35. import org.apache.fop.fo.extensions.Bookmarks;
  36. import org.apache.fop.fo.pagination.PageSequence;
  37. import org.apache.fop.layoutmgr.PageSequenceLayoutManager;
  38. import org.apache.commons.logging.Log;
  39. import org.apache.commons.logging.LogFactory;
  40. /**
  41. * Area tree handler for formatting objects.
  42. *
  43. * Concepts:
  44. * The area tree is to be as small as possible. With minimal classes
  45. * and data to fully represent an area tree for formatting objects.
  46. * The area tree needs to be simple to render and follow the spec
  47. * closely.
  48. * This area tree has the concept of page sequences.
  49. * Where ever possible information is discarded or optimized to
  50. * keep memory use low. The data is also organized to make it
  51. * possible for renderers to minimize their output.
  52. * A page can be saved if not fully resolved and once rendered
  53. * a page contains only size and id reference information.
  54. * The area tree pages are organized in a model that depends on the
  55. * type of renderer.
  56. */
  57. public class AreaTreeHandler extends FOEventHandler {
  58. // TODO: Collecting of statistics should be configurable
  59. private final boolean collectStatistics = true;
  60. private static final boolean MEM_PROFILE_WITH_GC = false;
  61. // for statistics gathering
  62. private Runtime runtime;
  63. // heap memory allocated (for statistics)
  64. private long initialMemory;
  65. // time used in rendering (for statistics)
  66. private long startTime;
  67. // count of number of pages rendered
  68. private int pageCount;
  69. // AreaTreeModel in use
  70. private AreaTreeModel model;
  71. // hashmap of arraylists containing pages with id area
  72. private Map idLocations = new HashMap();
  73. // list of id's yet to be resolved and arraylists of pages
  74. private Map resolve = new HashMap();
  75. private static Log log = LogFactory.getLog(AreaTreeHandler.class);
  76. /**
  77. * Constructor.
  78. * @param userAgent FOUserAgent object for process
  79. * @param renderType Desired fo.Constants output type (RENDER_PDF,
  80. * RENDER_PS, etc.)
  81. * @param stream OutputStream
  82. */
  83. public AreaTreeHandler (FOUserAgent userAgent, int renderType,
  84. OutputStream stream) throws FOPException {
  85. super(userAgent);
  86. // model = new CachedRenderPagesModel(userAgent, renderType,
  87. // fontInfo, stream);
  88. model = new RenderPagesModel(userAgent, renderType, fontInfo,
  89. stream);
  90. if (collectStatistics) {
  91. runtime = Runtime.getRuntime();
  92. }
  93. }
  94. /**
  95. * Get the area tree model for this area tree.
  96. *
  97. * @return AreaTreeModel the model being used for this area tree
  98. */
  99. public AreaTreeModel getAreaTreeModel() {
  100. return model;
  101. }
  102. /**
  103. * Add an id reference pointing to a page viewport.
  104. * @param id the id of the reference
  105. * @param pv the page viewport that contains the id reference
  106. */
  107. public void addIDRef(String id, PageViewport pv) {
  108. List list = (List)idLocations.get(id);
  109. if (list == null) {
  110. list = new ArrayList();
  111. idLocations.put(id, list);
  112. }
  113. list.add(pv);
  114. Set todo = (Set)resolve.get(id);
  115. if (todo != null) {
  116. for (Iterator iter = todo.iterator(); iter.hasNext();) {
  117. Resolvable res = (Resolvable)iter.next();
  118. res.resolve(id, list);
  119. }
  120. resolve.remove(id);
  121. }
  122. }
  123. /**
  124. * Get the list of id references for an id.
  125. * @param id the id to lookup
  126. * @return the list of id references.
  127. */
  128. public List getIDReferences(String id) {
  129. return (List)idLocations.get(id);
  130. }
  131. /**
  132. * Add an unresolved object with a given id.
  133. * @param id the id reference that needs resolving
  134. * @param res the Resolvable object to resolve
  135. */
  136. public void addUnresolvedID(String id, Resolvable res) {
  137. Set todo = (Set)resolve.get(id);
  138. if (todo == null) {
  139. todo = new HashSet();
  140. resolve.put(id, todo);
  141. }
  142. todo.add(res);
  143. }
  144. /**
  145. * Prepare AreaTreeHandler for document processing
  146. * This is called from FOTreeBuilder.startDocument()
  147. *
  148. * @throws SAXException if there is an error
  149. */
  150. public void startDocument() throws SAXException {
  151. //Initialize statistics
  152. if (collectStatistics) {
  153. pageCount = 0;
  154. if (MEM_PROFILE_WITH_GC) {
  155. System.gc(); // This takes time but gives better results
  156. }
  157. initialMemory = runtime.totalMemory() - runtime.freeMemory();
  158. startTime = System.currentTimeMillis();
  159. }
  160. }
  161. /**
  162. * End the document.
  163. *
  164. * @throws SAXException if there is some error
  165. */
  166. public void endDocument() throws SAXException {
  167. // deal with unresolved references
  168. for (Iterator iter = resolve.keySet().iterator(); iter.hasNext();) {
  169. String id = (String)iter.next();
  170. Set list = (Set)resolve.get(id);
  171. for (Iterator resIter = list.iterator(); resIter.hasNext();) {
  172. Resolvable res = (Resolvable)resIter.next();
  173. if (!res.isResolved()) {
  174. res.resolve(id, null);
  175. }
  176. }
  177. }
  178. model.endDocument();
  179. if (collectStatistics) {
  180. if (MEM_PROFILE_WITH_GC) {
  181. // This takes time but gives better results
  182. System.gc();
  183. }
  184. long memoryNow = runtime.totalMemory() - runtime.freeMemory();
  185. long memoryUsed = (memoryNow - initialMemory) / 1024L;
  186. long timeUsed = System.currentTimeMillis() - startTime;
  187. if (logger != null && logger.isDebugEnabled()) {
  188. logger.debug("Initial heap size: " + (initialMemory / 1024L) + "Kb");
  189. logger.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
  190. logger.debug("Total memory used: " + memoryUsed + "Kb");
  191. if (!MEM_PROFILE_WITH_GC) {
  192. logger.debug(" Memory use is indicative; no GC was performed");
  193. logger.debug(" These figures should not be used comparatively");
  194. }
  195. logger.debug("Total time used: " + timeUsed + "ms");
  196. logger.debug("Pages rendered: " + pageCount);
  197. if (pageCount > 0) {
  198. logger.debug("Avg render time: " + (timeUsed / pageCount) + "ms/page");
  199. }
  200. }
  201. }
  202. }
  203. /**
  204. * End the PageSequence.
  205. * The PageSequence formats Pages and adds them to the AreaTree.
  206. * The area tree then handles what happens with the pages.
  207. *
  208. * @param pageSequence the page sequence ending
  209. */
  210. public void endPageSequence(PageSequence pageSequence) {
  211. if (collectStatistics) {
  212. if (MEM_PROFILE_WITH_GC) {
  213. // This takes time but gives better results
  214. System.gc();
  215. }
  216. long memoryNow = runtime.totalMemory() - runtime.freeMemory();
  217. if (logger != null) {
  218. logger.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
  219. }
  220. }
  221. // If no main flow, nothing to layout!
  222. if (pageSequence.getMainFlow() != null) {
  223. addBookmarks(pageSequence.getRoot().getBookmarks());
  224. PageSequenceLayoutManager pageSLM
  225. = new PageSequenceLayoutManager(this, pageSequence);
  226. pageSLM.run();
  227. pageSequence.setCurrentPageNumber(pageSLM.getPageCount());
  228. }
  229. }
  230. /**
  231. * Create the bookmark data in the area tree.
  232. */
  233. private void addBookmarks(Bookmarks bookmarks) {
  234. if (bookmarks == null) {
  235. return;
  236. }
  237. log.debug("adding bookmarks to area tree");
  238. BookmarkData data = new BookmarkData();
  239. for (int count = 0; count < bookmarks.getOutlines().size(); count++) {
  240. Outline out = (Outline)(bookmarks.getOutlines()).get(count);
  241. data.addSubData(createBookmarkData(out));
  242. }
  243. addTreeExtension(data);
  244. data.setAreaTreeModel(model);
  245. }
  246. /**
  247. * Create and return the bookmark data for this outline.
  248. * This creates a bookmark data with the destination
  249. * and adds all the data from child outlines.
  250. *
  251. * @param outline the Outline object for which a bookmark entry should be
  252. * created
  253. * @return the new bookmark data
  254. */
  255. private BookmarkData createBookmarkData(Outline outline) {
  256. BookmarkData data = new BookmarkData(outline.getInternalDestination());
  257. data.setLabel(outline.getLabel());
  258. for (int count = 0; count < outline.getOutlines().size(); count++) {
  259. Outline out = (Outline)(outline.getOutlines()).get(count);
  260. data.addSubData(createBookmarkData(out));
  261. }
  262. return data;
  263. }
  264. /**
  265. * Add a tree extension.
  266. * This checks if the extension is resolvable and attempts
  267. * to resolve or add the resolvable ids for later resolution.
  268. * @param ext the tree extension to add.
  269. */
  270. private void addTreeExtension(TreeExt ext) {
  271. if (ext instanceof Resolvable) {
  272. Resolvable res = (Resolvable)ext;
  273. String[] ids = res.getIDs();
  274. for (int count = 0; count < ids.length; count++) {
  275. if (idLocations.containsKey(ids[count])) {
  276. res.resolve(ids[count], (List)idLocations.get(ids[count]));
  277. } else {
  278. Set todo = (Set)resolve.get(ids[count]);
  279. if (todo == null) {
  280. todo = new HashSet();
  281. resolve.put(ids[count], todo);
  282. }
  283. todo.add(ext);
  284. }
  285. }
  286. } else {
  287. model.handleExtension(ext, TreeExt.IMMEDIATELY);
  288. }
  289. }
  290. }