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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.svg;
  19. import java.awt.BasicStroke;
  20. import java.awt.Color;
  21. import java.awt.Graphics2D;
  22. import java.awt.Paint;
  23. import java.awt.Shape;
  24. import java.awt.Stroke;
  25. import java.awt.font.TextAttribute;
  26. import java.awt.geom.AffineTransform;
  27. import java.awt.geom.Ellipse2D;
  28. import java.awt.geom.GeneralPath;
  29. import java.awt.geom.Point2D;
  30. import java.lang.reflect.Method;
  31. import java.text.AttributedCharacterIterator;
  32. import java.util.Iterator;
  33. import java.util.List;
  34. import org.apache.batik.bridge.SVGFontFamily;
  35. import org.apache.batik.gvt.font.GVTFont;
  36. import org.apache.batik.gvt.font.GVTFontFamily;
  37. import org.apache.batik.gvt.font.GVTGlyphVector;
  38. import org.apache.batik.gvt.renderer.StrokingTextPainter;
  39. import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
  40. import org.apache.batik.gvt.text.TextPaintInfo;
  41. import org.apache.batik.gvt.text.TextSpanLayout;
  42. import org.apache.fop.fonts.Font;
  43. import org.apache.fop.fonts.FontInfo;
  44. import org.apache.fop.fonts.FontTriplet;
  45. import org.apache.fop.util.CharUtilities;
  46. /**
  47. * Renders the attributed character iterator of a <tt>TextNode</tt>.
  48. * This class draws the text directly into the PDFGraphics2D so that
  49. * the text is not drawn using shapes which makes the PDF files larger.
  50. * If the text is simple enough to draw then it sets the font and calls
  51. * drawString. If the text is complex or the cannot be translated
  52. * into a simple drawString the StrokingTextPainter is used instead.
  53. *
  54. * @version $Id$
  55. */
  56. public class PDFTextPainter extends StrokingTextPainter {
  57. private static final boolean DEBUG = false;
  58. private boolean strokeText = false;
  59. private FontInfo fontInfo;
  60. /**
  61. * Create a new PDF text painter with the given font information.
  62. * @param fi the font info
  63. */
  64. public PDFTextPainter(FontInfo fi) {
  65. fontInfo = fi;
  66. }
  67. /** {@inheritDoc} */
  68. protected void paintTextRuns(List textRuns, Graphics2D g2d) {
  69. if (DEBUG) {
  70. System.out.println("paintTextRuns: count = " + textRuns.size());
  71. //fontInfo.dumpAllTripletsToSystemOut();
  72. }
  73. if (!(g2d instanceof PDFGraphics2D) || strokeText) {
  74. super.paintTextRuns(textRuns, g2d);
  75. return;
  76. }
  77. final PDFGraphics2D pdf = (PDFGraphics2D)g2d;
  78. PDFTextUtil textUtil = new PDFTextUtil(pdf.fontInfo) {
  79. protected void write(String code) {
  80. pdf.currentStream.write(code);
  81. }
  82. };
  83. for (int i = 0; i < textRuns.size(); i++) {
  84. TextRun textRun = (TextRun)textRuns.get(i);
  85. AttributedCharacterIterator runaci = textRun.getACI();
  86. runaci.first();
  87. TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
  88. if (tpi == null || !tpi.visible) {
  89. continue;
  90. }
  91. if ((tpi != null) && (tpi.composite != null)) {
  92. g2d.setComposite(tpi.composite);
  93. }
  94. //------------------------------------
  95. TextSpanLayout layout = textRun.getLayout();
  96. if (DEBUG) {
  97. int charCount = runaci.getEndIndex() - runaci.getBeginIndex();
  98. System.out.println("================================================");
  99. System.out.println("New text run:");
  100. System.out.println("char count: " + charCount);
  101. System.out.println("range: "
  102. + runaci.getBeginIndex() + " - " + runaci.getEndIndex());
  103. System.out.println("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs()
  104. }
  105. //Gather all characters of the run
  106. StringBuffer chars = new StringBuffer();
  107. for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) {
  108. chars.append(runaci.current());
  109. runaci.next();
  110. }
  111. runaci.first();
  112. if (DEBUG) {
  113. System.out.println("Text: " + chars);
  114. pdf.currentStream.write("%Text: " + chars + "\n");
  115. }
  116. GeneralPath debugShapes = null;
  117. if (DEBUG) {
  118. debugShapes = new GeneralPath();
  119. }
  120. Font[] fonts = findFonts(runaci);
  121. if (fonts == null || fonts.length == 0) {
  122. //Draw using Java2D
  123. textRun.getLayout().draw(g2d);
  124. continue;
  125. }
  126. textUtil.saveGraphicsState();
  127. textUtil.concatMatrix(g2d.getTransform());
  128. Shape imclip = g2d.getClip();
  129. pdf.writeClip(imclip);
  130. applyColorAndPaint(tpi, pdf);
  131. textUtil.beginTextObject();
  132. textUtil.setFonts(fonts);
  133. boolean stroke = (tpi.strokePaint != null)
  134. && (tpi.strokeStroke != null);
  135. textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false);
  136. AffineTransform localTransform = new AffineTransform();
  137. Point2D prevPos = null;
  138. double prevVisibleCharWidth = 0.0;
  139. GVTGlyphVector gv = layout.getGlyphVector();
  140. for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
  141. char ch = chars.charAt(index);
  142. boolean visibleChar = gv.isGlyphVisible(index)
  143. || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch));
  144. if (DEBUG) {
  145. System.out.println("glyph " + index
  146. + " -> " + layout.getGlyphIndex(index) + " => " + ch);
  147. if (CharUtilities.isAnySpace(ch) && ch != 32) {
  148. System.out.println("Space found: " + Integer.toHexString(ch));
  149. }
  150. if (ch == CharUtilities.ZERO_WIDTH_JOINER) {
  151. System.out.println("ZWJ found: " + Integer.toHexString(ch));
  152. }
  153. if (ch == CharUtilities.SOFT_HYPHEN) {
  154. System.out.println("Soft hyphen found: " + Integer.toHexString(ch));
  155. }
  156. if (!visibleChar) {
  157. System.out.println("Invisible glyph found: " + Integer.toHexString(ch));
  158. }
  159. }
  160. if (!visibleChar) {
  161. continue;
  162. }
  163. Point2D p = gv.getGlyphPosition(index);
  164. AffineTransform glyphTransform = gv.getGlyphTransform(index);
  165. //TODO Glyph transforms could be refined so not every char has to be painted
  166. //with its own TJ command (stretch/squeeze case could be optimized)
  167. if (DEBUG) {
  168. System.out.println("pos " + p + ", transform " + glyphTransform);
  169. Shape sh;
  170. sh = gv.getGlyphLogicalBounds(index);
  171. if (sh == null) {
  172. sh = new Ellipse2D.Double(p.getX(), p.getY(), 2, 2);
  173. }
  174. debugShapes.append(sh, false);
  175. }
  176. //Exact position of the glyph
  177. localTransform.setToIdentity();
  178. localTransform.translate(p.getX(), p.getY());
  179. if (glyphTransform != null) {
  180. localTransform.concatenate(glyphTransform);
  181. }
  182. localTransform.scale(1, -1);
  183. boolean yPosChanged = (prevPos == null
  184. || prevPos.getY() != p.getY()
  185. || glyphTransform != null);
  186. if (yPosChanged) {
  187. if (index > 0) {
  188. textUtil.writeTJ();
  189. textUtil.writeTextMatrix(localTransform);
  190. }
  191. } else {
  192. double xdiff = p.getX() - prevPos.getX();
  193. //Width of previous character
  194. Font font = textUtil.getCurrentFont();
  195. double cw = prevVisibleCharWidth;
  196. double effxdiff = (1000 * xdiff) - cw;
  197. if (effxdiff != 0) {
  198. double adjust = (-effxdiff / font.getFontSize());
  199. textUtil.adjustGlyphTJ(adjust * 1000);
  200. }
  201. if (DEBUG) {
  202. System.out.println("==> x diff: " + xdiff + ", " + effxdiff
  203. + ", charWidth: " + cw);
  204. }
  205. }
  206. Font f = textUtil.selectFontForChar(ch);
  207. if (f != textUtil.getCurrentFont()) {
  208. textUtil.writeTJ();
  209. textUtil.setCurrentFont(f);
  210. textUtil.writeTf(f);
  211. textUtil.writeTextMatrix(localTransform);
  212. }
  213. char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch);
  214. textUtil.writeTJChar(paintChar);
  215. //Update last position
  216. prevPos = p;
  217. prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index));
  218. }
  219. textUtil.writeTJ();
  220. textUtil.endTextObject();
  221. textUtil.restoreGraphicsState();
  222. if (DEBUG) {
  223. g2d.setStroke(new BasicStroke(0));
  224. g2d.setColor(Color.LIGHT_GRAY);
  225. g2d.draw(debugShapes);
  226. }
  227. }
  228. }
  229. private void applyColorAndPaint(TextPaintInfo tpi, PDFGraphics2D pdf) {
  230. Paint fillPaint = tpi.fillPaint;
  231. Paint strokePaint = tpi.strokePaint;
  232. Stroke stroke = tpi.strokeStroke;
  233. int fillAlpha = PDFGraphics2D.OPAQUE;
  234. if (fillPaint instanceof Color) {
  235. Color col = (Color)fillPaint;
  236. pdf.applyColor(col, true);
  237. fillAlpha = col.getAlpha();
  238. }
  239. if (strokePaint instanceof Color) {
  240. Color col = (Color)strokePaint;
  241. pdf.applyColor(col, false);
  242. }
  243. pdf.applyPaint(fillPaint, true);
  244. pdf.applyStroke(stroke);
  245. if (strokePaint != null) {
  246. pdf.applyPaint(strokePaint, false);
  247. }
  248. pdf.applyAlpha(fillAlpha, PDFGraphics2D.OPAQUE);
  249. }
  250. private Font[] findFonts(AttributedCharacterIterator aci) {
  251. List fonts = new java.util.ArrayList();
  252. List gvtFonts = (List) aci.getAttribute(
  253. GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
  254. Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
  255. Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
  256. Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE);
  257. String style = ((posture != null) && (posture.floatValue() > 0.0))
  258. ? "italic" : "normal";
  259. int weight = ((taWeight != null)
  260. && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD
  261. : Font.WEIGHT_NORMAL;
  262. String fontFamily = null;
  263. //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES
  264. //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set
  265. /* The following code section is not available until Batik 1.7 is released. */
  266. GVTFont gvtFont = (GVTFont)aci.getAttribute(
  267. GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
  268. if (gvtFont != null) {
  269. try {
  270. Method method = gvtFont.getClass().getMethod("getFamilyName", null);
  271. String gvtFontFamily = (String)method.invoke(gvtFont, null);
  272. //TODO Uncomment the following line when Batik 1.7 is shipped with FOP
  273. //String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6
  274. if (DEBUG) {
  275. System.out.print(gvtFontFamily + ", ");
  276. }
  277. if (fontInfo.hasFont(gvtFontFamily, style, weight)) {
  278. FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style,
  279. weight);
  280. int fsize = (int)(fontSize.floatValue() * 1000);
  281. fonts.add(fontInfo.getFontInstance(triplet, fsize));
  282. }
  283. } catch (Exception e) {
  284. //Most likely NoSuchMethodError here when using Batik 1.6
  285. //Just skip this section in this case
  286. }
  287. }
  288. if (gvtFonts != null) {
  289. Iterator i = gvtFonts.iterator();
  290. while (i.hasNext()) {
  291. GVTFontFamily fam = (GVTFontFamily) i.next();
  292. if (fam instanceof SVGFontFamily) {
  293. return null; //Let Batik paint this text!
  294. }
  295. fontFamily = fam.getFamilyName();
  296. if (DEBUG) {
  297. System.out.print(fontFamily + ", ");
  298. }
  299. if (fontInfo.hasFont(fontFamily, style, weight)) {
  300. FontTriplet triplet = fontInfo.fontLookup(fontFamily, style,
  301. weight);
  302. int fsize = (int)(fontSize.floatValue() * 1000);
  303. fonts.add(fontInfo.getFontInstance(triplet, fsize));
  304. }
  305. }
  306. }
  307. if (fonts.size() == 0) {
  308. FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL);
  309. int fsize = (int)(fontSize.floatValue() * 1000);
  310. fonts.add(fontInfo.getFontInstance(triplet, fsize));
  311. if (DEBUG) {
  312. System.out.print("fallback to 'any' font");
  313. }
  314. }
  315. if (DEBUG) {
  316. System.out.println();
  317. }
  318. return (Font[])fonts.toArray(new Font[fonts.size()]);
  319. }
  320. }