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.

PDFRenderer.java 72KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.render.pdf;
  19. // Java
  20. import java.awt.Color;
  21. import java.awt.Point;
  22. import java.awt.Rectangle;
  23. import java.awt.color.ICC_Profile;
  24. import java.awt.geom.AffineTransform;
  25. import java.awt.geom.Point2D;
  26. import java.awt.geom.Rectangle2D;
  27. import java.io.FileNotFoundException;
  28. import java.io.IOException;
  29. import java.io.InputStream;
  30. import java.io.OutputStream;
  31. import java.net.URL;
  32. import java.util.Iterator;
  33. import java.util.List;
  34. import java.util.Map;
  35. import javax.xml.transform.Source;
  36. import javax.xml.transform.stream.StreamSource;
  37. import org.apache.commons.io.IOUtils;
  38. import org.apache.fop.apps.FOPException;
  39. import org.apache.fop.apps.FOUserAgent;
  40. import org.apache.fop.apps.MimeConstants;
  41. import org.apache.fop.area.Area;
  42. import org.apache.fop.area.Block;
  43. import org.apache.fop.area.BookmarkData;
  44. import org.apache.fop.area.CTM;
  45. import org.apache.fop.area.DestinationData;
  46. import org.apache.fop.area.LineArea;
  47. import org.apache.fop.area.OffDocumentExtensionAttachment;
  48. import org.apache.fop.area.OffDocumentItem;
  49. import org.apache.fop.area.PageSequence;
  50. import org.apache.fop.area.PageViewport;
  51. import org.apache.fop.area.Trait;
  52. import org.apache.fop.area.inline.AbstractTextArea;
  53. import org.apache.fop.area.inline.Image;
  54. import org.apache.fop.area.inline.InlineArea;
  55. import org.apache.fop.area.inline.InlineParent;
  56. import org.apache.fop.area.inline.Leader;
  57. import org.apache.fop.area.inline.SpaceArea;
  58. import org.apache.fop.area.inline.TextArea;
  59. import org.apache.fop.area.inline.WordArea;
  60. import org.apache.fop.datatypes.URISpecification;
  61. import org.apache.fop.events.ResourceEventProducer;
  62. import org.apache.fop.fo.Constants;
  63. import org.apache.fop.fo.extensions.ExtensionAttachment;
  64. import org.apache.fop.fo.extensions.xmp.XMPMetadata;
  65. import org.apache.fop.fonts.Font;
  66. import org.apache.fop.fonts.LazyFont;
  67. import org.apache.fop.fonts.SingleByteFont;
  68. import org.apache.fop.fonts.Typeface;
  69. import org.apache.fop.pdf.PDFAMode;
  70. import org.apache.fop.pdf.PDFAction;
  71. import org.apache.fop.pdf.PDFAnnotList;
  72. import org.apache.fop.pdf.PDFColor;
  73. import org.apache.fop.pdf.PDFConformanceException;
  74. import org.apache.fop.pdf.PDFDictionary;
  75. import org.apache.fop.pdf.PDFDocument;
  76. import org.apache.fop.pdf.PDFEncryptionManager;
  77. import org.apache.fop.pdf.PDFEncryptionParams;
  78. import org.apache.fop.pdf.PDFFactory;
  79. import org.apache.fop.pdf.PDFFilterList;
  80. import org.apache.fop.pdf.PDFGoTo;
  81. import org.apache.fop.pdf.PDFICCBasedColorSpace;
  82. import org.apache.fop.pdf.PDFICCStream;
  83. import org.apache.fop.pdf.PDFInfo;
  84. import org.apache.fop.pdf.PDFLink;
  85. import org.apache.fop.pdf.PDFMetadata;
  86. import org.apache.fop.pdf.PDFNumber;
  87. import org.apache.fop.pdf.PDFNumsArray;
  88. import org.apache.fop.pdf.PDFOutline;
  89. import org.apache.fop.pdf.PDFOutputIntent;
  90. import org.apache.fop.pdf.PDFPage;
  91. import org.apache.fop.pdf.PDFPageLabels;
  92. import org.apache.fop.pdf.PDFPaintingState;
  93. import org.apache.fop.pdf.PDFResourceContext;
  94. import org.apache.fop.pdf.PDFResources;
  95. import org.apache.fop.pdf.PDFStream;
  96. import org.apache.fop.pdf.PDFTextUtil;
  97. import org.apache.fop.pdf.PDFXMode;
  98. import org.apache.fop.pdf.PDFXObject;
  99. import org.apache.fop.render.AbstractPathOrientedRenderer;
  100. import org.apache.fop.render.Graphics2DAdapter;
  101. import org.apache.fop.render.RendererContext;
  102. import org.apache.fop.util.AbstractPaintingState;
  103. import org.apache.fop.util.CharUtilities;
  104. import org.apache.fop.util.ColorProfileUtil;
  105. import org.apache.fop.util.ColorUtil;
  106. import org.apache.fop.util.AbstractPaintingState.AbstractData;
  107. import org.apache.xmlgraphics.image.loader.ImageException;
  108. import org.apache.xmlgraphics.image.loader.ImageInfo;
  109. import org.apache.xmlgraphics.image.loader.ImageManager;
  110. import org.apache.xmlgraphics.image.loader.ImageSessionContext;
  111. import org.apache.xmlgraphics.image.loader.util.ImageUtil;
  112. import org.apache.xmlgraphics.xmp.Metadata;
  113. import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
  114. import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
  115. /**
  116. * Renderer that renders areas to PDF.
  117. */
  118. public class PDFRenderer extends AbstractPathOrientedRenderer {
  119. /**
  120. * The mime type for pdf
  121. */
  122. public static final String MIME_TYPE = MimeConstants.MIME_PDF;
  123. /** Normal PDF resolution (72dpi) */
  124. public static final int NORMAL_PDF_RESOLUTION = 72;
  125. /** PDF encryption parameter: all parameters as object, datatype: PDFEncryptionParams */
  126. public static final String ENCRYPTION_PARAMS = "encryption-params";
  127. /** PDF encryption parameter: user password, datatype: String */
  128. public static final String USER_PASSWORD = "user-password";
  129. /** PDF encryption parameter: owner password, datatype: String */
  130. public static final String OWNER_PASSWORD = "owner-password";
  131. /** PDF encryption parameter: Forbids printing, datatype: Boolean or "true"/"false" */
  132. public static final String NO_PRINT = "noprint";
  133. /** PDF encryption parameter: Forbids copying content, datatype: Boolean or "true"/"false" */
  134. public static final String NO_COPY_CONTENT = "nocopy";
  135. /** PDF encryption parameter: Forbids editing content, datatype: Boolean or "true"/"false" */
  136. public static final String NO_EDIT_CONTENT = "noedit";
  137. /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */
  138. public static final String NO_ANNOTATIONS = "noannotations";
  139. /** Rendering Options key for the PDF/A mode. */
  140. public static final String PDF_A_MODE = "pdf-a-mode";
  141. /** Rendering Options key for the PDF/X mode. */
  142. public static final String PDF_X_MODE = "pdf-x-mode";
  143. /** Rendering Options key for the ICC profile for the output intent. */
  144. public static final String KEY_OUTPUT_PROFILE = "output-profile";
  145. /**
  146. * Rendering Options key for disabling the sRGB color space (only possible if no PDF/A or
  147. * PDF/X profile is active).
  148. */
  149. public static final String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace";
  150. /** Controls whether comments are written to the PDF stream. */
  151. protected static final boolean WRITE_COMMENTS = true;
  152. /**
  153. * the PDF Document being created
  154. */
  155. protected PDFDocument pdfDoc;
  156. /** the PDF/A mode (Default: disabled) */
  157. protected PDFAMode pdfAMode = PDFAMode.DISABLED;
  158. /** the PDF/X mode (Default: disabled) */
  159. protected PDFXMode pdfXMode = PDFXMode.DISABLED;
  160. /**
  161. * Map of pages using the PageViewport as the key
  162. * this is used for prepared pages that cannot be immediately
  163. * rendered
  164. */
  165. protected Map pages = null;
  166. /**
  167. * Maps unique PageViewport key to PDF page reference
  168. */
  169. protected Map pageReferences = new java.util.HashMap();
  170. /**
  171. * Maps unique PageViewport key back to PageViewport itself
  172. */
  173. protected Map pvReferences = new java.util.HashMap();
  174. /**
  175. * Maps XSL-FO element IDs to their on-page XY-positions
  176. * Must be used in conjunction with the page reference to fully specify the PDFGoTo details
  177. */
  178. protected Map idPositions = new java.util.HashMap();
  179. /**
  180. * Maps XSL-FO element IDs to PDFGoTo objects targeting the corresponding areas
  181. * These objects may not all be fully filled in yet
  182. */
  183. protected Map idGoTos = new java.util.HashMap();
  184. /**
  185. * The PDFGoTos in idGoTos that are not complete yet
  186. */
  187. protected List unfinishedGoTos = new java.util.ArrayList();
  188. // can't use a Set because PDFGoTo.equals returns true if the target is the same,
  189. // even if the object number differs
  190. /**
  191. * The output stream to write the document to
  192. */
  193. protected OutputStream ostream;
  194. /**
  195. * the /Resources object of the PDF document being created
  196. */
  197. protected PDFResources pdfResources;
  198. /**
  199. * the current stream to add PDF commands to
  200. */
  201. protected PDFStream currentStream;
  202. /**
  203. * the current annotation list to add annotations to
  204. */
  205. protected PDFResourceContext currentContext = null;
  206. /**
  207. * the current page to add annotations to
  208. */
  209. protected PDFPage currentPage;
  210. /**
  211. * the current page's PDF reference string (to avoid numerous function calls)
  212. */
  213. protected String currentPageRef;
  214. /** the (optional) encryption parameters */
  215. protected PDFEncryptionParams encryptionParams;
  216. /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */
  217. protected PDFICCStream outputProfile;
  218. /** the default sRGB color space. */
  219. protected PDFICCBasedColorSpace sRGBColorSpace;
  220. /** controls whether the sRGB color space should be installed */
  221. protected boolean disableSRGBColorSpace = false;
  222. /** Optional URI to an output profile to be used. */
  223. protected String outputProfileURI;
  224. /** Painting state */
  225. protected PDFPaintingState paintingState = null;
  226. /** Text generation utility holding the current font status */
  227. protected PDFTextUtil textutil;
  228. /** page height */
  229. protected int pageHeight;
  230. /** Registry of PDF filters */
  231. protected Map filterMap;
  232. /** Image handler registry */
  233. private final PDFImageHandlerRegistry imageHandlerRegistry = new PDFImageHandlerRegistry();
  234. /**
  235. * create the PDF renderer
  236. */
  237. public PDFRenderer() {
  238. }
  239. private boolean booleanValueOf(Object obj) {
  240. if (obj instanceof Boolean) {
  241. return ((Boolean)obj).booleanValue();
  242. } else if (obj instanceof String) {
  243. return Boolean.valueOf((String)obj).booleanValue();
  244. } else {
  245. throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected.");
  246. }
  247. }
  248. /**
  249. * {@inheritDoc}
  250. */
  251. public void setUserAgent(FOUserAgent agent) {
  252. super.setUserAgent(agent);
  253. PDFEncryptionParams params
  254. = (PDFEncryptionParams)agent.getRendererOptions().get(ENCRYPTION_PARAMS);
  255. if (params != null) {
  256. this.encryptionParams = params; //overwrite if available
  257. }
  258. String pwd;
  259. pwd = (String)agent.getRendererOptions().get(USER_PASSWORD);
  260. if (pwd != null) {
  261. if (encryptionParams == null) {
  262. this.encryptionParams = new PDFEncryptionParams();
  263. }
  264. this.encryptionParams.setUserPassword(pwd);
  265. }
  266. pwd = (String)agent.getRendererOptions().get(OWNER_PASSWORD);
  267. if (pwd != null) {
  268. if (encryptionParams == null) {
  269. this.encryptionParams = new PDFEncryptionParams();
  270. }
  271. this.encryptionParams.setOwnerPassword(pwd);
  272. }
  273. Object setting;
  274. setting = agent.getRendererOptions().get(NO_PRINT);
  275. if (setting != null) {
  276. if (encryptionParams == null) {
  277. this.encryptionParams = new PDFEncryptionParams();
  278. }
  279. this.encryptionParams.setAllowPrint(!booleanValueOf(setting));
  280. }
  281. setting = agent.getRendererOptions().get(NO_COPY_CONTENT);
  282. if (setting != null) {
  283. if (encryptionParams == null) {
  284. this.encryptionParams = new PDFEncryptionParams();
  285. }
  286. this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting));
  287. }
  288. setting = agent.getRendererOptions().get(NO_EDIT_CONTENT);
  289. if (setting != null) {
  290. if (encryptionParams == null) {
  291. this.encryptionParams = new PDFEncryptionParams();
  292. }
  293. this.encryptionParams.setAllowEditContent(!booleanValueOf(setting));
  294. }
  295. setting = agent.getRendererOptions().get(NO_ANNOTATIONS);
  296. if (setting != null) {
  297. if (encryptionParams == null) {
  298. this.encryptionParams = new PDFEncryptionParams();
  299. }
  300. this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting));
  301. }
  302. String s = (String)agent.getRendererOptions().get(PDF_A_MODE);
  303. if (s != null) {
  304. this.pdfAMode = PDFAMode.valueOf(s);
  305. }
  306. s = (String)agent.getRendererOptions().get(PDF_X_MODE);
  307. if (s != null) {
  308. this.pdfXMode = PDFXMode.valueOf(s);
  309. }
  310. s = (String)agent.getRendererOptions().get(KEY_OUTPUT_PROFILE);
  311. if (s != null) {
  312. this.outputProfileURI = s;
  313. }
  314. setting = agent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE);
  315. if (setting != null) {
  316. this.disableSRGBColorSpace = booleanValueOf(setting);
  317. }
  318. }
  319. /**
  320. * {@inheritDoc}
  321. */
  322. public void startRenderer(OutputStream stream) throws IOException {
  323. if (userAgent == null) {
  324. throw new IllegalStateException("UserAgent must be set before starting the renderer");
  325. }
  326. ostream = stream;
  327. this.pdfDoc = new PDFDocument(
  328. userAgent.getProducer() != null ? userAgent.getProducer() : "");
  329. this.pdfDoc.getProfile().setPDFAMode(this.pdfAMode);
  330. this.pdfDoc.getProfile().setPDFXMode(this.pdfXMode);
  331. this.pdfDoc.getInfo().setCreator(userAgent.getCreator());
  332. this.pdfDoc.getInfo().setCreationDate(userAgent.getCreationDate());
  333. this.pdfDoc.getInfo().setAuthor(userAgent.getAuthor());
  334. this.pdfDoc.getInfo().setTitle(userAgent.getTitle());
  335. this.pdfDoc.getInfo().setKeywords(userAgent.getKeywords());
  336. this.pdfDoc.setFilterMap(filterMap);
  337. this.pdfDoc.outputHeader(ostream);
  338. //Setup encryption if necessary
  339. PDFEncryptionManager.setupPDFEncryption(encryptionParams, this.pdfDoc);
  340. addsRGBColorSpace();
  341. if (this.outputProfileURI != null) {
  342. addDefaultOutputProfile();
  343. }
  344. if (pdfXMode != PDFXMode.DISABLED) {
  345. log.debug(pdfXMode + " is active.");
  346. log.warn("Note: " + pdfXMode
  347. + " support is work-in-progress and not fully implemented, yet!");
  348. addPDFXOutputIntent();
  349. }
  350. if (pdfAMode.isPDFA1LevelB()) {
  351. log.debug("PDF/A is active. Conformance Level: " + pdfAMode);
  352. addPDFA1OutputIntent();
  353. }
  354. }
  355. private void addsRGBColorSpace() throws IOException {
  356. if (disableSRGBColorSpace) {
  357. if (this.pdfAMode != PDFAMode.DISABLED
  358. || this.pdfXMode != PDFXMode.DISABLED
  359. || this.outputProfileURI != null) {
  360. throw new IllegalStateException("It is not possible to disable the sRGB color"
  361. + " space if PDF/A or PDF/X functionality is enabled or an"
  362. + " output profile is set!");
  363. }
  364. } else {
  365. if (this.sRGBColorSpace != null) {
  366. return;
  367. }
  368. //Map sRGB as default RGB profile for DeviceRGB
  369. this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc);
  370. }
  371. }
  372. private void addDefaultOutputProfile() throws IOException {
  373. if (this.outputProfile != null) {
  374. return;
  375. }
  376. ICC_Profile profile;
  377. InputStream in = null;
  378. if (this.outputProfileURI != null) {
  379. this.outputProfile = pdfDoc.getFactory().makePDFICCStream();
  380. Source src = userAgent.resolveURI(this.outputProfileURI);
  381. if (src == null) {
  382. throw new IOException("Output profile not found: " + this.outputProfileURI);
  383. }
  384. if (src instanceof StreamSource) {
  385. in = ((StreamSource)src).getInputStream();
  386. } else {
  387. in = new URL(src.getSystemId()).openStream();
  388. }
  389. try {
  390. profile = ICC_Profile.getInstance(in);
  391. } finally {
  392. IOUtils.closeQuietly(in);
  393. }
  394. this.outputProfile.setColorSpace(profile, null);
  395. } else {
  396. //Fall back to sRGB profile
  397. outputProfile = sRGBColorSpace.getICCStream();
  398. }
  399. }
  400. /**
  401. * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces
  402. * are used (which is true if we use DeviceRGB to represent sRGB colors).
  403. * @throws IOException in case of an I/O problem
  404. */
  405. private void addPDFA1OutputIntent() throws IOException {
  406. addDefaultOutputProfile();
  407. String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());
  408. PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent();
  409. outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1);
  410. outputIntent.setDestOutputProfile(this.outputProfile);
  411. outputIntent.setOutputConditionIdentifier(desc);
  412. outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());
  413. pdfDoc.getRoot().addOutputIntent(outputIntent);
  414. }
  415. /**
  416. * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces
  417. * are used (which is true if we use DeviceRGB to represent sRGB colors).
  418. * @throws IOException in case of an I/O problem
  419. */
  420. private void addPDFXOutputIntent() throws IOException {
  421. addDefaultOutputProfile();
  422. String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile());
  423. int deviceClass = this.outputProfile.getICCProfile().getProfileClass();
  424. if (deviceClass != ICC_Profile.CLASS_OUTPUT) {
  425. throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that"
  426. + " the DestOutputProfile be an Output Device Profile. "
  427. + desc + " does not match that requirement.");
  428. }
  429. PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent();
  430. outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX);
  431. outputIntent.setDestOutputProfile(this.outputProfile);
  432. outputIntent.setOutputConditionIdentifier(desc);
  433. outputIntent.setInfo(outputIntent.getOutputConditionIdentifier());
  434. pdfDoc.getRoot().addOutputIntent(outputIntent);
  435. }
  436. /**
  437. * Checks if there are any unfinished PDFGoTos left in the list and resolves them
  438. * to a default position on the page. Logs a warning, as this should not happen.
  439. */
  440. protected void finishOpenGoTos() {
  441. int count = unfinishedGoTos.size();
  442. if (count > 0) {
  443. // TODO : page height may not be the same for all targeted pages
  444. Point2D.Float defaultPos = new Point2D.Float(0f, pageHeight / 1000f); // top-o-page
  445. while (!unfinishedGoTos.isEmpty()) {
  446. PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0);
  447. finishIDGoTo(gt, defaultPos);
  448. }
  449. PDFEventProducer eventProducer = PDFEventProducer.Provider.get(
  450. getUserAgent().getEventBroadcaster());
  451. eventProducer.nonFullyResolvedLinkTargets(this, count);
  452. // dysfunctional if pageref is null
  453. }
  454. }
  455. /**
  456. * {@inheritDoc}
  457. */
  458. public void stopRenderer() throws IOException {
  459. finishOpenGoTos();
  460. pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
  461. pdfDoc.outputTrailer(ostream);
  462. this.pdfDoc = null;
  463. ostream = null;
  464. pages = null;
  465. pageReferences.clear();
  466. pvReferences.clear();
  467. pdfResources = null;
  468. currentStream = null;
  469. currentContext = null;
  470. currentPage = null;
  471. paintingState = null;
  472. this.textutil = null;
  473. idPositions.clear();
  474. idGoTos.clear();
  475. }
  476. /**
  477. * {@inheritDoc}
  478. */
  479. public boolean supportsOutOfOrder() {
  480. //return false;
  481. return true;
  482. }
  483. /**
  484. * {@inheritDoc}
  485. */
  486. public void processOffDocumentItem(OffDocumentItem odi) {
  487. if (odi instanceof DestinationData) {
  488. // render Destinations
  489. renderDestination((DestinationData) odi);
  490. } else if (odi instanceof BookmarkData) {
  491. // render Bookmark-Tree
  492. renderBookmarkTree((BookmarkData) odi);
  493. } else if (odi instanceof OffDocumentExtensionAttachment) {
  494. ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
  495. if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
  496. renderXMPMetadata((XMPMetadata)attachment);
  497. }
  498. }
  499. }
  500. private void renderDestination(DestinationData dd) {
  501. String targetID = dd.getIDRef();
  502. if (targetID == null || targetID.length() == 0) {
  503. throw new IllegalArgumentException("DestinationData must contain a ID reference");
  504. }
  505. PageViewport pv = dd.getPageViewport();
  506. if (pv != null) {
  507. PDFGoTo gt = getPDFGoToForID(targetID, pv.getKey());
  508. pdfDoc.getFactory().makeDestination(
  509. dd.getIDRef(), gt.makeReference());
  510. } else {
  511. //Warning already issued by AreaTreeHandler (debug level is sufficient)
  512. log.debug("Unresolved destination item received: " + dd.getIDRef());
  513. }
  514. }
  515. /**
  516. * Renders a Bookmark-Tree object
  517. * @param bookmarks the BookmarkData object containing all the Bookmark-Items
  518. */
  519. protected void renderBookmarkTree(BookmarkData bookmarks) {
  520. for (int i = 0; i < bookmarks.getCount(); i++) {
  521. BookmarkData ext = bookmarks.getSubData(i);
  522. renderBookmarkItem(ext, null);
  523. }
  524. }
  525. private void renderBookmarkItem(BookmarkData bookmarkItem,
  526. PDFOutline parentBookmarkItem) {
  527. PDFOutline pdfOutline = null;
  528. String targetID = bookmarkItem.getIDRef();
  529. if (targetID == null || targetID.length() == 0) {
  530. throw new IllegalArgumentException("DestinationData must contain a ID reference");
  531. }
  532. PageViewport pv = bookmarkItem.getPageViewport();
  533. if (pv != null) {
  534. String pvKey = pv.getKey();
  535. PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
  536. // create outline object:
  537. PDFOutline parent = parentBookmarkItem != null
  538. ? parentBookmarkItem
  539. : pdfDoc.getOutlineRoot();
  540. pdfOutline = pdfDoc.getFactory().makeOutline(parent,
  541. bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems());
  542. } else {
  543. //Warning already issued by AreaTreeHandler (debug level is sufficient)
  544. log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
  545. }
  546. for (int i = 0; i < bookmarkItem.getCount(); i++) {
  547. renderBookmarkItem(bookmarkItem.getSubData(i), pdfOutline);
  548. }
  549. }
  550. private void renderXMPMetadata(XMPMetadata metadata) {
  551. Metadata docXMP = metadata.getMetadata();
  552. Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc);
  553. //Merge FOP's own metadata into the one from the XSL-FO document
  554. fopXMP.mergeInto(docXMP);
  555. XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP);
  556. //Metadata was changed so update metadata date
  557. xmpBasic.setMetadataDate(new java.util.Date());
  558. PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo());
  559. PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
  560. docXMP, metadata.isReadOnly());
  561. pdfDoc.getRoot().setMetadata(pdfMetadata);
  562. }
  563. /** {@inheritDoc} */
  564. public Graphics2DAdapter getGraphics2DAdapter() {
  565. return new PDFGraphics2DAdapter(this);
  566. }
  567. /**
  568. * writes out a comment.
  569. * @param text text for the comment
  570. */
  571. protected void comment(String text) {
  572. if (WRITE_COMMENTS) {
  573. currentStream.add("% " + text + "\n");
  574. }
  575. }
  576. /** {@inheritDoc} */
  577. protected void saveGraphicsState() {
  578. endTextObject();
  579. paintingState.push();
  580. currentStream.add("q\n");
  581. }
  582. private void restoreGraphicsState(boolean popState) {
  583. endTextObject();
  584. currentStream.add("Q\n");
  585. if (popState) {
  586. paintingState.pop();
  587. }
  588. }
  589. /** {@inheritDoc} */
  590. protected void restoreGraphicsState() {
  591. restoreGraphicsState(true);
  592. }
  593. /** Indicates the beginning of a text object. */
  594. protected void beginTextObject() {
  595. if (!textutil.isInTextObject()) {
  596. textutil.beginTextObject();
  597. }
  598. }
  599. /** Indicates the end of a text object. */
  600. protected void endTextObject() {
  601. if (textutil.isInTextObject()) {
  602. textutil.endTextObject();
  603. }
  604. }
  605. /**
  606. * Start the next page sequence.
  607. * For the PDF renderer there is no concept of page sequences
  608. * but it uses the first available page sequence title to set
  609. * as the title of the PDF document, and the language of the
  610. * document.
  611. * @param pageSequence the page sequence
  612. */
  613. public void startPageSequence(PageSequence pageSequence) {
  614. super.startPageSequence(pageSequence);
  615. LineArea seqTitle = pageSequence.getTitle();
  616. if (seqTitle != null) {
  617. String str = convertTitleToString(seqTitle);
  618. PDFInfo info = this.pdfDoc.getInfo();
  619. if (info.getTitle() == null) {
  620. info.setTitle(str);
  621. }
  622. }
  623. if (pageSequence.getLanguage() != null) {
  624. String lang = pageSequence.getLanguage();
  625. String country = pageSequence.getCountry();
  626. String langCode = lang + (country != null ? "-" + country : "");
  627. if (pdfDoc.getRoot().getLanguage() == null) {
  628. //Only set if not set already (first non-null is used)
  629. //Note: No checking is performed whether the values are valid!
  630. pdfDoc.getRoot().setLanguage(langCode);
  631. }
  632. }
  633. if (pdfDoc.getRoot().getMetadata() == null) {
  634. //If at this time no XMP metadata for the overall document has been set, create it
  635. //from the PDFInfo object.
  636. Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc);
  637. PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
  638. xmp, true);
  639. pdfDoc.getRoot().setMetadata(pdfMetadata);
  640. }
  641. }
  642. /**
  643. * The pdf page is prepared by making the page.
  644. * The page is made in the pdf document without any contents
  645. * and then stored to add the contents later.
  646. * The page objects is stored using the area tree PageViewport
  647. * as a key.
  648. *
  649. * @param page the page to prepare
  650. */
  651. public void preparePage(PageViewport page) {
  652. setupPage(page);
  653. if (pages == null) {
  654. pages = new java.util.HashMap();
  655. }
  656. pages.put(page, currentPage);
  657. }
  658. private void setupPage(PageViewport page) {
  659. this.pdfResources = this.pdfDoc.getResources();
  660. Rectangle2D bounds = page.getViewArea();
  661. double w = bounds.getWidth();
  662. double h = bounds.getHeight();
  663. currentPage = this.pdfDoc.getFactory().makePage(
  664. this.pdfResources,
  665. (int) Math.round(w / 1000), (int) Math.round(h / 1000),
  666. page.getPageIndex());
  667. pageReferences.put(page.getKey(), currentPage.referencePDF());
  668. pvReferences.put(page.getKey(), page);
  669. //Produce page labels
  670. PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels();
  671. if (pageLabels == null) {
  672. //Set up PageLabels
  673. pageLabels = this.pdfDoc.getFactory().makePageLabels();
  674. this.pdfDoc.getRoot().setPageLabels(pageLabels);
  675. }
  676. PDFNumsArray nums = pageLabels.getNums();
  677. PDFDictionary dict = new PDFDictionary(nums);
  678. dict.put("P", page.getPageNumberString());
  679. //TODO If the sequence of generated page numbers were inspected, this could be
  680. //expressed in a more space-efficient way
  681. nums.put(page.getPageIndex(), dict);
  682. }
  683. /**
  684. * This method creates a pdf stream for the current page
  685. * uses it as the contents of a new page. The page is written
  686. * immediately to the output stream.
  687. * {@inheritDoc}
  688. */
  689. public void renderPage(PageViewport page)
  690. throws IOException, FOPException {
  691. if (pages != null
  692. && (currentPage = (PDFPage) pages.get(page)) != null) {
  693. //Retrieve previously prepared page (out-of-line rendering)
  694. pages.remove(page);
  695. } else {
  696. setupPage(page);
  697. }
  698. currentPageRef = currentPage.referencePDF();
  699. Rectangle2D bounds = page.getViewArea();
  700. double h = bounds.getHeight();
  701. pageHeight = (int) h;
  702. currentStream = this.pdfDoc.getFactory()
  703. .makeStream(PDFFilterList.CONTENT_FILTER, false);
  704. this.textutil = new PDFTextUtil() {
  705. protected void write(String code) {
  706. currentStream.add(code);
  707. }
  708. };
  709. paintingState = new PDFPaintingState();
  710. // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's
  711. AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0,
  712. pageHeight / 1000f);
  713. paintingState.concatenate(basicPageTransform);
  714. currentStream.add(CTMHelper.toPDFString(basicPageTransform, false) + " cm\n");
  715. super.renderPage(page);
  716. this.pdfDoc.registerObject(currentStream);
  717. currentPage.setContents(currentStream);
  718. PDFAnnotList annots = currentPage.getAnnotations();
  719. if (annots != null) {
  720. this.pdfDoc.addObject(annots);
  721. }
  722. this.pdfDoc.addObject(currentPage);
  723. this.pdfDoc.output(ostream);
  724. this.textutil = null;
  725. }
  726. /** {@inheritDoc} */
  727. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  728. saveGraphicsState();
  729. // Set the given CTM in the graphics state
  730. paintingState.concatenate(
  731. new AffineTransform(CTMHelper.toPDFArray(ctm)));
  732. if (clippingRect != null) {
  733. clipRect((float)clippingRect.getX() / 1000f,
  734. (float)clippingRect.getY() / 1000f,
  735. (float)clippingRect.getWidth() / 1000f,
  736. (float)clippingRect.getHeight() / 1000f);
  737. }
  738. // multiply with current CTM
  739. currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n");
  740. }
  741. /** {@inheritDoc} */
  742. protected void endVParea() {
  743. restoreGraphicsState();
  744. }
  745. /** {@inheritDoc} */
  746. protected void concatenateTransformationMatrix(AffineTransform at) {
  747. if (!at.isIdentity()) {
  748. paintingState.concatenate(at);
  749. currentStream.add(CTMHelper.toPDFString(at, false) + " cm\n");
  750. }
  751. }
  752. /**
  753. * Formats a float value (normally coordinates) as Strings.
  754. * @param value the value
  755. * @return the formatted value
  756. */
  757. protected static String format(float value) {
  758. return PDFNumber.doubleOut(value);
  759. }
  760. /** {@inheritDoc} */
  761. protected void drawBorderLine(float x1, float y1, float x2, float y2,
  762. boolean horz, boolean startOrBefore, int style, Color col) {
  763. float w = x2 - x1;
  764. float h = y2 - y1;
  765. if ((w < 0) || (h < 0)) {
  766. log.error("Negative extent received (w=" + w + ", h=" + h
  767. + "). Border won't be painted.");
  768. return;
  769. }
  770. switch (style) {
  771. case Constants.EN_DASHED:
  772. setColor(col, false, null);
  773. if (horz) {
  774. float unit = Math.abs(2 * h);
  775. int rep = (int)(w / unit);
  776. if (rep % 2 == 0) {
  777. rep++;
  778. }
  779. unit = w / rep;
  780. currentStream.add("[" + format(unit) + "] 0 d ");
  781. currentStream.add(format(h) + " w\n");
  782. float ym = y1 + (h / 2);
  783. currentStream.add(format(x1) + " " + format(ym) + " m "
  784. + format(x2) + " " + format(ym) + " l S\n");
  785. } else {
  786. float unit = Math.abs(2 * w);
  787. int rep = (int)(h / unit);
  788. if (rep % 2 == 0) {
  789. rep++;
  790. }
  791. unit = h / rep;
  792. currentStream.add("[" + format(unit) + "] 0 d ");
  793. currentStream.add(format(w) + " w\n");
  794. float xm = x1 + (w / 2);
  795. currentStream.add(format(xm) + " " + format(y1) + " m "
  796. + format(xm) + " " + format(y2) + " l S\n");
  797. }
  798. break;
  799. case Constants.EN_DOTTED:
  800. setColor(col, false, null);
  801. currentStream.add("1 J ");
  802. if (horz) {
  803. float unit = Math.abs(2 * h);
  804. int rep = (int)(w / unit);
  805. if (rep % 2 == 0) {
  806. rep++;
  807. }
  808. unit = w / rep;
  809. currentStream.add("[0 " + format(unit) + "] 0 d ");
  810. currentStream.add(format(h) + " w\n");
  811. float ym = y1 + (h / 2);
  812. currentStream.add(format(x1) + " " + format(ym) + " m "
  813. + format(x2) + " " + format(ym) + " l S\n");
  814. } else {
  815. float unit = Math.abs(2 * w);
  816. int rep = (int)(h / unit);
  817. if (rep % 2 == 0) {
  818. rep++;
  819. }
  820. unit = h / rep;
  821. currentStream.add("[0 " + format(unit) + " ] 0 d ");
  822. currentStream.add(format(w) + " w\n");
  823. float xm = x1 + (w / 2);
  824. currentStream.add(format(xm) + " " + format(y1) + " m "
  825. + format(xm) + " " + format(y2) + " l S\n");
  826. }
  827. break;
  828. case Constants.EN_DOUBLE:
  829. setColor(col, false, null);
  830. currentStream.add("[] 0 d ");
  831. if (horz) {
  832. float h3 = h / 3;
  833. currentStream.add(format(h3) + " w\n");
  834. float ym1 = y1 + (h3 / 2);
  835. float ym2 = ym1 + h3 + h3;
  836. currentStream.add(format(x1) + " " + format(ym1) + " m "
  837. + format(x2) + " " + format(ym1) + " l S\n");
  838. currentStream.add(format(x1) + " " + format(ym2) + " m "
  839. + format(x2) + " " + format(ym2) + " l S\n");
  840. } else {
  841. float w3 = w / 3;
  842. currentStream.add(format(w3) + " w\n");
  843. float xm1 = x1 + (w3 / 2);
  844. float xm2 = xm1 + w3 + w3;
  845. currentStream.add(format(xm1) + " " + format(y1) + " m "
  846. + format(xm1) + " " + format(y2) + " l S\n");
  847. currentStream.add(format(xm2) + " " + format(y1) + " m "
  848. + format(xm2) + " " + format(y2) + " l S\n");
  849. }
  850. break;
  851. case Constants.EN_GROOVE:
  852. case Constants.EN_RIDGE:
  853. {
  854. float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
  855. currentStream.add("[] 0 d ");
  856. if (horz) {
  857. Color uppercol = ColorUtil.lightenColor(col, -colFactor);
  858. Color lowercol = ColorUtil.lightenColor(col, colFactor);
  859. float h3 = h / 3;
  860. currentStream.add(format(h3) + " w\n");
  861. float ym1 = y1 + (h3 / 2);
  862. setColor(uppercol, false, null);
  863. currentStream.add(format(x1) + " " + format(ym1) + " m "
  864. + format(x2) + " " + format(ym1) + " l S\n");
  865. setColor(col, false, null);
  866. currentStream.add(format(x1) + " " + format(ym1 + h3) + " m "
  867. + format(x2) + " " + format(ym1 + h3) + " l S\n");
  868. setColor(lowercol, false, null);
  869. currentStream.add(format(x1) + " " + format(ym1 + h3 + h3) + " m "
  870. + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n");
  871. } else {
  872. Color leftcol = ColorUtil.lightenColor(col, -colFactor);
  873. Color rightcol = ColorUtil.lightenColor(col, colFactor);
  874. float w3 = w / 3;
  875. currentStream.add(format(w3) + " w\n");
  876. float xm1 = x1 + (w3 / 2);
  877. setColor(leftcol, false, null);
  878. currentStream.add(format(xm1) + " " + format(y1) + " m "
  879. + format(xm1) + " " + format(y2) + " l S\n");
  880. setColor(col, false, null);
  881. currentStream.add(format(xm1 + w3) + " " + format(y1) + " m "
  882. + format(xm1 + w3) + " " + format(y2) + " l S\n");
  883. setColor(rightcol, false, null);
  884. currentStream.add(format(xm1 + w3 + w3) + " " + format(y1) + " m "
  885. + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n");
  886. }
  887. break;
  888. }
  889. case Constants.EN_INSET:
  890. case Constants.EN_OUTSET:
  891. {
  892. float colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
  893. currentStream.add("[] 0 d ");
  894. Color c = col;
  895. if (horz) {
  896. c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
  897. currentStream.add(format(h) + " w\n");
  898. float ym1 = y1 + (h / 2);
  899. setColor(c, false, null);
  900. currentStream.add(format(x1) + " " + format(ym1) + " m "
  901. + format(x2) + " " + format(ym1) + " l S\n");
  902. } else {
  903. c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
  904. currentStream.add(format(w) + " w\n");
  905. float xm1 = x1 + (w / 2);
  906. setColor(c, false, null);
  907. currentStream.add(format(xm1) + " " + format(y1) + " m "
  908. + format(xm1) + " " + format(y2) + " l S\n");
  909. }
  910. break;
  911. }
  912. case Constants.EN_HIDDEN:
  913. break;
  914. default:
  915. setColor(col, false, null);
  916. currentStream.add("[] 0 d ");
  917. if (horz) {
  918. currentStream.add(format(h) + " w\n");
  919. float ym = y1 + (h / 2);
  920. currentStream.add(format(x1) + " " + format(ym) + " m "
  921. + format(x2) + " " + format(ym) + " l S\n");
  922. } else {
  923. currentStream.add(format(w) + " w\n");
  924. float xm = x1 + (w / 2);
  925. currentStream.add(format(xm) + " " + format(y1) + " m "
  926. + format(xm) + " " + format(y2) + " l S\n");
  927. }
  928. }
  929. }
  930. /**
  931. * Sets the current line width in points.
  932. * @param width line width in points
  933. */
  934. private void updateLineWidth(float width) {
  935. if (paintingState.setLineWidth(width)) {
  936. //Only write if value has changed WRT the current line width
  937. currentStream.add(format(width) + " w\n");
  938. }
  939. }
  940. /** {@inheritDoc} */
  941. protected void clipRect(float x, float y, float width, float height) {
  942. currentStream.add(format(x) + " " + format(y) + " "
  943. + format(width) + " " + format(height) + " re ");
  944. clip();
  945. }
  946. /**
  947. * Clip an area.
  948. */
  949. protected void clip() {
  950. currentStream.add("W\n");
  951. currentStream.add("n\n");
  952. }
  953. /**
  954. * Moves the current point to (x, y), omitting any connecting line segment.
  955. * @param x x coordinate
  956. * @param y y coordinate
  957. */
  958. protected void moveTo(float x, float y) {
  959. currentStream.add(format(x) + " " + format(y) + " m ");
  960. }
  961. /**
  962. * Appends a straight line segment from the current point to (x, y). The
  963. * new current point is (x, y).
  964. * @param x x coordinate
  965. * @param y y coordinate
  966. */
  967. protected void lineTo(float x, float y) {
  968. currentStream.add(format(x) + " " + format(y) + " l ");
  969. }
  970. /**
  971. * Closes the current subpath by appending a straight line segment from
  972. * the current point to the starting point of the subpath.
  973. */
  974. protected void closePath() {
  975. currentStream.add("h ");
  976. }
  977. /**
  978. * {@inheritDoc}
  979. */
  980. protected void fillRect(float x, float y, float width, float height) {
  981. if (width > 0 && height > 0) {
  982. currentStream.add(format(x) + " " + format(y) + " "
  983. + format(width) + " " + format(height) + " re f\n");
  984. }
  985. }
  986. /**
  987. * Draw a line.
  988. *
  989. * @param startx the start x position
  990. * @param starty the start y position
  991. * @param endx the x end position
  992. * @param endy the y end position
  993. */
  994. private void drawLine(float startx, float starty, float endx, float endy) {
  995. currentStream.add(format(startx) + " " + format(starty) + " m ");
  996. currentStream.add(format(endx) + " " + format(endy) + " l S\n");
  997. }
  998. /**
  999. * Breaks out of the state stack to handle fixed block-containers.
  1000. * @return the saved state stack to recreate later
  1001. */
  1002. protected List breakOutOfStateStack() {
  1003. // return currentState.popAll();
  1004. List breakOutList = new java.util.ArrayList();
  1005. AbstractPaintingState.AbstractData data;
  1006. while (true) {
  1007. data = paintingState.getData();
  1008. if (paintingState.pop() == null) {
  1009. break;
  1010. }
  1011. if (breakOutList.size() == 0) {
  1012. comment("------ break out!");
  1013. }
  1014. breakOutList.add(0, data); //Insert because of stack-popping
  1015. restoreGraphicsState(false);
  1016. }
  1017. return breakOutList;
  1018. }
  1019. /**
  1020. * Restores the state stack after a break out.
  1021. * @param breakOutList the state stack to restore.
  1022. */
  1023. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  1024. comment("------ restoring context after break-out...");
  1025. // currentState.pushAll(breakOutList);
  1026. AbstractData data;
  1027. Iterator i = breakOutList.iterator();
  1028. while (i.hasNext()) {
  1029. data = (AbstractData)i.next();
  1030. saveGraphicsState();
  1031. AffineTransform at = data.getTransform();
  1032. concatenateTransformationMatrix(at);
  1033. //TODO Break-out: Also restore items such as line width and color
  1034. //Left out for now because all this painting stuff is very
  1035. //inconsistent. Some values go over PDFState, some don't.
  1036. }
  1037. comment("------ done.");
  1038. }
  1039. /**
  1040. * Returns area's id if it is the first area in the document with that id
  1041. * (i.e. if the area qualifies as a link target).
  1042. * Otherwise, or if the area has no id, null is returned.
  1043. *
  1044. * <i>NOTE</i>: area must be on currentPageViewport, otherwise result may be wrong!
  1045. *
  1046. * @param area the area for which to return the id
  1047. * @return the area's id (null if the area has no id or
  1048. * other preceding areas have the same id)
  1049. */
  1050. protected String getTargetableID(Area area) {
  1051. String id = (String) area.getTrait(Trait.PROD_ID);
  1052. if (id == null || id.length() == 0
  1053. || !currentPageViewport.isFirstWithID(id)
  1054. || idPositions.containsKey(id)) {
  1055. return null;
  1056. } else {
  1057. return id;
  1058. }
  1059. }
  1060. /**
  1061. * Set XY position in the PDFGoTo and add it to the PDF trailer.
  1062. *
  1063. * @param gt the PDFGoTo object
  1064. * @param position the X,Y position to set
  1065. */
  1066. protected void finishIDGoTo(PDFGoTo gt, Point2D.Float position) {
  1067. gt.setPosition(position);
  1068. pdfDoc.addTrailerObject(gt);
  1069. unfinishedGoTos.remove(gt);
  1070. }
  1071. /**
  1072. * Set page reference and XY position in the PDFGoTo and add it to the PDF trailer.
  1073. *
  1074. * @param gt the PDFGoTo object
  1075. * @param pdfPageRef the PDF reference string of the target page object
  1076. * @param position the X,Y position to set
  1077. */
  1078. protected void finishIDGoTo(PDFGoTo gt, String pdfPageRef, Point2D.Float position) {
  1079. gt.setPageReference(pdfPageRef);
  1080. finishIDGoTo(gt, position);
  1081. }
  1082. /**
  1083. * Get a PDFGoTo pointing to the given id. Create one if necessary.
  1084. * It is possible that the PDFGoTo is not fully resolved yet. In that case
  1085. * it must be completed (and added to the PDF trailer) later.
  1086. *
  1087. * @param targetID the target id of the PDFGoTo
  1088. * @param pvKey the unique key of the target PageViewport
  1089. *
  1090. * @return the PDFGoTo that was found or created
  1091. */
  1092. protected PDFGoTo getPDFGoToForID(String targetID, String pvKey) {
  1093. // Already a PDFGoTo present for this target? If not, create.
  1094. PDFGoTo gt = (PDFGoTo) idGoTos.get(targetID);
  1095. if (gt == null) {
  1096. String pdfPageRef = (String) pageReferences.get(pvKey);
  1097. Point2D.Float position = (Point2D.Float) idPositions.get(targetID);
  1098. // can the GoTo already be fully filled in?
  1099. if (pdfPageRef != null && position != null) {
  1100. // getPDFGoTo shares PDFGoTo objects as much as possible.
  1101. // It also takes care of assignObjectNumber and addTrailerObject.
  1102. gt = pdfDoc.getFactory().getPDFGoTo(pdfPageRef, position);
  1103. } else {
  1104. // Not complete yet, can't use getPDFGoTo:
  1105. gt = new PDFGoTo(pdfPageRef);
  1106. pdfDoc.assignObjectNumber(gt);
  1107. // pdfDoc.addTrailerObject() will be called later, from finishIDGoTo()
  1108. unfinishedGoTos.add(gt);
  1109. }
  1110. idGoTos.put(targetID, gt);
  1111. }
  1112. return gt;
  1113. }
  1114. /**
  1115. * Saves id's absolute position on page for later retrieval by PDFGoTos
  1116. *
  1117. * @param id the id of the area whose position must be saved
  1118. * @param pdfPageRef the PDF page reference string
  1119. * @param relativeIPP the *relative* IP position in millipoints
  1120. * @param relativeBPP the *relative* BP position in millipoints
  1121. * @param tf the transformation to apply once the relative positions have been
  1122. * converted to points
  1123. */
  1124. protected void saveAbsolutePosition(String id, String pdfPageRef,
  1125. int relativeIPP, int relativeBPP, AffineTransform tf) {
  1126. Point2D.Float position = new Point2D.Float(relativeIPP / 1000f, relativeBPP / 1000f);
  1127. tf.transform(position, position);
  1128. idPositions.put(id, position);
  1129. // is there already a PDFGoTo waiting to be completed?
  1130. PDFGoTo gt = (PDFGoTo) idGoTos.get(id);
  1131. if (gt != null) {
  1132. finishIDGoTo(gt, pdfPageRef, position);
  1133. }
  1134. /*
  1135. // The code below auto-creates a named destination for every id in the document.
  1136. // This should probably be controlled by a user-configurable setting, as it may
  1137. // make the PDF file grow noticeably.
  1138. // *** NOT YET WELL-TESTED ! ***
  1139. if (true) {
  1140. PDFFactory factory = pdfDoc.getFactory();
  1141. if (gt == null) {
  1142. gt = factory.getPDFGoTo(pdfPageRef, position);
  1143. idGoTos.put(id, gt); // so others can pick it up too
  1144. }
  1145. factory.makeDestination(id, gt.referencePDF(), currentPageViewport);
  1146. // Note: using currentPageViewport is only correct if the id is indeed on
  1147. // the current PageViewport. But even if incorrect, it won't interfere with
  1148. // what gets created in the PDF.
  1149. // For speedup, we should also create a lookup map id -> PDFDestination
  1150. }
  1151. */
  1152. }
  1153. /**
  1154. * Saves id's absolute position on page for later retrieval by PDFGoTos,
  1155. * using the currently valid transformation and the currently valid PDF page reference
  1156. *
  1157. * @param id the id of the area whose position must be saved
  1158. * @param relativeIPP the *relative* IP position in millipoints
  1159. * @param relativeBPP the *relative* BP position in millipoints
  1160. */
  1161. protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
  1162. saveAbsolutePosition(id, currentPageRef,
  1163. relativeIPP, relativeBPP, paintingState.getTransform());
  1164. }
  1165. /**
  1166. * If the given block area is a possible link target, its id + absolute position will
  1167. * be saved. The saved position is only correct if this function is called at the very
  1168. * start of renderBlock!
  1169. *
  1170. * @param block the block area in question
  1171. */
  1172. protected void saveBlockPosIfTargetable(Block block) {
  1173. String id = getTargetableID(block);
  1174. if (id != null) {
  1175. // FIXME: Like elsewhere in the renderer code, absolute and relative
  1176. // directions are happily mixed here. This makes sure that the
  1177. // links point to the right location, but it is not correct.
  1178. int ipp = block.getXOffset();
  1179. int bpp = block.getYOffset() + block.getSpaceBefore();
  1180. int positioning = block.getPositioning();
  1181. if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
  1182. ipp += currentIPPosition;
  1183. bpp += currentBPPosition;
  1184. }
  1185. AffineTransform tf = positioning == Block.FIXED
  1186. ? paintingState.getBaseTransform()
  1187. : paintingState.getTransform();
  1188. saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf);
  1189. }
  1190. }
  1191. /**
  1192. * If the given inline area is a possible link target, its id + absolute position will
  1193. * be saved. The saved position is only correct if this function is called at the very
  1194. * start of renderInlineArea!
  1195. *
  1196. * @param inlineArea the inline area in question
  1197. */
  1198. protected void saveInlinePosIfTargetable(InlineArea inlineArea) {
  1199. String id = getTargetableID(inlineArea);
  1200. if (id != null) {
  1201. int extraMarginBefore = 5000; // millipoints
  1202. int ipp = currentIPPosition;
  1203. int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
  1204. saveAbsolutePosition(id, ipp, bpp);
  1205. }
  1206. }
  1207. /**
  1208. * {@inheritDoc}
  1209. */
  1210. protected void renderBlock(Block block) {
  1211. saveBlockPosIfTargetable(block);
  1212. super.renderBlock(block);
  1213. }
  1214. /** {@inheritDoc} */
  1215. protected void renderLineArea(LineArea line) {
  1216. super.renderLineArea(line);
  1217. }
  1218. /**
  1219. * {@inheritDoc}
  1220. */
  1221. protected void renderInlineArea(InlineArea inlineArea) {
  1222. saveInlinePosIfTargetable(inlineArea);
  1223. super.renderInlineArea(inlineArea);
  1224. }
  1225. /**
  1226. * Render inline parent area.
  1227. * For pdf this handles the inline parent area traits such as
  1228. * links, border, background.
  1229. * @param ip the inline parent area
  1230. */
  1231. public void renderInlineParent(InlineParent ip) {
  1232. boolean annotsAllowed = pdfDoc.getProfile().isAnnotationAllowed();
  1233. // stuff we only need if a link must be created:
  1234. Rectangle2D ipRect = null;
  1235. PDFFactory factory = null;
  1236. PDFAction action = null;
  1237. if (annotsAllowed) {
  1238. // make sure the rect is determined *before* calling super!
  1239. int ipp = currentIPPosition;
  1240. int bpp = currentBPPosition + ip.getOffset();
  1241. ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f,
  1242. ip.getIPD() / 1000f, ip.getBPD() / 1000f);
  1243. AffineTransform transform = paintingState.getTransform();
  1244. ipRect = transform.createTransformedShape(ipRect).getBounds2D();
  1245. factory = pdfDoc.getFactory();
  1246. }
  1247. // render contents
  1248. super.renderInlineParent(ip);
  1249. boolean linkTraitFound = false;
  1250. // try INTERNAL_LINK first
  1251. Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK);
  1252. if (intLink != null) {
  1253. linkTraitFound = true;
  1254. String pvKey = intLink.getPVKey();
  1255. String idRef = intLink.getIDRef();
  1256. boolean pvKeyOK = pvKey != null && pvKey.length() > 0;
  1257. boolean idRefOK = idRef != null && idRef.length() > 0;
  1258. if (pvKeyOK && idRefOK) {
  1259. if (annotsAllowed) {
  1260. action = getPDFGoToForID(idRef, pvKey);
  1261. }
  1262. } else {
  1263. //Warnings already issued by AreaTreeHandler
  1264. }
  1265. }
  1266. // no INTERNAL_LINK, look for EXTERNAL_LINK
  1267. if (!linkTraitFound) {
  1268. Trait.ExternalLink extLink = (Trait.ExternalLink) ip.getTrait(Trait.EXTERNAL_LINK);
  1269. if (extLink != null) {
  1270. String extDest = extLink.getDestination();
  1271. if (extDest != null && extDest.length() > 0) {
  1272. linkTraitFound = true;
  1273. if (annotsAllowed) {
  1274. action = factory.getExternalAction(extDest, extLink.newWindow());
  1275. }
  1276. }
  1277. }
  1278. }
  1279. // warn if link trait found but not allowed, else create link
  1280. if (linkTraitFound) {
  1281. if (!annotsAllowed) {
  1282. log.warn("Skipping annotation for a link due to PDF profile: "
  1283. + pdfDoc.getProfile());
  1284. } else if (action != null) {
  1285. PDFLink pdfLink = factory.makeLink(ipRect, action);
  1286. currentPage.addAnnotation(pdfLink);
  1287. }
  1288. }
  1289. }
  1290. private Typeface getTypeface(String fontName) {
  1291. Typeface tf = (Typeface) fontInfo.getFonts().get(fontName);
  1292. if (tf instanceof LazyFont) {
  1293. tf = ((LazyFont)tf).getRealFont();
  1294. }
  1295. return tf;
  1296. }
  1297. /** {@inheritDoc} */
  1298. public void renderText(TextArea text) {
  1299. renderInlineAreaBackAndBorders(text);
  1300. Color ct = (Color) text.getTrait(Trait.COLOR);
  1301. updateColor(ct, true);
  1302. beginTextObject();
  1303. String fontName = getInternalFontNameForArea(text);
  1304. int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue();
  1305. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  1306. Typeface tf = getTypeface(fontName);
  1307. textutil.updateTf(fontName, size / 1000f, tf.isMultiByte());
  1308. // word.getOffset() = only height of text itself
  1309. // currentBlockIPPosition: 0 for beginning of line; nonzero
  1310. // where previous line area failed to take up entire allocated space
  1311. int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
  1312. int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
  1313. textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, rx / 1000f, bl / 1000f));
  1314. super.renderText(text);
  1315. textutil.writeTJ();
  1316. renderTextDecoration(tf, size, text, bl, rx);
  1317. }
  1318. /** {@inheritDoc} */
  1319. public void renderWord(WordArea word) {
  1320. Font font = getFontFromArea(word.getParentArea());
  1321. String s = word.getWord();
  1322. escapeText(s, word.getLetterAdjustArray(),
  1323. font, (AbstractTextArea)word.getParentArea());
  1324. super.renderWord(word);
  1325. }
  1326. /** {@inheritDoc} */
  1327. public void renderSpace(SpaceArea space) {
  1328. Font font = getFontFromArea(space.getParentArea());
  1329. String s = space.getSpace();
  1330. AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
  1331. escapeText(s, null, font, textArea);
  1332. if (space.isAdjustable()) {
  1333. int tws = -((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
  1334. - 2 * textArea.getTextLetterSpaceAdjust();
  1335. if (tws != 0) {
  1336. float adjust = tws / (font.getFontSize() / 1000f);
  1337. textutil.adjustGlyphTJ(adjust);
  1338. }
  1339. }
  1340. super.renderSpace(space);
  1341. }
  1342. /**
  1343. * Escapes text according to PDF rules.
  1344. * @param s Text to escape
  1345. * @param letterAdjust an array of widths for letter adjustment (may be null)
  1346. * @param font to font in use
  1347. * @param parentArea the parent text area to retrieve certain traits from
  1348. */
  1349. protected void escapeText(String s,
  1350. int[] letterAdjust,
  1351. Font font, AbstractTextArea parentArea) {
  1352. escapeText(s, 0, s.length(), letterAdjust, font, parentArea);
  1353. }
  1354. /**
  1355. * Escapes text according to PDF rules.
  1356. * @param s Text to escape
  1357. * @param start the start position in the text
  1358. * @param end the end position in the text
  1359. * @param letterAdjust an array of widths for letter adjustment (may be null)
  1360. * @param font to font in use
  1361. * @param parentArea the parent text area to retrieve certain traits from
  1362. */
  1363. protected void escapeText(String s, int start, int end,
  1364. int[] letterAdjust,
  1365. Font font, AbstractTextArea parentArea) {
  1366. String fontName = font.getFontName();
  1367. float fontSize = font.getFontSize() / 1000f;
  1368. Typeface tf = getTypeface(fontName);
  1369. SingleByteFont singleByteFont = null;
  1370. if (tf instanceof SingleByteFont) {
  1371. singleByteFont = (SingleByteFont)tf;
  1372. }
  1373. int l = s.length();
  1374. for (int i = start; i < end; i++) {
  1375. char orgChar = s.charAt(i);
  1376. char ch;
  1377. float glyphAdjust = 0;
  1378. if (font.hasChar(orgChar)) {
  1379. ch = font.mapChar(orgChar);
  1380. if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
  1381. int encoding = ch / 256;
  1382. if (encoding == 0) {
  1383. textutil.updateTf(fontName, fontSize, tf.isMultiByte());
  1384. } else {
  1385. textutil.updateTf(fontName + "_" + Integer.toString(encoding),
  1386. fontSize, tf.isMultiByte());
  1387. ch = (char)(ch % 256);
  1388. }
  1389. }
  1390. int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
  1391. glyphAdjust -= tls;
  1392. } else {
  1393. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  1394. //Fixed width space are rendered as spaces so copy/paste works in a reader
  1395. ch = font.mapChar(CharUtilities.SPACE);
  1396. glyphAdjust = font.getCharWidth(ch) - font.getCharWidth(orgChar);
  1397. } else {
  1398. ch = font.mapChar(orgChar);
  1399. }
  1400. }
  1401. if (letterAdjust != null && i < l - 1) {
  1402. glyphAdjust -= letterAdjust[i + 1];
  1403. }
  1404. textutil.writeTJMappedChar(ch);
  1405. float adjust = glyphAdjust / fontSize;
  1406. if (adjust != 0) {
  1407. textutil.adjustGlyphTJ(adjust);
  1408. }
  1409. }
  1410. }
  1411. /**
  1412. * Establishes a new foreground or fill color. In contrast to updateColor
  1413. * this method does not check the PDFState for optimization possibilities.
  1414. * @param col the color to apply
  1415. * @param fill true to set the fill color, false for the foreground color
  1416. * @param pdf StringBuffer to write the PDF code to, if null, the code is
  1417. * written to the current stream.
  1418. */
  1419. protected void setColor(Color col, boolean fill, StringBuffer pdf) {
  1420. PDFColor color = new PDFColor(this.pdfDoc, col);
  1421. if (pdf != null) {
  1422. pdf.append(color.getColorSpaceOut(fill));
  1423. } else {
  1424. currentStream.add(color.getColorSpaceOut(fill));
  1425. }
  1426. }
  1427. /**
  1428. * Establishes a new foreground or fill color.
  1429. * @param col the color to apply (null skips this operation)
  1430. * @param fill true to set the fill color, false for the foreground color
  1431. * @param pdf StringBuffer to write the PDF code to, if null, the code is
  1432. * written to the current stream.
  1433. */
  1434. private void updateColor(Color col, boolean fill, StringBuffer pdf) {
  1435. if (col == null) {
  1436. return;
  1437. }
  1438. boolean update = false;
  1439. if (fill) {
  1440. update = paintingState.setBackColor(col);
  1441. } else {
  1442. update = paintingState.setColor(col);
  1443. }
  1444. if (update) {
  1445. setColor(col, fill, pdf);
  1446. }
  1447. }
  1448. /** {@inheritDoc} */
  1449. protected void updateColor(Color col, boolean fill) {
  1450. updateColor(col, fill, null);
  1451. }
  1452. /** {@inheritDoc} */
  1453. public void renderImage(Image image, Rectangle2D pos) {
  1454. endTextObject();
  1455. String url = image.getURL();
  1456. putImage(url, pos, image.getForeignAttributes());
  1457. }
  1458. /** {@inheritDoc} */
  1459. protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) {
  1460. endTextObject();
  1461. putImage(url, pos, foreignAttributes);
  1462. }
  1463. /**
  1464. * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced.
  1465. * @param uri URL of the bitmap
  1466. * @param pos Position of the bitmap
  1467. * @deprecated Use {@link #putImage(String, Rectangle2D, Map)} instead.
  1468. */
  1469. protected void putImage(String uri, Rectangle2D pos) {
  1470. putImage(uri, pos, null);
  1471. }
  1472. /**
  1473. * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced.
  1474. * @param uri URL of the bitmap
  1475. * @param pos Position of the bitmap
  1476. * @param foreignAttributes foreign attributes associated with the image
  1477. */
  1478. protected void putImage(String uri, Rectangle2D pos, Map foreignAttributes) {
  1479. Rectangle posInt = new Rectangle(
  1480. (int)pos.getX(),
  1481. (int)pos.getY(),
  1482. (int)pos.getWidth(),
  1483. (int)pos.getHeight());
  1484. uri = URISpecification.getURL(uri);
  1485. PDFXObject xobject = pdfDoc.getXObject(uri);
  1486. if (xobject != null) {
  1487. float w = (float) pos.getWidth() / 1000f;
  1488. float h = (float) pos.getHeight() / 1000f;
  1489. placeImage((float)pos.getX() / 1000f,
  1490. (float)pos.getY() / 1000f, w, h, xobject);
  1491. return;
  1492. }
  1493. Point origin = new Point(currentIPPosition, currentBPPosition);
  1494. int x = origin.x + posInt.x;
  1495. int y = origin.y + posInt.y;
  1496. ImageManager manager = getUserAgent().getFactory().getImageManager();
  1497. ImageInfo info = null;
  1498. try {
  1499. ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
  1500. info = manager.getImageInfo(uri, sessionContext);
  1501. Map hints = ImageUtil.getDefaultHints(sessionContext);
  1502. org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
  1503. info, imageHandlerRegistry.getSupportedFlavors(), hints, sessionContext);
  1504. //First check for a dynamically registered handler
  1505. PDFImageHandler handler = imageHandlerRegistry.getHandler(img.getClass());
  1506. if (handler != null) {
  1507. if (log.isDebugEnabled()) {
  1508. log.debug("Using PDFImageHandler: " + handler.getClass().getName());
  1509. }
  1510. try {
  1511. RendererContext context = createRendererContext(
  1512. x, y, posInt.width, posInt.height, foreignAttributes);
  1513. handler.generateImage(context, img, origin, posInt);
  1514. } catch (IOException ioe) {
  1515. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1516. getUserAgent().getEventBroadcaster());
  1517. eventProducer.imageWritingError(this, ioe);
  1518. return;
  1519. }
  1520. } else {
  1521. throw new UnsupportedOperationException(
  1522. "No PDFImageHandler available for image: "
  1523. + info + " (" + img.getClass().getName() + ")");
  1524. }
  1525. } catch (ImageException ie) {
  1526. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1527. getUserAgent().getEventBroadcaster());
  1528. eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
  1529. } catch (FileNotFoundException fe) {
  1530. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1531. getUserAgent().getEventBroadcaster());
  1532. eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
  1533. } catch (IOException ioe) {
  1534. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1535. getUserAgent().getEventBroadcaster());
  1536. eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
  1537. }
  1538. // output new data
  1539. try {
  1540. this.pdfDoc.output(ostream);
  1541. } catch (IOException ioe) {
  1542. // ioexception will be caught later
  1543. log.error(ioe.getMessage());
  1544. }
  1545. }
  1546. /**
  1547. * Places a previously registered image at a certain place on the page.
  1548. * @param x X coordinate
  1549. * @param y Y coordinate
  1550. * @param w width for image
  1551. * @param h height for image
  1552. * @param xobj the image XObject
  1553. */
  1554. public void placeImage(float x, float y, float w, float h, PDFXObject xobj) {
  1555. saveGraphicsState();
  1556. currentStream.add(format(w) + " 0 0 "
  1557. + format(-h) + " "
  1558. + format(currentIPPosition / 1000f + x) + " "
  1559. + format(currentBPPosition / 1000f + h + y)
  1560. + " cm\n" + xobj.getName() + " Do\n");
  1561. restoreGraphicsState();
  1562. }
  1563. /** {@inheritDoc} */
  1564. protected RendererContext createRendererContext(int x, int y, int width, int height,
  1565. Map foreignAttributes) {
  1566. RendererContext context = super.createRendererContext(
  1567. x, y, width, height, foreignAttributes);
  1568. context.setProperty(PDFRendererContextConstants.PDF_DOCUMENT, pdfDoc);
  1569. context.setProperty(PDFRendererContextConstants.OUTPUT_STREAM, ostream);
  1570. context.setProperty(PDFRendererContextConstants.PDF_PAINTING_STATE, paintingState);
  1571. context.setProperty(PDFRendererContextConstants.PDF_PAGE, currentPage);
  1572. context.setProperty(PDFRendererContextConstants.PDF_CONTEXT,
  1573. currentContext == null ? currentPage : currentContext);
  1574. context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext);
  1575. context.setProperty(PDFRendererContextConstants.PDF_STREAM, currentStream);
  1576. context.setProperty(PDFRendererContextConstants.PDF_FONT_INFO, fontInfo);
  1577. context.setProperty(PDFRendererContextConstants.PDF_FONT_NAME, "");
  1578. context.setProperty(PDFRendererContextConstants.PDF_FONT_SIZE, new Integer(0));
  1579. return context;
  1580. }
  1581. /**
  1582. * Render leader area.
  1583. * This renders a leader area which is an area with a rule.
  1584. * @param area the leader area to render
  1585. */
  1586. public void renderLeader(Leader area) {
  1587. renderInlineAreaBackAndBorders(area);
  1588. paintingState.push();
  1589. saveGraphicsState();
  1590. int style = area.getRuleStyle();
  1591. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  1592. float starty = (currentBPPosition + area.getOffset()) / 1000f;
  1593. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  1594. + area.getIPD()) / 1000f;
  1595. float ruleThickness = area.getRuleThickness() / 1000f;
  1596. Color col = (Color)area.getTrait(Trait.COLOR);
  1597. switch (style) {
  1598. case EN_SOLID:
  1599. case EN_DASHED:
  1600. case EN_DOUBLE:
  1601. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1602. true, true, style, col);
  1603. break;
  1604. case EN_DOTTED:
  1605. clipRect(startx, starty, endx - startx, ruleThickness);
  1606. //This displaces the dots to the right by half a dot's width
  1607. //TODO There's room for improvement here
  1608. currentStream.add("1 0 0 1 " + format(ruleThickness / 2) + " 0 cm\n");
  1609. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1610. true, true, style, col);
  1611. break;
  1612. case EN_GROOVE:
  1613. case EN_RIDGE:
  1614. float half = area.getRuleThickness() / 2000f;
  1615. setColor(ColorUtil.lightenColor(col, 0.6f), true, null);
  1616. currentStream.add(format(startx) + " " + format(starty) + " m\n");
  1617. currentStream.add(format(endx) + " " + format(starty) + " l\n");
  1618. currentStream.add(format(endx) + " " + format(starty + 2 * half) + " l\n");
  1619. currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n");
  1620. currentStream.add("h\n");
  1621. currentStream.add("f\n");
  1622. setColor(col, true, null);
  1623. if (style == EN_GROOVE) {
  1624. currentStream.add(format(startx) + " " + format(starty) + " m\n");
  1625. currentStream.add(format(endx) + " " + format(starty) + " l\n");
  1626. currentStream.add(format(endx) + " " + format(starty + half) + " l\n");
  1627. currentStream.add(format(startx + half) + " " + format(starty + half) + " l\n");
  1628. currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n");
  1629. } else {
  1630. currentStream.add(format(endx) + " " + format(starty) + " m\n");
  1631. currentStream.add(format(endx) + " " + format(starty + 2 * half) + " l\n");
  1632. currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n");
  1633. currentStream.add(format(startx) + " " + format(starty + half) + " l\n");
  1634. currentStream.add(format(endx - half) + " " + format(starty + half) + " l\n");
  1635. }
  1636. currentStream.add("h\n");
  1637. currentStream.add("f\n");
  1638. break;
  1639. default:
  1640. throw new UnsupportedOperationException("rule style not supported");
  1641. }
  1642. restoreGraphicsState();
  1643. paintingState.pop();
  1644. beginTextObject();
  1645. super.renderLeader(area);
  1646. }
  1647. /** {@inheritDoc} */
  1648. public String getMimeType() {
  1649. return MIME_TYPE;
  1650. }
  1651. /**
  1652. * Sets the PDF/A mode for the PDF renderer.
  1653. * @param mode the PDF/A mode
  1654. */
  1655. public void setAMode(PDFAMode mode) {
  1656. this.pdfAMode = mode;
  1657. }
  1658. /**
  1659. * Sets the PDF/X mode for the PDF renderer.
  1660. * @param mode the PDF/X mode
  1661. */
  1662. public void setXMode(PDFXMode mode) {
  1663. this.pdfXMode = mode;
  1664. }
  1665. /**
  1666. * Sets the output color profile for the PDF renderer.
  1667. * @param outputProfileURI the URI to the output color profile
  1668. */
  1669. public void setOutputProfileURI(String outputProfileURI) {
  1670. this.outputProfileURI = outputProfileURI;
  1671. }
  1672. /**
  1673. * Sets the filter map to be used by the PDF renderer.
  1674. * @param filterMap the filter map
  1675. */
  1676. public void setFilterMap(Map filterMap) {
  1677. this.filterMap = filterMap;
  1678. }
  1679. /**
  1680. * Sets the encryption parameters used by the PDF renderer.
  1681. * @param encryptionParams the encryption parameters
  1682. */
  1683. public void setEncryptionParams(PDFEncryptionParams encryptionParams) {
  1684. this.encryptionParams = encryptionParams;
  1685. }
  1686. }