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

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