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

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