/*
 * 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: PDFDocumentHandler.java 1760942 2016-09-15 14:37:18Z ssteiner $ */

package org.apache.fop.render.pdf;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.xmp.Metadata;

import org.apache.fop.accessibility.StructureTreeEventHandler;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.fo.extensions.xmp.XMPMetadata;
import org.apache.fop.pdf.PDFAnnotList;
import org.apache.fop.pdf.PDFArray;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFPage;
import org.apache.fop.pdf.PDFReference;
import org.apache.fop.pdf.PDFResources;
import org.apache.fop.pdf.PDFStream;
import org.apache.fop.render.extensions.prepress.PageBoundaries;
import org.apache.fop.render.extensions.prepress.PageScale;
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFPainter;
import org.apache.fop.render.pdf.PDFRendererConfig.PDFRendererConfigParser;
import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment;
import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment;

IFDocumentHandler implementation that produces PDF.
/** * {@link org.apache.fop.render.intermediate.IFDocumentHandler} implementation that produces PDF. */
public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
logging instance
/** logging instance */
private static Log log = LogFactory.getLog(PDFDocumentHandler.class); private boolean accessEnabled; private PDFLogicalStructureHandler logicalStructureHandler; private PDFStructureTreeBuilder structureTreeBuilder;
the PDF Document being created
/** the PDF Document being created */
private PDFDocument pdfDoc;
Utility class which enables all sorts of features that are not directly connected to the normal rendering process.
/** * Utility class which enables all sorts of features that are not directly connected to the * normal rendering process. */
private final PDFRenderingUtil pdfUtil;
the /Resources object of the PDF document being created
/** the /Resources object of the PDF document being created */
private PDFResources pdfResources;
The current content generator
/** The current content generator */
private PDFContentGenerator generator;
the current page to add annotations to
/** the current page to add annotations to */
private PDFPage currentPage;
the current page's PDF reference
/** the current page's PDF reference */
private PageReference currentPageRef;
Used for bookmarks/outlines.
/** Used for bookmarks/outlines. */
private Map<Integer, PageReference> pageReferences = new HashMap<Integer, PageReference>(); private final PDFDocumentNavigationHandler documentNavigationHandler = new PDFDocumentNavigationHandler(this); private Map<Integer, PDFArray> pageNumbers = new HashMap<Integer, PDFArray>(); private Map<Integer, PDFReference> contents = new HashMap<Integer, PDFReference>();
Default constructor.
/** * Default constructor. */
public PDFDocumentHandler(IFContext context) { super(context); this.pdfUtil = new PDFRenderingUtil(context.getUserAgent()); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean supportsPagesOutOfOrder() { return !accessEnabled; }
{@inheritDoc}
/** {@inheritDoc} */
public String getMimeType() { return MimeConstants.MIME_PDF; }
{@inheritDoc}
/** {@inheritDoc} */
public IFDocumentHandlerConfigurator getConfigurator() { return new PDFRendererConfigurator(getUserAgent(), new PDFRendererConfigParser()); }
{@inheritDoc}
/** {@inheritDoc} */
public IFDocumentNavigationHandler getDocumentNavigationHandler() { return this.documentNavigationHandler; } void mergeRendererOptionsConfig(PDFRendererOptionsConfig config) { pdfUtil.mergeRendererOptionsConfig(config); } PDFLogicalStructureHandler getLogicalStructureHandler() { return logicalStructureHandler; } PDFDocument getPDFDocument() { return pdfDoc; } PDFPage getCurrentPage() { return currentPage; } PageReference getCurrentPageRef() { return currentPageRef; } PDFContentGenerator getGenerator() { return generator; }
{@inheritDoc}
/** {@inheritDoc} */
public void startDocument() throws IFException { super.startDocument(); try { this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); this.accessEnabled = getUserAgent().isAccessibilityEnabled(); if (accessEnabled) { setupAccessibility(); } } catch (IOException e) { throw new IFException("I/O error in startDocument()", e); } } private void setupAccessibility() { pdfDoc.getRoot().makeTagged(); logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc); // TODO this is ugly. All the necessary information should be available // at creation time in order to enforce immutability structureTreeBuilder.setPdfFactory(pdfDoc.getFactory()); structureTreeBuilder.setLogicalStructureHandler(logicalStructureHandler); structureTreeBuilder.setEventBroadcaster(getUserAgent().getEventBroadcaster()); }
{@inheritDoc}
/** {@inheritDoc} */
public void endDocumentHeader() throws IFException { pdfUtil.generateDefaultXMPMetadata(); }
{@inheritDoc}
/** {@inheritDoc} */
public void endDocument() throws IFException { pdfDoc.getResources().addFonts(pdfDoc, fontInfo); try { if (pdfDoc.isLinearizationEnabled()) { generator.flushPDFDoc(); } else { pdfDoc.outputTrailer(this.outputStream); } this.pdfDoc = null; pdfResources = null; this.generator = null; currentPage = null; } catch (IOException ioe) { throw new IFException("I/O error in endDocument()", ioe); } super.endDocument(); }
{@inheritDoc}
/** {@inheritDoc} */
public void startPageSequence(String id) throws IFException { //nop }
{@inheritDoc}
/** {@inheritDoc} */
public void endPageSequence() throws IFException { //nop }
{@inheritDoc}
/** {@inheritDoc} */
public void startPage(int index, String name, String pageMasterName, Dimension size) throws IFException { this.pdfResources = this.pdfDoc.getResources(); PageBoundaries boundaries = new PageBoundaries(size, getContext().getForeignAttributes()); Rectangle trimBox = boundaries.getTrimBox(); Rectangle bleedBox = boundaries.getBleedBox(); Rectangle mediaBox = boundaries.getMediaBox(); Rectangle cropBox = boundaries.getCropBox(); // set scale attributes double scaleX = 1; double scaleY = 1; String scale = (String) getContext().getForeignAttribute( PageScale.EXT_PAGE_SCALE); Point2D scales = PageScale.getScale(scale); if (scales != null) { scaleX = scales.getX(); scaleY = scales.getY(); } //PDF uses the lower left as origin, need to transform from FOP's internal coord system AffineTransform boxTransform = new AffineTransform( scaleX / 1000, 0, 0, -scaleY / 1000, 0, scaleY * size.getHeight() / 1000); this.currentPage = this.pdfDoc.getFactory().makePage( this.pdfResources, index, toPDFCoordSystem(mediaBox, boxTransform), toPDFCoordSystem(cropBox, boxTransform), toPDFCoordSystem(bleedBox, boxTransform), toPDFCoordSystem(trimBox, boxTransform)); if (pdfDoc.getProfile().isPDFVTActive()) { pdfDoc.getFactory().makeDPart(currentPage, pageMasterName); } if (accessEnabled) { logicalStructureHandler.startPage(currentPage); } pdfUtil.generatePageLabel(index, name); currentPageRef = new PageReference(currentPage, size); this.pageReferences.put(index, currentPageRef); this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, this.currentPage); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, (scaleY * size.height) / 1000f); basicPageTransform.scale(scaleX, scaleY); generator.saveGraphicsState(); generator.concatenate(basicPageTransform); } private Rectangle2D toPDFCoordSystem(Rectangle box, AffineTransform transform) { return transform.createTransformedShape(box).getBounds2D(); }
{@inheritDoc}
/** {@inheritDoc} */
public IFPainter startPageContent() throws IFException { return new PDFPainter(this, logicalStructureHandler); }
{@inheritDoc}
/** {@inheritDoc} */
public void endPageContent() throws IFException { generator.restoreGraphicsState(); //for top-level transform to change the default coordinate system }
{@inheritDoc}
/** {@inheritDoc} */
public void endPage() throws IFException { if (accessEnabled) { logicalStructureHandler.endPage(); } try { this.documentNavigationHandler.commit(); setUpContents(); PDFAnnotList annots = currentPage.getAnnotations(); if (annots != null) { this.pdfDoc.addObject(annots); } this.pdfDoc.addObject(currentPage); if (!pdfDoc.isLinearizationEnabled()) { this.generator.flushPDFDoc(); this.generator = null; } } catch (IOException ioe) { throw new IFException("I/O error in endPage()", ioe); } } private void setUpContents() throws IOException { PDFStream stream = generator.getStream(); int hash = stream.streamHashCode(); if (!contents.containsKey(hash)) { pdfDoc.registerObject(stream); PDFReference ref = new PDFReference(stream); contents.put(hash, ref); } currentPage.setContents(contents.get(hash)); }
{@inheritDoc}
/** {@inheritDoc} */
public void handleExtensionObject(Object extension) throws IFException { if (extension instanceof XMPMetadata) { pdfUtil.renderXMPMetadata((XMPMetadata) extension); } else if (extension instanceof Metadata) { XMPMetadata wrapper = new XMPMetadata(((Metadata) extension)); pdfUtil.renderXMPMetadata(wrapper); } else if (extension instanceof PDFEmbeddedFileAttachment) { PDFEmbeddedFileAttachment embeddedFile = (PDFEmbeddedFileAttachment)extension; try { pdfUtil.addEmbeddedFile(embeddedFile); } catch (IOException ioe) { throw new IFException("Error adding embedded file: " + embeddedFile.getSrc(), ioe); } } else if (extension instanceof PDFDictionaryAttachment) { pdfUtil.renderDictionaryExtension((PDFDictionaryAttachment) extension, currentPage); } else if (extension != null) { log.debug("Don't know how to handle extension object. Ignoring: " + extension + " (" + extension.getClass().getName() + ")"); } else { log.debug("Ignoring null extension object."); } }
{@inheritDoc}
/** {@inheritDoc} */
public void setDocumentLocale(Locale locale) { pdfDoc.getRoot().setLanguage(locale); } PageReference getPageReference(int pageIndex) { return this.pageReferences.get(pageIndex); } static final class PageReference { private final PDFReference pageRef; private final Dimension pageDimension; private PageReference(PDFPage page, Dimension dim) { this.pageRef = page.makeReference(); this.pageDimension = new Dimension(dim); } public PDFReference getPageRef() { return this.pageRef; } public Dimension getPageDimension() { return this.pageDimension; } } @Override public StructureTreeEventHandler getStructureTreeEventHandler() { if (structureTreeBuilder == null) { structureTreeBuilder = new PDFStructureTreeBuilder(); } return structureTreeBuilder; } public Map<Integer, PDFArray> getPageNumbers() { return pageNumbers; } }