001    /*
002     * SVGRoot.java
003     *
004     *
005     *  The Salamander Project - 2D and 3D graphics libraries in Java
006     *  Copyright (C) 2004 Mark McKay
007     *
008     *  This library is free software; you can redistribute it and/or
009     *  modify it under the terms of the GNU Lesser General Public
010     *  License as published by the Free Software Foundation; either
011     *  version 2.1 of the License, or (at your option) any later version.
012     *
013     *  This library is distributed in the hope that it will be useful,
014     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
015     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016     *  Lesser General Public License for more details.
017     *
018     *  You should have received a copy of the GNU Lesser General Public
019     *  License along with this library; if not, write to the Free Software
020     *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021     *
022     *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
023     *  projects can be found at http://www.kitfox.com
024     *
025     * Created on February 18, 2004, 5:33 PM
026     */
027    
028    package com.kitfox.svg;
029    
030    import com.kitfox.svg.xml.NumberWithUnits;
031    import com.kitfox.svg.xml.StyleAttribute;
032    import java.awt.geom.*;
033    import java.awt.*;
034    
035    /**
036     * The root element of an SVG tree.
037     *
038     * @author Mark McKay
039     * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
040     */
041    public class SVGRoot extends Group
042    {
043        NumberWithUnits x;
044        NumberWithUnits y;
045        NumberWithUnits width;
046        NumberWithUnits height;
047    
048        
049    //    final Rectangle2D.Float viewBox = new Rectangle2D.Float();
050        Rectangle2D.Float viewBox = null;
051    
052        public static final int PA_X_NONE = 0;
053        public static final int PA_X_MIN = 1;
054        public static final int PA_X_MID = 2;
055        public static final int PA_X_MAX = 3;
056    
057        public static final int PA_Y_NONE = 0;
058        public static final int PA_Y_MIN = 1;
059        public static final int PA_Y_MID = 2;
060        public static final int PA_Y_MAX = 3;
061    
062        public static final int PS_MEET = 0;
063        public static final int PS_SLICE = 1;
064    
065        int parSpecifier = PS_MEET;
066        int parAlignX = PA_X_MID;
067        int parAlignY = PA_Y_MID;
068    
069        final AffineTransform viewXform = new AffineTransform();
070        final Rectangle2D.Float clipRect = new Rectangle2D.Float();
071    
072        /** Creates a new instance of SVGRoot */
073        public SVGRoot()
074        {
075        }
076    /*
077        public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
078        {
079                    //Load style string
080            super.loaderStartElement(helper, attrs, parent);
081    
082            x = XMLParseUtil.parseNumberWithUnits(attrs.getValue("x"));
083            y = XMLParseUtil.parseNumberWithUnits(attrs.getValue("y"));
084            width = XMLParseUtil.parseNumberWithUnits(attrs.getValue("width"));
085            height = XMLParseUtil.parseNumberWithUnits(attrs.getValue("height"));
086    
087            String viewBox = attrs.getValue("viewBox");
088            float[] coords = XMLParseUtil.parseFloatList(viewBox);
089    
090            if (coords == null)
091            {
092                //this.viewBox.setRect(0, 0, width.getValue(), height.getValue());
093                this.viewBox = null;
094            }
095            else
096            {
097                this.viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
098            }
099    
100            String par = attrs.getValue("preserveAspectRatio");
101            if (par != null)
102            {
103                String[] parList = XMLParseUtil.parseStringList(par);
104    
105                if (parList[0].equals("none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
106                else if (parList[0].equals("xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
107                else if (parList[0].equals("xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
108                else if (parList[0].equals("xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
109                else if (parList[0].equals("xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
110                else if (parList[0].equals("xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
111                else if (parList[0].equals("xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
112                else if (parList[0].equals("xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
113                else if (parList[0].equals("xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
114                else if (parList[0].equals("xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
115    
116                if (parList[1].equals("meet")) parSpecifier = PS_MEET;
117                else if (parList[1].equals("slice")) parSpecifier = PS_SLICE;
118            }
119    
120            build();
121        }
122    */
123        
124        public void build() throws SVGException
125        {
126            super.build();
127            
128            StyleAttribute sty = new StyleAttribute();
129            
130            if (getPres(sty.setName("x"))) x = sty.getNumberWithUnits();
131            
132            if (getPres(sty.setName("y"))) y = sty.getNumberWithUnits();
133            
134            if (getPres(sty.setName("width"))) width = sty.getNumberWithUnits();
135            
136            if (getPres(sty.setName("height"))) height = sty.getNumberWithUnits();
137            
138            if (getPres(sty.setName("viewBox"))) 
139            {
140                float[] coords = sty.getFloatList();
141                viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
142            }
143            
144            if (getPres(sty.setName("preserveAspectRatio")))
145            {
146                String preserve = sty.getStringValue();
147                
148                if (contains(preserve, "none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
149                else if (contains(preserve, "xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
150                else if (contains(preserve, "xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
151                else if (contains(preserve, "xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
152                else if (contains(preserve, "xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
153                else if (contains(preserve, "xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
154                else if (contains(preserve, "xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
155                else if (contains(preserve, "xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
156                else if (contains(preserve, "xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
157                else if (contains(preserve, "xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
158    
159                if (contains(preserve, "meet")) parSpecifier = PS_MEET;
160                else if (contains(preserve, "slice")) parSpecifier = PS_SLICE;
161                
162                /*
163                String[] parList = sty.getStringList();
164    
165                if (parList[0].equals("none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; }
166                else if (parList[0].equals("xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; }
167                else if (parList[0].equals("xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; }
168                else if (parList[0].equals("xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; }
169                else if (parList[0].equals("xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; }
170                else if (parList[0].equals("xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; }
171                else if (parList[0].equals("xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; }
172                else if (parList[0].equals("xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; }
173                else if (parList[0].equals("xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; }
174                else if (parList[0].equals("xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; }
175    
176                if (parList[1].equals("meet")) parSpecifier = PS_MEET;
177                else if (parList[1].equals("slice")) parSpecifier = PS_SLICE;
178                 */
179            }
180            
181            prepareViewport();
182        }
183        
184        private boolean contains(String text, String find) 
185        {
186            return (text.indexOf(find) != -1);
187        }
188        
189        protected void prepareViewport()
190        {
191            Rectangle deviceViewport = diagram.getDeviceViewport();
192            
193            Rectangle2D defaultBounds;
194            try
195            {
196                defaultBounds = getBoundingBox();
197            } catch (SVGException ex)
198            {
199                defaultBounds= new Rectangle2D.Float();
200            }
201            
202            //Determine destination rectangle
203            float xx, yy, ww, hh;
204            if (width != null)
205            {
206                xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue());
207                if (width.getUnits() == NumberWithUnits.UT_PERCENT)
208                {
209                    ww = width.getValue() * deviceViewport.width;
210                }
211                else
212                {
213                    ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue());
214                }
215    //            setAttribute("x", AnimationElement.AT_XML, "" + xx);
216    //            setAttribute("width", AnimationElement.AT_XML, "" + ww);
217            }
218            else if (viewBox != null)
219            {
220                xx = (float)viewBox.x;
221                ww = (float)viewBox.width;
222                width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
223                x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
224            }
225            else
226            {
227                //Estimate size from scene bounding box
228                xx = (float)defaultBounds.getX();
229                ww = (float)defaultBounds.getWidth();
230                width = new NumberWithUnits(ww, NumberWithUnits.UT_PX);
231                x = new NumberWithUnits(xx, NumberWithUnits.UT_PX);
232            }
233            
234            if (height != null)
235            {
236                yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue());
237                if (height.getUnits() == NumberWithUnits.UT_PERCENT)
238                {
239                    hh = height.getValue() * deviceViewport.height;
240                }
241                else
242                {
243                    hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue());
244                }
245            }
246            else if (viewBox != null)
247            {
248                yy = (float)viewBox.y;
249                hh = (float)viewBox.height;
250                height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
251                y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
252            }
253            else
254            {
255                //Estimate size from scene bounding box
256                yy = (float)defaultBounds.getY();
257                hh = (float)defaultBounds.getHeight();
258                height = new NumberWithUnits(hh, NumberWithUnits.UT_PX);
259                y = new NumberWithUnits(yy, NumberWithUnits.UT_PX);
260            }
261    
262            clipRect.setRect(xx, yy, ww, hh);
263    
264            if (viewBox == null)
265            {
266                viewXform.setToIdentity();
267            }
268            else
269            {
270                viewXform.setToTranslation(clipRect.x, clipRect.y);
271                viewXform.scale(clipRect.width, clipRect.height);
272                viewXform.scale(1 / viewBox.width, 1 / viewBox.height);
273                viewXform.translate(-viewBox.x, -viewBox.y);
274            }
275    
276            
277            //For now, treat all preserveAspectRatio as 'none'
278    //        viewXform.setToTranslation(viewBox.x, viewBox.y);
279    //        viewXform.scale(clipRect.width / viewBox.width, clipRect.height / viewBox.height);
280    //        viewXform.translate(-clipRect.x, -clipRect.y);
281        }
282    
283        public void render(Graphics2D g) throws SVGException
284        {
285            prepareViewport();
286            
287            AffineTransform cachedXform = g.getTransform();
288            g.transform(viewXform);
289            
290            super.render(g);
291            
292            g.setTransform(cachedXform);
293        }
294    
295        public Shape getShape()
296        {
297            Shape shape = super.getShape();
298            return viewXform.createTransformedShape(shape);
299        }
300    
301        public Rectangle2D getBoundingBox() throws SVGException
302        {
303            Rectangle2D bbox = super.getBoundingBox();
304            return viewXform.createTransformedShape(bbox).getBounds2D();
305        }
306        
307        public float getDeviceWidth()
308        {
309            return clipRect.width;
310        }
311        
312        public float getDeviceHeight()
313        {
314            return clipRect.height;
315        }
316        
317        public Rectangle2D getDeviceRect(Rectangle2D rect)
318        {
319            rect.setRect(clipRect);
320            return rect;
321        }
322    
323        /**
324         * Updates all attributes in this diagram associated with a time event.
325         * Ie, all attributes with track information.
326         * @return - true if this node has changed state as a result of the time
327         * update
328         */
329        public boolean updateTime(double curTime) throws SVGException
330        {
331            boolean changeState = super.updateTime(curTime);
332            
333            StyleAttribute sty = new StyleAttribute();
334            boolean shapeChange = false;
335            
336            if (getPres(sty.setName("x")))
337            {
338                NumberWithUnits newVal = sty.getNumberWithUnits();
339                if (!newVal.equals(x))
340                {
341                    x = newVal;
342                    shapeChange = true;
343                }
344            }
345    
346            if (getPres(sty.setName("y")))
347            {
348                NumberWithUnits newVal = sty.getNumberWithUnits();
349                if (!newVal.equals(y))
350                {
351                    y = newVal;
352                    shapeChange = true;
353                }
354            }
355    
356            if (getPres(sty.setName("width")))
357            {
358                NumberWithUnits newVal = sty.getNumberWithUnits();
359                if (!newVal.equals(width))
360                {
361                    width = newVal;
362                    shapeChange = true;
363                }
364            }
365    
366            if (getPres(sty.setName("height")))
367            {
368                NumberWithUnits newVal = sty.getNumberWithUnits();
369                if (!newVal.equals(height))
370                {
371                    height = newVal;
372                    shapeChange = true;
373                }
374            }
375            
376            if (getPres(sty.setName("viewBox"))) 
377            {
378                float[] coords = sty.getFloatList();
379                Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]);
380                if (!newViewBox.equals(viewBox))
381                {
382                    viewBox = newViewBox;
383                    shapeChange = true;
384                }
385            }
386    
387            if (shapeChange)
388            {
389                build();
390            }
391    
392            return changeState || shapeChange;
393        }
394    
395    }