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;
public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
private static Log log = LogFactory.getLog(PDFDocumentHandler.class);
private boolean accessEnabled;
private PDFLogicalStructureHandler logicalStructureHandler;
private PDFStructureTreeBuilder structureTreeBuilder;
private PDFDocument pdfDoc;
private final PDFRenderingUtil pdfUtil;
private PDFResources pdfResources;
private PDFContentGenerator generator;
private PDFPage currentPage;
private PageReference currentPageRef;
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>();
public PDFDocumentHandler(IFContext context) {
super(context);
this.pdfUtil = new PDFRenderingUtil(context.getUserAgent());
}
public boolean supportsPagesOutOfOrder() {
return !accessEnabled;
}
public String getMimeType() {
return MimeConstants.MIME_PDF;
}
public IFDocumentHandlerConfigurator getConfigurator() {
return new PDFRendererConfigurator(getUserAgent(), new PDFRendererConfigParser());
}
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;
}
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);
structureTreeBuilder.setPdfFactory(pdfDoc.getFactory());
structureTreeBuilder.setLogicalStructureHandler(logicalStructureHandler);
structureTreeBuilder.setEventBroadcaster(getUserAgent().getEventBroadcaster());
}
public void endDocumentHeader() throws IFException {
pdfUtil.generateDefaultXMPMetadata();
}
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();
}
public void startPageSequence(String id) throws IFException {
}
public void endPageSequence() throws IFException {
}
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();
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();
}
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);
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();
}
public IFPainter startPageContent() throws IFException {
return new PDFPainter(this, logicalStructureHandler);
}
public void endPageContent() throws IFException {
generator.restoreGraphicsState();
}
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));
}
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.");
}
}
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;
}
}