/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.area; // Java import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.List; import java.util.Locale; import org.xml.sax.SAXException; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fonts.FontInfo; import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererEventProducer; /** * This uses the AreaTreeModel to store the pages * Each page is either rendered if ready or prepared * for later rendering. * Once a page is rendered it is cleared to release the * contents but the PageViewport is retained. So even * though the pages are stored the contents are discarded. */ public class RenderPagesModel extends AreaTreeModel { /** * The renderer that will render the pages. */ protected Renderer renderer; /** * Pages that have been prepared but not rendered yet. */ protected List<PageViewport> prepared = new java.util.ArrayList<PageViewport>(); private List<OffDocumentItem> pendingODI = new java.util.ArrayList<OffDocumentItem>(); private List<OffDocumentItem> endDocODI = new java.util.ArrayList<OffDocumentItem>(); /** * Create a new render pages model with the given renderer. * @param userAgent FOUserAgent object for process * @param outputFormat the MIME type of the output format to use (ex. "application/pdf"). * @param fontInfo FontInfo object * @param stream OutputStream * @throws FOPException if the renderer cannot be properly initialized */ public RenderPagesModel(FOUserAgent userAgent, String outputFormat, FontInfo fontInfo, OutputStream stream) throws FOPException { super(); this.renderer = userAgent.getRendererFactory().createRenderer( userAgent, outputFormat); try { renderer.setupFontInfo(fontInfo); // check that the "any,normal,400" font exists if (!fontInfo.isSetupValid()) { throw new FOPException( "No default font defined by OutputConverter"); } renderer.startRenderer(stream); } catch (IOException e) { throw new FOPException(e); } } @Override public void setDocumentLocale(Locale locale) { renderer.setDocumentLocale(locale); } /** {@inheritDoc} */ @Override public void startPageSequence(PageSequence pageSequence) { super.startPageSequence(pageSequence); if (renderer.supportsOutOfOrder()) { renderer.startPageSequence(getCurrentPageSequence()); } } /** * Add a page to the render page model. * If the page is finished it can be rendered immediately. * If the page needs resolving then if the renderer supports * out of order rendering it can prepare the page. Otherwise * the page is added to a queue. * @param page the page to add to the model */ @Override public void addPage(PageViewport page) { super.addPage(page); // for links the renderer needs to prepare the page // it is more appropriate to do this after queued pages but // it will mean that the renderer has not prepared a page that // could be referenced boolean ready = renderer.supportsOutOfOrder() && page.isResolved(); if (ready) { if (!renderer.supportsOutOfOrder() && page.getPageSequence().isFirstPage(page)) { renderer.startPageSequence(getCurrentPageSequence()); } try { renderer.renderPage(page); } catch (RuntimeException re) { String err = "Error while rendering page " + page.getPageNumberString(); log.error(err, re); throw re; } catch (IOException ioe) { RendererEventProducer eventProducer = RendererEventProducer.Provider.get( renderer.getUserAgent().getEventBroadcaster()); eventProducer.ioError(this, ioe); } catch (FOPException e) { //TODO use error handler to handle this FOPException or propagate exception String err = "Error while rendering page " + page.getPageNumberString(); log.error(err, e); throw new IllegalStateException("Fatal error occurred. Cannot continue. " + e.getClass().getName() + ": " + err); } page.clear(); } else { preparePage(page); } // check prepared pages boolean cont = checkPreparedPages(page, false); if (cont) { processOffDocumentItems(pendingODI); pendingODI.clear(); } } /** * Check prepared pages * * @param newPageViewport the new page being added * @param renderUnresolved render pages with unresolved idref's * (done at end-of-document processing) * @return true if the current page should be rendered * false if the renderer doesn't support out of order * rendering and there are pending pages */ protected boolean checkPreparedPages(PageViewport newPageViewport, boolean renderUnresolved) { for (Iterator iter = prepared.iterator(); iter.hasNext();) { PageViewport pageViewport = (PageViewport)iter.next(); if (pageViewport.isResolved() || renderUnresolved) { if (!renderer.supportsOutOfOrder() && pageViewport.getPageSequence().isFirstPage(pageViewport)) { renderer.startPageSequence(pageViewport.getPageSequence()); } renderPage(pageViewport); pageViewport.clear(); iter.remove(); } else { // if keeping order then stop at first page not resolved if (!renderer.supportsOutOfOrder()) { break; } } } return renderer.supportsOutOfOrder() || prepared.isEmpty(); } /** * Renders the given page and notified about unresolved IDs if any. * @param pageViewport the page to be rendered. */ protected void renderPage(PageViewport pageViewport) { try { renderer.renderPage(pageViewport); if (!pageViewport.isResolved()) { String[] idrefs = pageViewport.getIDRefs(); for (String idref : idrefs) { AreaEventProducer eventProducer = AreaEventProducer.Provider.get( renderer.getUserAgent().getEventBroadcaster()); eventProducer.unresolvedIDReferenceOnPage(this, pageViewport.getPageNumberString(), idref); } } } catch (Exception e) { AreaEventProducer eventProducer = AreaEventProducer.Provider.get( renderer.getUserAgent().getEventBroadcaster()); eventProducer.pageRenderingError(this, pageViewport.getPageNumberString(), e); if (e instanceof RuntimeException) { throw (RuntimeException)e; } } } /** * Prepare a page. * An unresolved page can be prepared if the renderer supports * it and the page will be rendered later. * @param page the page to prepare */ protected void preparePage(PageViewport page) { if (renderer.supportsOutOfOrder()) { renderer.preparePage(page); } prepared.add(page); } /** {@inheritDoc} */ @Override public void handleOffDocumentItem(OffDocumentItem oDI) { switch(oDI.getWhenToProcess()) { case OffDocumentItem.IMMEDIATELY: renderer.processOffDocumentItem(oDI); break; case OffDocumentItem.AFTER_PAGE: pendingODI.add(oDI); break; case OffDocumentItem.END_OF_DOC: endDocODI.add(oDI); break; default: throw new RuntimeException(); } } private void processOffDocumentItems(List<OffDocumentItem> list) { for (OffDocumentItem oDI : list) { renderer.processOffDocumentItem(oDI); } } /** * End the document. Render any end document OffDocumentItems * {@inheritDoc} */ @Override public void endDocument() throws SAXException { // render any pages that had unresolved ids checkPreparedPages(null, true); processOffDocumentItems(pendingODI); pendingODI.clear(); processOffDocumentItems(endDocODI); try { renderer.stopRenderer(); } catch (IOException ex) { throw new SAXException(ex); } } }