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