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

Fop.java gets two new constructors: Fop(String) and Fop(String, FOUserAgent) where the String is the MIME type for the desired output format. MimeConstants provides a comprehensive list of MIME types used in Fop.java. Non-standard, FOP-specific MIME types changed to a uniform pattern: application/X-fop-awt-preview, application/X-fop-print and application/X-fop-areatree. RendererFactory now supports manual registration and dynamic discovery of Renderers and FOEventHandlers by their MIME types. Instantitation is done using MIME types everywhere. The RENDER_* constants are mapped to MIME types in Fop.java. RendererFactory is now an instantiable class whose reference is held by FOUserAgent just like it is done for the XLMHandlers. Renderers and FOEventHandlers now each have a *Maker class which is a kind of factory class which is used to register a Renderer/FOEventHandler and additionally serves to provide additional information about the thing, such as the MIME types it supports and if the implementation requires an OutputStream. The command-line gets a new option: -out application/pdf myfile.pdf is the generic way to create an output file. If someone created a WordML output handler and provided the right service resource file he could specify "-out text/xml+msword out.xml". ".out list" lists all MIME types that are available for output. Renderers can now potionally expose a Graphics2DAdapter which in concert with Graphics2DImagePainter can be used by FOP extensions to paint their content directly using a Graphics2D instance. That makes it possible to avoid a detour via SVG/Batik in certain cases. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@332549 13f79535-47bb-0310-9956-ffa450edef68
18 years ago
Fop.java gets two new constructors: Fop(String) and Fop(String, FOUserAgent) where the String is the MIME type for the desired output format. MimeConstants provides a comprehensive list of MIME types used in Fop.java. Non-standard, FOP-specific MIME types changed to a uniform pattern: application/X-fop-awt-preview, application/X-fop-print and application/X-fop-areatree. RendererFactory now supports manual registration and dynamic discovery of Renderers and FOEventHandlers by their MIME types. Instantitation is done using MIME types everywhere. The RENDER_* constants are mapped to MIME types in Fop.java. RendererFactory is now an instantiable class whose reference is held by FOUserAgent just like it is done for the XLMHandlers. Renderers and FOEventHandlers now each have a *Maker class which is a kind of factory class which is used to register a Renderer/FOEventHandler and additionally serves to provide additional information about the thing, such as the MIME types it supports and if the implementation requires an OutputStream. The command-line gets a new option: -out application/pdf myfile.pdf is the generic way to create an output file. If someone created a WordML output handler and provided the right service resource file he could specify "-out text/xml+msword out.xml". ".out list" lists all MIME types that are available for output. Renderers can now potionally expose a Graphics2DAdapter which in concert with Graphics2DImagePainter can be used by FOP extensions to paint their content directly using a Graphics2D instance. That makes it possible to avoid a detour via SVG/Batik in certain cases. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@332549 13f79535-47bb-0310-9956-ffa450edef68
18 years ago
Fop.java gets two new constructors: Fop(String) and Fop(String, FOUserAgent) where the String is the MIME type for the desired output format. MimeConstants provides a comprehensive list of MIME types used in Fop.java. Non-standard, FOP-specific MIME types changed to a uniform pattern: application/X-fop-awt-preview, application/X-fop-print and application/X-fop-areatree. RendererFactory now supports manual registration and dynamic discovery of Renderers and FOEventHandlers by their MIME types. Instantitation is done using MIME types everywhere. The RENDER_* constants are mapped to MIME types in Fop.java. RendererFactory is now an instantiable class whose reference is held by FOUserAgent just like it is done for the XLMHandlers. Renderers and FOEventHandlers now each have a *Maker class which is a kind of factory class which is used to register a Renderer/FOEventHandler and additionally serves to provide additional information about the thing, such as the MIME types it supports and if the implementation requires an OutputStream. The command-line gets a new option: -out application/pdf myfile.pdf is the generic way to create an output file. If someone created a WordML output handler and provided the right service resource file he could specify "-out text/xml+msword out.xml". ".out list" lists all MIME types that are available for output. Renderers can now potionally expose a Graphics2DAdapter which in concert with Graphics2DImagePainter can be used by FOP extensions to paint their content directly using a Graphics2D instance. That makes it possible to avoid a detour via SVG/Batik in certain cases. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@332549 13f79535-47bb-0310-9956-ffa450edef68
18 years ago
Fop.java gets two new constructors: Fop(String) and Fop(String, FOUserAgent) where the String is the MIME type for the desired output format. MimeConstants provides a comprehensive list of MIME types used in Fop.java. Non-standard, FOP-specific MIME types changed to a uniform pattern: application/X-fop-awt-preview, application/X-fop-print and application/X-fop-areatree. RendererFactory now supports manual registration and dynamic discovery of Renderers and FOEventHandlers by their MIME types. Instantitation is done using MIME types everywhere. The RENDER_* constants are mapped to MIME types in Fop.java. RendererFactory is now an instantiable class whose reference is held by FOUserAgent just like it is done for the XLMHandlers. Renderers and FOEventHandlers now each have a *Maker class which is a kind of factory class which is used to register a Renderer/FOEventHandler and additionally serves to provide additional information about the thing, such as the MIME types it supports and if the implementation requires an OutputStream. The command-line gets a new option: -out application/pdf myfile.pdf is the generic way to create an output file. If someone created a WordML output handler and provided the right service resource file he could specify "-out text/xml+msword out.xml". ".out list" lists all MIME types that are available for output. Renderers can now potionally expose a Graphics2DAdapter which in concert with Graphics2DImagePainter can be used by FOP extensions to paint their content directly using a Graphics2D instance. That makes it possible to avoid a detour via SVG/Batik in certain cases. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@332549 13f79535-47bb-0310-9956-ffa450edef68
18 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569
  1. /*
  2. * Copyright 1999-2005 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.render.pdf;
  18. // Java
  19. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.awt.Color;
  22. import java.awt.geom.Rectangle2D;
  23. import java.awt.geom.AffineTransform;
  24. import java.util.Iterator;
  25. import java.util.Map;
  26. import java.util.List;
  27. // XML
  28. import org.w3c.dom.Document;
  29. // Avalon
  30. import org.apache.avalon.framework.configuration.Configuration;
  31. import org.apache.avalon.framework.configuration.ConfigurationException;
  32. // FOP
  33. import org.apache.fop.apps.FOPException;
  34. import org.apache.fop.apps.FOUserAgent;
  35. import org.apache.fop.apps.MimeConstants;
  36. import org.apache.fop.area.CTM;
  37. import org.apache.fop.area.LineArea;
  38. import org.apache.fop.area.Page;
  39. import org.apache.fop.area.PageViewport;
  40. import org.apache.fop.area.RegionViewport;
  41. import org.apache.fop.area.Trait;
  42. import org.apache.fop.area.OffDocumentItem;
  43. import org.apache.fop.area.BookmarkData;
  44. import org.apache.fop.area.inline.Character;
  45. import org.apache.fop.area.inline.TextArea;
  46. import org.apache.fop.area.inline.ForeignObject;
  47. import org.apache.fop.area.inline.Image;
  48. import org.apache.fop.area.inline.Leader;
  49. import org.apache.fop.area.inline.InlineParent;
  50. import org.apache.fop.area.inline.WordArea;
  51. import org.apache.fop.area.inline.SpaceArea;
  52. import org.apache.fop.datatypes.ColorType;
  53. import org.apache.fop.fonts.Typeface;
  54. import org.apache.fop.fonts.Font;
  55. import org.apache.fop.fonts.FontSetup;
  56. import org.apache.fop.fonts.FontMetrics;
  57. import org.apache.fop.image.FopImage;
  58. import org.apache.fop.image.ImageFactory;
  59. import org.apache.fop.image.XMLImage;
  60. import org.apache.fop.pdf.PDFAnnotList;
  61. import org.apache.fop.pdf.PDFColor;
  62. import org.apache.fop.pdf.PDFDocument;
  63. import org.apache.fop.pdf.PDFEncryptionManager;
  64. import org.apache.fop.pdf.PDFFilterList;
  65. import org.apache.fop.pdf.PDFInfo;
  66. import org.apache.fop.pdf.PDFLink;
  67. import org.apache.fop.pdf.PDFOutline;
  68. import org.apache.fop.pdf.PDFPage;
  69. import org.apache.fop.pdf.PDFResourceContext;
  70. import org.apache.fop.pdf.PDFResources;
  71. import org.apache.fop.pdf.PDFState;
  72. import org.apache.fop.pdf.PDFStream;
  73. import org.apache.fop.pdf.PDFText;
  74. import org.apache.fop.pdf.PDFXObject;
  75. import org.apache.fop.render.AbstractPathOrientedRenderer;
  76. import org.apache.fop.render.Graphics2DAdapter;
  77. import org.apache.fop.render.RendererContext;
  78. import org.apache.fop.fo.Constants;
  79. /*
  80. todo:
  81. word rendering and optimistion
  82. pdf state optimisation
  83. line and border
  84. background pattern
  85. writing mode
  86. text decoration
  87. */
  88. /**
  89. * Renderer that renders areas to PDF
  90. *
  91. */
  92. public class PDFRenderer extends AbstractPathOrientedRenderer {
  93. /**
  94. * The mime type for pdf
  95. */
  96. public static final String MIME_TYPE = MimeConstants.MIME_PDF;
  97. /** Controls whether comments are written to the PDF stream. */
  98. protected static final boolean WRITE_COMMENTS = true;
  99. /**
  100. * the PDF Document being created
  101. */
  102. protected PDFDocument pdfDoc;
  103. /**
  104. * Map of pages using the PageViewport as the key
  105. * this is used for prepared pages that cannot be immediately
  106. * rendered
  107. */
  108. protected Map pages = null;
  109. /**
  110. * Page references are stored using the PageViewport as the key
  111. * when a reference is made the PageViewport is used
  112. * for pdf this means we need the pdf page reference
  113. */
  114. protected Map pageReferences = new java.util.HashMap();
  115. /** Page viewport references */
  116. protected Map pvReferences = new java.util.HashMap();
  117. /**
  118. * The output stream to write the document to
  119. */
  120. protected OutputStream ostream;
  121. /**
  122. * the /Resources object of the PDF document being created
  123. */
  124. protected PDFResources pdfResources;
  125. /**
  126. * the current stream to add PDF commands to
  127. */
  128. protected PDFStream currentStream;
  129. /**
  130. * the current annotation list to add annotations to
  131. */
  132. protected PDFResourceContext currentContext = null;
  133. /**
  134. * the current page to add annotations to
  135. */
  136. protected PDFPage currentPage;
  137. /** The current Transform */
  138. protected AffineTransform currentBasicTransform;
  139. /** drawing state */
  140. protected PDFState currentState = null;
  141. /** Name of currently selected font */
  142. protected String currentFontName = "";
  143. /** Size of currently selected font */
  144. protected int currentFontSize = 0;
  145. /** page height */
  146. protected int pageHeight;
  147. /** Registry of PDF filters */
  148. protected Map filterMap;
  149. /**
  150. * true if a TJ command is left to be written
  151. */
  152. protected boolean textOpen = false;
  153. /**
  154. * true if a BT command has been written.
  155. */
  156. protected boolean inTextMode = false;
  157. /**
  158. * the previous Y coordinate of the last word written.
  159. * Used to decide if we can draw the next word on the same line.
  160. */
  161. protected int prevWordY = 0;
  162. /**
  163. * the previous X coordinate of the last word written.
  164. * used to calculate how much space between two words
  165. */
  166. protected int prevWordX = 0;
  167. /**
  168. * The width of the previous word. Used to calculate space between
  169. */
  170. protected int prevWordWidth = 0;
  171. /**
  172. * reusable word area string buffer to reduce memory usage
  173. */
  174. //private StringBuffer wordAreaPDF = new StringBuffer();
  175. /**
  176. * create the PDF renderer
  177. */
  178. public PDFRenderer() {
  179. }
  180. /**
  181. * Configure the PDF renderer.
  182. * Get the configuration to be used for pdf stream filters,
  183. * fonts etc.
  184. * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
  185. */
  186. public void configure(Configuration cfg) throws ConfigurationException {
  187. //PDF filters
  188. this.filterMap = PDFFilterList.buildFilterMapFromConfiguration(cfg);
  189. //Font configuration
  190. List cfgFonts = FontSetup.buildFontListFromConfiguration(cfg);
  191. if (this.fontList == null) {
  192. this.fontList = cfgFonts;
  193. } else {
  194. this.fontList.addAll(cfgFonts);
  195. }
  196. }
  197. /**
  198. * @see org.apache.fop.render.Renderer#setUserAgent(FOUserAgent)
  199. */
  200. public void setUserAgent(FOUserAgent agent) {
  201. super.setUserAgent(agent);
  202. }
  203. /**
  204. * @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
  205. */
  206. public void startRenderer(OutputStream stream) throws IOException {
  207. if (userAgent == null) {
  208. throw new IllegalStateException("UserAgent must be set before starting the renderer");
  209. }
  210. ostream = stream;
  211. this.pdfDoc = new PDFDocument(
  212. userAgent.getProducer() != null ? userAgent.getProducer() : "");
  213. this.pdfDoc.setCreator(userAgent.getCreator());
  214. this.pdfDoc.setCreationDate(userAgent.getCreationDate());
  215. this.pdfDoc.getInfo().setAuthor(userAgent.getAuthor());
  216. this.pdfDoc.getInfo().setTitle(userAgent.getTitle());
  217. this.pdfDoc.getInfo().setKeywords(userAgent.getKeywords());
  218. this.pdfDoc.setFilterMap(filterMap);
  219. this.pdfDoc.outputHeader(stream);
  220. //Setup encryption if necessary
  221. PDFEncryptionManager.setupPDFEncryption(
  222. userAgent.getPDFEncryptionParams(), this.pdfDoc);
  223. }
  224. /**
  225. * @see org.apache.fop.render.Renderer#stopRenderer()
  226. */
  227. public void stopRenderer() throws IOException {
  228. pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
  229. pdfDoc.outputTrailer(ostream);
  230. this.pdfDoc = null;
  231. ostream = null;
  232. pages = null;
  233. pageReferences.clear();
  234. pvReferences.clear();
  235. pdfResources = null;
  236. currentStream = null;
  237. currentContext = null;
  238. currentPage = null;
  239. currentState = null;
  240. currentFontName = "";
  241. }
  242. /**
  243. * @see org.apache.fop.render.Renderer#supportsOutOfOrder()
  244. */
  245. public boolean supportsOutOfOrder() {
  246. //return false;
  247. return true;
  248. }
  249. /**
  250. * @see org.apache.fop.render.Renderer#processOffDocumentItem(OffDocumentItem)
  251. */
  252. public void processOffDocumentItem(OffDocumentItem odi) {
  253. // render Bookmark-Tree
  254. if (odi instanceof BookmarkData) {
  255. renderBookmarkTree((BookmarkData) odi);
  256. }
  257. }
  258. /**
  259. * Renders a Bookmark-Tree object
  260. * @param bookmarks the BookmarkData object containing all the Bookmark-Items
  261. */
  262. protected void renderBookmarkTree(BookmarkData bookmarks) {
  263. for (int i = 0; i < bookmarks.getCount(); i++) {
  264. BookmarkData ext = bookmarks.getSubData(i);
  265. renderBookmarkItem(ext, null);
  266. }
  267. }
  268. private void renderBookmarkItem(BookmarkData bookmarkItem,
  269. PDFOutline parentBookmarkItem) {
  270. PDFOutline pdfOutline = null;
  271. PageViewport pv = bookmarkItem.getPageViewport();
  272. if (pv != null) {
  273. Rectangle2D bounds = pv.getViewArea();
  274. double h = bounds.getHeight();
  275. float yoffset = (float)h / 1000f;
  276. String intDest = (String)pageReferences.get(pv.getKey());
  277. if (parentBookmarkItem == null) {
  278. PDFOutline outlineRoot = pdfDoc.getOutlineRoot();
  279. pdfOutline = pdfDoc.getFactory().makeOutline(outlineRoot,
  280. bookmarkItem.getBookmarkTitle(),
  281. intDest, yoffset,
  282. bookmarkItem.showChildItems());
  283. } else {
  284. pdfOutline = pdfDoc.getFactory().makeOutline(parentBookmarkItem,
  285. bookmarkItem.getBookmarkTitle(),
  286. intDest, yoffset,
  287. bookmarkItem.showChildItems());
  288. }
  289. }
  290. for (int i = 0; i < bookmarkItem.getCount(); i++) {
  291. renderBookmarkItem(bookmarkItem.getSubData(i), pdfOutline);
  292. }
  293. }
  294. /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
  295. public Graphics2DAdapter getGraphics2DAdapter() {
  296. return new PDFGraphics2DAdapter(this);
  297. }
  298. /**
  299. * writes out a comment.
  300. * @param text text for the comment
  301. */
  302. protected void comment(String text) {
  303. if (WRITE_COMMENTS) {
  304. currentStream.add("% " + text + "\n");
  305. }
  306. }
  307. /** Saves the graphics state of the rendering engine. */
  308. protected void saveGraphicsState() {
  309. endTextObject();
  310. currentStream.add("q\n");
  311. }
  312. /** Restores the last graphics state of the rendering engine. */
  313. protected void restoreGraphicsState() {
  314. endTextObject();
  315. currentStream.add("Q\n");
  316. }
  317. /** Indicates the beginning of a text object. */
  318. protected void beginTextObject() {
  319. if (!inTextMode) {
  320. currentStream.add("BT\n");
  321. inTextMode = true;
  322. }
  323. }
  324. /** Indicates the end of a text object. */
  325. protected void endTextObject() {
  326. closeText();
  327. if (inTextMode) {
  328. currentStream.add("ET\n");
  329. inTextMode = false;
  330. }
  331. }
  332. /**
  333. * Start the next page sequence.
  334. * For the pdf renderer there is no concept of page sequences
  335. * but it uses the first available page sequence title to set
  336. * as the title of the pdf document.
  337. *
  338. * @param seqTitle the title of the page sequence
  339. */
  340. public void startPageSequence(LineArea seqTitle) {
  341. if (seqTitle != null) {
  342. String str = convertTitleToString(seqTitle);
  343. PDFInfo info = this.pdfDoc.getInfo();
  344. if (info.getTitle() == null) {
  345. info.setTitle(str);
  346. }
  347. }
  348. }
  349. /**
  350. * The pdf page is prepared by making the page.
  351. * The page is made in the pdf document without any contents
  352. * and then stored to add the contents later.
  353. * The page objects is stored using the area tree PageViewport
  354. * as a key.
  355. *
  356. * @param page the page to prepare
  357. */
  358. public void preparePage(PageViewport page) {
  359. setupPage(page);
  360. if (pages == null) {
  361. pages = new java.util.HashMap();
  362. }
  363. pages.put(page, currentPage);
  364. }
  365. private void setupPage(PageViewport page) {
  366. this.pdfResources = this.pdfDoc.getResources();
  367. Rectangle2D bounds = page.getViewArea();
  368. double w = bounds.getWidth();
  369. double h = bounds.getHeight();
  370. currentPage = this.pdfDoc.getFactory().makePage(
  371. this.pdfResources,
  372. (int) Math.round(w / 1000), (int) Math.round(h / 1000),
  373. page.getPageIndex());
  374. pageReferences.put(page.getKey(), currentPage.referencePDF());
  375. pvReferences.put(page.getKey(), page);
  376. }
  377. /**
  378. * This method creates a pdf stream for the current page
  379. * uses it as the contents of a new page. The page is written
  380. * immediately to the output stream.
  381. * @see org.apache.fop.render.Renderer#renderPage(PageViewport)
  382. */
  383. public void renderPage(PageViewport page)
  384. throws IOException, FOPException {
  385. if (pages != null
  386. && (currentPage = (PDFPage) pages.get(page)) != null) {
  387. //Retrieve previously prepared page (out-of-line rendering)
  388. pages.remove(page);
  389. } else {
  390. setupPage(page);
  391. }
  392. Rectangle2D bounds = page.getViewArea();
  393. double h = bounds.getHeight();
  394. pageHeight = (int) h;
  395. currentStream = this.pdfDoc.getFactory()
  396. .makeStream(PDFFilterList.CONTENT_FILTER, false);
  397. currentState = new PDFState();
  398. /* This transform shouldn't affect PDFState as it only sets the basic
  399. * coordinate system for the rendering process.
  400. *
  401. currentState.setTransform(new AffineTransform(1, 0, 0, -1, 0,
  402. (int) Math.round(pageHeight / 1000)));
  403. */
  404. // Transform origin at top left to origin at bottom left
  405. currentBasicTransform = new AffineTransform(1, 0, 0, -1, 0,
  406. pageHeight / 1000f);
  407. currentStream.add(CTMHelper.toPDFString(currentBasicTransform, false) + " cm\n");
  408. currentFontName = "";
  409. Page p = page.getPage();
  410. renderPageAreas(p);
  411. this.pdfDoc.registerObject(currentStream);
  412. currentPage.setContents(currentStream);
  413. PDFAnnotList annots = currentPage.getAnnotations();
  414. if (annots != null) {
  415. this.pdfDoc.addObject(annots);
  416. }
  417. this.pdfDoc.addObject(currentPage);
  418. this.pdfDoc.output(ostream);
  419. }
  420. /**
  421. * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM, Rectangle2D)
  422. */
  423. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  424. // Set the given CTM in the graphics state
  425. currentState.push();
  426. currentState.setTransform(
  427. new AffineTransform(CTMHelper.toPDFArray(ctm)));
  428. saveGraphicsState();
  429. if (clippingRect != null) {
  430. clipRect((float)clippingRect.getX() / 1000f,
  431. (float)clippingRect.getY() / 1000f,
  432. (float)clippingRect.getWidth() / 1000f,
  433. (float)clippingRect.getHeight() / 1000f);
  434. }
  435. // multiply with current CTM
  436. currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n");
  437. }
  438. /**
  439. * @see org.apache.fop.render.AbstractRenderer#endVParea()
  440. */
  441. protected void endVParea() {
  442. restoreGraphicsState();
  443. currentState.pop();
  444. }
  445. /**
  446. * Handle the traits for a region
  447. * This is used to draw the traits for the given page region.
  448. * (See Sect. 6.4.1.2 of XSL-FO spec.)
  449. * @param region the RegionViewport whose region is to be drawn
  450. */
  451. protected void handleRegionTraits(RegionViewport region) {
  452. currentFontName = "";
  453. super.handleRegionTraits(region);
  454. }
  455. /** @see org.apache.fop.render.AbstractPathOrientedRenderer */
  456. protected void drawBorderLine(float x1, float y1, float x2, float y2,
  457. boolean horz, boolean startOrBefore, int style, ColorType col) {
  458. float w = x2 - x1;
  459. float h = y2 - y1;
  460. if ((w < 0) || (h < 0)) {
  461. log.error("Negative extent received. Border won't be painted.");
  462. return;
  463. }
  464. switch (style) {
  465. case Constants.EN_DASHED:
  466. setColor(toColor(col), false, null);
  467. if (horz) {
  468. float unit = Math.abs(2 * h);
  469. int rep = (int)(w / unit);
  470. if (rep % 2 == 0) {
  471. rep++;
  472. }
  473. unit = w / rep;
  474. currentStream.add("[" + unit + "] 0 d ");
  475. currentStream.add(h + " w\n");
  476. float ym = y1 + (h / 2);
  477. currentStream.add(x1 + " " + ym + " m " + x2 + " " + ym + " l S\n");
  478. } else {
  479. float unit = Math.abs(2 * w);
  480. int rep = (int)(h / unit);
  481. if (rep % 2 == 0) {
  482. rep++;
  483. }
  484. unit = h / rep;
  485. currentStream.add("[" + unit + "] 0 d ");
  486. currentStream.add(w + " w\n");
  487. float xm = x1 + (w / 2);
  488. currentStream.add(xm + " " + y1 + " m " + xm + " " + y2 + " l S\n");
  489. }
  490. break;
  491. case Constants.EN_DOTTED:
  492. setColor(toColor(col), false, null);
  493. currentStream.add("1 J ");
  494. if (horz) {
  495. float unit = Math.abs(2 * h);
  496. int rep = (int)(w / unit);
  497. if (rep % 2 == 0) {
  498. rep++;
  499. }
  500. unit = w / rep;
  501. currentStream.add("[0 " + unit + "] 0 d ");
  502. currentStream.add(h + " w\n");
  503. float ym = y1 + (h / 2);
  504. currentStream.add(x1 + " " + ym + " m " + x2 + " " + ym + " l S\n");
  505. } else {
  506. float unit = Math.abs(2 * w);
  507. int rep = (int)(h / unit);
  508. if (rep % 2 == 0) {
  509. rep++;
  510. }
  511. unit = h / rep;
  512. currentStream.add("[0 " + unit + " ] 0 d ");
  513. currentStream.add(w + " w\n");
  514. float xm = x1 + (w / 2);
  515. currentStream.add(xm + " " + y1 + " m " + xm + " " + y2 + " l S\n");
  516. }
  517. break;
  518. case Constants.EN_DOUBLE:
  519. setColor(toColor(col), false, null);
  520. currentStream.add("[] 0 d ");
  521. if (horz) {
  522. float h3 = h / 3;
  523. currentStream.add(h3 + " w\n");
  524. float ym1 = y1 + (h3 / 2);
  525. float ym2 = ym1 + h3 + h3;
  526. currentStream.add(x1 + " " + ym1 + " m " + x2 + " " + ym1 + " l S\n");
  527. currentStream.add(x1 + " " + ym2 + " m " + x2 + " " + ym2 + " l S\n");
  528. } else {
  529. float w3 = w / 3;
  530. currentStream.add(w3 + " w\n");
  531. float xm1 = x1 + (w3 / 2);
  532. float xm2 = xm1 + w3 + w3;
  533. currentStream.add(xm1 + " " + y1 + " m " + xm1 + " " + y2 + " l S\n");
  534. currentStream.add(xm2 + " " + y1 + " m " + xm2 + " " + y2 + " l S\n");
  535. }
  536. break;
  537. case Constants.EN_GROOVE:
  538. case Constants.EN_RIDGE:
  539. {
  540. float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
  541. currentStream.add("[] 0 d ");
  542. Color c = toColor(col);
  543. if (horz) {
  544. Color uppercol = lightenColor(c, -colFactor);
  545. Color lowercol = lightenColor(c, colFactor);
  546. float h3 = h / 3;
  547. currentStream.add(h3 + " w\n");
  548. float ym1 = y1 + (h3 / 2);
  549. setColor(uppercol, false, null);
  550. currentStream.add(x1 + " " + ym1 + " m " + x2 + " " + ym1 + " l S\n");
  551. setColor(c, false, null);
  552. currentStream.add(x1 + " " + (ym1 + h3) + " m "
  553. + x2 + " " + (ym1 + h3) + " l S\n");
  554. setColor(lowercol, false, null);
  555. currentStream.add(x1 + " " + (ym1 + h3 + h3) + " m "
  556. + x2 + " " + (ym1 + h3 + h3) + " l S\n");
  557. } else {
  558. Color leftcol = lightenColor(c, -colFactor);
  559. Color rightcol = lightenColor(c, colFactor);
  560. float w3 = w / 3;
  561. currentStream.add(w3 + " w\n");
  562. float xm1 = x1 + (w3 / 2);
  563. setColor(leftcol, false, null);
  564. currentStream.add(xm1 + " " + y1 + " m " + xm1 + " " + y2 + " l S\n");
  565. setColor(c, false, null);
  566. currentStream.add((xm1 + w3) + " " + y1 + " m "
  567. + (xm1 + w3) + " " + y2 + " l S\n");
  568. setColor(rightcol, false, null);
  569. currentStream.add((xm1 + w3 + w3) + " " + y1 + " m "
  570. + (xm1 + w3 + w3) + " " + y2 + " l S\n");
  571. }
  572. break;
  573. }
  574. case Constants.EN_INSET:
  575. case Constants.EN_OUTSET:
  576. {
  577. float colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
  578. currentStream.add("[] 0 d ");
  579. Color c = toColor(col);
  580. if (horz) {
  581. c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
  582. currentStream.add(h + " w\n");
  583. float ym1 = y1 + (h / 2);
  584. setColor(c, false, null);
  585. currentStream.add(x1 + " " + ym1 + " m " + x2 + " " + ym1 + " l S\n");
  586. } else {
  587. c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
  588. currentStream.add(w + " w\n");
  589. float xm1 = x1 + (w / 2);
  590. setColor(c, false, null);
  591. currentStream.add(xm1 + " " + y1 + " m " + xm1 + " " + y2 + " l S\n");
  592. }
  593. break;
  594. }
  595. case Constants.EN_HIDDEN:
  596. break;
  597. default:
  598. setColor(toColor(col), false, null);
  599. currentStream.add("[] 0 d ");
  600. if (horz) {
  601. currentStream.add(h + " w\n");
  602. float ym = y1 + (h / 2);
  603. currentStream.add(x1 + " " + ym + " m " + x2 + " " + ym + " l S\n");
  604. } else {
  605. currentStream.add(w + " w\n");
  606. float xm = x1 + (w / 2);
  607. currentStream.add(xm + " " + y1 + " m " + xm + " " + y2 + " l S\n");
  608. }
  609. }
  610. }
  611. /**
  612. * Sets the current line width in points.
  613. * @param width line width in points
  614. */
  615. private void updateLineWidth(float width) {
  616. if (currentState.setLineWidth(width)) {
  617. //Only write if value has changed WRT the current line width
  618. currentStream.add(width + " w\n");
  619. }
  620. }
  621. /**
  622. * Clip a rectangular area.
  623. * write a clipping operation given coordinates in the current
  624. * transform.
  625. * @param x the x coordinate
  626. * @param y the y coordinate
  627. * @param width the width of the area
  628. * @param height the height of the area
  629. */
  630. protected void clipRect(float x, float y, float width, float height) {
  631. currentStream.add(x + " " + y + " " + width + " " + height + " re ");
  632. clip();
  633. }
  634. /**
  635. * Clip an area.
  636. */
  637. protected void clip() {
  638. currentStream.add("W\n");
  639. currentStream.add("n\n");
  640. }
  641. /**
  642. * Moves the current point to (x, y), omitting any connecting line segment.
  643. * @param x x coordinate
  644. * @param y y coordinate
  645. */
  646. protected void moveTo(float x, float y) {
  647. currentStream.add(x + " " + y + " m ");
  648. }
  649. /**
  650. * Appends a straight line segment from the current point to (x, y). The
  651. * new current point is (x, y).
  652. * @param x x coordinate
  653. * @param y y coordinate
  654. */
  655. protected void lineTo(float x, float y) {
  656. currentStream.add(x + " " + y + " l ");
  657. }
  658. /**
  659. * Closes the current subpath by appending a straight line segment from
  660. * the current point to the starting point of the subpath.
  661. */
  662. protected void closePath() {
  663. currentStream.add("h ");
  664. }
  665. /**
  666. * @see org.apache.fop.render.AbstractPathOrientedRenderer#fillRect(float, float, float, float)
  667. */
  668. protected void fillRect(float x, float y, float w, float h) {
  669. if (w != 0 && h != 0) {
  670. currentStream.add(x + " " + y + " " + w + " " + h + " re f\n");
  671. }
  672. }
  673. /**
  674. * Draw a line.
  675. *
  676. * @param startx the start x position
  677. * @param starty the start y position
  678. * @param endx the x end position
  679. * @param endy the y end position
  680. */
  681. private void drawLine(float startx, float starty, float endx, float endy) {
  682. currentStream.add(startx + " " + starty + " m ");
  683. currentStream.add(endx + " " + endy + " l S\n");
  684. }
  685. /**
  686. * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport, List)
  687. *//*
  688. protected void renderBlockViewport(BlockViewport bv, List children) {
  689. // clip and position viewport if necessary
  690. // save positions
  691. int saveIP = currentIPPosition;
  692. int saveBP = currentBPPosition;
  693. String saveFontName = currentFontName;
  694. CTM ctm = bv.getCTM();
  695. int borderPaddingStart = bv.getBorderAndPaddingWidthStart();
  696. int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore();
  697. float x, y;
  698. x = (float)(bv.getXOffset() + containingIPPosition) / 1000f;
  699. y = (float)(bv.getYOffset() + containingBPPosition) / 1000f;
  700. if (bv.getPositioning() == Block.ABSOLUTE
  701. || bv.getPositioning() == Block.FIXED) {
  702. //For FIXED, we need to break out of the current viewports to the
  703. //one established by the page. We save the state stack for restoration
  704. //after the block-container has been painted. See below.
  705. List breakOutList = null;
  706. if (bv.getPositioning() == Block.FIXED) {
  707. breakOutList = breakOutOfStateStack();
  708. }
  709. CTM tempctm = new CTM(containingIPPosition, containingBPPosition);
  710. ctm = tempctm.multiply(ctm);
  711. //This is the content-rect
  712. float width = (float)bv.getIPD() / 1000f;
  713. float height = (float)bv.getBPD() / 1000f;
  714. //Adjust for spaces (from margin or indirectly by start-indent etc.
  715. Integer spaceStart = (Integer) bv.getTrait(Trait.SPACE_START);
  716. if (spaceStart != null) {
  717. x += spaceStart.floatValue() / 1000;
  718. }
  719. Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE);
  720. if (spaceBefore != null) {
  721. y += spaceBefore.floatValue() / 1000;
  722. }
  723. float bpwidth = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd()) / 1000f;
  724. float bpheight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter()) / 1000f;
  725. drawBackAndBorders(bv, x, y, width + bpwidth, height + bpheight);
  726. //Now adjust for border/padding
  727. x += borderPaddingStart / 1000f;
  728. y += borderPaddingBefore / 1000f;
  729. if (bv.getClip()) {
  730. saveGraphicsState();
  731. clipRect(x, y, width, height);
  732. }
  733. startVParea(ctm);
  734. currentIPPosition = 0;
  735. currentBPPosition = 0;
  736. renderBlocks(bv, children);
  737. endVParea();
  738. if (bv.getClip()) {
  739. restoreGraphicsState();
  740. }
  741. // clip if necessary
  742. if (breakOutList != null) {
  743. restoreStateStackAfterBreakOut(breakOutList);
  744. }
  745. currentIPPosition = saveIP;
  746. currentBPPosition = saveBP;
  747. } else {
  748. Integer spaceBefore = (Integer)bv.getTrait(Trait.SPACE_BEFORE);
  749. if (spaceBefore != null) {
  750. currentBPPosition += spaceBefore.intValue();
  751. }
  752. //borders and background in the old coordinate system
  753. handleBlockTraits(bv);
  754. CTM tempctm = new CTM(containingIPPosition, currentBPPosition);
  755. ctm = tempctm.multiply(ctm);
  756. //Now adjust for border/padding
  757. x += borderPaddingStart / 1000f;
  758. y += borderPaddingBefore / 1000f;
  759. // clip if necessary
  760. if (bv.getClip()) {
  761. saveGraphicsState();
  762. float width = (float)bv.getIPD() / 1000f;
  763. float height = (float)bv.getBPD() / 1000f;
  764. clipRect(x, y, width, height);
  765. }
  766. if (ctm != null) {
  767. startVParea(ctm);
  768. currentIPPosition = 0;
  769. currentBPPosition = 0;
  770. }
  771. renderBlocks(bv, children);
  772. if (ctm != null) {
  773. endVParea();
  774. }
  775. if (bv.getClip()) {
  776. restoreGraphicsState();
  777. }
  778. currentIPPosition = saveIP;
  779. currentBPPosition = saveBP;
  780. //Adjust BP position (alloc BPD + spaces)
  781. if (spaceBefore != null) {
  782. currentBPPosition += spaceBefore.intValue();
  783. }
  784. currentBPPosition += (int)(bv.getAllocBPD());
  785. Integer spaceAfter = (Integer)bv.getTrait(Trait.SPACE_AFTER);
  786. if (spaceAfter != null) {
  787. currentBPPosition += spaceAfter.intValue();
  788. }
  789. }
  790. currentFontName = saveFontName;
  791. }*/
  792. /**
  793. * Breaks out of the state stack to handle fixed block-containers.
  794. * @return the saved state stack to recreate later
  795. */
  796. protected List breakOutOfStateStack() {
  797. List breakOutList = new java.util.ArrayList();
  798. PDFState.Data data;
  799. while (true) {
  800. data = currentState.getData();
  801. if (currentState.pop() == null) {
  802. break;
  803. }
  804. if (breakOutList.size() == 0) {
  805. comment("------ break out!");
  806. }
  807. breakOutList.add(0, data); //Insert because of stack-popping
  808. restoreGraphicsState();
  809. }
  810. return breakOutList;
  811. }
  812. /**
  813. * Restores the state stack after a break out.
  814. * @param breakOutList the state stack to restore.
  815. */
  816. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  817. CTM tempctm;
  818. comment("------ restoring context after break-out...");
  819. PDFState.Data data;
  820. Iterator i = breakOutList.iterator();
  821. double[] matrix = new double[6];
  822. while (i.hasNext()) {
  823. data = (PDFState.Data)i.next();
  824. currentState.push();
  825. saveGraphicsState();
  826. AffineTransform at = data.getTransform();
  827. if (!at.isIdentity()) {
  828. currentState.setTransform(at);
  829. at.getMatrix(matrix);
  830. tempctm = new CTM(matrix[0], matrix[1], matrix[2], matrix[3],
  831. matrix[4] * 1000, matrix[5] * 1000);
  832. currentStream.add(CTMHelper.toPDFString(tempctm) + " cm\n");
  833. }
  834. //TODO Break-out: Also restore items such as line width and color
  835. //Left out for now because all this painting stuff is very
  836. //inconsistent. Some values go over PDFState, some don't.
  837. }
  838. comment("------ done.");
  839. }
  840. /**
  841. * @see org.apache.fop.render.AbstractRenderer#renderLineArea(LineArea)
  842. */
  843. protected void renderLineArea(LineArea line) {
  844. super.renderLineArea(line);
  845. closeText();
  846. }
  847. /**
  848. * Render inline parent area.
  849. * For pdf this handles the inline parent area traits such as
  850. * links, border, background.
  851. * @param ip the inline parent area
  852. */
  853. public void renderInlineParent(InlineParent ip) {
  854. float start = currentIPPosition / 1000f;
  855. float top = (ip.getOffset() + currentBPPosition) / 1000f;
  856. float width = ip.getIPD() / 1000f;
  857. float height = ip.getBPD() / 1000f;
  858. // render contents
  859. super.renderInlineParent(ip);
  860. // place the link over the top
  861. Object tr = ip.getTrait(Trait.INTERNAL_LINK);
  862. boolean internal = false;
  863. String dest = null;
  864. float yoffset = 0;
  865. if (tr == null) {
  866. dest = (String)ip.getTrait(Trait.EXTERNAL_LINK);
  867. } else {
  868. String pvKey = (String)tr;
  869. dest = (String)pageReferences.get(pvKey);
  870. if (dest != null) {
  871. PageViewport pv = (PageViewport)pvReferences.get(pvKey);
  872. Rectangle2D bounds = pv.getViewArea();
  873. double h = bounds.getHeight();
  874. yoffset = (float)h / 1000f;
  875. internal = true;
  876. }
  877. }
  878. if (dest != null) {
  879. // add link to pdf document
  880. Rectangle2D rect = new Rectangle2D.Float(start, top, width, height);
  881. // transform rect to absolute coords
  882. AffineTransform transform = currentState.getTransform();
  883. rect = transform.createTransformedShape(rect).getBounds2D();
  884. rect = currentBasicTransform.createTransformedShape(rect).getBounds2D();
  885. int type = internal ? PDFLink.INTERNAL : PDFLink.EXTERNAL;
  886. PDFLink pdflink = pdfDoc.getFactory().makeLink(
  887. rect, dest, type, yoffset);
  888. currentPage.addAnnotation(pdflink);
  889. }
  890. }
  891. /**
  892. * @see org.apache.fop.render.AbstractRenderer#renderCharacter(Character)
  893. */
  894. public void renderCharacter(Character ch) {
  895. renderInlineAreaBackAndBorders(ch);
  896. beginTextObject();
  897. StringBuffer pdf = new StringBuffer();
  898. String name = (String) ch.getTrait(Trait.FONT_NAME);
  899. int size = ((Integer) ch.getTrait(Trait.FONT_SIZE)).intValue();
  900. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  901. Typeface tf = (Typeface) fontInfo.getFonts().get(name);
  902. boolean useMultiByte = tf.isMultiByte();
  903. // String startText = useMultiByte ? "<FEFF" : "(";
  904. String startText = useMultiByte ? "<" : "(";
  905. String endText = useMultiByte ? "> " : ") ";
  906. updateFont(name, size, pdf);
  907. ColorType ct = (ColorType) ch.getTrait(Trait.COLOR);
  908. if (ct != null) {
  909. updateColor(ct, true, pdf);
  910. }
  911. // word.getOffset() = only height of text itself
  912. // currentBlockIPPosition: 0 for beginning of line; nonzero
  913. // where previous line area failed to take up entire allocated space
  914. int rx = currentIPPosition + ch.getBorderAndPaddingWidthStart();
  915. int bl = currentBPPosition + ch.getOffset() + ch.getBaselineOffset();
  916. /* log.debug("Text = " + ch.getTextArea() +
  917. "; text width: " + ch.getWidth() +
  918. "; BlockIP Position: " + currentBlockIPPosition +
  919. "; currentBPPosition: " + currentBPPosition +
  920. "; offset: " + ch.getOffset());
  921. */
  922. // Set letterSpacing
  923. //float ls = fs.getLetterSpacing() / this.currentFontSize;
  924. //pdf.append(ls).append(" Tc\n");
  925. if (!textOpen || bl != prevWordY) {
  926. closeText();
  927. pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
  928. + (ch.getTextLetterSpaceAdjust() / 1000f) + " Tc "
  929. + (ch.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText);
  930. prevWordY = bl;
  931. textOpen = true;
  932. } else {
  933. closeText();
  934. pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
  935. + (ch.getTextLetterSpaceAdjust() / 1000f) + " Tc "
  936. + (ch.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText);
  937. textOpen = true;
  938. }
  939. prevWordWidth = ch.getIPD();
  940. prevWordX = rx;
  941. String s = ch.getChar();
  942. FontMetrics metrics = fontInfo.getMetricsFor(name);
  943. Font fs = new Font(name, metrics, size);
  944. escapeText(s, fs, useMultiByte, pdf);
  945. pdf.append(endText);
  946. currentStream.add(pdf.toString());
  947. renderTextDecoration(tf, size, ch, bl, rx);
  948. super.renderCharacter(ch);
  949. }
  950. /**
  951. * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea)
  952. */
  953. public void renderText(TextArea text) {
  954. renderInlineAreaBackAndBorders(text);
  955. beginTextObject();
  956. StringBuffer pdf = new StringBuffer();
  957. String name = (String) text.getTrait(Trait.FONT_NAME);
  958. int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue();
  959. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  960. Typeface tf = (Typeface) fontInfo.getFonts().get(name);
  961. boolean useMultiByte = tf.isMultiByte();
  962. updateFont(name, size, pdf);
  963. ColorType ct = (ColorType) text.getTrait(Trait.COLOR);
  964. updateColor(ct, true, pdf);
  965. // word.getOffset() = only height of text itself
  966. // currentBlockIPPosition: 0 for beginning of line; nonzero
  967. // where previous line area failed to take up entire allocated space
  968. int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
  969. int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
  970. /* log.debug("Text = " + text.getTextArea() +
  971. "; text width: " + text.getWidth() +
  972. "; BlockIP Position: " + currentBlockIPPosition +
  973. "; currentBPPosition: " + currentBPPosition +
  974. "; offset: " + text.getOffset());
  975. */
  976. // Set letterSpacing
  977. //float ls = fs.getLetterSpacing() / this.currentFontSize;
  978. //pdf.append(ls).append(" Tc\n");
  979. if (!textOpen || bl != prevWordY) {
  980. closeText();
  981. pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
  982. + (text.getTextLetterSpaceAdjust() / 1000f) + " Tc "
  983. + (text.getTextWordSpaceAdjust() / 1000f) + " Tw [");
  984. prevWordY = bl;
  985. textOpen = true;
  986. } else {
  987. closeText();
  988. pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + (bl / 1000f) + " Tm "
  989. + (text.getTextLetterSpaceAdjust() / 1000f) + " Tc "
  990. + (text.getTextWordSpaceAdjust() / 1000f) + " Tw [");
  991. textOpen = true;
  992. }
  993. prevWordWidth = text.getIPD();
  994. prevWordX = rx;
  995. currentStream.add(pdf.toString());
  996. super.renderText(text);
  997. renderTextDecoration(tf, size, text, bl, rx);
  998. }
  999. /**
  1000. * @see org.apache.fop.render.AbstractRenderer#renderWord(WordArea)
  1001. */
  1002. public void renderWord(WordArea word) {
  1003. String name = (String) word.getParentArea().getTrait(Trait.FONT_NAME);
  1004. int size = ((Integer) word.getParentArea().getTrait(Trait.FONT_SIZE)).intValue();
  1005. Typeface tf = (Typeface) fontInfo.getFonts().get(name);
  1006. boolean useMultiByte = tf.isMultiByte();
  1007. String startText = useMultiByte ? "<" : "(";
  1008. String endText = useMultiByte ? "> " : ") ";
  1009. StringBuffer pdf = new StringBuffer();
  1010. pdf.append(startText);
  1011. String s = word.getWord();
  1012. FontMetrics metrics = fontInfo.getMetricsFor(name);
  1013. Font fs = new Font(name, metrics, size);
  1014. escapeText(s, fs, useMultiByte, pdf);
  1015. pdf.append(endText);
  1016. currentStream.add(pdf.toString());
  1017. super.renderWord(word);
  1018. }
  1019. /**
  1020. * @see org.apache.fop.render.AbstractRenderer#renderSpace(SpaceArea)
  1021. */
  1022. public void renderSpace(SpaceArea space) {
  1023. String name = (String) space.getParentArea().getTrait(Trait.FONT_NAME);
  1024. int size = ((Integer) space.getParentArea().getTrait(Trait.FONT_SIZE)).intValue();
  1025. Typeface tf = (Typeface) fontInfo.getFonts().get(name);
  1026. boolean useMultiByte = tf.isMultiByte();
  1027. String startText = useMultiByte ? "<" : "(";
  1028. String endText = useMultiByte ? "> " : ") ";
  1029. StringBuffer pdf = new StringBuffer();
  1030. pdf.append(startText);
  1031. String s = space.getSpace();
  1032. FontMetrics metrics = fontInfo.getMetricsFor(name);
  1033. Font fs = new Font(name, metrics, size);
  1034. escapeText(s, fs, useMultiByte, pdf);
  1035. pdf.append(endText);
  1036. if (useMultiByte) {
  1037. pdf.append(-(((TextArea) space.getParentArea()).getTextWordSpaceAdjust() / (size / 1000)) + " ");
  1038. }
  1039. currentStream.add(pdf.toString());
  1040. super.renderSpace(space);
  1041. }
  1042. /**
  1043. * Escapes text according to PDF rules.
  1044. * @param s Text to escape
  1045. * @param fs Font state
  1046. * @param useMultiByte Indicates the use of multi byte convention
  1047. * @param pdf target buffer for the escaped text
  1048. */
  1049. public void escapeText(String s, Font fs,
  1050. boolean useMultiByte, StringBuffer pdf) {
  1051. String startText = useMultiByte ? "<" : "(";
  1052. String endText = useMultiByte ? "> " : ") ";
  1053. boolean kerningAvailable = false;
  1054. Map kerning = fs.getKerning();
  1055. if (kerning != null && !kerning.isEmpty()) {
  1056. //kerningAvailable = true;
  1057. //TODO Reenable me when the layout engine supports kerning, too
  1058. log.warn("Kerning support is disabled until it is supported by the layout engine!");
  1059. }
  1060. int l = s.length();
  1061. for (int i = 0; i < l; i++) {
  1062. char ch = fs.mapChar(s.charAt(i));
  1063. if (!useMultiByte) {
  1064. if (ch > 127) {
  1065. pdf.append("\\");
  1066. pdf.append(Integer.toOctalString((int) ch));
  1067. } else {
  1068. switch (ch) {
  1069. case '(':
  1070. case ')':
  1071. case '\\':
  1072. pdf.append("\\");
  1073. break;
  1074. }
  1075. pdf.append(ch);
  1076. }
  1077. } else {
  1078. pdf.append(PDFText.toUnicodeHex(ch));
  1079. }
  1080. if (kerningAvailable && (i + 1) < l) {
  1081. addKerning(pdf, (new Integer((int) ch)),
  1082. (new Integer((int) fs.mapChar(s.charAt(i + 1)))
  1083. ), kerning, startText, endText);
  1084. }
  1085. }
  1086. }
  1087. private void addKerning(StringBuffer buf, Integer ch1, Integer ch2,
  1088. Map kerning, String startText, String endText) {
  1089. Map kernPair = (Map) kerning.get(ch1);
  1090. if (kernPair != null) {
  1091. Integer width = (Integer) kernPair.get(ch2);
  1092. if (width != null) {
  1093. buf.append(endText).append(-width.intValue());
  1094. buf.append(' ').append(startText);
  1095. }
  1096. }
  1097. }
  1098. /**
  1099. * Checks to see if we have some text rendering commands open
  1100. * still and writes out the TJ command to the stream if we do
  1101. */
  1102. protected void closeText() {
  1103. if (textOpen) {
  1104. currentStream.add("] TJ\n");
  1105. textOpen = false;
  1106. prevWordX = 0;
  1107. prevWordY = 0;
  1108. currentFontName = "";
  1109. }
  1110. }
  1111. /**
  1112. * Establishes a new foreground or fill color. In contrast to updateColor
  1113. * this method does not check the PDFState for optimization possibilities.
  1114. * @param col the color to apply
  1115. * @param fill true to set the fill color, false for the foreground color
  1116. * @param pdf StringBuffer to write the PDF code to, if null, the code is
  1117. * written to the current stream.
  1118. */
  1119. protected void setColor(Color col, boolean fill, StringBuffer pdf) {
  1120. PDFColor color = new PDFColor(col);
  1121. closeText();
  1122. if (pdf != null) {
  1123. pdf.append(color.getColorSpaceOut(fill));
  1124. } else {
  1125. currentStream.add(color.getColorSpaceOut(fill));
  1126. }
  1127. }
  1128. /**
  1129. * Establishes a new foreground or fill color.
  1130. * @param col the color to apply (null skips this operation)
  1131. * @param fill true to set the fill color, false for the foreground color
  1132. * @param pdf StringBuffer to write the PDF code to, if null, the code is
  1133. * written to the current stream.
  1134. */
  1135. private void updateColor(ColorType col, boolean fill, StringBuffer pdf) {
  1136. if (col == null) {
  1137. return;
  1138. }
  1139. Color newCol = toColor(col);
  1140. boolean update = false;
  1141. if (fill) {
  1142. update = currentState.setBackColor(newCol);
  1143. } else {
  1144. update = currentState.setColor(newCol);
  1145. }
  1146. if (update) {
  1147. setColor(newCol, fill, pdf);
  1148. }
  1149. }
  1150. /** @see org.apache.fop.render.AbstractPathOrientedRenderer */
  1151. protected void updateColor(ColorType col, boolean fill) {
  1152. updateColor(col, fill, null);
  1153. }
  1154. private void updateFont(String name, int size, StringBuffer pdf) {
  1155. if ((!name.equals(this.currentFontName))
  1156. || (size != this.currentFontSize)) {
  1157. closeText();
  1158. this.currentFontName = name;
  1159. this.currentFontSize = size;
  1160. pdf = pdf.append("/" + name + " " + ((float) size / 1000f)
  1161. + " Tf\n");
  1162. }
  1163. }
  1164. /**
  1165. * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, Rectangle2D)
  1166. */
  1167. public void renderImage(Image image, Rectangle2D pos) {
  1168. endTextObject();
  1169. String url = image.getURL();
  1170. putImage(url, pos);
  1171. }
  1172. /** @see org.apache.fop.render.AbstractPathOrientedRenderer */
  1173. protected void drawImage(String url, Rectangle2D pos) {
  1174. endTextObject();
  1175. putImage(url, pos);
  1176. }
  1177. /**
  1178. * Adds a PDF XObject (a bitmap) to the PDF that will later be referenced.
  1179. * @param url URL of the bitmap
  1180. * @param pos Position of the bitmap
  1181. */
  1182. protected void putImage(String url, Rectangle2D pos) {
  1183. PDFXObject xobject = pdfDoc.getImage(url);
  1184. if (xobject != null) {
  1185. float w = (float) pos.getWidth() / 1000f;
  1186. float h = (float) pos.getHeight() / 1000f;
  1187. placeImage((float)pos.getX() / 1000f,
  1188. (float)pos.getY() / 1000f, w, h, xobject.getXNumber());
  1189. return;
  1190. }
  1191. url = ImageFactory.getURL(url);
  1192. ImageFactory fact = ImageFactory.getInstance();
  1193. FopImage fopimage = fact.getImage(url, userAgent);
  1194. if (fopimage == null) {
  1195. return;
  1196. }
  1197. if (!fopimage.load(FopImage.DIMENSIONS)) {
  1198. return;
  1199. }
  1200. String mime = fopimage.getMimeType();
  1201. if ("text/xml".equals(mime)) {
  1202. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  1203. return;
  1204. }
  1205. Document doc = ((XMLImage) fopimage).getDocument();
  1206. String ns = ((XMLImage) fopimage).getNameSpace();
  1207. renderDocument(doc, ns, pos);
  1208. } else if ("image/svg+xml".equals(mime)) {
  1209. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  1210. return;
  1211. }
  1212. Document doc = ((XMLImage) fopimage).getDocument();
  1213. String ns = ((XMLImage) fopimage).getNameSpace();
  1214. renderDocument(doc, ns, pos);
  1215. } else if ("image/eps".equals(mime)) {
  1216. FopPDFImage pdfimage = new FopPDFImage(fopimage, url);
  1217. int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber();
  1218. fact.releaseImage(url, userAgent);
  1219. } else if ("image/jpeg".equals(mime) || "image/tiff".equals(mime)) {
  1220. FopPDFImage pdfimage = new FopPDFImage(fopimage, url);
  1221. int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber();
  1222. fact.releaseImage(url, userAgent);
  1223. float w = (float)pos.getWidth() / 1000f;
  1224. float h = (float)pos.getHeight() / 1000f;
  1225. placeImage((float) pos.getX() / 1000,
  1226. (float) pos.getY() / 1000, w, h, xobj);
  1227. } else {
  1228. if (!fopimage.load(FopImage.BITMAP)) {
  1229. return;
  1230. }
  1231. FopPDFImage pdfimage = new FopPDFImage(fopimage, url);
  1232. int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber();
  1233. fact.releaseImage(url, userAgent);
  1234. float w = (float) pos.getWidth() / 1000f;
  1235. float h = (float) pos.getHeight() / 1000f;
  1236. placeImage((float) pos.getX() / 1000f,
  1237. (float) pos.getY() / 1000f, w, h, xobj);
  1238. }
  1239. // output new data
  1240. try {
  1241. this.pdfDoc.output(ostream);
  1242. } catch (IOException ioe) {
  1243. // ioexception will be caught later
  1244. }
  1245. }
  1246. /**
  1247. * Places a previously registered image at a certain place on the page.
  1248. * @param x X coordinate
  1249. * @param y Y coordinate
  1250. * @param w width for image
  1251. * @param h height for image
  1252. * @param xobj object number of the referenced image
  1253. */
  1254. protected void placeImage(float x, float y, float w, float h, int xobj) {
  1255. saveGraphicsState();
  1256. currentStream.add(w + " 0 0 "
  1257. + -h + " "
  1258. + (currentIPPosition / 1000f + x) + " "
  1259. + (currentBPPosition / 1000f + h + y)
  1260. + " cm\n" + "/Im" + xobj + " Do\n");
  1261. restoreGraphicsState();
  1262. }
  1263. /**
  1264. * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, Rectangle2D)
  1265. */
  1266. public void renderForeignObject(ForeignObject fo, Rectangle2D pos) {
  1267. endTextObject();
  1268. Document doc = fo.getDocument();
  1269. String ns = fo.getNameSpace();
  1270. renderDocument(doc, ns, pos);
  1271. }
  1272. /**
  1273. * Renders an XML document (SVG for example).
  1274. * @param doc DOM document representing the XML document
  1275. * @param ns Namespace for the document
  1276. * @param pos Position on the page
  1277. */
  1278. public void renderDocument(Document doc, String ns, Rectangle2D pos) {
  1279. RendererContext context;
  1280. context = new RendererContext(this, MIME_TYPE);
  1281. context.setUserAgent(userAgent);
  1282. context.setProperty(PDFRendererContextConstants.PDF_DOCUMENT, pdfDoc);
  1283. context.setProperty(PDFRendererContextConstants.OUTPUT_STREAM, ostream);
  1284. context.setProperty(PDFRendererContextConstants.PDF_STATE, currentState);
  1285. context.setProperty(PDFRendererContextConstants.PDF_PAGE, currentPage);
  1286. context.setProperty(PDFRendererContextConstants.PDF_CONTEXT,
  1287. currentContext == null ? currentPage : currentContext);
  1288. context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext);
  1289. context.setProperty(PDFRendererContextConstants.PDF_STREAM, currentStream);
  1290. context.setProperty(PDFRendererContextConstants.XPOS,
  1291. new Integer(currentIPPosition + (int) pos.getX()));
  1292. context.setProperty(PDFRendererContextConstants.YPOS,
  1293. new Integer(currentBPPosition + (int) pos.getY()));
  1294. context.setProperty(PDFRendererContextConstants.PDF_FONT_INFO, fontInfo);
  1295. context.setProperty(PDFRendererContextConstants.PDF_FONT_NAME, currentFontName);
  1296. context.setProperty(PDFRendererContextConstants.PDF_FONT_SIZE,
  1297. new Integer(currentFontSize));
  1298. context.setProperty(PDFRendererContextConstants.WIDTH,
  1299. new Integer((int) pos.getWidth()));
  1300. context.setProperty(PDFRendererContextConstants.HEIGHT,
  1301. new Integer((int) pos.getHeight()));
  1302. renderXML(context, doc, ns);
  1303. }
  1304. /**
  1305. * Render leader area.
  1306. * This renders a leader area which is an area with a rule.
  1307. * @param area the leader area to render
  1308. */
  1309. public void renderLeader(Leader area) {
  1310. renderInlineAreaBackAndBorders(area);
  1311. currentState.push();
  1312. saveGraphicsState();
  1313. int style = area.getRuleStyle();
  1314. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  1315. float starty = (currentBPPosition + area.getOffset()) / 1000f;
  1316. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  1317. + area.getIPD()) / 1000f;
  1318. float ruleThickness = area.getRuleThickness() / 1000f;
  1319. ColorType col = (ColorType)area.getTrait(Trait.COLOR);
  1320. switch (style) {
  1321. case EN_SOLID:
  1322. case EN_DASHED:
  1323. case EN_DOUBLE:
  1324. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1325. true, true, style, col);
  1326. break;
  1327. case EN_DOTTED:
  1328. clipRect(startx, starty, endx - startx, ruleThickness);
  1329. //This displaces the dots to the right by half a dot's width
  1330. //TODO There's room for improvement here
  1331. currentStream.add("1 0 0 1 " + (ruleThickness / 2) + " 0 cm\n");
  1332. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1333. true, true, style, col);
  1334. break;
  1335. case EN_GROOVE:
  1336. case EN_RIDGE:
  1337. float half = area.getRuleThickness() / 2000f;
  1338. setColor(lightenColor(toColor(col), 0.6f), true, null);
  1339. currentStream.add(startx + " " + starty + " m\n");
  1340. currentStream.add(endx + " " + starty + " l\n");
  1341. currentStream.add(endx + " " + (starty + 2 * half) + " l\n");
  1342. currentStream.add(startx + " " + (starty + 2 * half) + " l\n");
  1343. currentStream.add("h\n");
  1344. currentStream.add("f\n");
  1345. setColor(toColor(col), true, null);
  1346. if (style == EN_GROOVE) {
  1347. currentStream.add(startx + " " + starty + " m\n");
  1348. currentStream.add(endx + " " + starty + " l\n");
  1349. currentStream.add(endx + " " + (starty + half) + " l\n");
  1350. currentStream.add((startx + half) + " " + (starty + half) + " l\n");
  1351. currentStream.add(startx + " " + (starty + 2 * half) + " l\n");
  1352. } else {
  1353. currentStream.add(endx + " " + starty + " m\n");
  1354. currentStream.add(endx + " " + (starty + 2 * half) + " l\n");
  1355. currentStream.add(startx + " " + (starty + 2 * half) + " l\n");
  1356. currentStream.add(startx + " " + (starty + half) + " l\n");
  1357. currentStream.add((endx - half) + " " + (starty + half) + " l\n");
  1358. }
  1359. currentStream.add("h\n");
  1360. currentStream.add("f\n");
  1361. break;
  1362. default:
  1363. throw new UnsupportedOperationException("rule style not supported");
  1364. }
  1365. restoreGraphicsState();
  1366. currentState.pop();
  1367. beginTextObject();
  1368. super.renderLeader(area);
  1369. }
  1370. /** @see org.apache.fop.render.AbstractRenderer */
  1371. public String getMimeType() {
  1372. return MIME_TYPE;
  1373. }
  1374. }