KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jcckit > renderer > SVGRenderer


1 /*
2  * Copyright 2003-2004, Franz-Josef Elmer, All rights reserved
3  *
4  * This library is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation; either version 2.1 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU Lesser General Public License for more details
13  * (http://www.gnu.org/copyleft/lesser.html).
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19 package jcckit.renderer;
20
21 import jcckit.graphic.Anchor;
22 import jcckit.graphic.BasicGraphicalElement;
23 import jcckit.graphic.ClippingShape;
24 import jcckit.graphic.FillAttributes;
25 import jcckit.graphic.FontStyle;
26 import jcckit.graphic.GraphicalComposite;
27 import jcckit.graphic.GraphicalCompositeRenderer;
28 import jcckit.graphic.GraphicAttributes;
29 import jcckit.graphic.LineAttributes;
30 import jcckit.graphic.Oval;
31 import jcckit.graphic.OvalRenderer;
32 import jcckit.graphic.Polygon;
33 import jcckit.graphic.PolygonRenderer;
34 import jcckit.graphic.Rectangle;
35 import jcckit.graphic.RectangleRenderer;
36 import jcckit.graphic.Text;
37 import jcckit.graphic.TextAttributes;
38 import jcckit.graphic.TextRenderer;
39 import jcckit.util.Format;
40
41 import java.awt.Color JavaDoc;
42 import java.util.Hashtable JavaDoc;
43
44 /**
45  * Renderer creating Scalable Vector Graphics (SVG) from
46  * {@link jcckit.graphic.GraphicalElement GraphicalElements}.
47  * SVG is an XML-based standard for vector graphics as recommended by
48  * <a HREF="http://www.w3.org/Graphics/SVG/Overview.htm8">W3C</a>.
49  * <p>
50  * There is no coordinate transformation of {@link jcckit.graphic.GraphPoint
51  * GraphPoints} except that the sign of the y-coordinate is flipped
52  * because in SVG the y-axis points downwards.
53  * <p>
54  * The renderer writes the XML elements into a <tt>StringBuffer</tt>.
55  * An appropriated header and root element has to be written elsewhere
56  * (e.g. by the class {@link jcckit.SVGPlotter}).
57  * <p>
58  * Shapes will be outlined in black if neither a fill color nor a line color
59  * is defined. A zero line thickness is translated into
60  * a stroke width of 10<super>-6</super>.
61  * <p>
62  * The Java standard font names <tt>Serif</tt>, <tt>SansSerif</tt>, and
63  * <tt>Monospaced</tt> are mapped onto the SVG font families <tt>serif</tt>,
64  * <tt>sans-serif</tt>, and <tt>monospace</tt>, respectively.
65  * The default font family is <tt>sans-serif</tt>.
66  * The default font color is black and the default font size is 1% of the
67  * paper size (as specified in the constructor).
68  *
69  * @author Franz-Josef Elmer
70  */

71 public class SVGRenderer implements GraphicalCompositeRenderer,
72                                          PolygonRenderer, OvalRenderer,
73                                          TextRenderer, RectangleRenderer {
74   private static final Format POLYGON_FORMAT = new Format("%9.7g,%9.7g");
75   private static final Format RECT_FORMAT
76       = new Format("<rect x='%9.7g' y='%9.7g' width='%9.7g' height='%9.7g'");
77   private static final Format OVAL_FORMAT
78       = new Format("<ellipse cx='%9.7g' cy='%9.7g' rx='%9.7g' ry='%9.7g'");
79   private static final Format TEXT_FORMAT
80       = new Format("<text x='%9.7g' y='%9.7g'");
81   private static final Format ORIENTATION_FORMAT
82       = new Format(" transform='rotate(%9.7g,%9.7g,%9.7g)'");
83   private static final Format FONT_FORMAT
84       = new Format(" font-size='%9.7g' text-anchor='");
85   private static final Format COLOR_FORMAT = new Format("='#%06x'");
86   private static final Format STROKE_WIDTH_FORMAT
87       = new Format(" stroke-width='%9.7g'");
88   private static final String JavaDoc DEFAULT_FONT_NAME = "SansSerif";
89   private static final Color JavaDoc DEFAULT_COLOR = Color.black;
90   private static final double DEFAULT_FONT_SIZE = 0.01;
91   private static final int BASELINE_SHIFT_OFFSET = 41;
92   private static final int BASELINE_SHIFT_FACTOR = 64;
93   private static final Hashtable JavaDoc FONT_MAP = new Hashtable JavaDoc();
94
95   private StringBuffer JavaDoc _document;
96   private double _defaultFontSize;
97   private int _level;
98   private int _id;
99
100   static {
101     FONT_MAP.put("SansSerif", "sans-serif");
102     FONT_MAP.put("Serif", "serif");
103     FONT_MAP.put("Monospaced", "monospace");
104   }
105
106   /**
107    * Initializes this instance.
108    * @param buffer The buffer into which the XML elements are written.
109    * @param initialIndentLevel Initial level of indentation. Nested XML
110    * elements are indented.
111    * @param paperSize The size of the paper. It is needed to determine
112    * the default font size.
113    * @return this instance.
114    */

115   public SVGRenderer init(StringBuffer JavaDoc buffer, int initialIndentLevel,
116                           double paperSize) {
117     _document = buffer;
118     _level = initialIndentLevel;
119     _defaultFontSize = paperSize * DEFAULT_FONT_SIZE;
120     return this;
121   }
122
123   /**
124    * Adds spaces in accordance with the current identation level and
125    * adds the specified text.
126    */

127   private void indentAndAdd(String JavaDoc text) {
128     for (int i = 2 * _level; i > 0; i--) {
129       _document.append(' ');
130     }
131     _document.append(text);
132   }
133
134   /**
135    * Starts renderering of the specified composite. The <tt>g</tt>
136    * element of SVG is used for this purpose.
137    * <p>
138    * If <tt>composite</tt> has
139    * a {@link jcckit.graphic.ClippingShape} a <tt>clip-path</tt> attribute
140    * is added with an URL which points onto a <tt>clipPath</tt> element.
141    * The id of this element is of the form <tt>path<i>number</i></tt>
142    * where <tt><i>number</i></tt> is an index counting all clip paths
143    * starting at zero. The clip path is defined by a <tt>rect</tt> element
144    * determined by the bounding box of the clipping shape.
145    */

146   public void startRendering(GraphicalComposite composite) {
147     indentAndAdd("<g");
148     ClippingShape shape = composite.getClippingShape();
149     if (shape == null) {
150       _document.append(">\n");
151     } else {
152       _document.append(" clip-path='url(#path").append(_id).append(")'>\n");
153     }
154     _level++;
155     if (shape != null) {
156       indentAndAdd("<clipPath id='path");
157       _document.append(_id++).append("'>\n");
158      _level++;
159       shape.getBoundingBox().getGraphicalElement().renderWith(this);
160       _level--;
161       indentAndAdd("</clipPath>\n");
162     }
163   }
164
165   /**
166    * Ends renderering of the specified composite.
167    * Closes the <tt>g</tt> element.
168    */

169   public void finishRendering(GraphicalComposite composite) {
170     _level--;
171     indentAndAdd("</g>\n");
172   }
173
174   /**
175    * Renders a polygon. Depending on whether <tt>polygon</tt> is closed or
176    * not an SVG <tt>polygon</tt> or <tt>polyline</tt> element will be created.
177    */

178   public void render(Polygon polygon) {
179     indentAndAdd(polygon.isClosed() ? "<polygon" : "<polyline");
180     int n = polygon.getNumberOfPoints();
181     if (n > 0) {
182       _document.append(" points='");
183       for (int i = 0; i < n; i++) {
184         _document.append(POLYGON_FORMAT.form(
185                           new double[] {polygon.getPoint(i).getX(),
186                                         -polygon.getPoint(i).getY()}))
187                  .append(i < n - 1 ? ' ' : '\'');
188       }
189     }
190     addShapeAttributes(polygon);
191   }
192
193   /** Renders a rectangle. An SVG <tt>rect</tt> element is created. */
194   public void render(Rectangle rectangle) {
195     double width = rectangle.getWidth();
196     double height = rectangle.getHeight();
197     indentAndAdd(RECT_FORMAT.form(new double[] {
198           rectangle.getCenter().getX() - width / 2,
199           -rectangle.getCenter().getY() - height / 2, width, height}));
200     addShapeAttributes(rectangle);
201   }
202
203   /** Renders an oval. An SVG <tt>ellipse</tt> element is created. */
204   public void render(Oval oval) {
205     indentAndAdd(OVAL_FORMAT.form(new double[] {oval.getCenter().getX(),
206         -oval.getCenter().getY(), oval.getWidth() / 2, oval.getHeight() / 2}));
207     addShapeAttributes(oval);
208   }
209
210   /** Renders text. An SVG <tt>text</tt> element is created. */
211   public void render(Text text) {
212     double x = text.getPosition().getX();
213     double y = -text.getPosition().getY();
214     indentAndAdd(TEXT_FORMAT.form(new double[] {x, y}));
215     TextAttributes ta = (TextAttributes) text.getGraphicAttributes();
216     addTextAttributes(ta);
217     if (ta != null && ta.getOrientationAngle() != 0) {
218       _document.append(ORIENTATION_FORMAT.form(
219                           new double[] {-ta.getOrientationAngle(), x, y}));
220     }
221     _document.append('>');
222     String JavaDoc str = text.getText();
223     for (int i = 0, n = str.length(); i < n; i++) {
224       char c = str.charAt(i);
225       switch (c) {
226       case '&': _document.append("&amp;"); break;
227       case '<': _document.append("&lt;"); break;
228       case '>': _document.append("&gt;"); break;
229       case '"': _document.append("&quot;"); break;
230       default: _document.append(c);
231       }
232     }
233     _document.append("</text>\n");
234   }
235
236   private void addShapeAttributes(BasicGraphicalElement element) {
237     GraphicAttributes ga = element.getGraphicAttributes();
238     Color JavaDoc fillColor = null;
239     if (ga instanceof FillAttributes) {
240       fillColor = ((FillAttributes) ga).getFillColor();
241       addColor(" fill", fillColor);
242     }
243     if (ga instanceof LineAttributes) {
244       addLineAttributes((LineAttributes) ga,
245                         fillColor == null ? DEFAULT_COLOR : null);
246     }
247     _document.append("/>\n");
248   }
249
250   private void addLineAttributes(LineAttributes attributes,
251                                  Color JavaDoc defaultColor) {
252     Color JavaDoc color = attributes.getLineColor();
253     if (color == null) {
254       color = defaultColor;
255     }
256     if (color != null) {
257       addColor(" stroke", color);
258       _document.append(STROKE_WIDTH_FORMAT.form(
259                           Math.max(1e-6, attributes.getLineThickness())));
260       double[] linePattern = attributes.getLinePattern();
261       if (linePattern != null && linePattern.length > 0) {
262         _document.append(" stroke-dasharray='");
263         for (int i = 0; i < linePattern.length; i++) {
264           _document.append(linePattern[i])
265                    .append(i < linePattern.length - 1 ? ',' : '\'');
266         }
267       }
268     }
269   }
270
271   private void addTextAttributes(TextAttributes ta) {
272     if (ta != null) {
273       addColor(" fill", ta.getTextColor() == null ? DEFAULT_COLOR
274                                                   : ta.getTextColor());
275       String JavaDoc fontName = ta.getFontName();
276       if (fontName == null) {
277         fontName = DEFAULT_FONT_NAME;
278       }
279       String JavaDoc fontFamily = (String JavaDoc) FONT_MAP.get(fontName);
280       if (fontFamily == null) {
281         fontFamily = fontName;
282       }
283       FontStyle fontStyle = ta.getFontStyle();
284       if (fontStyle == FontStyle.BOLD || fontStyle == FontStyle.BOLD_ITALIC) {
285         _document.append(" font-weight='bold'");
286       }
287       if (fontStyle == FontStyle.ITALIC
288           || fontStyle == FontStyle.BOLD_ITALIC) {
289         _document.append(" font-style='italic'");
290       }
291       _document.append(FONT_FORMAT.form(
292                         ta.getFontSize() == 0 ? _defaultFontSize
293                                               : ta.getFontSize()))
294                .append(ta.getHorizontalAnchor() == Anchor.CENTER ? "middle'"
295                        : (ta.getHorizontalAnchor() == Anchor.RIGHT_TOP ?
296                           "end'" : "start'"))
297                .append(" font-family='").append(fontFamily)
298                .append("' baseline-shift='")
299                .append(BASELINE_SHIFT_OFFSET
300                        - (ta.getVerticalAnchor() == null ?
301                            1 : ta.getVerticalAnchor().getFactor())
302                          * BASELINE_SHIFT_FACTOR)
303                .append("%'");
304     }
305   }
306
307   private void addColor(String JavaDoc name, Color JavaDoc color) {
308     _document.append(name);
309     if (color == null) {
310       _document.append("='none'");
311     } else {
312       _document.append(COLOR_FORMAT.form(color.getRGB() & 0xffffff));
313     }
314   }
315 }
316
Popular Tags