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.

PDFTextPainter.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. * Copyright 1999-2004 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.svg;
  18. import java.awt.Graphics2D;
  19. import java.awt.geom.Point2D;
  20. import java.awt.geom.Rectangle2D;
  21. /* java.awt.Font is not imported to avoid confusion with
  22. org.apache.fop.fonts.Font */
  23. import java.text.AttributedCharacterIterator;
  24. import java.awt.font.TextAttribute;
  25. import java.awt.Shape;
  26. import java.awt.Paint;
  27. import java.awt.Stroke;
  28. import java.awt.Color;
  29. import java.util.List;
  30. import java.util.Iterator;
  31. import org.apache.batik.gvt.text.Mark;
  32. import org.apache.batik.gvt.TextPainter;
  33. import org.apache.batik.gvt.TextNode;
  34. import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
  35. import org.apache.batik.gvt.text.TextPaintInfo;
  36. import org.apache.batik.gvt.font.GVTFontFamily;
  37. import org.apache.batik.bridge.SVGFontFamily;
  38. import org.apache.batik.gvt.renderer.StrokingTextPainter;
  39. import org.apache.fop.fonts.FontMetrics;
  40. import org.apache.fop.fonts.Font;
  41. import org.apache.fop.fonts.FontInfo;
  42. /**
  43. * Renders the attributed character iterator of a <tt>TextNode</tt>.
  44. * This class draws the text directly into the PDFGraphics2D so that
  45. * the text is not drawn using shapes which makes the PDF files larger.
  46. * If the text is simple enough to draw then it sets the font and calls
  47. * drawString. If the text is complex or the cannot be translated
  48. * into a simple drawString the StrokingTextPainter is used instead.
  49. *
  50. * (todo) handle underline, overline and strikethrough
  51. * (todo) use drawString(AttributedCharacterIterator iterator...) for some
  52. *
  53. * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
  54. * @version $Id: PDFTextPainter.java,v 1.16 2003/03/07 09:51:25 jeremias Exp $
  55. */
  56. public class PDFTextPainter implements TextPainter {
  57. private FontInfo fontInfo;
  58. /**
  59. * Use the stroking text painter to get the bounds and shape.
  60. * Also used as a fallback to draw the string with strokes.
  61. */
  62. protected static final TextPainter PROXY_PAINTER =
  63. StrokingTextPainter.getInstance();
  64. /**
  65. * Create a new PDF text painter with the given font information.
  66. * @param fi the fint info
  67. */
  68. public PDFTextPainter(FontInfo fi) {
  69. fontInfo = fi;
  70. }
  71. /**
  72. * Paints the specified attributed character iterator using the
  73. * specified Graphics2D and context and font context.
  74. * @param node the TextNode to paint
  75. * @param g2d the Graphics2D to use
  76. */
  77. public void paint(TextNode node, Graphics2D g2d) {
  78. String txt = node.getText();
  79. Point2D loc = node.getLocation();
  80. AttributedCharacterIterator aci =
  81. node.getAttributedCharacterIterator();
  82. // reset position to start of char iterator
  83. if (aci.getBeginIndex() == aci.getEndIndex()) {
  84. return;
  85. }
  86. char ch = aci.first();
  87. if (ch == AttributedCharacterIterator.DONE) {
  88. return;
  89. }
  90. TextNode.Anchor anchor;
  91. anchor = (TextNode.Anchor) aci.getAttribute(
  92. GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE);
  93. List gvtFonts;
  94. gvtFonts = (List) aci.getAttribute(
  95. GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
  96. TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
  97. GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
  98. if (tpi == null) {
  99. return;
  100. }
  101. Paint forg = tpi.fillPaint;
  102. Paint strokePaint = tpi.strokePaint;
  103. Float size = (Float) aci.getAttribute(TextAttribute.SIZE);
  104. if (size == null) {
  105. return;
  106. }
  107. Stroke stroke = tpi.strokeStroke;
  108. /*
  109. Float xpos = (Float) aci.getAttribute(
  110. GVTAttributedCharacterIterator.TextAttribute.X);
  111. Float ypos = (Float) aci.getAttribute(
  112. GVTAttributedCharacterIterator.TextAttribute.Y);
  113. */
  114. Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
  115. Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
  116. boolean useStrokePainter = false;
  117. if (forg instanceof Color) {
  118. Color col = (Color) forg;
  119. if (col.getAlpha() != 255) {
  120. useStrokePainter = true;
  121. }
  122. g2d.setColor(col);
  123. }
  124. g2d.setPaint(forg);
  125. g2d.setStroke(stroke);
  126. if (strokePaint != null) {
  127. // need to draw using AttributedCharacterIterator
  128. useStrokePainter = true;
  129. }
  130. if (hasUnsupportedAttributes(aci)) {
  131. useStrokePainter = true;
  132. }
  133. // text contains unsupported information
  134. if (useStrokePainter) {
  135. PROXY_PAINTER.paint(node, g2d);
  136. return;
  137. }
  138. String style = ((posture != null) && (posture.floatValue() > 0.0))
  139. ? "italic" : "normal";
  140. int weight = ((taWeight != null)
  141. && (taWeight.floatValue() > 1.0)) ? Font.BOLD
  142. : Font.NORMAL;
  143. Font fontState = null;
  144. FontInfo fi = fontInfo;
  145. boolean found = false;
  146. String fontFamily = null;
  147. if (gvtFonts != null) {
  148. Iterator i = gvtFonts.iterator();
  149. while (i.hasNext()) {
  150. GVTFontFamily fam = (GVTFontFamily) i.next();
  151. if (fam instanceof SVGFontFamily) {
  152. PROXY_PAINTER.paint(node, g2d);
  153. return;
  154. }
  155. fontFamily = fam.getFamilyName();
  156. if (fi.hasFont(fontFamily, style, weight)) {
  157. String fname = fontInfo.fontLookup(fontFamily, style,
  158. weight);
  159. FontMetrics metrics = fontInfo.getMetricsFor(fname);
  160. int fsize = (int)(size.floatValue() * 1000);
  161. fontState = new Font(fname, metrics, fsize);
  162. found = true;
  163. break;
  164. }
  165. }
  166. }
  167. if (!found) {
  168. String fname =
  169. fontInfo.fontLookup("any", style, Font.NORMAL);
  170. FontMetrics metrics = fontInfo.getMetricsFor(fname);
  171. int fsize = (int)(size.floatValue() * 1000);
  172. fontState = new Font(fname, metrics, fsize);
  173. } else {
  174. if (g2d instanceof PDFGraphics2D) {
  175. ((PDFGraphics2D) g2d).setOverrideFontState(fontState);
  176. }
  177. }
  178. int fStyle = java.awt.Font.PLAIN;
  179. if (weight == Font.BOLD) {
  180. if (style.equals("italic")) {
  181. fStyle = java.awt.Font.BOLD | java.awt.Font.ITALIC;
  182. } else {
  183. fStyle = java.awt.Font.BOLD;
  184. }
  185. } else {
  186. if (style.equals("italic")) {
  187. fStyle = java.awt.Font.ITALIC;
  188. } else {
  189. fStyle = java.awt.Font.PLAIN;
  190. }
  191. }
  192. java.awt.Font font = new java.awt.Font(fontFamily, fStyle,
  193. (int)(fontState.getFontSize() / 1000));
  194. g2d.setFont(font);
  195. float advance = getStringWidth(txt, fontState);
  196. float tx = 0;
  197. if (anchor != null) {
  198. switch (anchor.getType()) {
  199. case TextNode.Anchor.ANCHOR_MIDDLE:
  200. tx = -advance / 2;
  201. break;
  202. case TextNode.Anchor.ANCHOR_END:
  203. tx = -advance;
  204. }
  205. }
  206. g2d.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY()));
  207. }
  208. private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) {
  209. boolean hasunsupported = false;
  210. Object letSpace = aci.getAttribute(
  211. GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING);
  212. if (letSpace != null) {
  213. hasunsupported = true;
  214. }
  215. Object wordSpace = aci.getAttribute(
  216. GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING);
  217. if (wordSpace != null) {
  218. hasunsupported = true;
  219. }
  220. AttributedCharacterIterator.Attribute key;
  221. key = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
  222. Object writeMod = aci.getAttribute(key);
  223. if (!GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals(
  224. writeMod)) {
  225. hasunsupported = true;
  226. }
  227. Object vertOr = aci.getAttribute(
  228. GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION);
  229. if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals(
  230. vertOr)) {
  231. hasunsupported = true;
  232. }
  233. return hasunsupported;
  234. }
  235. private float getStringWidth(String str, Font fontState) {
  236. float wordWidth = 0;
  237. float whitespaceWidth = fontState.getWidth(fontState.mapChar(' '));
  238. for (int i = 0; i < str.length(); i++) {
  239. float charWidth;
  240. char c = str.charAt(i);
  241. if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) {
  242. charWidth = fontState.getWidth(fontState.mapChar(c));
  243. if (charWidth <= 0) {
  244. charWidth = whitespaceWidth;
  245. }
  246. } else {
  247. charWidth = whitespaceWidth;
  248. }
  249. wordWidth += charWidth;
  250. }
  251. return wordWidth / 1000f;
  252. }
  253. /**
  254. * Get the outline shape of the text characters.
  255. * This uses the StrokingTextPainter to get the outline
  256. * shape since in theory it should be the same.
  257. *
  258. * @param node the text node
  259. * @return the outline shape of the text characters
  260. */
  261. public Shape getOutline(TextNode node) {
  262. return PROXY_PAINTER.getOutline(node);
  263. }
  264. /**
  265. * Get the bounds.
  266. * This uses the StrokingTextPainter to get the bounds
  267. * since in theory it should be the same.
  268. *
  269. * @param node the text node
  270. * @return the bounds of the text
  271. */
  272. public Rectangle2D getBounds2D(TextNode node) {
  273. return PROXY_PAINTER.getBounds2D(node);
  274. }
  275. /**
  276. * Get the geometry bounds.
  277. * This uses the StrokingTextPainter to get the bounds
  278. * since in theory it should be the same.
  279. * @param node the text node
  280. * @return the bounds of the text
  281. */
  282. public Rectangle2D getGeometryBounds(TextNode node) {
  283. return PROXY_PAINTER.getGeometryBounds(node);
  284. }
  285. // Methods that have no purpose for PDF
  286. /**
  287. * Get the mark.
  288. * This does nothing since the output is pdf and not interactive.
  289. * @param node the text node
  290. * @param pos the position
  291. * @param all select all
  292. * @return null
  293. */
  294. public Mark getMark(TextNode node, int pos, boolean all) {
  295. return null;
  296. }
  297. /**
  298. * Select at.
  299. * This does nothing since the output is pdf and not interactive.
  300. * @param x the x position
  301. * @param y the y position
  302. * @param node the text node
  303. * @return null
  304. */
  305. public Mark selectAt(double x, double y, TextNode node) {
  306. return null;
  307. }
  308. /**
  309. * Select to.
  310. * This does nothing since the output is pdf and not interactive.
  311. * @param x the x position
  312. * @param y the y position
  313. * @param beginMark the start mark
  314. * @return null
  315. */
  316. public Mark selectTo(double x, double y, Mark beginMark) {
  317. return null;
  318. }
  319. /**
  320. * Selec first.
  321. * This does nothing since the output is pdf and not interactive.
  322. * @param node the text node
  323. * @return null
  324. */
  325. public Mark selectFirst(TextNode node) {
  326. return null;
  327. }
  328. /**
  329. * Select last.
  330. * This does nothing since the output is pdf and not interactive.
  331. * @param node the text node
  332. * @return null
  333. */
  334. public Mark selectLast(TextNode node) {
  335. return null;
  336. }
  337. /**
  338. * Get selected.
  339. * This does nothing since the output is pdf and not interactive.
  340. * @param start the start mark
  341. * @param finish the finish mark
  342. * @return null
  343. */
  344. public int[] getSelected(Mark start, Mark finish) {
  345. return null;
  346. }
  347. /**
  348. * Get the highlighted shape.
  349. * This does nothing since the output is pdf and not interactive.
  350. * @param beginMark the start mark
  351. * @param endMark the end mark
  352. * @return null
  353. */
  354. public Shape getHighlightShape(Mark beginMark, Mark endMark) {
  355. return null;
  356. }
  357. }