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

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