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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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 = true;
  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. PDFGraphics2D pdf = (PDFGraphics2D)g2d;
  78. PDFTextUtil textUtil = new PDFTextUtil(pdf);
  79. for (int i = 0; i < textRuns.size(); i++) {
  80. TextRun textRun = (TextRun)textRuns.get(i);
  81. AttributedCharacterIterator runaci = textRun.getACI();
  82. runaci.first();
  83. TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO);
  84. if (tpi == null || !tpi.visible) {
  85. continue;
  86. }
  87. if ((tpi != null) && (tpi.composite != null)) {
  88. g2d.setComposite(tpi.composite);
  89. }
  90. //------------------------------------
  91. TextSpanLayout layout = textRun.getLayout();
  92. if (DEBUG) {
  93. int charCount = runaci.getEndIndex() - runaci.getBeginIndex();
  94. System.out.println("================================================");
  95. System.out.println("New text run:");
  96. System.out.println("char count: " + charCount);
  97. System.out.println("range: "
  98. + runaci.getBeginIndex() + " - " + runaci.getEndIndex());
  99. System.out.println("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs()
  100. }
  101. //Gather all characters of the run
  102. StringBuffer chars = new StringBuffer();
  103. for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) {
  104. chars.append(runaci.current());
  105. runaci.next();
  106. }
  107. runaci.first();
  108. if (DEBUG) {
  109. System.out.println("Text: " + chars);
  110. pdf.currentStream.write("%Text: " + chars + "\n");
  111. }
  112. GeneralPath debugShapes = null;
  113. if (DEBUG) {
  114. debugShapes = new GeneralPath();
  115. }
  116. Font[] fonts = findFonts(runaci);
  117. if (fonts == null || fonts.length == 0) {
  118. //Draw using Java2D
  119. textRun.getLayout().draw(g2d);
  120. continue;
  121. }
  122. textUtil.saveGraphicsState();
  123. textUtil.concatMatrixCurrentTransform();
  124. Shape imclip = g2d.getClip();
  125. pdf.writeClip(imclip);
  126. applyColorAndPaint(tpi, pdf);
  127. textUtil.beginTextObject();
  128. textUtil.setFonts(fonts);
  129. textUtil.setTextRenderingMode(tpi.fillPaint != null, tpi.strokePaint != null, false);
  130. AffineTransform localTransform = new AffineTransform();
  131. Point2D prevPos = null;
  132. double prevVisibleCharWidth = 0.0;
  133. GVTGlyphVector gv = layout.getGlyphVector();
  134. for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
  135. char ch = chars.charAt(index);
  136. boolean visibleChar = gv.isGlyphVisible(index)
  137. || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch));
  138. if (DEBUG) {
  139. System.out.println("glyph " + index
  140. + " -> " + layout.getGlyphIndex(index) + " => " + ch);
  141. if (CharUtilities.isAnySpace(ch) && ch != 32) {
  142. System.out.println("Space found: " + Integer.toHexString(ch));
  143. }
  144. if (ch == CharUtilities.ZERO_WIDTH_JOINER) {
  145. System.out.println("ZWJ found: " + Integer.toHexString(ch));
  146. }
  147. if (ch == CharUtilities.SOFT_HYPHEN) {
  148. System.out.println("Soft hyphen found: " + Integer.toHexString(ch));
  149. }
  150. if (!visibleChar) {
  151. System.out.println("Invisible glyph found: " + Integer.toHexString(ch));
  152. }
  153. }
  154. if (!visibleChar) {
  155. continue;
  156. }
  157. Point2D p = gv.getGlyphPosition(index);
  158. AffineTransform glyphTransform = gv.getGlyphTransform(index);
  159. //TODO Glyph transforms could be refined so not every char has to be painted
  160. //with its own TJ command (stretch/squeeze case could be optimized)
  161. if (DEBUG) {
  162. System.out.println("pos " + p + ", transform " + glyphTransform);
  163. Shape sh;
  164. sh = gv.getGlyphLogicalBounds(index);
  165. if (sh == null) {
  166. sh = new Ellipse2D.Double(p.getX(), p.getY(), 2, 2);
  167. }
  168. debugShapes.append(sh, false);
  169. }
  170. //Exact position of the glyph
  171. localTransform.setToIdentity();
  172. localTransform.translate(p.getX(), p.getY());
  173. if (glyphTransform != null) {
  174. localTransform.concatenate(glyphTransform);
  175. }
  176. localTransform.scale(1, -1);
  177. boolean yPosChanged = (prevPos == null
  178. || prevPos.getY() != p.getY()
  179. || glyphTransform != null);
  180. if (yPosChanged) {
  181. if (index > 0) {
  182. textUtil.writeTJ();
  183. textUtil.writeTextMatrix(localTransform);
  184. }
  185. } else {
  186. double xdiff = p.getX() - prevPos.getX();
  187. //Width of previous character
  188. Font font = textUtil.getCurrentFont();
  189. double cw = prevVisibleCharWidth;
  190. double effxdiff = (1000 * xdiff) - cw;
  191. if (effxdiff != 0) {
  192. double adjust = (-effxdiff / font.getFontSize());
  193. textUtil.adjustGlyphTJ(adjust * 1000);
  194. }
  195. if (DEBUG) {
  196. System.out.println("==> x diff: " + xdiff + ", " + effxdiff
  197. + ", charWidth: " + cw);
  198. }
  199. }
  200. Font f = textUtil.selectFontForChar(ch);
  201. if (f != textUtil.getCurrentFont()) {
  202. textUtil.writeTJ();
  203. textUtil.setCurrentFont(f);
  204. textUtil.writeTf(f);
  205. textUtil.writeTextMatrix(localTransform);
  206. }
  207. char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch);
  208. textUtil.writeTJChar(paintChar);
  209. //Update last position
  210. prevPos = p;
  211. prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index));
  212. }
  213. textUtil.writeTJ();
  214. textUtil.endTextObject();
  215. textUtil.restoreGraphicsState();
  216. if (DEBUG) {
  217. g2d.setStroke(new BasicStroke(0));
  218. g2d.setColor(Color.LIGHT_GRAY);
  219. g2d.draw(debugShapes);
  220. }
  221. }
  222. }
  223. private void applyColorAndPaint(TextPaintInfo tpi, PDFGraphics2D pdf) {
  224. Paint fillPaint = tpi.fillPaint;
  225. Paint strokePaint = tpi.strokePaint;
  226. Stroke stroke = tpi.strokeStroke;
  227. int fillAlpha = PDFGraphics2D.OPAQUE;
  228. if (fillPaint instanceof Color) {
  229. Color col = (Color)fillPaint;
  230. pdf.applyColor(col, true);
  231. fillAlpha = col.getAlpha();
  232. }
  233. if (strokePaint instanceof Color) {
  234. Color col = (Color)strokePaint;
  235. pdf.applyColor(col, false);
  236. }
  237. pdf.applyPaint(fillPaint, true);
  238. pdf.applyStroke(stroke);
  239. if (strokePaint != null) {
  240. pdf.applyPaint(strokePaint, false);
  241. }
  242. pdf.applyAlpha(fillAlpha, PDFGraphics2D.OPAQUE);
  243. }
  244. private Font[] findFonts(AttributedCharacterIterator aci) {
  245. List fonts = new java.util.ArrayList();
  246. List gvtFonts = (List) aci.getAttribute(
  247. GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
  248. Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE);
  249. Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT);
  250. Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE);
  251. String style = ((posture != null) && (posture.floatValue() > 0.0))
  252. ? "italic" : "normal";
  253. int weight = ((taWeight != null)
  254. && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD
  255. : Font.WEIGHT_NORMAL;
  256. String fontFamily = null;
  257. //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES
  258. //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set
  259. /* The following code section is not available until Batik 1.7 is released. */
  260. GVTFont gvtFont = (GVTFont)aci.getAttribute(
  261. GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
  262. if (gvtFont != null) {
  263. try {
  264. Method method = gvtFont.getClass().getMethod("getFamilyName", null);
  265. String gvtFontFamily = (String)method.invoke(gvtFont, null);
  266. //TODO Uncomment the following line when Batik 1.7 is shipped with FOP
  267. //String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6
  268. if (DEBUG) {
  269. System.out.print(gvtFontFamily + ", ");
  270. }
  271. if (fontInfo.hasFont(gvtFontFamily, style, weight)) {
  272. FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style,
  273. weight);
  274. int fsize = (int)(fontSize.floatValue() * 1000);
  275. fonts.add(fontInfo.getFontInstance(triplet, fsize));
  276. }
  277. } catch (Exception e) {
  278. //Most likely NoSuchMethodError here when using Batik 1.6
  279. //Just skip this section in this case
  280. }
  281. }
  282. if (gvtFonts != null) {
  283. Iterator i = gvtFonts.iterator();
  284. while (i.hasNext()) {
  285. GVTFontFamily fam = (GVTFontFamily) i.next();
  286. if (fam instanceof SVGFontFamily) {
  287. return null; //Let Batik paint this text!
  288. }
  289. fontFamily = fam.getFamilyName();
  290. if (DEBUG) {
  291. System.out.print(fontFamily + ", ");
  292. }
  293. if (fontInfo.hasFont(fontFamily, style, weight)) {
  294. FontTriplet triplet = fontInfo.fontLookup(fontFamily, style,
  295. weight);
  296. int fsize = (int)(fontSize.floatValue() * 1000);
  297. fonts.add(fontInfo.getFontInstance(triplet, fsize));
  298. }
  299. }
  300. }
  301. if (fonts.size() == 0) {
  302. FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL);
  303. int fsize = (int)(fontSize.floatValue() * 1000);
  304. fonts.add(fontInfo.getFontInstance(triplet, fsize));
  305. if (DEBUG) {
  306. System.out.print("fallback to 'any' font");
  307. }
  308. }
  309. if (DEBUG) {
  310. System.out.println();
  311. }
  312. return (Font[])fonts.toArray(new Font[fonts.size()]);
  313. }
  314. }