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.Point2D;
047import java.awt.geom.Rectangle2D;
048
049/**
050 * @author Mark McKay
051 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
052 */
053public class Tspan extends ShapeElement
054{
055
056    public static final String TAG_NAME = "tspan";
057    float[] x = null;
058    float[] y = null;
059    float[] dx = null;
060    float[] dy = null;
061    float[] rotate = null;
062    private String text = "";
063//    float cursorX;
064//    float cursorY;
065
066//    Shape tspanShape;
067    /**
068     * Creates a new instance of Stop
069     */
070    public Tspan()
071    {
072    }
073
074    public String getTagName()
075    {
076        return TAG_NAME;
077    }
078
079//    public float getCursorX()
080//    {
081//        return cursorX;
082//    }
083//
084//    public float getCursorY()
085//    {
086//        return cursorY;
087//    }
088//
089//    public void setCursorX(float cursorX)
090//    {
091//        this.cursorX = cursorX;
092//    }
093//
094//    public void setCursorY(float cursorY)
095//    {
096//        this.cursorY = cursorY;
097//    }
098    /*
099     public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
100     {
101     //Load style string
102     super.loaderStartElement(helper, attrs, parent);
103
104     String x = attrs.getValue("x");
105     String y = attrs.getValue("y");
106     String dx = attrs.getValue("dx");
107     String dy = attrs.getValue("dy");
108     String rotate = attrs.getValue("rotate");
109
110     if (x != null) this.x = XMLParseUtil.parseFloatList(x);
111     if (y != null) this.y = XMLParseUtil.parseFloatList(y);
112     if (dx != null) this.dx = XMLParseUtil.parseFloatList(dx);
113     if (dy != null) this.dy = XMLParseUtil.parseFloatList(dy);
114     if (rotate != null)
115     {
116     this.rotate = XMLParseUtil.parseFloatList(rotate);
117     for (int i = 0; i < this.rotate.length; i++)
118     this.rotate[i] = (float)Math.toRadians(this.rotate[i]);
119     }
120     }
121     */
122
123    /**
124     * Called during load process to add text scanned within a tag
125     */
126    public void loaderAddText(SVGLoaderHelper helper, String text)
127    {
128        this.text += text;
129    }
130
131    protected void build() throws SVGException
132    {
133        super.build();
134
135        StyleAttribute sty = new StyleAttribute();
136
137        if (getPres(sty.setName("x")))
138        {
139            x = sty.getFloatList();
140        }
141
142        if (getPres(sty.setName("y")))
143        {
144            y = sty.getFloatList();
145        }
146
147        if (getPres(sty.setName("dx")))
148        {
149            dx = sty.getFloatList();
150        }
151
152        if (getPres(sty.setName("dy")))
153        {
154            dy = sty.getFloatList();
155        }
156
157        if (getPres(sty.setName("rotate")))
158        {
159            rotate = sty.getFloatList();
160            for (int i = 0; i < this.rotate.length; i++)
161            {
162                rotate[i] = (float) Math.toRadians(this.rotate[i]);
163            }
164
165        }
166    }
167
168    public void appendToShape(GeneralPath addShape, Point2D cursor) throws SVGException
169    {
170//        if (x != null)
171//        {
172//            cursorX = x[0];
173//        } else if (dx != null)
174//        {
175//            cursorX += dx[0];
176//        }
177//
178//        if (y != null)
179//        {
180//            cursorY = y[0];
181//        } else if (dy != null)
182//        {
183//            cursorY += dy[0];
184//        }
185
186        StyleAttribute sty = new StyleAttribute();
187
188        String fontFamily = null;
189        if (getStyle(sty.setName("font-family")))
190        {
191            fontFamily = sty.getStringValue();
192        }
193
194
195        float fontSize = 12f;
196        if (getStyle(sty.setName("font-size")))
197        {
198            fontSize = sty.getFloatValueWithUnits();
199        }
200
201        float letterSpacing = 0;
202        if (getStyle(sty.setName("letter-spacing")))
203        {
204            letterSpacing = sty.getFloatValueWithUnits();
205        }
206
207
208        //Get font
209        Font font = diagram.getUniverse().getFont(fontFamily);
210        if (font == null)
211        {
212            addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing, cursor);
213            return;
214        }
215
216        FontFace fontFace = font.getFontFace();
217        int ascent = fontFace.getAscent();
218        float fontScale = fontSize / (float) ascent;
219
220        AffineTransform xform = new AffineTransform();
221
222        strokeWidthScalar = 1f / fontScale;
223
224        float cursorX = (float)cursor.getX();
225        float cursorY = (float)cursor.getY();
226    
227//        int i = 0;
228
229        String drawText = this.text;
230        drawText = drawText.trim();
231        for (int i = 0; i < drawText.length(); i++)
232        {
233            if (x != null && i < x.length)
234            {
235                cursorX = x[i];
236            } else if (dx != null && i < dx.length)
237            {
238                cursorX += dx[i];
239            }
240            
241            if (y != null && i < y.length)
242            {
243                cursorY = y[i];
244            } else if (dy != null && i < dy.length)
245            {
246                cursorY += dy[i];
247            }
248  //          i++;
249            
250            xform.setToIdentity();
251            xform.setToTranslation(cursorX, cursorY);
252            xform.scale(fontScale, fontScale);
253            if (rotate != null)
254            {
255                xform.rotate(rotate[i]);
256            }
257
258            String unicode = drawText.substring(i, i + 1);
259            MissingGlyph glyph = font.getGlyph(unicode);
260
261            Shape path = glyph.getPath();
262            if (path != null)
263            {
264                path = xform.createTransformedShape(path);
265                addShape.append(path, false);
266            }
267
268            cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing;
269        }
270
271        //Save final draw point so calling method knows where to begin next
272        // text draw
273        cursor.setLocation(cursorX, cursorY);
274        strokeWidthScalar = 1f;
275    }
276
277    private void addShapeSysFont(GeneralPath addShape, Font font,
278        String fontFamily, float fontSize, float letterSpacing, Point2D cursor)
279    {
280
281        java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize);
282
283        FontRenderContext frc = new FontRenderContext(null, true, true);
284        String renderText = this.text.trim();
285
286        AffineTransform xform = new AffineTransform();
287
288        float cursorX = (float)cursor.getX();
289        float cursorY = (float)cursor.getY();
290//        int i = 0;
291        for (int i = 0; i < renderText.length(); i++)
292        {
293            if (x != null && i < x.length)
294            {
295                cursorX = x[i];
296            } else if (dx != null && i < dx.length)
297            {
298                cursorX += dx[i];
299            }
300
301            if (y != null && i < y.length)
302            {
303                cursorY = y[i];
304            } else if (dy != null && i < dy.length)
305            {
306                cursorY += dy[i];
307            }
308//            i++;
309            
310            xform.setToIdentity();
311            xform.setToTranslation(cursorX, cursorY);
312            if (rotate != null)
313            {
314                xform.rotate(rotate[Math.min(i, rotate.length - 1)]);
315            }
316
317//            String unicode = renderText.substring(i, i + 1);
318            GlyphVector textVector = sysFont.createGlyphVector(frc, renderText.substring(i, i + 1));
319            Shape glyphOutline = textVector.getGlyphOutline(0);
320            GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(0);
321
322            glyphOutline = xform.createTransformedShape(glyphOutline);
323            addShape.append(glyphOutline, false);
324
325
326//            cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing;
327            cursorX += glyphMetrics.getAdvance() + letterSpacing;
328        }
329        
330        cursor.setLocation(cursorX, cursorY);
331    }
332
333    public void render(Graphics2D g) throws SVGException
334    {
335        float cursorX = 0;
336        float cursorY = 0;
337    
338        if (x != null)
339        {
340            cursorX = x[0];
341            cursorY = y[0];
342        } else if (dx != null)
343        {
344            cursorX += dx[0];
345            cursorY += dy[0];
346        }
347
348        StyleAttribute sty = new StyleAttribute();
349
350        String fontFamily = null;
351        if (getPres(sty.setName("font-family")))
352        {
353            fontFamily = sty.getStringValue();
354        }
355
356
357        float fontSize = 12f;
358        if (getPres(sty.setName("font-size")))
359        {
360            fontSize = sty.getFloatValueWithUnits();
361        }
362
363        //Get font
364        Font font = diagram.getUniverse().getFont(fontFamily);
365        if (font == null)
366        {
367            System.err.println("Could not load font");
368            java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize);
369            renderSysFont(g, sysFont);
370            return;
371        }
372
373
374        FontFace fontFace = font.getFontFace();
375        int ascent = fontFace.getAscent();
376        float fontScale = fontSize / (float) ascent;
377
378        AffineTransform oldXform = g.getTransform();
379        AffineTransform xform = new AffineTransform();
380
381        strokeWidthScalar = 1f / fontScale;
382
383        int posPtr = 1;
384
385        for (int i = 0; i < text.length(); i++)
386        {
387            xform.setToTranslation(cursorX, cursorY);
388            xform.scale(fontScale, fontScale);
389            g.transform(xform);
390
391            String unicode = text.substring(i, i + 1);
392            MissingGlyph glyph = font.getGlyph(unicode);
393
394            Shape path = glyph.getPath();
395            if (path != null)
396            {
397                renderShape(g, path);
398            } else
399            {
400                glyph.render(g);
401            }
402
403            if (x != null && posPtr < x.length)
404            {
405                cursorX = x[posPtr];
406                cursorY = y[posPtr++];
407            } else if (dx != null && posPtr < dx.length)
408            {
409                cursorX += dx[posPtr];
410                cursorY += dy[posPtr++];
411            }
412
413            cursorX += fontScale * glyph.getHorizAdvX();
414
415            g.setTransform(oldXform);
416        }
417
418        strokeWidthScalar = 1f;
419    }
420
421    protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException
422    {
423        float cursorX = 0;
424        float cursorY = 0;
425    
426        int posPtr = 1;
427        FontRenderContext frc = g.getFontRenderContext();
428
429        Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
430        renderShape(g, textShape);
431        Rectangle2D rect = font.getStringBounds(text, frc);
432        cursorX += (float) rect.getWidth();
433    }
434
435    public Shape getShape()
436    {
437        return null;
438        //return shapeToParent(tspanShape);
439    }
440
441    public Rectangle2D getBoundingBox()
442    {
443        return null;
444        //return boundsToParent(tspanShape.getBounds2D());
445    }
446
447    /**
448     * Updates all attributes in this diagram associated with a time event. Ie,
449     * all attributes with track information.
450     *
451     * @return - true if this node has changed state as a result of the time
452     * update
453     */
454    public boolean updateTime(double curTime) throws SVGException
455    {
456        //Tspan does not change
457        return false;
458    }
459
460    public String getText()
461    {
462        return text;
463    }
464
465    public void setText(String text)
466    {
467        this.text = text;
468    }
469}