001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on January 26, 2004, 1:56 AM
035 */
036package com.kitfox.svg;
037
038import com.kitfox.svg.xml.StyleAttribute;
039import java.awt.Graphics2D;
040import java.awt.Shape;
041import java.awt.font.FontRenderContext;
042import java.awt.font.GlyphMetrics;
043import java.awt.font.GlyphVector;
044import java.awt.geom.AffineTransform;
045import java.awt.geom.GeneralPath;
046import java.awt.geom.Rectangle2D;
047
048/**
049 * @author Mark McKay
050 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
051 */
052public class Tspan extends ShapeElement
053{
054
055    public static final String TAG_NAME = "tspan";
056    float[] x = null;
057    float[] y = null;
058    float[] dx = null;
059    float[] dy = null;
060    float[] rotate = null;
061    private String text = "";
062    float cursorX;
063    float cursorY;
064
065//    Shape tspanShape;
066    /**
067     * Creates a new instance of Stop
068     */
069    public Tspan()
070    {
071    }
072
073    public String getTagName()
074    {
075        return TAG_NAME;
076    }
077
078    public float getCursorX()
079    {
080        return cursorX;
081    }
082
083    public float getCursorY()
084    {
085        return cursorY;
086    }
087
088    public void setCursorX(float cursorX)
089    {
090        this.cursorX = cursorX;
091    }
092
093    public void setCursorY(float cursorY)
094    {
095        this.cursorY = cursorY;
096    }
097    /*
098     public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
099     {
100     //Load style string
101     super.loaderStartElement(helper, attrs, parent);
102
103     String x = attrs.getValue("x");
104     String y = attrs.getValue("y");
105     String dx = attrs.getValue("dx");
106     String dy = attrs.getValue("dy");
107     String rotate = attrs.getValue("rotate");
108
109     if (x != null) this.x = XMLParseUtil.parseFloatList(x);
110     if (y != null) this.y = XMLParseUtil.parseFloatList(y);
111     if (dx != null) this.dx = XMLParseUtil.parseFloatList(dx);
112     if (dy != null) this.dy = XMLParseUtil.parseFloatList(dy);
113     if (rotate != null)
114     {
115     this.rotate = XMLParseUtil.parseFloatList(rotate);
116     for (int i = 0; i < this.rotate.length; i++)
117     this.rotate[i] = (float)Math.toRadians(this.rotate[i]);
118     }
119     }
120     */
121
122    /**
123     * Called during load process to add text scanned within a tag
124     */
125    public void loaderAddText(SVGLoaderHelper helper, String text)
126    {
127        this.text += text;
128    }
129
130    protected void build() throws SVGException
131    {
132        super.build();
133
134        StyleAttribute sty = new StyleAttribute();
135
136        if (getPres(sty.setName("x")))
137        {
138            x = sty.getFloatList();
139        }
140
141        if (getPres(sty.setName("y")))
142        {
143            y = sty.getFloatList();
144        }
145
146        if (getPres(sty.setName("dx")))
147        {
148            dx = sty.getFloatList();
149        }
150
151        if (getPres(sty.setName("dy")))
152        {
153            dy = sty.getFloatList();
154        }
155
156        if (getPres(sty.setName("rotate")))
157        {
158            rotate = sty.getFloatList();
159            for (int i = 0; i < this.rotate.length; i++)
160            {
161                rotate[i] = (float) Math.toRadians(this.rotate[i]);
162            }
163
164        }
165    }
166
167    public void addShape(GeneralPath addShape) throws SVGException
168    {
169        if (x != null)
170        {
171            cursorX = x[0];
172        } else if (dx != null)
173        {
174            cursorX += dx[0];
175        }
176
177        if (y != null)
178        {
179            cursorY = y[0];
180        } else if (dy != null)
181        {
182            cursorY += dy[0];
183        }
184
185        StyleAttribute sty = new StyleAttribute();
186
187        String fontFamily = null;
188        if (getStyle(sty.setName("font-family")))
189        {
190            fontFamily = sty.getStringValue();
191        }
192
193
194        float fontSize = 12f;
195        if (getStyle(sty.setName("font-size")))
196        {
197            fontSize = sty.getFloatValueWithUnits();
198        }
199
200        float letterSpacing = 0;
201        if (getStyle(sty.setName("letter-spacing")))
202        {
203            letterSpacing = sty.getFloatValueWithUnits();
204        }
205
206
207        //Get font
208        Font font = diagram.getUniverse().getFont(fontFamily);
209        if (font == null)
210        {
211            addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing);
212            return;
213        }
214
215        FontFace fontFace = font.getFontFace();
216        int ascent = fontFace.getAscent();
217        float fontScale = fontSize / (float) ascent;
218
219        AffineTransform xform = new AffineTransform();
220
221        strokeWidthScalar = 1f / fontScale;
222
223        int posPtr = 1;
224
225        for (int i = 0; i < text.length(); i++)
226        {
227            xform.setToIdentity();
228            xform.setToTranslation(cursorX, cursorY);
229            xform.scale(fontScale, fontScale);
230            if (rotate != null)
231            {
232                xform.rotate(rotate[posPtr]);
233            }
234
235            String unicode = text.substring(i, i + 1);
236            MissingGlyph glyph = font.getGlyph(unicode);
237
238            Shape path = glyph.getPath();
239            if (path != null)
240            {
241                path = xform.createTransformedShape(path);
242                addShape.append(path, false);
243            }
244
245            if (x != null && posPtr < x.length)
246            {
247                cursorX = x[posPtr];
248                cursorY = y[posPtr++];
249            } else if (dx != null && posPtr < dx.length)
250            {
251                cursorX += dx[posPtr];
252                cursorY += dy[posPtr++];
253            }
254
255            cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing;
256        }
257
258        strokeWidthScalar = 1f;
259    }
260
261    private void addShapeSysFont(GeneralPath addShape, Font font,
262        String fontFamily, float fontSize, float letterSpacing)
263    {
264        java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize);
265
266        FontRenderContext frc = new FontRenderContext(null, true, true);
267        GlyphVector textVector = sysFont.createGlyphVector(frc, text);
268
269        AffineTransform xform = new AffineTransform();
270
271        int posPtr = 1;
272        for (int i = 0; i < text.length(); i++)
273        {
274            xform.setToIdentity();
275            xform.setToTranslation(cursorX + i * letterSpacing, cursorY);
276            if (rotate != null)
277            {
278                xform.rotate(rotate[Math.min(i, rotate.length - 1)]);
279            }
280
281            String unicode = text.substring(i, i + 1);
282            Shape glyphOutline = textVector.getGlyphOutline(i);
283            GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(i);
284
285            glyphOutline = xform.createTransformedShape(glyphOutline);
286            addShape.append(glyphOutline, false);
287
288            if (x != null && posPtr < x.length)
289            {
290                cursorX = x[posPtr];
291                cursorY = y[posPtr++];
292            } else if (dx != null && posPtr < dx.length)
293            {
294                cursorX += dx[posPtr];
295                cursorY += dy[posPtr++];
296            }
297        }
298    }
299
300    public void render(Graphics2D g) throws SVGException
301    {
302        if (x != null)
303        {
304            cursorX = x[0];
305            cursorY = y[0];
306        } else if (dx != null)
307        {
308            cursorX += dx[0];
309            cursorY += dy[0];
310        }
311
312        StyleAttribute sty = new StyleAttribute();
313
314        String fontFamily = null;
315        if (getPres(sty.setName("font-family")))
316        {
317            fontFamily = sty.getStringValue();
318        }
319
320
321        float fontSize = 12f;
322        if (getPres(sty.setName("font-size")))
323        {
324            fontSize = sty.getFloatValueWithUnits();
325        }
326
327        //Get font
328        Font font = diagram.getUniverse().getFont(fontFamily);
329        if (font == null)
330        {
331            System.err.println("Could not load font");
332            java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize);
333            renderSysFont(g, sysFont);
334            return;
335        }
336
337
338        FontFace fontFace = font.getFontFace();
339        int ascent = fontFace.getAscent();
340        float fontScale = fontSize / (float) ascent;
341
342        AffineTransform oldXform = g.getTransform();
343        AffineTransform xform = new AffineTransform();
344
345        strokeWidthScalar = 1f / fontScale;
346
347        int posPtr = 1;
348
349        for (int i = 0; i < text.length(); i++)
350        {
351            xform.setToTranslation(cursorX, cursorY);
352            xform.scale(fontScale, fontScale);
353            g.transform(xform);
354
355            String unicode = text.substring(i, i + 1);
356            MissingGlyph glyph = font.getGlyph(unicode);
357
358            Shape path = glyph.getPath();
359            if (path != null)
360            {
361                renderShape(g, path);
362            } else
363            {
364                glyph.render(g);
365            }
366
367            if (x != null && posPtr < x.length)
368            {
369                cursorX = x[posPtr];
370                cursorY = y[posPtr++];
371            } else if (dx != null && posPtr < dx.length)
372            {
373                cursorX += dx[posPtr];
374                cursorY += dy[posPtr++];
375            }
376
377            cursorX += fontScale * glyph.getHorizAdvX();
378
379            g.setTransform(oldXform);
380        }
381
382        strokeWidthScalar = 1f;
383    }
384
385    protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException
386    {
387        int posPtr = 1;
388        FontRenderContext frc = g.getFontRenderContext();
389
390        Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
391        renderShape(g, textShape);
392        Rectangle2D rect = font.getStringBounds(text, frc);
393        cursorX += (float) rect.getWidth();
394    }
395
396    public Shape getShape()
397    {
398        return null;
399        //return shapeToParent(tspanShape);
400    }
401
402    public Rectangle2D getBoundingBox()
403    {
404        return null;
405        //return boundsToParent(tspanShape.getBounds2D());
406    }
407
408    /**
409     * Updates all attributes in this diagram associated with a time event. Ie,
410     * all attributes with track information.
411     *
412     * @return - true if this node has changed state as a result of the time
413     * update
414     */
415    public boolean updateTime(double curTime) throws SVGException
416    {
417        //Tspan does not change
418        return false;
419    }
420
421    public String getText()
422    {
423        return text;
424    }
425
426    public void setText(String text)
427    {
428        this.text = text;
429    }
430}