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 37KB

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