KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > render > ps > PSTextPainter


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
18 /* $Id: PSTextPainter.java 426576 2006-07-28 15:44:37Z jeremias $ */
19
20 package org.apache.fop.render.ps;
21
22 import java.awt.Graphics2D JavaDoc;
23 import java.awt.geom.Point2D JavaDoc;
24 import java.awt.geom.Rectangle2D JavaDoc;
25 /* java.awt.Font is not imported to avoid confusion with
26    org.apache.fop.fonts.Font */

27
28 import java.text.AttributedCharacterIterator JavaDoc;
29 import java.text.CharacterIterator JavaDoc;
30 import java.awt.font.TextAttribute JavaDoc;
31 import java.awt.Shape JavaDoc;
32 import java.awt.Paint JavaDoc;
33 import java.awt.Stroke JavaDoc;
34 import java.awt.Color JavaDoc;
35 import java.io.IOException JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.Iterator JavaDoc;
38
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41
42 import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
43
44 import org.apache.batik.dom.svg.SVGOMTextElement;
45 import org.apache.batik.gvt.text.Mark;
46 import org.apache.batik.gvt.TextPainter;
47 import org.apache.batik.gvt.TextNode;
48 import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
49 import org.apache.batik.gvt.text.TextPaintInfo;
50 import org.apache.batik.gvt.font.GVTFontFamily;
51 import org.apache.batik.gvt.renderer.StrokingTextPainter;
52
53 import org.apache.fop.fonts.Font;
54 import org.apache.fop.fonts.FontInfo;
55 import org.apache.fop.fonts.FontTriplet;
56
57 /**
58  * Renders the attributed character iterator of a <tt>TextNode</tt>.
59  * This class draws the text directly into the PSGraphics2D so that
60  * the text is not drawn using shapes which makes the PS files larger.
61  * If the text is simple enough to draw then it sets the font and calls
62  * drawString. If the text is complex or the cannot be translated
63  * into a simple drawString the StrokingTextPainter is used instead.
64  *
65  * (todo) handle underline, overline and strikethrough
66  * (todo) use drawString(AttributedCharacterIterator iterator...) for some
67  *
68  * @author <a HREF="mailto:keiron@aftexsw.com">Keiron Liddle</a>
69  * @version $Id: PSTextPainter.java 426576 2006-07-28 15:44:37Z jeremias $
70  */

71 public class PSTextPainter implements TextPainter {
72     
73     /** the logger for this class */
74     protected Log log = LogFactory.getLog(PSTextPainter.class);
75     
76     private NativeTextHandler nativeTextHandler;
77     //private FontInfo fontInfo;
78

79     /**
80      * Use the stroking text painter to get the bounds and shape.
81      * Also used as a fallback to draw the string with strokes.
82      */

83     protected static final TextPainter
84         PROXY_PAINTER = StrokingTextPainter.getInstance();
85
86     /**
87      * Create a new PS text painter with the given font information.
88      * @param nativeTextHandler the NativeTextHandler instance used for text painting
89      */

90     public PSTextPainter(NativeTextHandler nativeTextHandler) {
91         this.nativeTextHandler = nativeTextHandler;
92     }
93
94     /**
95      * Paints the specified attributed character iterator using the
96      * specified Graphics2D and context and font context.
97      * @param node the TextNode to paint
98      * @param g2d the Graphics2D to use
99      */

100     public void paint(TextNode node, Graphics2D JavaDoc g2d) {
101         String JavaDoc txt = node.getText();
102         Point2D JavaDoc loc = node.getLocation();
103     
104         if (hasUnsupportedAttributes(node)) {
105             PROXY_PAINTER.paint(node, g2d);
106         } else {
107             paintTextRuns(node.getTextRuns(), g2d, loc);
108         }
109     }
110     
111     
112     private boolean hasUnsupportedAttributes(TextNode node) {
113         Iterator JavaDoc i = node.getTextRuns().iterator();
114         while (i.hasNext()) {
115             StrokingTextPainter.TextRun
116                     run = (StrokingTextPainter.TextRun)i.next();
117             AttributedCharacterIterator JavaDoc aci = run.getACI();
118             boolean hasUnsupported = hasUnsupportedAttributes(aci);
119             if (hasUnsupported) {
120                 return true;
121             }
122         }
123         return false;
124     }
125
126     private boolean hasUnsupportedAttributes(AttributedCharacterIterator JavaDoc aci) {
127         boolean hasunsupported = false;
128         
129         String JavaDoc text = getText(aci);
130         Font font = makeFont(aci);
131         if (hasUnsupportedGlyphs(text, font)) {
132             log.trace("-> Unsupported glyphs found");
133             hasunsupported = true;
134         }
135         
136         TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
137             GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
138         if ((tpi != null)
139                 && ((tpi.strokeStroke != null && tpi.strokePaint != null)
140                     || (tpi.strikethroughStroke != null)
141                     || (tpi.underlineStroke != null)
142                     || (tpi.overlineStroke != null))) {
143                         log.trace("-> under/overlines etc. found");
144             hasunsupported = true;
145         }
146
147         //Alpha is not supported
148
Paint JavaDoc foreground = (Paint JavaDoc) aci.getAttribute(TextAttribute.FOREGROUND);
149         if (foreground instanceof Color JavaDoc) {
150             Color JavaDoc col = (Color JavaDoc)foreground;
151             if (col.getAlpha() != 255) {
152                 log.trace("-> transparency found");
153                 hasunsupported = true;
154             }
155         }
156
157         Object JavaDoc letSpace = aci.getAttribute(
158                             GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING);
159         if (letSpace != null) {
160             log.trace("-> letter spacing found");
161             hasunsupported = true;
162         }
163
164         Object JavaDoc wordSpace = aci.getAttribute(
165                              GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING);
166         if (wordSpace != null) {
167             log.trace("-> word spacing found");
168             hasunsupported = true;
169         }
170         
171         Object JavaDoc lengthAdjust = aci.getAttribute(
172                             GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST);
173         if (lengthAdjust != null) {
174             log.trace("-> length adjustments found");
175             hasunsupported = true;
176         }
177
178         Object JavaDoc writeMod = aci.getAttribute(
179                 GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE);
180         if (writeMod != null
181             && !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals(
182                   writeMod)) {
183             log.trace("-> Unsupported writing modes found");
184             hasunsupported = true;
185         }
186
187         Object JavaDoc vertOr = aci.getAttribute(
188                 GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION);
189         if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals(
190                   vertOr)) {
191             log.trace("-> vertical orientation found");
192             hasunsupported = true;
193         }
194         
195         Object JavaDoc rcDel = aci.getAttribute(
196                 GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
197         //Batik 1.6 returns null here which makes it impossible to determine whether this can
198
//be painted or not, i.e. fall back to stroking. :-(
199
if (/*rcDel != null &&*/ !(rcDel instanceof SVGOMTextElement)) {
200             log.trace("-> spans found");
201             hasunsupported = true; //Filter spans
202
}
203         
204         if (hasunsupported) {
205             log.trace("Unsupported attributes found in ACI, using StrokingTextPainter");
206         }
207         return hasunsupported;
208     }
209
210     /**
211      * Paint a list of text runs on the Graphics2D at a given location.
212      * @param textRuns the list of text runs
213      * @param g2d the Graphics2D to paint to
214      * @param loc the current location of the "cursor"
215      */

216     protected void paintTextRuns(List JavaDoc textRuns, Graphics2D JavaDoc g2d, Point2D JavaDoc loc) {
217         Point2D JavaDoc currentloc = loc;
218         Iterator JavaDoc i = textRuns.iterator();
219         while (i.hasNext()) {
220             StrokingTextPainter.TextRun
221                     run = (StrokingTextPainter.TextRun)i.next();
222             currentloc = paintTextRun(run, g2d, currentloc);
223         }
224     }
225
226     /**
227      * Paint a single text run on the Graphics2D at a given location.
228      * @param run the text run to paint
229      * @param g2d the Graphics2D to paint to
230      * @param loc the current location of the "cursor"
231      * @return the new location of the "cursor" after painting the text run
232      */

233     protected Point2D JavaDoc paintTextRun(StrokingTextPainter.TextRun run, Graphics2D JavaDoc g2d, Point2D JavaDoc loc) {
234         AttributedCharacterIterator JavaDoc aci = run.getACI();
235         return paintACI(aci, g2d, loc);
236     }
237
238     /**
239      * Extract the raw text from an ACI.
240      * @param aci ACI to inspect
241      * @return the extracted text
242      */

243     protected String JavaDoc getText(AttributedCharacterIterator JavaDoc aci) {
244         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(aci.getEndIndex() - aci.getBeginIndex());
245         for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) {
246             sb.append(c);
247         }
248         return sb.toString();
249     }
250
251     /**
252      * Paint an ACI on a Graphics2D at a given location. The method has to
253      * update the location after painting.
254      * @param aci ACI to paint
255      * @param g2d Graphics2D to paint on
256      * @param loc start location
257      * @return new current location
258      */

259     protected Point2D JavaDoc paintACI(AttributedCharacterIterator JavaDoc aci, Graphics2D JavaDoc g2d, Point2D JavaDoc loc) {
260         //ACIUtils.dumpAttrs(aci);
261

262         aci.first();
263
264         updateLocationFromACI(aci, loc);
265
266         TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute(
267             GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO);
268         
269         if (tpi == null) {
270             return loc;
271         }
272         
273         TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute(
274                 GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE);
275
276         //Set up font
277
List JavaDoc gvtFonts = (List JavaDoc)aci.getAttribute(
278                 GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
279         Paint JavaDoc foreground = tpi.fillPaint;
280         Paint JavaDoc strokePaint = tpi.strokePaint;
281         Stroke JavaDoc stroke = tpi.strokeStroke;
282
283         Float JavaDoc fontSize = (Float JavaDoc)aci.getAttribute(TextAttribute.SIZE);
284         if (fontSize == null) {
285             return loc;
286         }
287         Float JavaDoc posture = (Float JavaDoc)aci.getAttribute(TextAttribute.POSTURE);
288         Float JavaDoc taWeight = (Float JavaDoc)aci.getAttribute(TextAttribute.WEIGHT);
289
290         if (foreground instanceof Color JavaDoc) {
291             Color JavaDoc col = (Color JavaDoc)foreground;
292             g2d.setColor(col);
293         }
294         g2d.setPaint(foreground);
295         g2d.setStroke(stroke);
296
297         Font font = makeFont(aci);
298         java.awt.Font JavaDoc awtFont = makeAWTFont(aci, font);
299
300         g2d.setFont(awtFont);
301
302         String JavaDoc txt = getText(aci);
303         float advance = getStringWidth(txt, font);
304         float tx = 0;
305         if (anchor != null) {
306             switch (anchor.getType()) {
307                 case TextNode.Anchor.ANCHOR_MIDDLE:
308                     tx = -advance / 2;
309                     break;
310                 case TextNode.Anchor.ANCHOR_END:
311                     tx = -advance;
312                     break;
313                 default: //nop
314
}
315         }
316         
317         //Finally draw text
318
nativeTextHandler.setOverrideFont(font);
319         try {
320             try {
321                 nativeTextHandler.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY()));
322             } catch (IOException JavaDoc ioe) {
323                 if (g2d instanceof PSGraphics2D) {
324                     ((PSGraphics2D)g2d).handleIOException(ioe);
325                 }
326             }
327         } finally {
328             nativeTextHandler.setOverrideFont(null);
329         }
330         loc.setLocation(loc.getX() + (double)advance, loc.getY());
331         return loc;
332     }
333
334     private void updateLocationFromACI(
335                 AttributedCharacterIterator JavaDoc aci,
336                 Point2D JavaDoc loc) {
337         //Adjust position of span
338
Float JavaDoc xpos = (Float JavaDoc)aci.getAttribute(
339                 GVTAttributedCharacterIterator.TextAttribute.X);
340         Float JavaDoc ypos = (Float JavaDoc)aci.getAttribute(
341                 GVTAttributedCharacterIterator.TextAttribute.Y);
342         Float JavaDoc dxpos = (Float JavaDoc)aci.getAttribute(
343                 GVTAttributedCharacterIterator.TextAttribute.DX);
344         Float JavaDoc dypos = (Float JavaDoc)aci.getAttribute(
345                 GVTAttributedCharacterIterator.TextAttribute.DY);
346         if (xpos != null) {
347             loc.setLocation(xpos.doubleValue(), loc.getY());
348         }
349         if (ypos != null) {
350             loc.setLocation(loc.getX(), ypos.doubleValue());
351         }
352         if (dxpos != null) {
353             loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY());
354         }
355         if (dypos != null) {
356             loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue());
357         }
358     }
359
360     private String JavaDoc getStyle(AttributedCharacterIterator JavaDoc aci) {
361         Float JavaDoc posture = (Float JavaDoc)aci.getAttribute(TextAttribute.POSTURE);
362         return ((posture != null) && (posture.floatValue() > 0.0))
363                        ? "italic"
364                        : "normal";
365     }
366
367     private int getWeight(AttributedCharacterIterator JavaDoc aci) {
368         Float JavaDoc taWeight = (Float JavaDoc)aci.getAttribute(TextAttribute.WEIGHT);
369         return ((taWeight != null) && (taWeight.floatValue() > 1.0))
370                        ? Font.BOLD
371                        : Font.NORMAL;
372     }
373
374     private Font makeFont(AttributedCharacterIterator JavaDoc aci) {
375         Float JavaDoc fontSize = (Float JavaDoc)aci.getAttribute(TextAttribute.SIZE);
376         if (fontSize == null) {
377             fontSize = new Float JavaDoc(10.0f);
378         }
379         String JavaDoc style = getStyle(aci);
380         int weight = getWeight(aci);
381
382         boolean found = false;
383         FontInfo fontInfo = nativeTextHandler.getFontInfo();
384         String JavaDoc fontFamily = null;
385         List JavaDoc gvtFonts = (List JavaDoc) aci.getAttribute(
386                       GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
387         if (gvtFonts != null) {
388             Iterator JavaDoc i = gvtFonts.iterator();
389             while (i.hasNext()) {
390                 GVTFontFamily fam = (GVTFontFamily) i.next();
391                 /* (todo) Enable SVG Font painting
392                 if (fam instanceof SVGFontFamily) {
393                     PROXY_PAINTER.paint(node, g2d);
394                     return;
395                 }*/

396                 fontFamily = fam.getFamilyName();
397                 if (fontInfo.hasFont(fontFamily, style, weight)) {
398                     FontTriplet triplet = fontInfo.fontLookup(
399                             fontFamily, style, weight);
400                     int fsize = (int)(fontSize.floatValue() * 1000);
401                     return fontInfo.getFontInstance(triplet, fsize);
402                 }
403             }
404         }
405         FontTriplet triplet = fontInfo.fontLookup("any", style, Font.NORMAL);
406         int fsize = (int)(fontSize.floatValue() * 1000);
407         return fontInfo.getFontInstance(triplet, fsize);
408     }
409
410     private java.awt.Font JavaDoc makeAWTFont(AttributedCharacterIterator JavaDoc aci, Font font) {
411         final String JavaDoc style = getStyle(aci);
412         final int weight = getWeight(aci);
413         int fStyle = java.awt.Font.PLAIN;
414         if (weight == Font.BOLD) {
415             fStyle |= java.awt.Font.BOLD;
416         }
417         if ("italic".equals(style)) {
418             fStyle |= java.awt.Font.ITALIC;
419         }
420         return new java.awt.Font JavaDoc(font.getFontName(), fStyle,
421                              (int)(font.getFontSize() / 1000));
422     }
423
424     private float getStringWidth(String JavaDoc str, Font font) {
425         float wordWidth = 0;
426         float whitespaceWidth = font.getWidth(font.mapChar(' '));
427
428         for (int i = 0; i < str.length(); i++) {
429             float charWidth;
430             char c = str.charAt(i);
431             if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) {
432                 charWidth = font.getWidth(font.mapChar(c));
433                 if (charWidth <= 0) {
434                     charWidth = whitespaceWidth;
435                 }
436             } else {
437                 charWidth = whitespaceWidth;
438             }
439             wordWidth += charWidth;
440         }
441         return wordWidth / 1000f;
442     }
443
444     private boolean hasUnsupportedGlyphs(String JavaDoc str, Font font) {
445         for (int i = 0; i < str.length(); i++) {
446             float charWidth;
447             char c = str.charAt(i);
448             if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) {
449                 if (!font.hasChar(c)) {
450                     return true;
451                 }
452             }
453         }
454         return false;
455     }
456
457     /**
458      * Get the outline shape of the text characters.
459      * This uses the StrokingTextPainter to get the outline
460      * shape since in theory it should be the same.
461      *
462      * @param node the text node
463      * @return the outline shape of the text characters
464      */

465     public Shape JavaDoc getOutline(TextNode node) {
466         return PROXY_PAINTER.getOutline(node);
467     }
468
469     /**
470      * Get the bounds.
471      * This uses the StrokingTextPainter to get the bounds
472      * since in theory it should be the same.
473      *
474      * @param node the text node
475      * @return the bounds of the text
476      */

477     public Rectangle2D JavaDoc getBounds2D(TextNode node) {
478         /* (todo) getBounds2D() is too slow
479          * because it uses the StrokingTextPainter. We should implement this
480          * method ourselves. */

481         return PROXY_PAINTER.getBounds2D(node);
482     }
483
484     /**
485      * Get the geometry bounds.
486      * This uses the StrokingTextPainter to get the bounds
487      * since in theory it should be the same.
488      * @param node the text node
489      * @return the bounds of the text
490      */

491     public Rectangle2D JavaDoc getGeometryBounds(TextNode node) {
492         return PROXY_PAINTER.getGeometryBounds(node);
493     }
494
495     // Methods that have no purpose for PS
496

497     /**
498      * Get the mark.
499      * This does nothing since the output is pdf and not interactive.
500      * @param node the text node
501      * @param pos the position
502      * @param all select all
503      * @return null
504      */

505     public Mark getMark(TextNode node, int pos, boolean all) {
506         return null;
507     }
508
509     /**
510      * Select at.
511      * This does nothing since the output is pdf and not interactive.
512      * @param x the x position
513      * @param y the y position
514      * @param node the text node
515      * @return null
516      */

517     public Mark selectAt(double x, double y, TextNode node) {
518         return null;
519     }
520
521     /**
522      * Select to.
523      * This does nothing since the output is pdf and not interactive.
524      * @param x the x position
525      * @param y the y position
526      * @param beginMark the start mark
527      * @return null
528      */

529     public Mark selectTo(double x, double y, Mark beginMark) {
530         return null;
531     }
532
533     /**
534      * Selec first.
535      * This does nothing since the output is pdf and not interactive.
536      * @param node the text node
537      * @return null
538      */

539     public Mark selectFirst(TextNode node) {
540         return null;
541     }
542
543     /**
544      * Select last.
545      * This does nothing since the output is pdf and not interactive.
546      * @param node the text node
547      * @return null
548      */

549     public Mark selectLast(TextNode node) {
550         return null;
551     }
552
553     /**
554      * Get selected.
555      * This does nothing since the output is pdf and not interactive.
556      * @param start the start mark
557      * @param finish the finish mark
558      * @return null
559      */

560     public int[] getSelected(Mark start, Mark finish) {
561         return null;
562     }
563
564     /**
565      * Get the highlighted shape.
566      * This does nothing since the output is pdf and not interactive.
567      * @param beginMark the start mark
568      * @param endMark the end mark
569      * @return null
570      */

571     public Shape JavaDoc getHighlightShape(Mark beginMark, Mark endMark) {
572         return null;
573     }
574
575 }
576
577
578
Popular Tags