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.

PDFDocumentHandler.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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.render.pdf;
  19. import java.awt.Dimension;
  20. import java.awt.Rectangle;
  21. import java.awt.geom.AffineTransform;
  22. import java.awt.geom.Point2D;
  23. import java.awt.geom.Rectangle2D;
  24. import java.io.IOException;
  25. import java.util.HashMap;
  26. import java.util.Locale;
  27. import java.util.Map;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.apache.xmlgraphics.xmp.Metadata;
  31. import org.apache.fop.accessibility.StructureTreeEventHandler;
  32. import org.apache.fop.apps.MimeConstants;
  33. import org.apache.fop.fo.extensions.xmp.XMPMetadata;
  34. import org.apache.fop.pdf.PDFAnnotList;
  35. import org.apache.fop.pdf.PDFArray;
  36. import org.apache.fop.pdf.PDFDocument;
  37. import org.apache.fop.pdf.PDFPage;
  38. import org.apache.fop.pdf.PDFReference;
  39. import org.apache.fop.pdf.PDFResources;
  40. import org.apache.fop.pdf.PDFStream;
  41. import org.apache.fop.render.extensions.prepress.PageBoundaries;
  42. import org.apache.fop.render.extensions.prepress.PageScale;
  43. import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
  44. import org.apache.fop.render.intermediate.IFContext;
  45. import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
  46. import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
  47. import org.apache.fop.render.intermediate.IFException;
  48. import org.apache.fop.render.intermediate.IFPainter;
  49. import org.apache.fop.render.pdf.PDFRendererConfig.PDFRendererConfigParser;
  50. import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment;
  51. import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment;
  52. /**
  53. * {@link org.apache.fop.render.intermediate.IFDocumentHandler} implementation that produces PDF.
  54. */
  55. public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
  56. /** logging instance */
  57. private static Log log = LogFactory.getLog(PDFDocumentHandler.class);
  58. private boolean accessEnabled;
  59. private PDFLogicalStructureHandler logicalStructureHandler;
  60. private PDFStructureTreeBuilder structureTreeBuilder;
  61. /** the PDF Document being created */
  62. private PDFDocument pdfDoc;
  63. /**
  64. * Utility class which enables all sorts of features that are not directly connected to the
  65. * normal rendering process.
  66. */
  67. private final PDFRenderingUtil pdfUtil;
  68. /** the /Resources object of the PDF document being created */
  69. private PDFResources pdfResources;
  70. /** The current content generator */
  71. private PDFContentGenerator generator;
  72. /** the current page to add annotations to */
  73. private PDFPage currentPage;
  74. /** the current page's PDF reference */
  75. private PageReference currentPageRef;
  76. /** Used for bookmarks/outlines. */
  77. private Map<Integer, PageReference> pageReferences = new HashMap<Integer, PageReference>();
  78. private final PDFDocumentNavigationHandler documentNavigationHandler
  79. = new PDFDocumentNavigationHandler(this);
  80. private Map<Integer, PDFArray> pageNumbers = new HashMap<Integer, PDFArray>();
  81. private Map<String, PDFReference> contents = new HashMap<String, PDFReference>();
  82. /**
  83. * Default constructor.
  84. */
  85. public PDFDocumentHandler(IFContext context) {
  86. super(context);
  87. this.pdfUtil = new PDFRenderingUtil(context.getUserAgent());
  88. }
  89. /** {@inheritDoc} */
  90. public boolean supportsPagesOutOfOrder() {
  91. return !accessEnabled;
  92. }
  93. /** {@inheritDoc} */
  94. public String getMimeType() {
  95. return MimeConstants.MIME_PDF;
  96. }
  97. /** {@inheritDoc} */
  98. public IFDocumentHandlerConfigurator getConfigurator() {
  99. return new PDFRendererConfigurator(getUserAgent(), new PDFRendererConfigParser());
  100. }
  101. /** {@inheritDoc} */
  102. public IFDocumentNavigationHandler getDocumentNavigationHandler() {
  103. return this.documentNavigationHandler;
  104. }
  105. void mergeRendererOptionsConfig(PDFRendererOptionsConfig config) {
  106. pdfUtil.mergeRendererOptionsConfig(config);
  107. }
  108. PDFLogicalStructureHandler getLogicalStructureHandler() {
  109. return logicalStructureHandler;
  110. }
  111. PDFDocument getPDFDocument() {
  112. return pdfDoc;
  113. }
  114. PDFPage getCurrentPage() {
  115. return currentPage;
  116. }
  117. PageReference getCurrentPageRef() {
  118. return currentPageRef;
  119. }
  120. PDFContentGenerator getGenerator() {
  121. return generator;
  122. }
  123. /** {@inheritDoc} */
  124. public void startDocument() throws IFException {
  125. super.startDocument();
  126. try {
  127. this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream);
  128. this.accessEnabled = getUserAgent().isAccessibilityEnabled();
  129. if (accessEnabled) {
  130. setupAccessibility();
  131. }
  132. } catch (IOException e) {
  133. throw new IFException("I/O error in startDocument()", e);
  134. }
  135. }
  136. private void setupAccessibility() {
  137. pdfDoc.getRoot().makeTagged();
  138. logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc);
  139. // TODO this is ugly. All the necessary information should be available
  140. // at creation time in order to enforce immutability
  141. structureTreeBuilder.setPdfFactory(pdfDoc.getFactory());
  142. structureTreeBuilder.setLogicalStructureHandler(logicalStructureHandler);
  143. structureTreeBuilder.setEventBroadcaster(getUserAgent().getEventBroadcaster());
  144. }
  145. /** {@inheritDoc} */
  146. public void endDocumentHeader() throws IFException {
  147. pdfUtil.generateDefaultXMPMetadata();
  148. }
  149. /** {@inheritDoc} */
  150. public void endDocument() throws IFException {
  151. pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
  152. try {
  153. if (pdfDoc.isLinearizationEnabled()) {
  154. generator.flushPDFDoc();
  155. } else {
  156. pdfDoc.outputTrailer(this.outputStream);
  157. }
  158. this.pdfDoc = null;
  159. pdfResources = null;
  160. this.generator = null;
  161. currentPage = null;
  162. } catch (IOException ioe) {
  163. throw new IFException("I/O error in endDocument()", ioe);
  164. }
  165. super.endDocument();
  166. }
  167. /** {@inheritDoc} */
  168. public void startPageSequence(String id) throws IFException {
  169. //nop
  170. }
  171. /** {@inheritDoc} */
  172. public void endPageSequence() throws IFException {
  173. //nop
  174. }
  175. /** {@inheritDoc} */
  176. public void startPage(int index, String name, String pageMasterName, Dimension size)
  177. throws IFException {
  178. this.pdfResources = this.pdfDoc.getResources();
  179. PageBoundaries boundaries = new PageBoundaries(size, getContext().getForeignAttributes());
  180. Rectangle trimBox = boundaries.getTrimBox();
  181. Rectangle bleedBox = boundaries.getBleedBox();
  182. Rectangle mediaBox = boundaries.getMediaBox();
  183. Rectangle cropBox = boundaries.getCropBox();
  184. // set scale attributes
  185. double scaleX = 1;
  186. double scaleY = 1;
  187. String scale = (String) getContext().getForeignAttribute(
  188. PageScale.EXT_PAGE_SCALE);
  189. Point2D scales = PageScale.getScale(scale);
  190. if (scales != null) {
  191. scaleX = scales.getX();
  192. scaleY = scales.getY();
  193. }
  194. //PDF uses the lower left as origin, need to transform from FOP's internal coord system
  195. AffineTransform boxTransform = new AffineTransform(
  196. scaleX / 1000, 0, 0, -scaleY / 1000, 0, scaleY * size.getHeight() / 1000);
  197. this.currentPage = this.pdfDoc.getFactory().makePage(
  198. this.pdfResources,
  199. index,
  200. toPDFCoordSystem(mediaBox, boxTransform),
  201. toPDFCoordSystem(cropBox, boxTransform),
  202. toPDFCoordSystem(bleedBox, boxTransform),
  203. toPDFCoordSystem(trimBox, boxTransform));
  204. if (pdfDoc.getProfile().isPDFVTActive()) {
  205. pdfDoc.getFactory().makeDPart(currentPage, pageMasterName);
  206. }
  207. if (accessEnabled) {
  208. logicalStructureHandler.startPage(currentPage);
  209. }
  210. pdfUtil.generatePageLabel(index, name);
  211. currentPageRef = new PageReference(currentPage, size);
  212. this.pageReferences.put(index, currentPageRef);
  213. this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream,
  214. this.currentPage);
  215. // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's
  216. AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0,
  217. (scaleY * size.height) / 1000f);
  218. basicPageTransform.scale(scaleX, scaleY);
  219. generator.saveGraphicsState();
  220. generator.concatenate(basicPageTransform);
  221. }
  222. private Rectangle2D toPDFCoordSystem(Rectangle box, AffineTransform transform) {
  223. return transform.createTransformedShape(box).getBounds2D();
  224. }
  225. /** {@inheritDoc} */
  226. public IFPainter startPageContent() throws IFException {
  227. return new PDFPainter(this, logicalStructureHandler);
  228. }
  229. /** {@inheritDoc} */
  230. public void endPageContent() throws IFException {
  231. generator.restoreGraphicsState();
  232. //for top-level transform to change the default coordinate system
  233. }
  234. /** {@inheritDoc} */
  235. public void endPage() throws IFException {
  236. if (accessEnabled) {
  237. logicalStructureHandler.endPage();
  238. }
  239. try {
  240. this.documentNavigationHandler.commit();
  241. setUpContents();
  242. PDFAnnotList annots = currentPage.getAnnotations();
  243. if (annots != null) {
  244. this.pdfDoc.addObject(annots);
  245. }
  246. this.pdfDoc.addObject(currentPage);
  247. if (!pdfDoc.isLinearizationEnabled()) {
  248. this.generator.flushPDFDoc();
  249. this.generator = null;
  250. }
  251. } catch (IOException ioe) {
  252. throw new IFException("I/O error in endPage()", ioe);
  253. }
  254. }
  255. private void setUpContents() throws IOException {
  256. PDFStream stream = generator.getStream();
  257. String hash = stream.streamHashCode();
  258. if (!contents.containsKey(hash)) {
  259. pdfDoc.registerObject(stream);
  260. PDFReference ref = new PDFReference(stream);
  261. contents.put(hash, ref);
  262. }
  263. currentPage.setContents(contents.get(hash));
  264. }
  265. /** {@inheritDoc} */
  266. public void handleExtensionObject(Object extension) throws IFException {
  267. if (extension instanceof XMPMetadata) {
  268. pdfUtil.renderXMPMetadata((XMPMetadata) extension);
  269. } else if (extension instanceof Metadata) {
  270. XMPMetadata wrapper = new XMPMetadata(((Metadata) extension));
  271. pdfUtil.renderXMPMetadata(wrapper);
  272. } else if (extension instanceof PDFEmbeddedFileAttachment) {
  273. PDFEmbeddedFileAttachment embeddedFile
  274. = (PDFEmbeddedFileAttachment)extension;
  275. try {
  276. pdfUtil.addEmbeddedFile(embeddedFile);
  277. } catch (IOException ioe) {
  278. throw new IFException("Error adding embedded file: " + embeddedFile.getSrc(), ioe);
  279. }
  280. } else if (extension instanceof PDFDictionaryAttachment) {
  281. pdfUtil.renderDictionaryExtension((PDFDictionaryAttachment) extension, currentPage);
  282. } else if (extension != null) {
  283. log.debug("Don't know how to handle extension object. Ignoring: "
  284. + extension + " (" + extension.getClass().getName() + ")");
  285. } else {
  286. log.debug("Ignoring null extension object.");
  287. }
  288. }
  289. /** {@inheritDoc} */
  290. public void setDocumentLocale(Locale locale) {
  291. pdfDoc.getRoot().setLanguage(locale);
  292. }
  293. PageReference getPageReference(int pageIndex) {
  294. return this.pageReferences.get(pageIndex);
  295. }
  296. static final class PageReference {
  297. private final PDFReference pageRef;
  298. private final Dimension pageDimension;
  299. private PageReference(PDFPage page, Dimension dim) {
  300. this.pageRef = page.makeReference();
  301. this.pageDimension = new Dimension(dim);
  302. }
  303. public PDFReference getPageRef() {
  304. return this.pageRef;
  305. }
  306. public Dimension getPageDimension() {
  307. return this.pageDimension;
  308. }
  309. }
  310. @Override
  311. public StructureTreeEventHandler getStructureTreeEventHandler() {
  312. if (structureTreeBuilder == null) {
  313. structureTreeBuilder = new PDFStructureTreeBuilder();
  314. }
  315. return structureTreeBuilder;
  316. }
  317. public Map<Integer, PDFArray> getPageNumbers() {
  318. return pageNumbers;
  319. }
  320. }