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.

PDFDocument.java 34KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  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.pdf;
  19. // Java
  20. import java.io.IOException;
  21. import java.io.OutputStream;
  22. import java.io.UnsupportedEncodingException;
  23. import java.security.NoSuchAlgorithmException;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.Collections;
  27. import java.util.Date;
  28. import java.util.HashMap;
  29. import java.util.Iterator;
  30. import java.util.LinkedList;
  31. import java.util.List;
  32. import java.util.Map;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;
  35. import org.apache.fop.pdf.xref.CrossReferenceStream;
  36. import org.apache.fop.pdf.xref.CrossReferenceTable;
  37. import org.apache.fop.pdf.xref.TrailerDictionary;
  38. /* image support modified from work of BoBoGi */
  39. /* font support based on work by Takayuki Takeuchi */
  40. /**
  41. * Class representing a PDF document.
  42. *
  43. * The document is built up by calling various methods and then finally
  44. * output to given filehandle using output method.
  45. *
  46. * A PDF document consists of a series of numbered objects preceded by a
  47. * header and followed by an xref table and trailer. The xref table
  48. * allows for quick access to objects by listing their character
  49. * positions within the document. For this reason the PDF document must
  50. * keep track of the character position of each object. The document
  51. * also keeps direct track of the /Root, /Info and /Resources objects.
  52. *
  53. * Modified by Mark Lillywhite, mark-fop@inomial.com. The changes
  54. * involve: ability to output pages one-at-a-time in a streaming
  55. * fashion (rather than storing them all for output at the end);
  56. * ability to write the /Pages object after writing the rest
  57. * of the document; ability to write to a stream and flush
  58. * the object list; enhanced trailer output; cleanups.
  59. *
  60. */
  61. public class PDFDocument {
  62. /** the encoding to use when converting strings to PDF commands */
  63. public static final String ENCODING = "ISO-8859-1";
  64. /** the counter for object numbering */
  65. protected int objectcount;
  66. /** the logger instance */
  67. private Log log = LogFactory.getLog("org.apache.fop.pdf");
  68. /** the current character position */
  69. private long position;
  70. /** the character position of each object */
  71. private List<Long> indirectObjectOffsets = new ArrayList<Long>();
  72. private Collection<PDFStructElem> structureTreeElements;
  73. /** List of objects to write in the trailer */
  74. private List<PDFObject> trailerObjects = new ArrayList<PDFObject>();
  75. /** the objects themselves */
  76. private List<PDFObject> objects = new LinkedList<PDFObject>();
  77. /** Controls the PDF version of this document */
  78. private VersionController versionController;
  79. /** Indicates which PDF profiles are active (PDF/A, PDF/X etc.) */
  80. private PDFProfile pdfProfile = new PDFProfile(this);
  81. /** the /Root object */
  82. private PDFRoot root;
  83. /** The root outline object */
  84. private PDFOutline outlineRoot;
  85. /** The /Pages object (mark-fop@inomial.com) */
  86. private PDFPages pages;
  87. /** the /Info object */
  88. private PDFInfo info;
  89. /** the /Resources object */
  90. private PDFResources resources;
  91. /** the document's encryption, if it exists */
  92. private PDFEncryption encryption;
  93. /** the colorspace (0=RGB, 1=CMYK) */
  94. private PDFDeviceColorSpace colorspace
  95. = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB);
  96. /** the counter for Pattern name numbering (e.g. 'Pattern1') */
  97. private int patternCount;
  98. /** the counter for Shading name numbering */
  99. private int shadingCount;
  100. /** the counter for XObject numbering */
  101. private int xObjectCount;
  102. /* TODO: Should be modified (works only for image subtype) */
  103. private Map<String, PDFXObject> xObjectsMap = new HashMap<String, PDFXObject>();
  104. private Map<String, PDFFont> fontMap = new HashMap<String, PDFFont>();
  105. private Map<String, List<String>> filterMap = new HashMap<String, List<String>>();
  106. private List<PDFGState> gstates = new ArrayList<PDFGState>();
  107. private List<PDFFunction> functions = new ArrayList<PDFFunction>();
  108. private List<PDFShading> shadings = new ArrayList<PDFShading>();
  109. private List<PDFPattern> patterns = new ArrayList<PDFPattern>();
  110. private List<PDFLink> links = new ArrayList<PDFLink>();
  111. private List<PDFDestination> destinations;
  112. private List<PDFFileSpec> filespecs = new ArrayList<PDFFileSpec>();
  113. private List<PDFGoToRemote> gotoremotes = new ArrayList<PDFGoToRemote>();
  114. private List<PDFGoTo> gotos = new ArrayList<PDFGoTo>();
  115. private List<PDFLaunch> launches = new ArrayList<PDFLaunch>();
  116. private PDFFactory factory;
  117. private FileIDGenerator fileIDGenerator;
  118. private boolean accessibilityEnabled;
  119. /**
  120. * Creates an empty PDF document.
  121. *
  122. * The constructor creates a /Root and /Pages object to
  123. * track the document but does not write these objects until
  124. * the trailer is written. Note that the object ID of the
  125. * pages object is determined now, and the xref table is
  126. * updated later. This allows Pages to refer to their
  127. * Parent before we write it out.
  128. *
  129. * @param prod the name of the producer of this pdf document
  130. */
  131. public PDFDocument(String prod) {
  132. this(prod, null);
  133. versionController = VersionController.getDynamicVersionController(Version.V1_4, this);
  134. }
  135. /**
  136. * Creates an empty PDF document.
  137. *
  138. * The constructor creates a /Root and /Pages object to
  139. * track the document but does not write these objects until
  140. * the trailer is written. Note that the object ID of the
  141. * pages object is determined now, and the xref table is
  142. * updated later. This allows Pages to refer to their
  143. * Parent before we write it out.
  144. *
  145. * @param prod the name of the producer of this pdf document
  146. * @param versionController the version controller of this PDF document
  147. */
  148. public PDFDocument(String prod, VersionController versionController) {
  149. this.factory = new PDFFactory(this);
  150. /* create the /Root, /Info and /Resources objects */
  151. this.pages = getFactory().makePages();
  152. // Create the Root object
  153. this.root = getFactory().makeRoot(this.pages);
  154. // Create the Resources object
  155. this.resources = getFactory().makeResources();
  156. // Make the /Info record
  157. this.info = getFactory().makeInfo(prod);
  158. this.versionController = versionController;
  159. }
  160. /**
  161. * Returns the current PDF version.
  162. *
  163. * @return returns the PDF version
  164. */
  165. public Version getPDFVersion() {
  166. return versionController.getPDFVersion();
  167. }
  168. /**
  169. * Sets the PDF version of this document.
  170. *
  171. * @param version the PDF version
  172. * @throws IllegalStateException if the version of this PDF is not allowed to change.
  173. */
  174. public void setPDFVersion(Version version) {
  175. versionController.setPDFVersion(version);
  176. }
  177. /** @return the String representing the current PDF version */
  178. public String getPDFVersionString() {
  179. return versionController.getPDFVersion().toString();
  180. }
  181. /** @return the PDF profile currently active. */
  182. public PDFProfile getProfile() {
  183. return this.pdfProfile;
  184. }
  185. /**
  186. * Returns the factory for PDF objects.
  187. *
  188. * @return the {@link PDFFactory} object
  189. */
  190. public PDFFactory getFactory() {
  191. return this.factory;
  192. }
  193. /**
  194. * Converts text to a byte array for writing to a PDF file.
  195. *
  196. * @param text text to convert/encode
  197. * @return the resulting <code>byte</code> array
  198. */
  199. public static byte[] encode(String text) {
  200. try {
  201. return text.getBytes(ENCODING);
  202. } catch (UnsupportedEncodingException uee) {
  203. return text.getBytes();
  204. }
  205. }
  206. /**
  207. * Flushes the given text buffer to an output stream with the right encoding and resets
  208. * the text buffer. This is used to efficiently switch between outputting text and binary
  209. * content.
  210. * @param textBuffer the text buffer
  211. * @param out the output stream to flush the text content to
  212. * @throws IOException if an I/O error occurs while writing to the output stream
  213. */
  214. public static void flushTextBuffer(StringBuilder textBuffer, OutputStream out)
  215. throws IOException {
  216. out.write(encode(textBuffer.toString()));
  217. textBuffer.setLength(0);
  218. }
  219. /**
  220. * Sets the producer of the document.
  221. *
  222. * @param producer string indicating application producing the PDF
  223. */
  224. public void setProducer(String producer) {
  225. this.info.setProducer(producer);
  226. }
  227. /**
  228. * Sets the creation date of the document.
  229. *
  230. * @param date Date to be stored as creation date in the PDF.
  231. */
  232. public void setCreationDate(Date date) {
  233. this.info.setCreationDate(date);
  234. }
  235. /**
  236. * Sets the creator of the document.
  237. *
  238. * @param creator string indicating application creating the document
  239. */
  240. public void setCreator(String creator) {
  241. this.info.setCreator(creator);
  242. }
  243. /**
  244. * Sets the filter map to use for filters in this document.
  245. *
  246. * @param map the map of filter lists for each stream type
  247. */
  248. public void setFilterMap(Map<String, List<String>> map) {
  249. this.filterMap = map;
  250. }
  251. /**
  252. * Returns the {@link PDFFilter}s map used for filters in this document.
  253. *
  254. * @return the map of filters being used
  255. */
  256. public Map<String, List<String>> getFilterMap() {
  257. return this.filterMap;
  258. }
  259. /**
  260. * Returns the {@link PDFPages} object associated with the root object.
  261. *
  262. * @return the {@link PDFPages} object
  263. */
  264. public PDFPages getPages() {
  265. return this.pages;
  266. }
  267. /**
  268. * Get the {@link PDFRoot} object for this document.
  269. *
  270. * @return the {@link PDFRoot} object
  271. */
  272. public PDFRoot getRoot() {
  273. return this.root;
  274. }
  275. /**
  276. * Creates and returns a StructTreeRoot object.
  277. *
  278. * @param parentTree the value of the ParenTree entry
  279. * @return the structure tree root
  280. */
  281. public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) {
  282. PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree);
  283. assignObjectNumber(structTreeRoot);
  284. addTrailerObject(structTreeRoot);
  285. root.setStructTreeRoot(structTreeRoot);
  286. structureTreeElements = new ArrayList<PDFStructElem>();
  287. return structTreeRoot;
  288. }
  289. /**
  290. * Creates and returns a structure element.
  291. *
  292. * @param structureType the structure type of the new element (value for the
  293. * S entry)
  294. * @param parent the parent of the new structure element in the structure
  295. * hierarchy
  296. * @return a dictionary of type StructElem
  297. */
  298. public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) {
  299. PDFStructElem structElem = new PDFStructElem(parent, structureType);
  300. assignObjectNumber(structElem);
  301. structureTreeElements.add(structElem);
  302. return structElem;
  303. }
  304. /**
  305. * Get the {@link PDFInfo} object for this document.
  306. *
  307. * @return the {@link PDFInfo} object
  308. */
  309. public PDFInfo getInfo() {
  310. return this.info;
  311. }
  312. /**
  313. * Registers a {@link PDFObject} in this PDF document.
  314. * The object is assigned a new object number.
  315. *
  316. * @param obj {@link PDFObject} to add
  317. * @return the added {@link PDFObject} added (with its object number set)
  318. */
  319. public PDFObject registerObject(PDFObject obj) {
  320. assignObjectNumber(obj);
  321. addObject(obj);
  322. return obj;
  323. }
  324. /**
  325. * Assigns the {@link PDFObject} an object number,
  326. * and sets the parent of the {@link PDFObject} to this document.
  327. *
  328. * @param obj {@link PDFObject} to assign a number to
  329. */
  330. public void assignObjectNumber(PDFObject obj) {
  331. if (obj == null) {
  332. throw new NullPointerException("obj must not be null");
  333. }
  334. if (obj.hasObjectNumber()) {
  335. throw new IllegalStateException(
  336. "Error registering a PDFObject: "
  337. + "PDFObject already has an object number");
  338. }
  339. PDFDocument currentParent = obj.getDocument();
  340. if (currentParent != null && currentParent != this) {
  341. throw new IllegalStateException(
  342. "Error registering a PDFObject: "
  343. + "PDFObject already has a parent PDFDocument");
  344. }
  345. obj.setObjectNumber(++this.objectcount);
  346. if (currentParent == null) {
  347. obj.setDocument(this);
  348. }
  349. }
  350. /**
  351. * Adds a {@link PDFObject} to this document.
  352. * The object <em>MUST</em> have an object number assigned.
  353. *
  354. * @param obj {@link PDFObject} to add
  355. */
  356. public void addObject(PDFObject obj) {
  357. if (obj == null) {
  358. throw new NullPointerException("obj must not be null");
  359. }
  360. if (!obj.hasObjectNumber()) {
  361. throw new IllegalStateException(
  362. "Error adding a PDFObject: "
  363. + "PDFObject doesn't have an object number");
  364. }
  365. //Add object to list
  366. this.objects.add(obj);
  367. //Add object to special lists where necessary
  368. if (obj instanceof PDFFunction) {
  369. this.functions.add((PDFFunction) obj);
  370. }
  371. if (obj instanceof PDFShading) {
  372. final String shadingName = "Sh" + (++this.shadingCount);
  373. ((PDFShading)obj).setName(shadingName);
  374. this.shadings.add((PDFShading) obj);
  375. }
  376. if (obj instanceof PDFPattern) {
  377. final String patternName = "Pa" + (++this.patternCount);
  378. ((PDFPattern)obj).setName(patternName);
  379. this.patterns.add((PDFPattern) obj);
  380. }
  381. if (obj instanceof PDFFont) {
  382. final PDFFont font = (PDFFont)obj;
  383. this.fontMap.put(font.getName(), font);
  384. }
  385. if (obj instanceof PDFGState) {
  386. this.gstates.add((PDFGState) obj);
  387. }
  388. if (obj instanceof PDFPage) {
  389. this.pages.notifyKidRegistered((PDFPage)obj);
  390. }
  391. if (obj instanceof PDFLaunch) {
  392. this.launches.add((PDFLaunch) obj);
  393. }
  394. if (obj instanceof PDFLink) {
  395. this.links.add((PDFLink) obj);
  396. }
  397. if (obj instanceof PDFFileSpec) {
  398. this.filespecs.add((PDFFileSpec) obj);
  399. }
  400. if (obj instanceof PDFGoToRemote) {
  401. this.gotoremotes.add((PDFGoToRemote) obj);
  402. }
  403. }
  404. /**
  405. * Add trailer object.
  406. * Adds an object to the list of trailer objects.
  407. *
  408. * @param obj the PDF object to add
  409. */
  410. public void addTrailerObject(PDFObject obj) {
  411. this.trailerObjects.add(obj);
  412. if (obj instanceof PDFGoTo) {
  413. this.gotos.add((PDFGoTo) obj);
  414. }
  415. }
  416. /**
  417. * Apply the encryption filter to a PDFStream if encryption is enabled.
  418. *
  419. * @param stream PDFStream to encrypt
  420. */
  421. public void applyEncryption(AbstractPDFStream stream) {
  422. if (isEncryptionActive()) {
  423. this.encryption.applyFilter(stream);
  424. }
  425. }
  426. /**
  427. * Enables PDF encryption.
  428. *
  429. * @param params The encryption parameters for the pdf file
  430. */
  431. public void setEncryption(PDFEncryptionParams params) {
  432. getProfile().verifyEncryptionAllowed();
  433. fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
  434. this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params, this);
  435. if (this.encryption != null) {
  436. PDFObject pdfObject = (PDFObject)this.encryption;
  437. addTrailerObject(pdfObject);
  438. } else {
  439. log.warn(
  440. "PDF encryption is unavailable. PDF will be "
  441. + "generated without encryption.");
  442. }
  443. }
  444. /**
  445. * Indicates whether encryption is active for this PDF or not.
  446. *
  447. * @return boolean True if encryption is active
  448. */
  449. public boolean isEncryptionActive() {
  450. return this.encryption != null;
  451. }
  452. /**
  453. * Returns the active Encryption object.
  454. *
  455. * @return the Encryption object
  456. */
  457. public PDFEncryption getEncryption() {
  458. return this.encryption;
  459. }
  460. private Object findPDFObject(List<? extends PDFObject> list, PDFObject compare) {
  461. for (PDFObject obj : list) {
  462. if (compare.contentEquals(obj)) {
  463. return obj;
  464. }
  465. }
  466. return null;
  467. }
  468. /**
  469. * Looks through the registered functions to see if one that is equal to
  470. * a reference object exists
  471. *
  472. * @param compare reference object
  473. * @return the function if it was found, null otherwise
  474. */
  475. protected PDFFunction findFunction(PDFFunction compare) {
  476. return (PDFFunction)findPDFObject(this.functions, compare);
  477. }
  478. /**
  479. * Looks through the registered shadings to see if one that is equal to
  480. * a reference object exists
  481. *
  482. * @param compare reference object
  483. * @return the shading if it was found, null otherwise
  484. */
  485. protected PDFShading findShading(PDFShading compare) {
  486. return (PDFShading)findPDFObject(this.shadings, compare);
  487. }
  488. /**
  489. * Find a previous pattern.
  490. * The problem with this is for tiling patterns the pattern
  491. * data stream is stored and may use up memory, usually this
  492. * would only be a small amount of data.
  493. *
  494. * @param compare reference object
  495. * @return the shading if it was found, null otherwise
  496. */
  497. protected PDFPattern findPattern(PDFPattern compare) {
  498. return (PDFPattern)findPDFObject(this.patterns, compare);
  499. }
  500. /**
  501. * Finds a font.
  502. *
  503. * @param fontname name of the font
  504. * @return PDFFont the requested font, null if it wasn't found
  505. */
  506. protected PDFFont findFont(String fontname) {
  507. return this.fontMap.get(fontname);
  508. }
  509. /**
  510. * Finds a named destination.
  511. *
  512. * @param compare reference object to use as search template
  513. * @return the link if found, null otherwise
  514. */
  515. protected PDFDestination findDestination(PDFDestination compare) {
  516. int index = getDestinationList().indexOf(compare);
  517. if (index >= 0) {
  518. return getDestinationList().get(index);
  519. } else {
  520. return null;
  521. }
  522. }
  523. /**
  524. * Finds a link.
  525. *
  526. * @param compare reference object to use as search template
  527. * @return the link if found, null otherwise
  528. */
  529. protected PDFLink findLink(PDFLink compare) {
  530. return (PDFLink)findPDFObject(this.links, compare);
  531. }
  532. /**
  533. * Finds a file spec.
  534. *
  535. * @param compare reference object to use as search template
  536. * @return the file spec if found, null otherwise
  537. */
  538. protected PDFFileSpec findFileSpec(PDFFileSpec compare) {
  539. return (PDFFileSpec)findPDFObject(this.filespecs, compare);
  540. }
  541. /**
  542. * Finds a goto remote.
  543. *
  544. * @param compare reference object to use as search template
  545. * @return the goto remote if found, null otherwise
  546. */
  547. protected PDFGoToRemote findGoToRemote(PDFGoToRemote compare) {
  548. return (PDFGoToRemote)findPDFObject(this.gotoremotes, compare);
  549. }
  550. /**
  551. * Finds a goto.
  552. *
  553. * @param compare reference object to use as search template
  554. * @return the goto if found, null otherwise
  555. */
  556. protected PDFGoTo findGoTo(PDFGoTo compare) {
  557. return (PDFGoTo)findPDFObject(this.gotos, compare);
  558. }
  559. /**
  560. * Finds a launch.
  561. *
  562. * @param compare reference object to use as search template
  563. * @return the launch if found, null otherwise
  564. */
  565. protected PDFLaunch findLaunch(PDFLaunch compare) {
  566. return (PDFLaunch) findPDFObject(this.launches, compare);
  567. }
  568. /**
  569. * Looks for an existing GState to use
  570. *
  571. * @param wanted requested features
  572. * @param current currently active features
  573. * @return the GState if found, null otherwise
  574. */
  575. protected PDFGState findGState(PDFGState wanted, PDFGState current) {
  576. PDFGState poss;
  577. Iterator<PDFGState> iter = this.gstates.iterator();
  578. while (iter.hasNext()) {
  579. PDFGState avail = iter.next();
  580. poss = new PDFGState();
  581. poss.addValues(current);
  582. poss.addValues(avail);
  583. if (poss.equals(wanted)) {
  584. return avail;
  585. }
  586. }
  587. return null;
  588. }
  589. /**
  590. * Returns the PDF color space object.
  591. *
  592. * @return the color space
  593. */
  594. public PDFDeviceColorSpace getPDFColorSpace() {
  595. return this.colorspace;
  596. }
  597. /**
  598. * Returns the color space.
  599. *
  600. * @return the color space
  601. */
  602. public int getColorSpace() {
  603. return getPDFColorSpace().getColorSpace();
  604. }
  605. /**
  606. * Set the color space.
  607. * This is used when creating gradients.
  608. *
  609. * @param theColorspace the new color space
  610. */
  611. public void setColorSpace(int theColorspace) {
  612. this.colorspace.setColorSpace(theColorspace);
  613. }
  614. /**
  615. * Returns the font map for this document.
  616. *
  617. * @return the map of fonts used in this document
  618. */
  619. public Map<String, PDFFont> getFontMap() {
  620. return this.fontMap;
  621. }
  622. /**
  623. * Get an image from the image map.
  624. *
  625. * @param key the image key to look for
  626. * @return the image or PDFXObject for the key if found
  627. * @deprecated Use getXObject instead (so forms are treated in the same way)
  628. */
  629. @Deprecated
  630. public PDFImageXObject getImage(String key) {
  631. return (PDFImageXObject)this.xObjectsMap.get(key);
  632. }
  633. /**
  634. * Get an XObject from the image map.
  635. *
  636. * @param key the XObject key to look for
  637. * @return the PDFXObject for the key if found
  638. */
  639. public PDFXObject getXObject(String key) {
  640. return this.xObjectsMap.get(key);
  641. }
  642. /**
  643. * Adds a destination to the document.
  644. * @param destination the destination object
  645. */
  646. public void addDestination(PDFDestination destination) {
  647. if (this.destinations == null) {
  648. this.destinations = new ArrayList<PDFDestination>();
  649. }
  650. this.destinations.add(destination);
  651. }
  652. /**
  653. * Gets the list of named destinations.
  654. *
  655. * @return the list of named destinations.
  656. */
  657. public List<PDFDestination> getDestinationList() {
  658. if (hasDestinations()) {
  659. return this.destinations;
  660. } else {
  661. return Collections.emptyList();
  662. }
  663. }
  664. /**
  665. * Gets whether the document has named destinations.
  666. *
  667. * @return whether the document has named destinations.
  668. */
  669. public boolean hasDestinations() {
  670. return this.destinations != null && !this.destinations.isEmpty();
  671. }
  672. /**
  673. * Add an image to the PDF document.
  674. * This adds an image to the PDF objects.
  675. * If an image with the same key already exists it will return the
  676. * old {@link PDFXObject}.
  677. *
  678. * @param res the PDF resource context to add to, may be null
  679. * @param img the PDF image to add
  680. * @return the PDF XObject that references the PDF image data
  681. */
  682. public PDFImageXObject addImage(PDFResourceContext res, PDFImage img) {
  683. // check if already created
  684. String key = img.getKey();
  685. PDFImageXObject xObject = (PDFImageXObject)this.xObjectsMap.get(key);
  686. if (xObject != null) {
  687. if (res != null) {
  688. res.getPDFResources().addXObject(xObject);
  689. }
  690. return xObject;
  691. }
  692. // setup image
  693. img.setup(this);
  694. // create a new XObject
  695. xObject = new PDFImageXObject(++this.xObjectCount, img);
  696. registerObject(xObject);
  697. this.resources.addXObject(xObject);
  698. if (res != null) {
  699. res.getPDFResources().addXObject(xObject);
  700. }
  701. this.xObjectsMap.put(key, xObject);
  702. return xObject;
  703. }
  704. /**
  705. * Add a form XObject to the PDF document.
  706. * This adds a Form XObject to the PDF objects.
  707. * If a Form XObject with the same key already exists it will return the
  708. * old {@link PDFFormXObject}.
  709. *
  710. * @param res the PDF resource context to add to, may be null
  711. * @param cont the PDF Stream contents of the Form XObject
  712. * @param formres a reference to the PDF Resources for the Form XObject data
  713. * @param key the key for the object
  714. * @return the PDF Form XObject that references the PDF data
  715. */
  716. public PDFFormXObject addFormXObject(
  717. PDFResourceContext res,
  718. PDFStream cont,
  719. PDFReference formres,
  720. String key) {
  721. // check if already created
  722. PDFFormXObject xObject = (PDFFormXObject)xObjectsMap.get(key);
  723. if (xObject != null) {
  724. if (res != null) {
  725. res.getPDFResources().addXObject(xObject);
  726. }
  727. return xObject;
  728. }
  729. xObject = new PDFFormXObject(
  730. ++this.xObjectCount,
  731. cont,
  732. formres);
  733. registerObject(xObject);
  734. this.resources.addXObject(xObject);
  735. if (res != null) {
  736. res.getPDFResources().addXObject(xObject);
  737. }
  738. this.xObjectsMap.put(key, xObject);
  739. return xObject;
  740. }
  741. /**
  742. * Get the root Outlines object. This method does not write
  743. * the outline to the PDF document, it simply creates a
  744. * reference for later.
  745. *
  746. * @return the PDF Outline root object
  747. */
  748. public PDFOutline getOutlineRoot() {
  749. if (this.outlineRoot != null) {
  750. return this.outlineRoot;
  751. }
  752. this.outlineRoot = new PDFOutline(null, null, true);
  753. assignObjectNumber(this.outlineRoot);
  754. addTrailerObject(this.outlineRoot);
  755. this.root.setRootOutline(this.outlineRoot);
  756. return this.outlineRoot;
  757. }
  758. /**
  759. * Get the /Resources object for the document
  760. *
  761. * @return the /Resources object
  762. */
  763. public PDFResources getResources() {
  764. return this.resources;
  765. }
  766. public void enableAccessibility(boolean enableAccessibility) {
  767. this.accessibilityEnabled = enableAccessibility;
  768. }
  769. /**
  770. * Writes out the entire document
  771. *
  772. * @param stream the OutputStream to output the document to
  773. * @throws IOException if there is an exception writing to the output stream
  774. */
  775. public void output(OutputStream stream) throws IOException {
  776. //Write out objects until the list is empty. This approach (used with a
  777. //LinkedList) allows for output() methods to create and register objects
  778. //on the fly even during serialization.
  779. while (this.objects.size() > 0) {
  780. PDFObject object = this.objects.remove(0);
  781. streamIndirectObject(object, stream);
  782. }
  783. }
  784. private void streamIndirectObject(PDFObject o, OutputStream stream) throws IOException {
  785. recordObjectOffset(o);
  786. this.position += outputIndirectObject(o, stream);
  787. }
  788. private void streamIndirectObjects(Collection<? extends PDFObject> objects, OutputStream stream)
  789. throws IOException {
  790. for (PDFObject o : objects) {
  791. streamIndirectObject(o, stream);
  792. }
  793. }
  794. private void recordObjectOffset(PDFObject object) {
  795. int index = object.getObjectNumber() - 1;
  796. while (indirectObjectOffsets.size() <= index) {
  797. indirectObjectOffsets.add(null);
  798. }
  799. indirectObjectOffsets.set(index, position);
  800. }
  801. /**
  802. * Outputs the given object, wrapped by obj/endobj, to the given stream.
  803. *
  804. * @param object an indirect object, as described in Section 3.2.9 of the PDF 1.5
  805. * Reference.
  806. * @param stream the stream to which the object must be output
  807. * @throws IllegalArgumentException if the object is not an indirect object
  808. */
  809. public static int outputIndirectObject(PDFObject object, OutputStream stream)
  810. throws IOException {
  811. if (!object.hasObjectNumber()) {
  812. throw new IllegalArgumentException("Not an indirect object");
  813. }
  814. byte[] obj = encode(object.getObjectID());
  815. stream.write(obj);
  816. int length = object.output(stream);
  817. byte[] endobj = encode("\nendobj\n");
  818. stream.write(endobj);
  819. return obj.length + length + endobj.length;
  820. }
  821. /**
  822. * Write the PDF header.
  823. *
  824. * This method must be called prior to formatting
  825. * and outputting AreaTrees.
  826. *
  827. * @param stream the OutputStream to write the header to
  828. * @throws IOException if there is an exception writing to the output stream
  829. */
  830. public void outputHeader(OutputStream stream) throws IOException {
  831. this.position = 0;
  832. getProfile().verifyPDFVersion();
  833. byte[] pdf = encode("%PDF-" + getPDFVersionString() + "\n");
  834. stream.write(pdf);
  835. this.position += pdf.length;
  836. // output a binary comment as recommended by the PDF spec (3.4.1)
  837. byte[] bin = {
  838. (byte)'%',
  839. (byte)0xAA,
  840. (byte)0xAB,
  841. (byte)0xAC,
  842. (byte)0xAD,
  843. (byte)'\n' };
  844. stream.write(bin);
  845. this.position += bin.length;
  846. }
  847. /**
  848. * Write the trailer
  849. *
  850. * @param stream the OutputStream to write the trailer to
  851. * @throws IOException if there is an exception writing to the output stream
  852. */
  853. public void outputTrailer(OutputStream stream) throws IOException {
  854. createDestinations();
  855. output(stream);
  856. outputTrailerObjectsAndXref(stream);
  857. }
  858. private void createDestinations() {
  859. if (hasDestinations()) {
  860. Collections.sort(this.destinations, new DestinationComparator());
  861. PDFDests dests = getFactory().makeDests(this.destinations);
  862. if (this.root.getNames() == null) {
  863. this.root.setNames(getFactory().makeNames());
  864. }
  865. this.root.getNames().setDests(dests);
  866. }
  867. }
  868. private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException {
  869. TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
  870. ? new CompressedTrailerOutputHelper()
  871. : new UncompressedTrailerOutputHelper();
  872. if (structureTreeElements != null) {
  873. trailerOutputHelper.outputStructureTreeElements(stream);
  874. }
  875. streamIndirectObjects(trailerObjects, stream);
  876. TrailerDictionary trailerDictionary = createTrailerDictionary();
  877. long startxref = trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary);
  878. String trailer = "startxref\n" + startxref + "\n%%EOF\n";
  879. stream.write(encode(trailer));
  880. }
  881. private boolean mayCompressStructureTreeElements() {
  882. return accessibilityEnabled
  883. && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0;
  884. }
  885. private TrailerDictionary createTrailerDictionary() {
  886. FileIDGenerator gen = getFileIDGenerator();
  887. TrailerDictionary trailerDictionary = new TrailerDictionary(this)
  888. .setRoot(root)
  889. .setInfo(info)
  890. .setFileID(gen.getOriginalFileID(), gen.getUpdatedFileID());
  891. if (isEncryptionActive()) {
  892. trailerDictionary.setEncryption(encryption);
  893. }
  894. return trailerDictionary;
  895. }
  896. private interface TrailerOutputHelper {
  897. void outputStructureTreeElements(OutputStream stream) throws IOException;
  898. /**
  899. * @return the offset of the cross-reference object (the value of startxref)
  900. */
  901. long outputCrossReferenceObject(OutputStream stream, TrailerDictionary trailerDictionary)
  902. throws IOException;
  903. }
  904. private class UncompressedTrailerOutputHelper implements TrailerOutputHelper {
  905. public void outputStructureTreeElements(OutputStream stream)
  906. throws IOException {
  907. streamIndirectObjects(structureTreeElements, stream);
  908. }
  909. public long outputCrossReferenceObject(OutputStream stream,
  910. TrailerDictionary trailerDictionary) throws IOException {
  911. new CrossReferenceTable(trailerDictionary, position,
  912. indirectObjectOffsets).output(stream);
  913. return position;
  914. }
  915. }
  916. private class CompressedTrailerOutputHelper implements TrailerOutputHelper {
  917. private ObjectStreamManager structureTreeObjectStreams;
  918. public void outputStructureTreeElements(OutputStream stream)
  919. throws IOException {
  920. assert structureTreeElements.size() > 0;
  921. structureTreeObjectStreams = new ObjectStreamManager(PDFDocument.this);
  922. for (PDFStructElem structElem : structureTreeElements) {
  923. structureTreeObjectStreams.add(structElem);
  924. }
  925. }
  926. public long outputCrossReferenceObject(OutputStream stream,
  927. TrailerDictionary trailerDictionary) throws IOException {
  928. // Outputting the object streams should not have created new indirect objects
  929. assert objects.isEmpty();
  930. new CrossReferenceStream(PDFDocument.this, ++objectcount, trailerDictionary, position,
  931. indirectObjectOffsets,
  932. structureTreeObjectStreams.getCompressedObjectReferences())
  933. .output(stream);
  934. return position;
  935. }
  936. }
  937. long getCurrentFileSize() {
  938. return position;
  939. }
  940. FileIDGenerator getFileIDGenerator() {
  941. if (fileIDGenerator == null) {
  942. try {
  943. fileIDGenerator = FileIDGenerator.getDigestFileIDGenerator(this);
  944. } catch (NoSuchAlgorithmException e) {
  945. fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
  946. }
  947. }
  948. return fileIDGenerator;
  949. }
  950. }