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

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