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

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