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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. /*
  2. * $Id$
  3. * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
  4. * For details on use and redistribution please refer to the
  5. * LICENSE file included with these sources.
  6. */
  7. package org.apache.fop.render.pdf;
  8. // FOP
  9. import org.apache.fop.render.PrintRenderer;
  10. import org.apache.fop.image.ImageArea;
  11. import org.apache.fop.image.FopImage;
  12. import org.apache.fop.apps.FOPException;
  13. import org.apache.fop.fo.properties.*;
  14. import org.apache.fop.layout.inline.*;
  15. import org.apache.fop.datatypes.*;
  16. import org.apache.fop.svg.*;
  17. import org.apache.fop.pdf.*;
  18. import org.apache.fop.layout.*;
  19. import org.apache.fop.image.*;
  20. import org.apache.fop.extensions.*;
  21. import org.apache.fop.datatypes.IDReferences;
  22. import org.apache.fop.render.pdf.fonts.LazyFont;
  23. import org.apache.batik.bridge.*;
  24. import org.apache.batik.swing.svg.*;
  25. import org.apache.batik.swing.gvt.*;
  26. import org.apache.batik.gvt.*;
  27. import org.apache.batik.gvt.renderer.*;
  28. import org.apache.batik.gvt.filter.*;
  29. import org.apache.batik.gvt.event.*;
  30. import org.w3c.dom.*;
  31. import org.w3c.dom.svg.*;
  32. import org.w3c.dom.css.*;
  33. import org.w3c.dom.svg.SVGLength;
  34. // Java
  35. import java.io.IOException;
  36. import java.io.OutputStream;
  37. import java.util.Enumeration;
  38. import java.util.Vector;
  39. import java.util.Hashtable;
  40. import java.awt.geom.AffineTransform;
  41. import java.awt.geom.Dimension2D;
  42. import java.awt.Point;
  43. import java.awt.RenderingHints;
  44. import java.awt.font.FontRenderContext;
  45. import java.awt.Dimension;
  46. /**
  47. * Renderer that renders areas to PDF
  48. *
  49. * Modified by Mark Lillywhite, mark-fop@inomial.com to use the
  50. * new Renderer interface. The PDF renderer is by far the trickiest
  51. * renderer and the best supported by FOP. It also required some
  52. * reworking in the way that Pages, Catalogs and the Root object
  53. * were written to the stream. The output document should now still
  54. * be a 100% compatible PDF document, but hte order of the document
  55. * writing is significantly different. See also the changes
  56. * to PDFPage, PDFPages and PDFRoot.
  57. */
  58. public class PDFRenderer extends PrintRenderer {
  59. /**
  60. * the PDF Document being created
  61. */
  62. protected PDFDocument pdfDoc;
  63. /**
  64. * the /Resources object of the PDF document being created
  65. */
  66. protected PDFResources pdfResources;
  67. /**
  68. * the current stream to add PDF commands to
  69. */
  70. PDFStream currentStream;
  71. /**
  72. * the current annotation list to add annotations to
  73. */
  74. PDFAnnotList currentAnnotList;
  75. /**
  76. * the current page to add annotations to
  77. */
  78. PDFPage currentPage;
  79. PDFColor currentColor;
  80. /**
  81. * true if a TJ command is left to be written
  82. */
  83. boolean textOpen = false;
  84. /**
  85. * the previous Y coordinate of the last word written.
  86. * Used to decide if we can draw the next word on the same line.
  87. */
  88. int prevWordY = 0;
  89. /**
  90. * the previous X coordinate of the last word written.
  91. * used to calculate how much space between two words
  92. */
  93. int prevWordX = 0;
  94. /**
  95. * The width of the previous word. Used to calculate space between
  96. */
  97. int prevWordWidth = 0;
  98. /**
  99. * reusable word area string buffer to reduce memory usage
  100. */
  101. private StringBuffer _wordAreaPDF = new StringBuffer();
  102. /**
  103. * options
  104. */
  105. protected Hashtable options;
  106. /**
  107. * create the PDF renderer
  108. */
  109. public PDFRenderer() {
  110. this.pdfDoc = new PDFDocument();
  111. }
  112. /**
  113. * set up renderer options
  114. */
  115. public void setOptions(Hashtable options) {
  116. this.options = options;
  117. }
  118. /**
  119. * set the PDF document's producer
  120. *
  121. * @param producer string indicating application producing PDF
  122. */
  123. public void setProducer(String producer) {
  124. this.pdfDoc.setProducer(producer);
  125. }
  126. public void startRenderer(OutputStream stream)
  127. throws IOException {
  128. pdfDoc.outputHeader(stream);
  129. }
  130. public void stopRenderer(OutputStream stream)
  131. throws IOException {
  132. FontSetup.addToResources(this.pdfDoc, fontInfo);
  133. pdfDoc.outputTrailer(stream);
  134. }
  135. /**
  136. * add a line to the current stream
  137. *
  138. * @param x1 the start x location in millipoints
  139. * @param y1 the start y location in millipoints
  140. * @param x2 the end x location in millipoints
  141. * @param y2 the end y location in millipoints
  142. * @param th the thickness in millipoints
  143. * @param r the red component
  144. * @param g the green component
  145. * @param b the blue component
  146. */
  147. protected void addLine(int x1, int y1, int x2, int y2, int th,
  148. PDFPathPaint stroke) {
  149. closeText();
  150. currentStream.add("ET\nq\n" + stroke.getColorSpaceOut(false)
  151. + (x1 / 1000f) + " " + (y1 / 1000f) + " m "
  152. + (x2 / 1000f) + " " + (y2 / 1000f) + " l "
  153. + (th / 1000f) + " w S\n" + "Q\nBT\n");
  154. }
  155. /**
  156. * add a line to the current stream
  157. *
  158. * @param x1 the start x location in millipoints
  159. * @param y1 the start y location in millipoints
  160. * @param x2 the end x location in millipoints
  161. * @param y2 the end y location in millipoints
  162. * @param th the thickness in millipoints
  163. * @param rs the rule style
  164. * @param r the red component
  165. * @param g the green component
  166. * @param b the blue component
  167. */
  168. protected void addLine(int x1, int y1, int x2, int y2, int th, int rs,
  169. PDFPathPaint stroke) {
  170. closeText();
  171. currentStream.add("ET\nq\n" + stroke.getColorSpaceOut(false)
  172. + setRuleStylePattern(rs) + (x1 / 1000f) + " "
  173. + (y1 / 1000f) + " m " + (x2 / 1000f) + " "
  174. + (y2 / 1000f) + " l " + (th / 1000f) + " w S\n"
  175. + "Q\nBT\n");
  176. }
  177. /**
  178. * add a rectangle to the current stream
  179. *
  180. * @param x the x position of left edge in millipoints
  181. * @param y the y position of top edge in millipoints
  182. * @param w the width in millipoints
  183. * @param h the height in millipoints
  184. * @param stroke the stroke color/gradient
  185. */
  186. protected void addRect(int x, int y, int w, int h, PDFPathPaint stroke) {
  187. closeText();
  188. currentStream.add("ET\nq\n" + stroke.getColorSpaceOut(false)
  189. + (x / 1000f) + " " + (y / 1000f) + " "
  190. + (w / 1000f) + " " + (h / 1000f) + " re s\n"
  191. + "Q\nBT\n");
  192. }
  193. /**
  194. * add a filled rectangle to the current stream
  195. *
  196. * @param x the x position of left edge in millipoints
  197. * @param y the y position of top edge in millipoints
  198. * @param w the width in millipoints
  199. * @param h the height in millipoints
  200. * @param fill the fill color/gradient
  201. * @param stroke the stroke color/gradient
  202. */
  203. protected void addRect(int x, int y, int w, int h, PDFPathPaint stroke,
  204. PDFPathPaint fill) {
  205. closeText();
  206. currentStream.add("ET\nq\n" + fill.getColorSpaceOut(true)
  207. + stroke.getColorSpaceOut(false) + (x / 1000f)
  208. + " " + (y / 1000f) + " " + (w / 1000f) + " "
  209. + (h / 1000f) + " re b\n" + "Q\nBT\n");
  210. }
  211. /**
  212. * add a filled rectangle to the current stream
  213. *
  214. * @param x the x position of left edge in millipoints
  215. * @param y the y position of top edge in millipoints
  216. * @param w the width in millipoints
  217. * @param h the height in millipoints
  218. * @param fill the fill color/gradient
  219. */
  220. protected void addFilledRect(int x, int y, int w, int h,
  221. PDFPathPaint fill) {
  222. closeText();
  223. currentStream.add("ET\nq\n" + fill.getColorSpaceOut(true)
  224. + (x / 1000f) + " " + (y / 1000f) + " "
  225. + (w / 1000f) + " " + (h / 1000f) + " re f\n"
  226. + "Q\nBT\n");
  227. }
  228. /**
  229. * render image area to PDF
  230. *
  231. * @param area the image area to render
  232. */
  233. public void renderImageArea(ImageArea area) {
  234. // adapted from contribution by BoBoGi
  235. int x = this.currentXPosition + area.getXOffset();
  236. int y = this.currentYPosition;
  237. int w = area.getContentWidth();
  238. int h = area.getHeight();
  239. this.currentYPosition -= h;
  240. FopImage img = area.getImage();
  241. if (img instanceof SVGImage) {
  242. try {
  243. closeText();
  244. SVGDocument svg = ((SVGImage)img).getSVGDocument();
  245. currentStream.add("ET\nq\n");
  246. renderSVGDocument(svg, (int)x, (int)y, area.getFontState());
  247. currentStream.add("Q\nBT\n");
  248. } catch (FopImageException e) {}
  249. } else {
  250. int xObjectNum = this.pdfDoc.addImage(img);
  251. closeText();
  252. currentStream.add("ET\nq\n" + (((float)w) / 1000f) + " 0 0 "
  253. + (((float)h) / 1000f) + " "
  254. + (((float)x) / 1000f) + " "
  255. + (((float)(y - h)) / 1000f) + " cm\n" + "/Im"
  256. + xObjectNum + " Do\nQ\nBT\n");
  257. }
  258. this.currentXPosition += area.getContentWidth();
  259. }
  260. /**
  261. * render a foreign object area
  262. */
  263. public void renderForeignObjectArea(ForeignObjectArea area) {
  264. // if necessary need to scale and align the content
  265. this.currentXPosition = this.currentXPosition + area.getXOffset();
  266. this.currentYPosition = this.currentYPosition;
  267. switch (area.getAlign()) {
  268. case TextAlign.START:
  269. break;
  270. case TextAlign.END:
  271. break;
  272. case TextAlign.CENTER:
  273. case TextAlign.JUSTIFY:
  274. break;
  275. }
  276. switch (area.getVerticalAlign()) {
  277. case VerticalAlign.BASELINE:
  278. break;
  279. case VerticalAlign.MIDDLE:
  280. break;
  281. case VerticalAlign.SUB:
  282. break;
  283. case VerticalAlign.SUPER:
  284. break;
  285. case VerticalAlign.TEXT_TOP:
  286. break;
  287. case VerticalAlign.TEXT_BOTTOM:
  288. break;
  289. case VerticalAlign.TOP:
  290. break;
  291. case VerticalAlign.BOTTOM:
  292. break;
  293. }
  294. closeText();
  295. // in general the content will not be text
  296. currentStream.add("ET\n");
  297. // align and scale
  298. currentStream.add("q\n");
  299. switch (area.scalingMethod()) {
  300. case Scaling.UNIFORM:
  301. break;
  302. case Scaling.NON_UNIFORM:
  303. break;
  304. }
  305. // if the overflow is auto (default), scroll or visible
  306. // then the contents should not be clipped, since this
  307. // is considered a printing medium.
  308. switch (area.getOverflow()) {
  309. case Overflow.VISIBLE:
  310. case Overflow.SCROLL:
  311. case Overflow.AUTO:
  312. break;
  313. case Overflow.HIDDEN:
  314. break;
  315. }
  316. area.getObject().render(this);
  317. currentStream.add("Q\n");
  318. currentStream.add("BT\n");
  319. this.currentXPosition += area.getEffectiveWidth();
  320. // this.currentYPosition -= area.getEffectiveHeight();
  321. }
  322. /**
  323. * render SVG area to PDF
  324. *
  325. * @param area the SVG area to render
  326. */
  327. public void renderSVGArea(SVGArea area) {
  328. // place at the current instream offset
  329. int x = this.currentXPosition;
  330. int y = this.currentYPosition;
  331. renderSVGDocument(area.getSVGDocument(), x, y, area.getFontState());
  332. }
  333. protected void renderSVGDocument(Document doc, int x, int y,
  334. FontState fs) {
  335. SVGSVGElement svg = ((SVGDocument)doc).getRootElement();
  336. int w = (int)(svg.getWidth().getBaseVal().getValue() * 1000);
  337. int h = (int)(svg.getHeight().getBaseVal().getValue() * 1000);
  338. float sx = 1, sy = -1;
  339. int xOffset = x, yOffset = y;
  340. /*
  341. * Clip to the svg area.
  342. * Note: To have the svg overlay (under) a text area then use
  343. * an fo:block-container
  344. */
  345. currentStream.add("q\n");
  346. if (w != 0 && h != 0) {
  347. currentStream.add(x / 1000f + " " + y / 1000f + " m\n");
  348. currentStream.add((x + w) / 1000f + " " + y / 1000f + " l\n");
  349. currentStream.add((x + w) / 1000f + " " + (y - h) / 1000f
  350. + " l\n");
  351. currentStream.add(x / 1000f + " " + (y - h) / 1000f + " l\n");
  352. currentStream.add("h\n");
  353. currentStream.add("W\n");
  354. currentStream.add("n\n");
  355. }
  356. // transform so that the coordinates (0,0) is from the top left
  357. // and positive is down and to the right. (0,0) is where the
  358. // viewBox puts it.
  359. currentStream.add(sx + " 0 0 " + sy + " " + xOffset / 1000f + " "
  360. + yOffset / 1000f + " cm\n");
  361. UserAgent userAgent = new MUserAgent(new AffineTransform());
  362. GVTBuilder builder = new GVTBuilder();
  363. GraphicsNodeRenderContext rc = getRenderContext(fs);
  364. BridgeContext ctx = new BridgeContext(userAgent, rc);
  365. PDFAElementBridge aBridge = new PDFAElementBridge();
  366. ctx.putBridge(aBridge);
  367. GraphicsNode root;
  368. PDFGraphics2D graphics = new PDFGraphics2D(true, fs, pdfDoc,
  369. currentFontName,
  370. currentFontSize,
  371. currentXPosition,
  372. currentYPosition);
  373. graphics.setGraphicContext(new org.apache.batik.ext.awt.g2d.GraphicContext());
  374. graphics.setRenderingHints(rc.getRenderingHints());
  375. aBridge.setCurrentTransform(new AffineTransform(sx, 0, 0, sy, xOffset / 1000f, yOffset / 1000f));
  376. try {
  377. root = builder.build(ctx, doc);
  378. root.paint(graphics, rc);
  379. currentStream.add(graphics.getString());
  380. } catch (Exception e) {
  381. log.error("svg graphic could not be rendered: "
  382. + e.getMessage(), e);
  383. }
  384. currentAnnotList = graphics.getAnnotList();
  385. currentStream.add("Q\n");
  386. }
  387. public GraphicsNodeRenderContext getRenderContext(FontState fs) {
  388. GraphicsNodeRenderContext nodeRenderContext = null;
  389. if (nodeRenderContext == null) {
  390. RenderingHints hints = new RenderingHints(null);
  391. hints.put(RenderingHints.KEY_ANTIALIASING,
  392. RenderingHints.VALUE_ANTIALIAS_ON);
  393. hints.put(RenderingHints.KEY_INTERPOLATION,
  394. RenderingHints.VALUE_INTERPOLATION_BILINEAR);
  395. FontRenderContext fontRenderContext =
  396. new FontRenderContext(new AffineTransform(), true, true);
  397. TextPainter textPainter = null;
  398. Boolean bl =
  399. org.apache.fop.configuration.Configuration.getBooleanValue("strokeSVGText");
  400. if (bl == null || bl.booleanValue()) {
  401. textPainter = new StrokingTextPainter();
  402. } else {
  403. textPainter = new PDFTextPainter(fs);
  404. }
  405. GraphicsNodeRableFactory gnrFactory =
  406. new ConcreteGraphicsNodeRableFactory();
  407. nodeRenderContext =
  408. new GraphicsNodeRenderContext(new AffineTransform(), null,
  409. hints, fontRenderContext,
  410. textPainter, gnrFactory);
  411. nodeRenderContext.setTextPainter(textPainter);
  412. }
  413. return nodeRenderContext;
  414. }
  415. /**
  416. * render inline area to PDF
  417. *
  418. * @param area inline area to render
  419. */
  420. public void renderWordArea(WordArea area) {
  421. synchronized (_wordAreaPDF) {
  422. StringBuffer pdf = _wordAreaPDF;
  423. pdf.setLength(0);
  424. Hashtable kerning = null;
  425. boolean kerningAvailable = false;
  426. kerning = area.getFontState().getKerning();
  427. if (kerning != null &&!kerning.isEmpty()) {
  428. kerningAvailable = true;
  429. }
  430. String name = area.getFontState().getFontName();
  431. int size = area.getFontState().getFontSize();
  432. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  433. boolean useMultiByte = false;
  434. Font f =
  435. (Font)area.getFontState().getFontInfo().getFonts().get(name);
  436. if (f instanceof LazyFont){
  437. if(((LazyFont) f).getRealFont() instanceof CIDFont){
  438. useMultiByte = true;
  439. }
  440. }else if (f instanceof CIDFont){
  441. useMultiByte = true;
  442. }
  443. // String startText = useMultiByte ? "<FEFF" : "(";
  444. String startText = useMultiByte ? "<" : "(";
  445. String endText = useMultiByte ? "> " : ") ";
  446. if ((!name.equals(this.currentFontName))
  447. || (size != this.currentFontSize)) {
  448. closeText();
  449. this.currentFontName = name;
  450. this.currentFontSize = size;
  451. pdf = pdf.append("/" + name + " " + (size / 1000) + " Tf\n");
  452. }
  453. PDFColor areaColor = null;
  454. if (this.currentFill instanceof PDFColor) {
  455. areaColor = (PDFColor)this.currentFill;
  456. }
  457. if (areaColor == null || areaColor.red() != (double)area.getRed()
  458. || areaColor.green() != (double)area.getGreen()
  459. || areaColor.blue() != (double)area.getBlue()) {
  460. areaColor = new PDFColor((double)area.getRed(),
  461. (double)area.getGreen(),
  462. (double)area.getBlue());
  463. closeText();
  464. this.currentFill = areaColor;
  465. pdf.append(this.currentFill.getColorSpaceOut(true));
  466. }
  467. int rx = this.currentXPosition;
  468. int bl = this.currentYPosition;
  469. addWordLines(area, rx, bl, size, areaColor);
  470. if (!textOpen || bl != prevWordY) {
  471. closeText();
  472. pdf.append("1 0 0 1 " + (rx / 1000f) + " " + (bl / 1000f)
  473. + " Tm [" + startText);
  474. prevWordY = bl;
  475. textOpen = true;
  476. } else {
  477. // express the space between words in thousandths of an em
  478. int space = prevWordX - rx + prevWordWidth;
  479. float emDiff = (float)space / (float)currentFontSize * 1000f;
  480. // this prevents a problem in Acrobat Reader where large
  481. // numbers cause text to disappear or default to a limit
  482. if (emDiff < -33000) {
  483. closeText();
  484. pdf.append("1 0 0 1 " + (rx / 1000f) + " " + (bl / 1000f)
  485. + " Tm [" + startText);
  486. textOpen = true;
  487. } else {
  488. pdf.append(Float.toString(emDiff));
  489. pdf.append(" ");
  490. pdf.append(startText);
  491. }
  492. }
  493. prevWordWidth = area.getContentWidth();
  494. prevWordX = rx;
  495. String s;
  496. if (area.getPageNumberID()
  497. != null) { // this text is a page number, so resolve it
  498. s = idReferences.getPageNumber(area.getPageNumberID());
  499. if (s == null) {
  500. s = "";
  501. }
  502. } else {
  503. s = area.getText();
  504. }
  505. int l = s.length();
  506. for (int i = 0; i < l; i++) {
  507. char ch = area.getFontState().mapChar(s.charAt(i));
  508. if (!useMultiByte) {
  509. if (ch > 127) {
  510. pdf.append("\\");
  511. pdf.append(Integer.toOctalString((int)ch));
  512. } else {
  513. switch (ch) {
  514. case '(':
  515. case ')':
  516. case '\\':
  517. pdf.append("\\");
  518. break;
  519. }
  520. pdf.append(ch);
  521. }
  522. } else {
  523. pdf.append(getUnicodeString(ch));
  524. }
  525. if (kerningAvailable && (i + 1) < l) {
  526. addKerning(pdf, (new Integer((int)ch)),
  527. (new Integer((int)area.getFontState().mapChar(s.charAt(i + 1)))),
  528. kerning, startText, endText);
  529. }
  530. }
  531. pdf.append(endText);
  532. currentStream.add(pdf.toString());
  533. this.currentXPosition += area.getContentWidth();
  534. }
  535. }
  536. /**
  537. * Convert a char to a multibyte hex representation
  538. */
  539. private String getUnicodeString(char c) {
  540. StringBuffer buf = new StringBuffer(4);
  541. byte[] uniBytes = null;
  542. try {
  543. char[] a = {
  544. c
  545. };
  546. uniBytes = new String(a).getBytes("UnicodeBigUnmarked");
  547. } catch (Exception e) {
  548. // This should never fail
  549. }
  550. for (int i = 0; i < uniBytes.length; i++) {
  551. int b = (uniBytes[i] < 0) ? (int)(256 + uniBytes[i])
  552. : (int)uniBytes[i];
  553. String hexString = Integer.toHexString(b);
  554. if (hexString.length() == 1)
  555. buf = buf.append("0" + hexString);
  556. else
  557. buf = buf.append(hexString);
  558. }
  559. return buf.toString();
  560. }
  561. /**
  562. * Checks to see if we have some text rendering commands open
  563. * still and writes out the TJ command to the stream if we do
  564. */
  565. private void closeText() {
  566. if (textOpen) {
  567. currentStream.add("] TJ\n");
  568. textOpen = false;
  569. prevWordX = 0;
  570. prevWordY = 0;
  571. }
  572. }
  573. private void addKerning(StringBuffer buf, Integer ch1, Integer ch2,
  574. Hashtable kerning, String startText,
  575. String endText) {
  576. Hashtable kernPair = (Hashtable)kerning.get(ch1);
  577. if (kernPair != null) {
  578. Integer width = (Integer)kernPair.get(ch2);
  579. if (width != null) {
  580. buf.append(endText).append(-(width.intValue())).append(' ').append(startText);
  581. }
  582. }
  583. }
  584. public void render(Page page, OutputStream outputStream)
  585. throws FOPException, IOException {
  586. // log.debug("rendering single page to PDF");
  587. this.idReferences = page.getIDReferences();
  588. this.pdfResources = this.pdfDoc.getResources();
  589. this.pdfDoc.setIDReferences(idReferences);
  590. this.renderPage(page);
  591. //FontSetup.addToResources(this.pdfDoc, fontInfo);
  592. // TODO: this needs to be implemented
  593. renderRootExtensions(page);
  594. // log.debug("writing out PDF");
  595. this.pdfDoc.output(outputStream);
  596. }
  597. /**
  598. * render page into PDF
  599. *
  600. * @param page page to render
  601. */
  602. public void renderPage(Page page) {
  603. BodyAreaContainer body;
  604. AreaContainer before, after, start, end;
  605. currentStream = this.pdfDoc.makeStream();
  606. body = page.getBody();
  607. before = page.getBefore();
  608. after = page.getAfter();
  609. start = page.getStart();
  610. end = page.getEnd();
  611. this.currentFontName = "";
  612. this.currentFontSize = 0;
  613. currentStream.add("BT\n");
  614. renderBodyAreaContainer(body);
  615. if (before != null) {
  616. renderAreaContainer(before);
  617. }
  618. if (after != null) {
  619. renderAreaContainer(after);
  620. }
  621. if (start != null) {
  622. renderAreaContainer(start);
  623. }
  624. if (end != null) {
  625. renderAreaContainer(end);
  626. }
  627. closeText();
  628. float w = page.getWidth();
  629. float h = page.getHeight();
  630. currentStream.add("ET\n");
  631. currentPage = this.pdfDoc.makePage(this.pdfResources, currentStream,
  632. Math.round(w / 1000),
  633. Math.round(h / 1000), page);
  634. if (page.hasLinks() || currentAnnotList != null) {
  635. if(currentAnnotList == null) {
  636. currentAnnotList = this.pdfDoc.makeAnnotList();
  637. }
  638. currentPage.setAnnotList(currentAnnotList);
  639. Enumeration e = page.getLinkSets().elements();
  640. while (e.hasMoreElements()) {
  641. LinkSet linkSet = (LinkSet)e.nextElement();
  642. linkSet.align();
  643. String dest = linkSet.getDest();
  644. int linkType = linkSet.getLinkType();
  645. Enumeration f = linkSet.getRects().elements();
  646. while (f.hasMoreElements()) {
  647. LinkedRectangle lrect = (LinkedRectangle)f.nextElement();
  648. currentAnnotList.addLink(this.pdfDoc.makeLink(lrect.getRectangle(),
  649. dest, linkType));
  650. }
  651. }
  652. currentAnnotList = null;
  653. } else {
  654. // just to be on the safe side
  655. currentAnnotList = null;
  656. }
  657. // ensures that color is properly reset for blocks that carry over pages
  658. this.currentFill = null;
  659. }
  660. /**
  661. * defines a string containing dashArray and dashPhase for the rule style
  662. */
  663. private String setRuleStylePattern(int style) {
  664. String rs = "";
  665. switch (style) {
  666. case org.apache.fop.fo.properties.RuleStyle.SOLID:
  667. rs = "[] 0 d ";
  668. break;
  669. case org.apache.fop.fo.properties.RuleStyle.DASHED:
  670. rs = "[3 3] 0 d ";
  671. break;
  672. case org.apache.fop.fo.properties.RuleStyle.DOTTED:
  673. rs = "[1 3] 0 d ";
  674. break;
  675. case org.apache.fop.fo.properties.RuleStyle.DOUBLE:
  676. rs = "[] 0 d ";
  677. break;
  678. default:
  679. rs = "[] 0 d ";
  680. }
  681. return rs;
  682. }
  683. protected void renderRootExtensions(Page page) {
  684. Vector v = page.getExtensions();
  685. if (v != null) {
  686. Enumeration e = v.elements();
  687. while (e.hasMoreElements()) {
  688. ExtensionObj ext = (ExtensionObj)e.nextElement();
  689. if (ext instanceof Outline) {
  690. renderOutline((Outline)ext);
  691. }
  692. }
  693. }
  694. }
  695. private void renderOutline(Outline outline) {
  696. PDFOutline outlineRoot = pdfDoc.getOutlineRoot();
  697. PDFOutline pdfOutline = null;
  698. Outline parent = outline.getParentOutline();
  699. if (parent == null) {
  700. pdfOutline =
  701. this.pdfDoc.makeOutline(outlineRoot,
  702. outline.getLabel().toString(),
  703. outline.getInternalDestination());
  704. } else {
  705. PDFOutline pdfParentOutline =
  706. (PDFOutline)parent.getRendererObject();
  707. if (pdfParentOutline == null) {
  708. log.error("pdfParentOutline is null");
  709. } else {
  710. pdfOutline =
  711. this.pdfDoc.makeOutline(pdfParentOutline,
  712. outline.getLabel().toString(),
  713. outline.getInternalDestination());
  714. }
  715. }
  716. outline.setRendererObject(pdfOutline);
  717. // handle sub outlines
  718. Vector v = outline.getOutlines();
  719. Enumeration e = v.elements();
  720. while (e.hasMoreElements()) {
  721. renderOutline((Outline)e.nextElement());
  722. }
  723. }
  724. protected class MUserAgent implements UserAgent {
  725. AffineTransform currentTransform = null;
  726. /**
  727. * Creates a new SVGUserAgent.
  728. */
  729. protected MUserAgent(AffineTransform at) {
  730. currentTransform = at;
  731. }
  732. /**
  733. * Displays an error message.
  734. */
  735. public void displayError(String message) {
  736. System.err.println(message);
  737. }
  738. /**
  739. * Displays an error resulting from the specified Exception.
  740. */
  741. public void displayError(Exception ex) {
  742. ex.printStackTrace(System.err);
  743. }
  744. /**
  745. * Displays a message in the User Agent interface.
  746. * The given message is typically displayed in a status bar.
  747. */
  748. public void displayMessage(String message) {
  749. System.out.println(message);
  750. }
  751. /**
  752. * Returns a customized the pixel to mm factor.
  753. */
  754. public float getPixelToMM() {
  755. // this is set to 72dpi as the values in fo are 72dpi
  756. return 0.35277777777777777778f; // 72 dpi
  757. // return 0.26458333333333333333333333333333f; // 96dpi
  758. }
  759. /**
  760. * Returns the language settings.
  761. */
  762. public String getLanguages() {
  763. return "en"; // userLanguages;
  764. }
  765. /**
  766. * Returns the user stylesheet uri.
  767. * @return null if no user style sheet was specified.
  768. */
  769. public String getUserStyleSheetURI() {
  770. return null; // userStyleSheetURI;
  771. }
  772. /**
  773. * Returns the class name of the XML parser.
  774. */
  775. public String getXMLParserClassName() {
  776. return org.apache.fop.apps.Driver.getParserClassName();
  777. }
  778. /**
  779. * Opens a link in a new component.
  780. * @param doc The current document.
  781. * @param uri The document URI.
  782. */
  783. public void openLink(SVGAElement elt) {
  784. // application.openLink(uri);
  785. }
  786. public Point getClientAreaLocationOnScreen() {
  787. return new Point(0, 0);
  788. }
  789. public void setSVGCursor(java.awt.Cursor cursor) {}
  790. public AffineTransform getTransform() {
  791. return currentTransform;
  792. }
  793. public Dimension2D getViewportSize() {
  794. return new Dimension(100, 100);
  795. }
  796. public EventDispatcher getEventDispatcher() {
  797. return null;
  798. }
  799. public boolean supportExtension(String str) {
  800. return false;
  801. }
  802. public boolean hasFeature(String str) {
  803. return false;
  804. }
  805. public void registerExtension(BridgeExtension be) {}
  806. public void handleElement(Element elt, Object data) {}
  807. }
  808. }