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