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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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.area;
  19. // Java
  20. import java.io.OutputStream;
  21. import java.util.List;
  22. import org.xml.sax.SAXException;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.fop.apps.FOPException;
  26. import org.apache.fop.apps.FOUserAgent;
  27. import org.apache.fop.apps.FormattingResults;
  28. import org.apache.fop.datatypes.Numeric;
  29. import org.apache.fop.fo.FOEventHandler;
  30. import org.apache.fop.fo.extensions.ExtensionAttachment;
  31. import org.apache.fop.fo.extensions.ExternalDocument;
  32. import org.apache.fop.fo.extensions.destination.Destination;
  33. import org.apache.fop.fo.pagination.AbstractPageSequence;
  34. import org.apache.fop.fo.pagination.PageSequence;
  35. import org.apache.fop.fo.pagination.Root;
  36. import org.apache.fop.fo.pagination.bookmarks.BookmarkTree;
  37. import org.apache.fop.layoutmgr.ExternalDocumentLayoutManager;
  38. import org.apache.fop.layoutmgr.LayoutManagerMaker;
  39. import org.apache.fop.layoutmgr.LayoutManagerMapping;
  40. import org.apache.fop.layoutmgr.PageSequenceLayoutManager;
  41. import org.apache.fop.layoutmgr.TopLevelLayoutManager;
  42. /**
  43. * Area tree handler for formatting objects.
  44. *
  45. * Concepts: The area tree is to be as small as possible. With minimal classes
  46. * and data to fully represent an area tree for formatting objects. The area
  47. * tree needs to be simple to render and follow the spec closely. This area tree
  48. * has the concept of page sequences. Wherever possible information is discarded
  49. * or optimized to keep memory use low. The data is also organized to make it
  50. * possible for renderers to minimize their output. A page can be saved if not
  51. * fully resolved and once rendered a page contains only size and id reference
  52. * information. The area tree pages are organized in a model that depends on the
  53. * type of renderer.
  54. */
  55. public class AreaTreeHandler extends FOEventHandler {
  56. private static Log log = LogFactory.getLog(AreaTreeHandler.class);
  57. // Recorder of debug statistics
  58. private Statistics statistics = null;
  59. // The LayoutManager maker
  60. private LayoutManagerMaker lmMaker;
  61. /** The AreaTreeModel in use */
  62. protected AreaTreeModel model;
  63. // Keeps track of all meaningful id references
  64. private IDTracker idTracker;
  65. // The fo:root node of the document
  66. private Root rootFObj;
  67. // The formatting results to be handed back to the caller.
  68. private FormattingResults results = new FormattingResults();
  69. private TopLevelLayoutManager prevPageSeqLM;
  70. private int idGen = 0;
  71. /**
  72. * Constructor.
  73. *
  74. * @param userAgent FOUserAgent object for process
  75. * @param outputFormat the MIME type of the output format to use (ex.
  76. * "application/pdf").
  77. * @param stream OutputStream
  78. * @throws FOPException if the RenderPagesModel cannot be created
  79. */
  80. public AreaTreeHandler(FOUserAgent userAgent, String outputFormat,
  81. OutputStream stream) throws FOPException {
  82. super(userAgent);
  83. setupModel(userAgent, outputFormat, stream);
  84. this.lmMaker = userAgent.getFactory().getLayoutManagerMakerOverride();
  85. if (lmMaker == null) {
  86. lmMaker = new LayoutManagerMapping();
  87. }
  88. this.idTracker = new IDTracker();
  89. if (log.isDebugEnabled()) {
  90. statistics = new Statistics();
  91. }
  92. }
  93. /**
  94. * Sets up the AreaTreeModel instance for use by the AreaTreeHandler.
  95. *
  96. * @param userAgent FOUserAgent object for process
  97. * @param outputFormat the MIME type of the output format to use (ex.
  98. * "application/pdf").
  99. * @param stream OutputStream
  100. * @throws FOPException if the RenderPagesModel cannot be created
  101. */
  102. protected void setupModel(FOUserAgent userAgent, String outputFormat,
  103. OutputStream stream) throws FOPException {
  104. if (userAgent.isConserveMemoryPolicyEnabled()) {
  105. this.model = new CachedRenderPagesModel(userAgent, outputFormat, fontInfo, stream);
  106. } else {
  107. this.model = new RenderPagesModel(userAgent, outputFormat, fontInfo, stream);
  108. }
  109. }
  110. /**
  111. * Get the area tree model for this area tree.
  112. *
  113. * @return AreaTreeModel the model being used for this area tree
  114. */
  115. public AreaTreeModel getAreaTreeModel() {
  116. return this.model;
  117. }
  118. /**
  119. * Get the LayoutManager maker for this area tree.
  120. *
  121. * @return LayoutManagerMaker the LayoutManager maker being used for this
  122. * area tree
  123. */
  124. public LayoutManagerMaker getLayoutManagerMaker() {
  125. return this.lmMaker;
  126. }
  127. /**
  128. * Get the IDTracker for this area tree.
  129. *
  130. * @return IDTracker used to track reference ids for items in this area tree
  131. */
  132. public IDTracker getIDTracker() {
  133. return this.idTracker;
  134. }
  135. /**
  136. * Get information about the rendered output, like number of pages created.
  137. *
  138. * @return the results structure
  139. */
  140. public FormattingResults getResults() {
  141. return this.results;
  142. }
  143. /**
  144. * Prepare AreaTreeHandler for document processing This is called from
  145. * FOTreeBuilder.startDocument()
  146. *
  147. * @throws SAXException
  148. * if there is an error
  149. */
  150. @Override
  151. public void startDocument() throws SAXException {
  152. // Initialize statistics
  153. if (statistics != null) {
  154. statistics.start();
  155. }
  156. }
  157. /**
  158. * finish the previous pageSequence
  159. */
  160. private void finishPrevPageSequence(Numeric initialPageNumber) {
  161. if (prevPageSeqLM != null) {
  162. prevPageSeqLM.doForcePageCount(initialPageNumber);
  163. prevPageSeqLM.finishPageSequence();
  164. prevPageSeqLM = null;
  165. }
  166. }
  167. /** {@inheritDoc} */
  168. @Override
  169. public void startPageSequence(PageSequence pageSequence) {
  170. startAbstractPageSequence(pageSequence);
  171. }
  172. private void startAbstractPageSequence(AbstractPageSequence pageSequence) {
  173. rootFObj = pageSequence.getRoot();
  174. //Before the first page-sequence...
  175. if (this.prevPageSeqLM == null) {
  176. // extension attachments from fo:root
  177. wrapAndAddExtensionAttachments(rootFObj.getExtensionAttachments());
  178. // extension attachments from fo:declarations
  179. if (rootFObj.getDeclarations() != null) {
  180. wrapAndAddExtensionAttachments(rootFObj.getDeclarations().getExtensionAttachments());
  181. }
  182. }
  183. finishPrevPageSequence(pageSequence.getInitialPageNumber());
  184. pageSequence.initPageNumber();
  185. }
  186. private void wrapAndAddExtensionAttachments(List<ExtensionAttachment> list) {
  187. for (ExtensionAttachment attachment : list) {
  188. addOffDocumentItem(new OffDocumentExtensionAttachment(attachment));
  189. }
  190. }
  191. /**
  192. * End the PageSequence. The PageSequence formats Pages and adds them to the
  193. * AreaTree. The area tree then handles what happens with the pages.
  194. *
  195. * @param pageSequence the page sequence ending
  196. */
  197. @Override
  198. public void endPageSequence(PageSequence pageSequence) {
  199. if (statistics != null) {
  200. statistics.end();
  201. }
  202. // If no main flow, nothing to layout!
  203. if (pageSequence.getMainFlow() != null) {
  204. PageSequenceLayoutManager pageSLM;
  205. pageSLM = getLayoutManagerMaker().makePageSequenceLayoutManager(
  206. this, pageSequence);
  207. pageSLM.activateLayout();
  208. // preserve the current PageSequenceLayoutManger for the
  209. // force-page-count check at the beginning of the next PageSequence
  210. prevPageSeqLM = pageSLM;
  211. }
  212. }
  213. /** {@inheritDoc} */
  214. @Override
  215. public void startExternalDocument(ExternalDocument document) {
  216. startAbstractPageSequence(document);
  217. }
  218. /** {@inheritDoc} */
  219. @Override
  220. public void endExternalDocument(ExternalDocument document) {
  221. if (statistics != null) {
  222. statistics.end();
  223. }
  224. ExternalDocumentLayoutManager edLM;
  225. edLM = getLayoutManagerMaker().makeExternalDocumentLayoutManager(this, document);
  226. edLM.activateLayout();
  227. // preserve the current PageSequenceLayoutManger for the
  228. // force-page-count check at the beginning of the next PageSequence
  229. prevPageSeqLM = edLM;
  230. }
  231. /**
  232. * Called by the PageSequenceLayoutManager when it is finished with a
  233. * page-sequence.
  234. *
  235. * @param pageSequence the page-sequence just finished
  236. * @param pageCount The number of pages generated for the page-sequence
  237. */
  238. public void notifyPageSequenceFinished(AbstractPageSequence pageSequence,
  239. int pageCount) {
  240. this.results.haveFormattedPageSequence(pageSequence, pageCount);
  241. if (log.isDebugEnabled()) {
  242. log.debug("Last page-sequence produced " + pageCount + " pages.");
  243. }
  244. }
  245. /**
  246. * End the document.
  247. *
  248. * @throws SAXException if there is some error
  249. */
  250. @Override
  251. public void endDocument() throws SAXException {
  252. finishPrevPageSequence(null);
  253. // process fox:destination elements
  254. if (rootFObj != null) {
  255. List<Destination> destinationList = rootFObj.getDestinationList();
  256. if (destinationList != null) {
  257. while (destinationList.size() > 0) {
  258. Destination destination = destinationList.remove(0);
  259. DestinationData destinationData = new DestinationData(destination);
  260. addOffDocumentItem(destinationData);
  261. }
  262. }
  263. // process fo:bookmark-tree
  264. BookmarkTree bookmarkTree = rootFObj.getBookmarkTree();
  265. if (bookmarkTree != null) {
  266. BookmarkData data = new BookmarkData(bookmarkTree);
  267. addOffDocumentItem(data);
  268. if (!data.isResolved()) {
  269. // bookmarks did not fully resolve, add anyway. (hacky? yeah)
  270. model.handleOffDocumentItem(data);
  271. }
  272. }
  273. idTracker.signalIDProcessed(rootFObj.getId());
  274. }
  275. model.endDocument();
  276. if (statistics != null) {
  277. statistics.logResults();
  278. }
  279. }
  280. /**
  281. * Add a OffDocumentItem to the area tree model. This checks if the
  282. * OffDocumentItem is resolvable and attempts to resolve or add the
  283. * resolvable ids for later resolution.
  284. *
  285. * @param odi the OffDocumentItem to add.
  286. */
  287. private void addOffDocumentItem(OffDocumentItem odi) {
  288. if (odi instanceof Resolvable) {
  289. Resolvable res = (Resolvable) odi;
  290. String[] ids = res.getIDRefs();
  291. for (String id : ids) {
  292. List<PageViewport> pageVPList = idTracker.getPageViewportsContainingID(id);
  293. if (pageVPList != null) {
  294. res.resolveIDRef(id, pageVPList);
  295. } else {
  296. AreaEventProducer eventProducer = AreaEventProducer.Provider.get(
  297. getUserAgent().getEventBroadcaster());
  298. eventProducer.unresolvedIDReference(this, odi.getName(), id);
  299. idTracker.addUnresolvedIDRef(id, res);
  300. }
  301. }
  302. // check to see if ODI is now fully resolved, if so process it
  303. if (res.isResolved()) {
  304. model.handleOffDocumentItem(odi);
  305. }
  306. } else {
  307. model.handleOffDocumentItem(odi);
  308. }
  309. }
  310. /**
  311. * Generates and returns a unique key for a page viewport.
  312. *
  313. * @return the generated key.
  314. */
  315. public String generatePageViewportKey() {
  316. this.idGen++;
  317. return "P" + this.idGen;
  318. }
  319. /**
  320. * Tie a PageViewport with an ID found on a child area of the PV. Note that
  321. * an area with a given ID may be on more than one PV, hence an ID may have
  322. * more than one PV associated with it.
  323. *
  324. * @param id the property ID of the area
  325. * @param pv a page viewport that contains the area with this ID
  326. * @deprecated use getIDTracker().associateIDWithPageViewport(id, pv) instead
  327. */
  328. @Deprecated
  329. public void associateIDWithPageViewport(String id, PageViewport pv) {
  330. idTracker.associateIDWithPageViewport(id, pv);
  331. }
  332. /**
  333. * This method tie an ID to the areaTreeHandler until this one is ready to
  334. * be processed. This is used in page-number-citation-last processing so we
  335. * know when an id can be resolved.
  336. *
  337. * @param id the id of the object being processed
  338. * @deprecated use getIDTracker().signalPendingID(id) instead
  339. */
  340. @Deprecated
  341. public void signalPendingID(String id) {
  342. idTracker.signalPendingID(id);
  343. }
  344. /**
  345. * Signals that all areas for the formatting object with the given ID have
  346. * been generated. This is used to determine when page-number-citation-last
  347. * ref-ids can be resolved.
  348. *
  349. * @param id the id of the formatting object which was just finished
  350. * @deprecated use getIDTracker().signalIDProcessed(id) instead
  351. */
  352. @Deprecated
  353. public void signalIDProcessed(String id) {
  354. idTracker.signalIDProcessed(id);
  355. }
  356. /**
  357. * Check if an ID has already been resolved
  358. *
  359. * @param id the id to check
  360. * @return true if the ID has been resolved
  361. * @deprecated use getIDTracker().alreadyResolvedID(id) instead
  362. */
  363. @Deprecated
  364. public boolean alreadyResolvedID(String id) {
  365. return idTracker.alreadyResolvedID(id);
  366. }
  367. /**
  368. * Tries to resolve all unresolved ID references on the given page.
  369. *
  370. * @param pv page viewport whose ID refs to resolve
  371. * @deprecated use getIDTracker().tryIDResolution(pv) instead
  372. */
  373. @Deprecated
  374. public void tryIDResolution(PageViewport pv) {
  375. idTracker.tryIDResolution(pv);
  376. }
  377. /**
  378. * Get the set of page viewports that have an area with a given id.
  379. *
  380. * @param id the id to lookup
  381. * @return the list of PageViewports
  382. * @deprecated use getIDTracker().getPageViewportsContainingID(id) instead
  383. */
  384. @Deprecated
  385. public List<PageViewport> getPageViewportsContainingID(String id) {
  386. return idTracker.getPageViewportsContainingID(id);
  387. }
  388. /**
  389. * Add an Resolvable object with an unresolved idref
  390. *
  391. * @param idref the idref whose target id has not yet been located
  392. * @param res the Resolvable object needing the idref to be resolved
  393. * @deprecated use getIDTracker().addUnresolvedIDRef(idref, res) instead
  394. */
  395. @Deprecated
  396. public void addUnresolvedIDRef(String idref, Resolvable res) {
  397. idTracker.addUnresolvedIDRef(idref, res);
  398. }
  399. private class Statistics {
  400. // for statistics gathering
  401. private Runtime runtime;
  402. // heap memory allocated (for statistics)
  403. private long initialMemory;
  404. // time used in rendering (for statistics)
  405. private long startTime;
  406. /**
  407. * Default constructor
  408. */
  409. protected Statistics() {
  410. this.runtime = Runtime.getRuntime();
  411. }
  412. /**
  413. * starts the area tree handler statistics gathering
  414. */
  415. protected void start() {
  416. this.initialMemory = runtime.totalMemory() - runtime.freeMemory();
  417. this.startTime = System.currentTimeMillis();
  418. }
  419. /**
  420. * ends the area tree handler statistics gathering
  421. */
  422. protected void end() {
  423. long memoryNow = runtime.totalMemory() - runtime.freeMemory();
  424. log.debug("Current heap size: " + (memoryNow / 1024L) + "KB");
  425. }
  426. /**
  427. * logs the results of the area tree handler statistics gathering
  428. */
  429. protected void logResults() {
  430. long memoryNow = runtime.totalMemory() - runtime.freeMemory();
  431. long memoryUsed = (memoryNow - initialMemory) / 1024L;
  432. long timeUsed = System.currentTimeMillis() - startTime;
  433. int pageCount = rootFObj.getTotalPagesGenerated();
  434. log.debug("Initial heap size: " + (initialMemory / 1024L) + "KB");
  435. log.debug("Current heap size: " + (memoryNow / 1024L) + "KB");
  436. log.debug("Total memory used: " + memoryUsed + "KB");
  437. log.debug("Total time used: " + timeUsed + "ms");
  438. log.debug("Pages rendered: " + pageCount);
  439. if (pageCount > 0) {
  440. long perPage = (timeUsed / pageCount);
  441. long ppm = (timeUsed != 0 ? Math.round(60000 * pageCount
  442. / (double) timeUsed) : -1);
  443. log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
  444. }
  445. }
  446. }
  447. }