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.geom.AffineTransform;
043import java.awt.geom.GeneralPath;
044import java.awt.geom.Point2D;
045import java.awt.geom.Rectangle2D;
046import java.util.Iterator;
047import java.util.LinkedList;
048import java.util.regex.Matcher;
049import java.util.regex.Pattern;
050
051/**
052 * @author Mark McKay
053 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
054 */
055public class Text extends ShapeElement
056{
057    public static final String TAG_NAME = "text";
058    
059    float x = 0;
060    float y = 0;
061    AffineTransform transform = null;
062    String fontFamily;
063    float fontSize;
064    //List of strings and tspans containing the content of this node
065    LinkedList content = new LinkedList();
066    Shape textShape;
067    public static final int TXAN_START = 0;
068    public static final int TXAN_MIDDLE = 1;
069    public static final int TXAN_END = 2;
070    int textAnchor = TXAN_START;
071    public static final int TXST_NORMAL = 0;
072    public static final int TXST_ITALIC = 1;
073    public static final int TXST_OBLIQUE = 2;
074    int fontStyle;
075    public static final int TXWE_NORMAL = 0;
076    public static final int TXWE_BOLD = 1;
077    public static final int TXWE_BOLDER = 2;
078    public static final int TXWE_LIGHTER = 3;
079    public static final int TXWE_100 = 4;
080    public static final int TXWE_200 = 5;
081    public static final int TXWE_300 = 6;
082    public static final int TXWE_400 = 7;
083    public static final int TXWE_500 = 8;
084    public static final int TXWE_600 = 9;
085    public static final int TXWE_700 = 10;
086    public static final int TXWE_800 = 11;
087    public static final int TXWE_900 = 12;
088    int fontWeight;
089
090    float textLength = -1;
091    String lengthAdjust = "spacing";
092    
093    /**
094     * Creates a new instance of Stop
095     */
096    public Text()
097    {
098    }
099
100    public String getTagName()
101    {
102        return TAG_NAME;
103    }
104
105    public void appendText(String text)
106    {
107        content.addLast(text);
108    }
109
110    public void appendTspan(Tspan tspan) throws SVGElementException
111    {
112        super.loaderAddChild(null, tspan);
113        content.addLast(tspan);
114    }
115
116    /**
117     * Discard cached information
118     */
119    public void rebuild() throws SVGException
120    {
121        build();
122    }
123
124    public java.util.List getContent()
125    {
126        return content;
127    }
128
129    /**
130     * Called after the start element but before the end element to indicate
131     * each child tag that has been processed
132     */
133    public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
134    {
135        super.loaderAddChild(helper, child);
136
137        content.addLast(child);
138    }
139
140    /**
141     * Called during load process to add text scanned within a tag
142     */
143    public void loaderAddText(SVGLoaderHelper helper, String text)
144    {
145        Matcher matchWs = Pattern.compile("\\s*").matcher(text);
146        if (!matchWs.matches())
147        {
148            content.addLast(text);
149        }
150    }
151
152    public void build() throws SVGException
153    {
154        super.build();
155
156        StyleAttribute sty = new StyleAttribute();
157
158        if (getPres(sty.setName("x")))
159        {
160            x = sty.getFloatValueWithUnits();
161        }
162
163        if (getPres(sty.setName("y")))
164        {
165            y = sty.getFloatValueWithUnits();
166        }
167
168        if (getStyle(sty.setName("font-family")))
169        {
170            fontFamily = sty.getStringValue();
171        }
172        else
173        {
174            fontFamily = "Sans Serif";
175        }
176
177        if (getStyle(sty.setName("font-size")))
178        {
179            fontSize = sty.getFloatValueWithUnits();
180        }
181        else
182        {
183            fontSize = 12f;
184        }
185
186        if (getStyle(sty.setName("textLength")))
187        {
188            textLength = sty.getFloatValueWithUnits();
189        }
190        else
191        {
192            textLength = -1;
193        }
194
195        if (getStyle(sty.setName("lengthAdjust")))
196        {
197            lengthAdjust = sty.getStringValue();
198        }
199        else
200        {
201            lengthAdjust = "spacing";
202        }
203
204        if (getStyle(sty.setName("font-style")))
205        {
206            String s = sty.getStringValue();
207            if ("normal".equals(s))
208            {
209                fontStyle = TXST_NORMAL;
210            } else if ("italic".equals(s))
211            {
212                fontStyle = TXST_ITALIC;
213            } else if ("oblique".equals(s))
214            {
215                fontStyle = TXST_OBLIQUE;
216            }
217        } else
218        {
219            fontStyle = TXST_NORMAL;
220        }
221
222        if (getStyle(sty.setName("font-weight")))
223        {
224            String s = sty.getStringValue();
225            if ("normal".equals(s))
226            {
227                fontWeight = TXWE_NORMAL;
228            } else if ("bold".equals(s))
229            {
230                fontWeight = TXWE_BOLD;
231            }
232        } else
233        {
234            fontWeight = TXWE_NORMAL;
235        }
236
237        if (getStyle(sty.setName("text-anchor")))
238        {
239            String s = sty.getStringValue();
240            if (s.equals("middle"))
241            {
242                textAnchor = TXAN_MIDDLE;
243            } else if (s.equals("end"))
244            {
245                textAnchor = TXAN_END;
246            } else
247            {
248                textAnchor = TXAN_START;
249            }
250        } else
251        {
252            textAnchor = TXAN_START;
253        }
254
255        //text anchor
256        //text-decoration
257        //text-rendering
258
259        buildText();
260    }
261
262    protected void buildText() throws SVGException
263    {
264        //Get font
265        String[] families = fontFamily.split(",");
266        Font font = null;
267        for (int i = 0; i < families.length; ++i)
268        {
269            font = diagram.getUniverse().getFont(fontFamily);
270            if (font != null)
271            {
272                break;
273            }
274        }
275        
276        if (font == null)
277        {
278//            System.err.println("Could not load font");
279
280            font = new FontSystem(fontFamily, fontStyle, fontWeight, (int)fontSize);
281//            java.awt.Font sysFont = new java.awt.Font(fontFamily, style | weight, (int)fontSize);
282//            buildSysFont(sysFont);
283//            return;
284        }
285
286//        font = new java.awt.Font(font.getFamily(), style | weight, font.getSize());
287
288//        Area textArea = new Area();
289        GeneralPath textPath = new GeneralPath();
290        textShape = textPath;
291
292        float cursorX = x, cursorY = y;
293
294        FontFace fontFace = font.getFontFace();
295        //int unitsPerEm = fontFace.getUnitsPerEm();
296//        int ascent = fontFace.getAscent();
297//        float fontScale = fontSize / (float) ascent;
298
299//        AffineTransform oldXform = g.getTransform();
300//        TextBuilder builder = new TextBuilder();
301//        
302//        for (Iterator it = content.iterator(); it.hasNext();)
303//        {
304//            Object obj = it.next();
305//
306//            if (obj instanceof String)
307//            {
308//                String text = (String) obj;
309//                if (text != null)
310//                {
311//                    text = text.trim();
312//                }
313//                
314//                for (int i = 0; i < text.length(); i++)
315//                {
316//                    String unicode = text.substring(i, i + 1);
317//                    MissingGlyph glyph = font.getGlyph(unicode);
318//                    
319//                    builder.appendGlyph(glyph);
320//                }
321//            }
322//            else if (obj instanceof Tspan)
323//            {
324//                Tspan tspan = (Tspan)obj;
325//                tspan.buildGlyphs(builder);
326//            }
327//        }
328//
329//        builder.formatGlyphs();
330        
331        
332
333                
334                
335        
336        
337        
338        AffineTransform xform = new AffineTransform();
339
340        for (Iterator it = content.iterator(); it.hasNext();)
341        {
342            Object obj = it.next();
343
344            if (obj instanceof String)
345            {
346                String text = (String) obj;
347                if (text != null)
348                {
349                    text = text.trim();
350                }
351
352//                strokeWidthScalar = 1f / fontScale;
353
354                for (int i = 0; i < text.length(); i++)
355                {
356                    xform.setToIdentity();
357                    xform.setToTranslation(cursorX, cursorY);
358//                    xform.scale(fontScale, fontScale);
359//                    g.transform(xform);
360
361                    String unicode = text.substring(i, i + 1);
362                    MissingGlyph glyph = font.getGlyph(unicode);
363
364                    Shape path = glyph.getPath();
365                    if (path != null)
366                    {
367                        path = xform.createTransformedShape(path);
368                        textPath.append(path, false);
369                    }
370//                    else glyph.render(g);
371
372//                    cursorX += fontScale * glyph.getHorizAdvX();
373                    cursorX += glyph.getHorizAdvX();
374
375//                    g.setTransform(oldXform);
376                }
377
378                strokeWidthScalar = 1f;
379            }
380            else if (obj instanceof Tspan)
381            {
382//                Tspan tspan = (Tspan) obj;
383//
384//                xform.setToIdentity();
385//                xform.setToTranslation(cursorX, cursorY);
386//                xform.scale(fontScale, fontScale);
387////                tspan.setCursorX(cursorX);
388////                tspan.setCursorY(cursorY);
389//
390//                Shape tspanShape = tspan.getShape();
391//                tspanShape = xform.createTransformedShape(tspanShape);
392//                textPath.append(tspanShape, false);
393////                tspan.render(g);
394////                cursorX = tspan.getCursorX();
395////                cursorY = tspan.getCursorY();
396                
397                
398                Tspan tspan = (Tspan)obj;
399                Point2D cursor = new Point2D.Float(cursorX, cursorY);
400//                tspan.setCursorX(cursorX);
401//                tspan.setCursorY(cursorY);
402                tspan.appendToShape(textPath, cursor);
403//                cursorX = tspan.getCursorX();
404//                cursorY = tspan.getCursorY();
405                cursorX = (float)cursor.getX();
406                cursorY = (float)cursor.getY();
407                
408            }
409
410        }
411
412        switch (textAnchor)
413        {
414            case TXAN_MIDDLE:
415            {
416                AffineTransform at = new AffineTransform();
417                at.translate(-textPath.getBounds().getWidth() / 2, 0);
418                textPath.transform(at);
419                break;
420            }
421            case TXAN_END:
422            {
423                AffineTransform at = new AffineTransform();
424                at.translate(-textPath.getBounds().getWidth(), 0);
425                textPath.transform(at);
426                break;
427            }
428        }
429    }
430
431//    private void buildSysFont(java.awt.Font font) throws SVGException
432//    {
433//        GeneralPath textPath = new GeneralPath();
434//        textShape = textPath;
435//
436//        float cursorX = x, cursorY = y;
437//
438////        FontMetrics fm = g.getFontMetrics(font);
439//        FontRenderContext frc = new FontRenderContext(null, true, true);
440//
441////        FontFace fontFace = font.getFontFace();
442//        //int unitsPerEm = fontFace.getUnitsPerEm();
443////        int ascent = fm.getAscent();
444////        float fontScale = fontSize / (float)ascent;
445//
446////        AffineTransform oldXform = g.getTransform();
447//        AffineTransform xform = new AffineTransform();
448//
449//        for (Iterator it = content.iterator(); it.hasNext();)
450//        {
451//            Object obj = it.next();
452//
453//            if (obj instanceof String)
454//            {
455//                String text = (String)obj;
456//                text = text.trim();
457//
458//                Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
459//                textPath.append(textShape, false);
460////                renderShape(g, textShape);
461////                g.drawString(text, cursorX, cursorY);
462//
463//                Rectangle2D rect = font.getStringBounds(text, frc);
464//                cursorX += (float) rect.getWidth();
465//            } else if (obj instanceof Tspan)
466//            {
467//                /*
468//                 Tspan tspan = (Tspan)obj;
469//                 
470//                 xform.setToIdentity();
471//                 xform.setToTranslation(cursorX, cursorY);
472//                 
473//                 Shape tspanShape = tspan.getShape();
474//                 tspanShape = xform.createTransformedShape(tspanShape);
475//                 textArea.add(new Area(tspanShape));
476//                 
477//                 cursorX += tspanShape.getBounds2D().getWidth();
478//                 */
479//
480//
481//                Tspan tspan = (Tspan)obj;
482//                Point2D cursor = new Point2D.Float(cursorX, cursorY);
483////                tspan.setCursorX(cursorX);
484////                tspan.setCursorY(cursorY);
485//                tspan.appendToShape(textPath, cursor);
486////                cursorX = tspan.getCursorX();
487////                cursorY = tspan.getCursorY();
488//                cursorX = (float)cursor.getX();
489//                cursorY = (float)cursor.getY();
490//
491//            }
492//        }
493//
494//        switch (textAnchor)
495//        {
496//            case TXAN_MIDDLE:
497//            {
498//                AffineTransform at = new AffineTransform();
499//                at.translate(-textPath.getBounds().getWidth() / 2, 0);
500//                textPath.transform(at);
501//                break;
502//            }
503//            case TXAN_END:
504//            {
505//                AffineTransform at = new AffineTransform();
506//                at.translate(-Math.ceil(textPath.getBounds().getWidth()), 0);
507//                textPath.transform(at);
508//                break;
509//            }
510//        }
511//    }
512
513    public void render(Graphics2D g) throws SVGException
514    {
515        beginLayer(g);
516        renderShape(g, textShape);
517        finishLayer(g);
518    }
519
520    public Shape getShape()
521    {
522        return shapeToParent(textShape);
523    }
524
525    public Rectangle2D getBoundingBox() throws SVGException
526    {
527        return boundsToParent(includeStrokeInBounds(textShape.getBounds2D()));
528    }
529
530    /**
531     * Updates all attributes in this diagram associated with a time event. Ie,
532     * all attributes with track information.
533     *
534     * @return - true if this node has changed state as a result of the time
535     * update
536     */
537    public boolean updateTime(double curTime) throws SVGException
538    {
539//        if (trackManager.getNumTracks() == 0) return false;
540        boolean changeState = super.updateTime(curTime);
541
542        //Get current values for parameters
543        StyleAttribute sty = new StyleAttribute();
544        boolean shapeChange = false;
545
546        if (getPres(sty.setName("x")))
547        {
548            float newVal = sty.getFloatValueWithUnits();
549            if (newVal != x)
550            {
551                x = newVal;
552                shapeChange = true;
553            }
554        }
555
556        if (getPres(sty.setName("y")))
557        {
558            float newVal = sty.getFloatValueWithUnits();
559            if (newVal != y)
560            {
561                y = newVal;
562                shapeChange = true;
563            }
564        }
565
566        if (getStyle(sty.setName("textLength")))
567        {
568            textLength = sty.getFloatValueWithUnits();
569        }
570        else
571        {
572            textLength = -1;
573        }
574
575        if (getStyle(sty.setName("lengthAdjust")))
576        {
577            lengthAdjust = sty.getStringValue();
578        }
579        else
580        {
581            lengthAdjust = "spacing";
582        }
583
584        if (getPres(sty.setName("font-family")))
585        {
586            String newVal = sty.getStringValue();
587            if (!newVal.equals(fontFamily))
588            {
589                fontFamily = newVal;
590                shapeChange = true;
591            }
592        }
593
594        if (getPres(sty.setName("font-size")))
595        {
596            float newVal = sty.getFloatValueWithUnits();
597            if (newVal != fontSize)
598            {
599                fontSize = newVal;
600                shapeChange = true;
601            }
602        }
603
604
605        if (getStyle(sty.setName("font-style")))
606        {
607            String s = sty.getStringValue();
608            int newVal = fontStyle;
609            if ("normal".equals(s))
610            {
611                newVal = TXST_NORMAL;
612            } else if ("italic".equals(s))
613            {
614                newVal = TXST_ITALIC;
615            } else if ("oblique".equals(s))
616            {
617                newVal = TXST_OBLIQUE;
618            }
619            if (newVal != fontStyle)
620            {
621                fontStyle = newVal;
622                shapeChange = true;
623            }
624        }
625
626        if (getStyle(sty.setName("font-weight")))
627        {
628            String s = sty.getStringValue();
629            int newVal = fontWeight;
630            if ("normal".equals(s))
631            {
632                newVal = TXWE_NORMAL;
633            } else if ("bold".equals(s))
634            {
635                newVal = TXWE_BOLD;
636            }
637            if (newVal != fontWeight)
638            {
639                fontWeight = newVal;
640                shapeChange = true;
641            }
642        }
643
644        if (shapeChange)
645        {
646            build();
647//            buildFont();
648//            return true;
649        }
650
651        return changeState || shapeChange;
652    }
653}