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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /*
  2. * Copyright 1999-2006 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.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. import org.apache.fop.apps.FOPException;
  33. import org.apache.fop.apps.FOUserAgent;
  34. import org.apache.fop.apps.FormattingResults;
  35. import org.apache.fop.datatypes.Numeric;
  36. import org.apache.fop.fo.FOEventHandler;
  37. import org.apache.fop.fo.extensions.ExtensionAttachment;
  38. import org.apache.fop.fo.pagination.PageSequence;
  39. import org.apache.fop.fo.pagination.Root;
  40. import org.apache.fop.fo.pagination.bookmarks.BookmarkTree;
  41. import org.apache.fop.layoutmgr.PageSequenceLayoutManager;
  42. import org.apache.fop.layoutmgr.LayoutManagerMaker;
  43. import org.apache.fop.layoutmgr.LayoutManagerMapping;
  44. /**
  45. * Area tree handler for formatting objects.
  46. *
  47. * Concepts:
  48. * The area tree is to be as small as possible. With minimal classes
  49. * and data to fully represent an area tree for formatting objects.
  50. * The area tree needs to be simple to render and follow the spec
  51. * closely.
  52. * This area tree has the concept of page sequences.
  53. * Wherever possible information is discarded or optimized to
  54. * keep memory use low. The data is also organized to make it
  55. * possible for renderers to minimize their output.
  56. * A page can be saved if not fully resolved and once rendered
  57. * a page contains only size and id reference information.
  58. * The area tree pages are organized in a model that depends on the
  59. * type of renderer.
  60. */
  61. public class AreaTreeHandler extends FOEventHandler {
  62. // show statistics after document complete?
  63. private boolean outputStatistics;
  64. // for statistics gathering
  65. private Runtime runtime;
  66. // heap memory allocated (for statistics)
  67. private long initialMemory;
  68. // time used in rendering (for statistics)
  69. private long startTime;
  70. // the LayoutManager maker
  71. private LayoutManagerMaker lmMaker;
  72. /** AreaTreeModel in use */
  73. protected AreaTreeModel model;
  74. // The fo:root node of the document
  75. private Root rootFObj;
  76. // HashMap of ID's whose area is located on one or more consecutive
  77. // PageViewports. Each ID has an arraylist of PageViewports that
  78. // form the defined area of this ID
  79. private Map idLocations = new HashMap();
  80. // idref's whose target PageViewports have yet to be identified
  81. // Each idref has a HashSet of Resolvable objects containing that idref
  82. private Map unresolvedIDRefs = new HashMap();
  83. // The formatting results to be handed back to the caller.
  84. private FormattingResults results = new FormattingResults();
  85. private PageSequenceLayoutManager prevPageSeqLM;
  86. private static Log log = LogFactory.getLog(AreaTreeHandler.class);
  87. /**
  88. * Constructor.
  89. * @param userAgent FOUserAgent object for process
  90. * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
  91. * @param stream OutputStream
  92. * @throws FOPException if the RenderPagesModel cannot be created
  93. */
  94. public AreaTreeHandler (FOUserAgent userAgent, String outputFormat,
  95. OutputStream stream) throws FOPException {
  96. super(userAgent);
  97. setupModel(userAgent, outputFormat, stream);
  98. lmMaker = userAgent.getFactory().getLayoutManagerMakerOverride();
  99. if (lmMaker == null) {
  100. lmMaker = new LayoutManagerMapping();
  101. }
  102. outputStatistics = log.isDebugEnabled();
  103. if (outputStatistics) {
  104. runtime = Runtime.getRuntime();
  105. }
  106. }
  107. /**
  108. * Sets up the AreaTreeModel instance for use by the AreaTreeHandler.
  109. * @param userAgent FOUserAgent object for process
  110. * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
  111. * @param stream OutputStream
  112. * @throws FOPException if the RenderPagesModel cannot be created
  113. */
  114. protected void setupModel(FOUserAgent userAgent, String outputFormat,
  115. OutputStream stream) throws FOPException {
  116. model = new RenderPagesModel(userAgent, outputFormat, fontInfo,
  117. stream);
  118. }
  119. /**
  120. * Get the area tree model for this area tree.
  121. *
  122. * @return AreaTreeModel the model being used for this area tree
  123. */
  124. public AreaTreeModel getAreaTreeModel() {
  125. return model;
  126. }
  127. /**
  128. * Get the LayoutManager maker for this area tree.
  129. *
  130. * @return LayoutManagerMaker the LayoutManager maker being used for this area tree
  131. */
  132. public LayoutManagerMaker getLayoutManagerMaker() {
  133. return lmMaker;
  134. }
  135. /**
  136. * Tie a PageViewport with an ID found on a child area of the PV.
  137. * Note that an area with a given ID may be on more than one PV, hence
  138. * an ID may have more than one PV associated with it.
  139. * @param id the property ID of the area
  140. * @param pv a page viewport that contains the area with this ID
  141. */
  142. public void associateIDWithPageViewport(String id, PageViewport pv) {
  143. if (log.isDebugEnabled()) {
  144. log.debug("associateIDWithPageViewport(" + id + ", " + pv + ")");
  145. }
  146. List pvList = (List) idLocations.get(id);
  147. if (pvList == null) { // first time ID located
  148. pvList = new ArrayList();
  149. idLocations.put(id, pvList);
  150. pvList.add(pv);
  151. /*
  152. * See if this ID is in the unresolved idref list, if so
  153. * resolve Resolvable objects tied to it.
  154. */
  155. tryIDResolution(id, pv, pvList);
  156. } else {
  157. pvList.add(pv);
  158. }
  159. }
  160. /**
  161. * Tries to resolve all unresolved ID references on the given page.
  162. * @param id ID to resolve
  163. * @param pv page viewport whose ID refs to resolve
  164. * @param List of PageViewports
  165. */
  166. private void tryIDResolution(String id, PageViewport pv, List pvList) {
  167. Set todo = (Set) unresolvedIDRefs.get(id);
  168. if (todo != null) {
  169. for (Iterator iter = todo.iterator(); iter.hasNext();) {
  170. Resolvable res = (Resolvable) iter.next();
  171. res.resolveIDRef(id, pvList);
  172. }
  173. unresolvedIDRefs.remove(id);
  174. }
  175. }
  176. /**
  177. * Tries to resolve all unresolved ID references on the given page.
  178. * @param pv page viewport whose ID refs to resolve
  179. */
  180. public void tryIDResolution(PageViewport pv) {
  181. String[] ids = pv.getIDRefs();
  182. if (ids != null) {
  183. for (int i = 0; i < ids.length; i++) {
  184. List pvList = (List) idLocations.get(ids[i]);
  185. if (pvList != null) {
  186. tryIDResolution(ids[i], pv, pvList);
  187. }
  188. }
  189. }
  190. }
  191. /**
  192. * Get the list of page viewports that have an area with a given id.
  193. * @param id the id to lookup
  194. * @return the list of PageViewports
  195. */
  196. public List getPageViewportsContainingID(String id) {
  197. return (List) idLocations.get(id);
  198. }
  199. /**
  200. * Get information about the rendered output, like
  201. * number of pages created.
  202. * @return the results structure
  203. */
  204. public FormattingResults getResults() {
  205. return this.results;
  206. }
  207. /**
  208. * Add an Resolvable object with an unresolved idref
  209. * @param idref the idref whose target id has not yet been located
  210. * @param res the Resolvable object needing the idref to be resolved
  211. */
  212. public void addUnresolvedIDRef(String idref, Resolvable res) {
  213. Set todo = (Set) unresolvedIDRefs.get(idref);
  214. if (todo == null) {
  215. todo = new HashSet();
  216. unresolvedIDRefs.put(idref, todo);
  217. }
  218. // add Resolvable object to this HashSet
  219. todo.add(res);
  220. }
  221. /**
  222. * Prepare AreaTreeHandler for document processing
  223. * This is called from FOTreeBuilder.startDocument()
  224. *
  225. * @throws SAXException if there is an error
  226. */
  227. public void startDocument() throws SAXException {
  228. //Initialize statistics
  229. if (outputStatistics) {
  230. initialMemory = runtime.totalMemory() - runtime.freeMemory();
  231. startTime = System.currentTimeMillis();
  232. }
  233. }
  234. /**
  235. * finish the previous pageSequence
  236. */
  237. private void finishPrevPageSequence(Numeric initialPageNumber) {
  238. if (prevPageSeqLM != null) {
  239. prevPageSeqLM.doForcePageCount(initialPageNumber);
  240. prevPageSeqLM.finishPageSequence();
  241. prevPageSeqLM = null;
  242. }
  243. }
  244. /** @see org.apache.fop.fo.FOEventHandler */
  245. public void startPageSequence(PageSequence pageSequence) {
  246. rootFObj = pageSequence.getRoot();
  247. finishPrevPageSequence(pageSequence.getInitialPageNumber());
  248. pageSequence.initPageNumber();
  249. //extension attachments from fo:root
  250. wrapAndAddExtensionAttachments(rootFObj.getExtensionAttachments());
  251. //extension attachments from fo:declarations
  252. if (rootFObj.getDeclarations() != null) {
  253. wrapAndAddExtensionAttachments(rootFObj.getDeclarations().getExtensionAttachments());
  254. }
  255. }
  256. private void wrapAndAddExtensionAttachments(List list) {
  257. Iterator i = list.iterator();
  258. while (i.hasNext()) {
  259. ExtensionAttachment attachment = (ExtensionAttachment)i.next();
  260. addOffDocumentItem(new OffDocumentExtensionAttachment(attachment));
  261. }
  262. }
  263. /**
  264. * End the PageSequence.
  265. * The PageSequence formats Pages and adds them to the AreaTree.
  266. * The area tree then handles what happens with the pages.
  267. *
  268. * @param pageSequence the page sequence ending
  269. */
  270. public void endPageSequence(PageSequence pageSequence) {
  271. if (outputStatistics) {
  272. long memoryNow = runtime.totalMemory() - runtime.freeMemory();
  273. log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
  274. }
  275. // If no main flow, nothing to layout!
  276. if (pageSequence.getMainFlow() != null) {
  277. PageSequenceLayoutManager pageSLM;
  278. pageSLM = getLayoutManagerMaker().makePageSequenceLayoutManager(
  279. this, pageSequence);
  280. pageSLM.activateLayout();
  281. // preserve the current PageSequenceLayoutManger for the
  282. // force-page-count check at the beginning of the next PageSequence
  283. prevPageSeqLM = pageSLM;
  284. }
  285. }
  286. /**
  287. * Called by the PageSequenceLayoutManager when it is finished with a page-sequence.
  288. * @param pageSequence the page-sequence just finished
  289. * @param pageCount The number of pages generated for the page-sequence
  290. */
  291. public void notifyPageSequenceFinished(PageSequence pageSequence,
  292. int pageCount) {
  293. this.results.haveFormattedPageSequence(pageSequence,
  294. pageCount);
  295. if (log.isDebugEnabled()) {
  296. log.debug("Last page-sequence produced "
  297. + pageCount + " pages.");
  298. }
  299. }
  300. /**
  301. * End the document.
  302. *
  303. * @throws SAXException if there is some error
  304. */
  305. public void endDocument() throws SAXException {
  306. finishPrevPageSequence(null);
  307. // process fo:bookmark-tree
  308. BookmarkTree bookmarkTree = rootFObj.getBookmarkTree();
  309. if (bookmarkTree != null) {
  310. BookmarkData data = new BookmarkData(bookmarkTree);
  311. addOffDocumentItem(data);
  312. }
  313. model.endDocument();
  314. if (outputStatistics) {
  315. long memoryNow = runtime.totalMemory() - runtime.freeMemory();
  316. long memoryUsed = (memoryNow - initialMemory) / 1024L;
  317. long timeUsed = System.currentTimeMillis() - startTime;
  318. int pageCount = rootFObj.getTotalPagesGenerated();
  319. log.debug("Initial heap size: " + (initialMemory / 1024L) + "Kb");
  320. log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
  321. log.debug("Total memory used: " + memoryUsed + "Kb");
  322. log.debug("Total time used: " + timeUsed + "ms");
  323. log.debug("Pages rendered: " + pageCount);
  324. if (pageCount > 0) {
  325. long perPage = (timeUsed / pageCount);
  326. long ppm = (timeUsed != 0 ? Math.round(60000 * pageCount / (double)timeUsed) : -1);
  327. log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
  328. }
  329. }
  330. }
  331. /**
  332. * Add a OffDocumentItem to the area tree model
  333. * This checks if the OffDocumentItem is resolvable and attempts
  334. * to resolve or add the resolvable ids for later resolution.
  335. * @param odi the OffDocumentItem to add.
  336. */
  337. private void addOffDocumentItem(OffDocumentItem odi) {
  338. if (odi instanceof Resolvable) {
  339. Resolvable res = (Resolvable) odi;
  340. String[] ids = res.getIDRefs();
  341. for (int count = 0; count < ids.length; count++) {
  342. if (idLocations.containsKey(ids[count])) {
  343. res.resolveIDRef(ids[count], (List) idLocations.get(ids[count]));
  344. } else {
  345. log.warn(odi.getName() + ": Unresolved id reference \""
  346. + ids[count] + "\" found.");
  347. addUnresolvedIDRef(ids[count], res);
  348. }
  349. }
  350. // check to see if ODI is now fully resolved, if so process it
  351. if (res.isResolved()) {
  352. model.handleOffDocumentItem(odi);
  353. }
  354. } else {
  355. model.handleOffDocumentItem(odi);
  356. }
  357. }
  358. }