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 February 18, 2004, 5:33 PM
035 */
036
037package com.kitfox.svg;
038
039import com.kitfox.svg.xml.NumberWithUnits;
040import com.kitfox.svg.xml.StyleAttribute;
041import com.kitfox.svg.xml.StyleSheet;
042import java.awt.Graphics2D;
043import java.awt.Rectangle;
044import java.awt.Shape;
045import java.awt.geom.AffineTransform;
046import java.awt.geom.Rectangle2D;
047
048/**
049 * The root element of an SVG tree.
050 *
051 * @author Mark McKay
052 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
053 */
054public class SVGRoot extends Group
055{
056    public static final String TAG_NAME = "svg";
057
058    NumberWithUnits x;
059    NumberWithUnits y;
060    NumberWithUnits width;
061    NumberWithUnits height;
062
063    Rectangle2D.Float viewBox = null;
064
065    public static final int PA_X_NONE = 0;
066    public static final int PA_X_MIN = 1;
067    public static final int PA_X_MID = 2;
068    public static final int PA_X_MAX = 3;
069
070    public static final int PA_Y_NONE = 0;
071    public static final int PA_Y_MIN = 1;
072    public static final int PA_Y_MID = 2;
073    public static final int PA_Y_MAX = 3;
074
075    public static final int PS_MEET = 0;
076    public static final int PS_SLICE = 1;
077
078    int parSpecifier = PS_MEET;
079    int parAlignX = PA_X_MID;
080    int parAlignY = PA_Y_MID;
081
082    final AffineTransform viewXform = new AffineTransform();
083    final Rectangle2D.Float clipRect = new Rectangle2D.Float();
084
085    private StyleSheet styleSheet;
086    
087    /** Creates a new instance of SVGRoot */
088    public SVGRoot()
089    {
090    }
091
092    public String getTagName()
093    {
094        return TAG_NAME;
095    }
096    
097    public void build() throws SVGException
098    {
099        super.build();
100        
101        StyleAttribute sty = new StyleAttribute();
102        
103        if (getPres(sty.setName("x")))
104        {
105            x = sty.getNumberWithUnits();
106        }
107        
108        if (getPres(sty.setName("y")))
109        {
110            y = sty.getNumberWithUnits();
111        }
112        
113        if (getPres(sty.setName("width")))
114        {
115            width = sty.getNumberWithUnits();
116        }
117        
118        if (getPres(sty.setName("height")))
119        {
120            height = sty.getNumberWithUnits();
121        }
122        
123        if (getPres(sty.setName("viewBox"))) 
124        {
125            float[] coords = sty.getFloatList();
126            viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
127        }
128        
129        if (getPres(sty.setName("preserveAspectRatio")))
130        {
131            String preserve = sty.getStringValue();
132            
133            if (contains(preserve, "none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
134            else if (contains(preserve, "xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
135            else if (contains(preserve, "xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
136            else if (contains(preserve, "xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
137            else if (contains(preserve, "xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
138            else if (contains(preserve, "xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
139            else if (contains(preserve, "xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
140            else if (contains(preserve, "xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
141            else if (contains(preserve, "xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
142            else if (contains(preserve, "xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
143
144            if (contains(preserve, "meet"))
145            {
146                parSpecifier = PS_MEET;
147            }
148            else if (contains(preserve, "slice"))
149            {
150                parSpecifier = PS_SLICE;
151            }
152        }
153        
154        prepareViewport();
155    }
156    
157    private boolean contains(String text, String find) 
158    {
159        return (text.indexOf(find) != -1);
160    }
161
162    public SVGRoot getRoot()
163    {
164        return this;
165    }
166    
167    protected void prepareViewport()
168    {
169        Rectangle deviceViewport = diagram.getDeviceViewport();
170        
171        Rectangle2D defaultBounds;
172        try
173        {
174            defaultBounds = getBoundingBox();
175        }
176        catch (SVGException ex)
177        {
178            defaultBounds= new Rectangle2D.Float();
179        }
180        
181        //Determine destination rectangle
182        float xx, yy, ww, hh;
183        if (width != null)
184        {
185            xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue());
186            if (width.getUnits() == NumberWithUnits.UT_PERCENT)
187            {
188                ww = width.getValue() * deviceViewport.width;
189            }
190            else
191            {
192                ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue());
193            }
194        }
195        else if (viewBox != null)
196        {
197            xx = (float)viewBox.x;
198            ww = (float)viewBox.width;
199            width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
200            x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
201        }
202        else
203        {
204            //Estimate size from scene bounding box
205            xx = (float)defaultBounds.getX();
206            ww = (float)defaultBounds.getWidth();
207            width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
208            x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
209        }
210        
211        if (height != null)
212        {
213            yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue());
214            if (height.getUnits() == NumberWithUnits.UT_PERCENT)
215            {
216                hh = height.getValue() * deviceViewport.height;
217            }
218            else
219            {
220                hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue());
221            }
222        }
223        else if (viewBox != null)
224        {
225            yy = (float)viewBox.y;
226            hh = (float)viewBox.height;
227            height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
228            y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
229        }
230        else
231        {
232            //Estimate size from scene bounding box
233            yy = (float)defaultBounds.getY();
234            hh = (float)defaultBounds.getHeight();
235            height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
236            y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
237        }
238
239        clipRect.setRect(xx, yy, ww, hh);
240
241        if (viewBox == null)
242        {
243            viewXform.setToIdentity();
244        }
245        else
246        {
247            viewXform.setToTranslation(clipRect.x, clipRect.y);
248            viewXform.scale(clipRect.width, clipRect.height);
249            viewXform.scale(1 / viewBox.width, 1 / viewBox.height);
250            viewXform.translate(-viewBox.x, -viewBox.y);
251        }
252    }
253
254    public void render(Graphics2D g) throws SVGException
255    {
256        prepareViewport();
257        
258        AffineTransform cachedXform = g.getTransform();
259        g.transform(viewXform);
260        
261        super.render(g);
262        
263        g.setTransform(cachedXform);
264    }
265
266    public Shape getShape()
267    {
268        Shape shape = super.getShape();
269        return viewXform.createTransformedShape(shape);
270    }
271
272    public Rectangle2D getBoundingBox() throws SVGException
273    {
274        Rectangle2D bbox = super.getBoundingBox();
275        return viewXform.createTransformedShape(bbox).getBounds2D();
276    }
277    
278    public float getDeviceWidth()
279    {
280        return clipRect.width;
281    }
282    
283    public float getDeviceHeight()
284    {
285        return clipRect.height;
286    }
287    
288    public Rectangle2D getDeviceRect(Rectangle2D rect)
289    {
290        rect.setRect(clipRect);
291        return rect;
292    }
293
294    /**
295     * Updates all attributes in this diagram associated with a time event.
296     * Ie, all attributes with track information.
297     * @return - true if this node has changed state as a result of the time
298     * update
299     */
300    public boolean updateTime(double curTime) throws SVGException
301    {
302        boolean changeState = super.updateTime(curTime);
303        
304        StyleAttribute sty = new StyleAttribute();
305        boolean shapeChange = false;
306        
307        if (getPres(sty.setName("x")))
308        {
309            NumberWithUnits newVal = sty.getNumberWithUnits();
310            if (!newVal.equals(x))
311            {
312                x = newVal;
313                shapeChange = true;
314            }
315        }
316
317        if (getPres(sty.setName("y")))
318        {
319            NumberWithUnits newVal = sty.getNumberWithUnits();
320            if (!newVal.equals(y))
321            {
322                y = newVal;
323                shapeChange = true;
324            }
325        }
326
327        if (getPres(sty.setName("width")))
328        {
329            NumberWithUnits newVal = sty.getNumberWithUnits();
330            if (!newVal.equals(width))
331            {
332                width = newVal;
333                shapeChange = true;
334            }
335        }
336
337        if (getPres(sty.setName("height")))
338        {
339            NumberWithUnits newVal = sty.getNumberWithUnits();
340            if (!newVal.equals(height))
341            {
342                height = newVal;
343                shapeChange = true;
344            }
345        }
346        
347        if (getPres(sty.setName("viewBox"))) 
348        {
349            float[] coords = sty.getFloatList();
350            Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
351            if (!newViewBox.equals(viewBox))
352            {
353                viewBox = newViewBox;
354                shapeChange = true;
355            }
356        }
357
358        if (shapeChange)
359        {
360            build();
361        }
362
363        return changeState || shapeChange;
364    }
365
366    /**
367     * @return the styleSheet
368     */
369    public StyleSheet getStyleSheet()
370    {
371        if (styleSheet == null)
372        {
373            for (int i = 0; i < getNumChildren(); ++i)
374            {
375                SVGElement ele = getChild(i);
376                if (ele instanceof Style)
377                {
378                    return ((Style)ele).getStyleSheet();
379                }
380            }
381        }
382        
383        return styleSheet;
384    }
385
386    /**
387     * @param styleSheet the styleSheet to set
388     */
389    public void setStyleSheet(StyleSheet styleSheet)
390    {
391        this.styleSheet = styleSheet;
392    }
393
394}