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.util.FontSystem;
039import com.kitfox.svg.xml.StyleAttribute;
040import java.awt.Graphics2D;
041import java.awt.Shape;
042import java.awt.font.FontRenderContext;
043import java.awt.geom.AffineTransform;
044import java.awt.geom.GeneralPath;
045import java.awt.geom.Point2D;
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    @Override
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    @Override
127    public void loaderAddText(SVGLoaderHelper helper, String text)
128    {
129        this.text += text;
130    }
131
132    @Override
133    protected void build() throws SVGException
134    {
135        super.build();
136
137        StyleAttribute sty = new StyleAttribute();
138
139        if (getPres(sty.setName("x")))
140        {
141            x = sty.getFloatList();
142        }
143
144        if (getPres(sty.setName("y")))
145        {
146            y = sty.getFloatList();
147        }
148
149        if (getPres(sty.setName("dx")))
150        {
151            dx = sty.getFloatList();
152        }
153
154        if (getPres(sty.setName("dy")))
155        {
156            dy = sty.getFloatList();
157        }
158
159        if (getPres(sty.setName("rotate")))
160        {
161            rotate = sty.getFloatList();
162            for (int i = 0; i < this.rotate.length; i++)
163            {
164                rotate[i] = (float) Math.toRadians(this.rotate[i]);
165            }
166
167        }
168    }
169
170    public void appendToShape(GeneralPath addShape, Point2D cursor) throws SVGException
171    {
172        StyleAttribute sty = new StyleAttribute();
173
174        String fontFamily = null;
175        if (getStyle(sty.setName("font-family")))
176        {
177            fontFamily = sty.getStringValue();
178        }
179
180
181        float fontSize = 12f;
182        if (getStyle(sty.setName("font-size")))
183        {
184            fontSize = sty.getFloatValueWithUnits();
185        }
186
187        float letterSpacing = 0;
188        if (getStyle(sty.setName("letter-spacing")))
189        {
190            letterSpacing = sty.getFloatValueWithUnits();
191        }
192
193        int fontStyle = 0;
194        if (getStyle(sty.setName("font-style")))
195        {
196            String s = sty.getStringValue();
197            if ("normal".equals(s))
198            {
199                fontStyle = Text.TXST_NORMAL;
200            } else if ("italic".equals(s))
201            {
202                fontStyle = Text.TXST_ITALIC;
203            } else if ("oblique".equals(s))
204            {
205                fontStyle = Text.TXST_OBLIQUE;
206            }
207        } else
208        {
209            fontStyle = Text.TXST_NORMAL;
210        }
211
212        int fontWeight = 0;
213        if (getStyle(sty.setName("font-weight")))
214        {
215            String s = sty.getStringValue();
216            if ("normal".equals(s))
217            {
218                fontWeight = Text.TXWE_NORMAL;
219            } else if ("bold".equals(s))
220            {
221                fontWeight = Text.TXWE_BOLD;
222            }
223        } else
224        {
225            fontWeight = Text.TXWE_NORMAL;
226        }
227
228
229        //Get font
230        Font font = diagram.getUniverse().getFont(fontFamily);
231        if (font == null && fontFamily != null)
232        {
233            font = FontSystem.createFont(fontFamily, fontStyle, fontWeight, (int)fontSize);
234//            addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing, cursor);
235//            return;
236        }
237
238        if (font == null)
239        {
240            font = FontSystem.createFont("Serif", fontStyle, fontWeight, fontStyle);
241        }
242
243//        FontFace fontFace = font.getFontFace();
244//        int ascent = fontFace.getAscent();
245//        float fontScale = fontSize / (float) ascent;
246
247        AffineTransform xform = new AffineTransform();
248
249//        strokeWidthScalar = 1f / fontScale;
250
251        float cursorX = (float)cursor.getX();
252        float cursorY = (float)cursor.getY();
253    
254//        int i = 0;
255
256        String drawText = this.text;
257        drawText = drawText.trim();
258        for (int i = 0; i < drawText.length(); i++)
259        {
260            if (x != null && i < x.length)
261            {
262                cursorX = x[i];
263            } else if (dx != null && i < dx.length)
264            {
265                cursorX += dx[i];
266            }
267            
268            if (y != null && i < y.length)
269            {
270                cursorY = y[i];
271            } else if (dy != null && i < dy.length)
272            {
273                cursorY += dy[i];
274            }
275  //          i++;
276            
277            xform.setToIdentity();
278            xform.setToTranslation(cursorX, cursorY);
279//            xform.scale(fontScale, fontScale);
280            if (rotate != null)
281            {
282                xform.rotate(rotate[i]);
283            }
284
285            String unicode = drawText.substring(i, i + 1);
286            MissingGlyph glyph = font.getGlyph(unicode);
287
288            Shape path = glyph.getPath();
289            if (path != null)
290            {
291                path = xform.createTransformedShape(path);
292                addShape.append(path, false);
293            }
294
295//            cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing;
296            cursorX += glyph.getHorizAdvX() + letterSpacing;
297        }
298
299        //Save final draw point so calling method knows where to begin next
300        // text draw
301        cursor.setLocation(cursorX, cursorY);
302        strokeWidthScalar = 1f;
303    }
304
305//    private void addShapeSysFont(GeneralPath addShape, Font font,
306//        String fontFamily, float fontSize, float letterSpacing, Point2D cursor)
307//    {
308//
309//        java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize);
310//
311//        FontRenderContext frc = new FontRenderContext(null, true, true);
312//        String renderText = this.text.trim();
313//
314//        AffineTransform xform = new AffineTransform();
315//
316//        float cursorX = (float)cursor.getX();
317//        float cursorY = (float)cursor.getY();
318////        int i = 0;
319//        for (int i = 0; i < renderText.length(); i++)
320//        {
321//            if (x != null && i < x.length)
322//            {
323//                cursorX = x[i];
324//            } else if (dx != null && i < dx.length)
325//            {
326//                cursorX += dx[i];
327//            }
328//
329//            if (y != null && i < y.length)
330//            {
331//                cursorY = y[i];
332//            } else if (dy != null && i < dy.length)
333//            {
334//                cursorY += dy[i];
335//            }
336////            i++;
337//            
338//            xform.setToIdentity();
339//            xform.setToTranslation(cursorX, cursorY);
340//            if (rotate != null)
341//            {
342//                xform.rotate(rotate[Math.min(i, rotate.length - 1)]);
343//            }
344//
345////            String unicode = renderText.substring(i, i + 1);
346//            GlyphVector textVector = sysFont.createGlyphVector(frc, renderText.substring(i, i + 1));
347//            Shape glyphOutline = textVector.getGlyphOutline(0);
348//            GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(0);
349//
350//            glyphOutline = xform.createTransformedShape(glyphOutline);
351//            addShape.append(glyphOutline, false);
352//
353//
354////            cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing;
355//            cursorX += glyphMetrics.getAdvance() + letterSpacing;
356//        }
357//        
358//        cursor.setLocation(cursorX, cursorY);
359//    }
360
361    @Override
362    public void render(Graphics2D g) throws SVGException
363    {
364        float cursorX = 0;
365        float cursorY = 0;
366    
367        if (x != null)
368        {
369            cursorX = x[0];
370            cursorY = y[0];
371        } else if (dx != null)
372        {
373            cursorX += dx[0];
374            cursorY += dy[0];
375        }
376
377        StyleAttribute sty = new StyleAttribute();
378
379        String fontFamily = null;
380        if (getPres(sty.setName("font-family")))
381        {
382            fontFamily = sty.getStringValue();
383        }
384
385
386        float fontSize = 12f;
387        if (getPres(sty.setName("font-size")))
388        {
389            fontSize = sty.getFloatValueWithUnits();
390        }
391
392        //Get font
393        Font font = diagram.getUniverse().getFont(fontFamily);
394        if (font == null)
395        {
396            System.err.println("Could not load font");
397            java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize);
398            renderSysFont(g, sysFont);
399            return;
400        }
401
402
403        FontFace fontFace = font.getFontFace();
404        int ascent = fontFace.getAscent();
405        float fontScale = fontSize / (float) ascent;
406
407        AffineTransform oldXform = g.getTransform();
408        AffineTransform xform = new AffineTransform();
409
410        strokeWidthScalar = 1f / fontScale;
411
412        int posPtr = 1;
413
414        for (int i = 0; i < text.length(); i++)
415        {
416            xform.setToTranslation(cursorX, cursorY);
417            xform.scale(fontScale, fontScale);
418            g.transform(xform);
419
420            String unicode = text.substring(i, i + 1);
421            MissingGlyph glyph = font.getGlyph(unicode);
422
423            Shape path = glyph.getPath();
424            if (path != null)
425            {
426                renderShape(g, path);
427            } else
428            {
429                glyph.render(g);
430            }
431
432            if (x != null && posPtr < x.length)
433            {
434                cursorX = x[posPtr];
435                cursorY = y[posPtr++];
436            } else if (dx != null && posPtr < dx.length)
437            {
438                cursorX += dx[posPtr];
439                cursorY += dy[posPtr++];
440            }
441
442            cursorX += fontScale * glyph.getHorizAdvX();
443
444            g.setTransform(oldXform);
445        }
446
447        strokeWidthScalar = 1f;
448    }
449
450    protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException
451    {
452        float cursorX = 0;
453        float cursorY = 0;
454    
455        FontRenderContext frc = g.getFontRenderContext();
456
457        Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
458        renderShape(g, textShape);
459        Rectangle2D rect = font.getStringBounds(text, frc);
460        cursorX += (float) rect.getWidth();
461    }
462
463    @Override
464    public Shape getShape()
465    {
466        return null;
467        //return shapeToParent(tspanShape);
468    }
469
470    @Override
471    public Rectangle2D getBoundingBox()
472    {
473        return null;
474        //return boundsToParent(tspanShape.getBounds2D());
475    }
476
477    /**
478     * Updates all attributes in this diagram associated with a time event. Ie,
479     * all attributes with track information.
480     *
481     * @return - true if this node has changed state as a result of the time
482     * update
483     */
484    @Override
485    public boolean updateTime(double curTime) throws SVGException
486    {
487        //Tspan does not change
488        return false;
489    }
490
491    public String getText()
492    {
493        return text;
494    }
495
496    public void setText(String text)
497    {
498        this.text = text;
499    }
500}