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 April 21, 2005, 10:45 AM
035 */
036
037package com.kitfox.svg.app.beans;
038
039import com.kitfox.svg.*;
040import java.awt.*;
041import java.awt.geom.*;
042import java.beans.*;
043import java.net.*;
044import javax.swing.*;
045
046/**
047 *
048 * @author kitfox
049 */
050public class SVGIcon implements Icon
051{
052    public static final long serialVersionUID = 1;
053    
054    private PropertyChangeSupport changes = new PropertyChangeSupport(this);
055    
056    SVGUniverse svgUniverse = SVGCache.getSVGUniverse();
057    public static final int INTERP_NEAREST_NEIGHBOR = 0;
058    public static final int INTERP_BILINEAR = 1;
059    public static final int INTERP_BICUBIC = 2;
060    
061    private boolean antiAlias;
062    private int interpolation = INTERP_NEAREST_NEIGHBOR;
063    private boolean clipToViewbox;
064    
065//    private String svgPath;
066    URI svgURI;
067    
068    private boolean scaleToFit;
069    AffineTransform scaleXform = new AffineTransform();
070    
071//    Dimension preferredSize = new Dimension(100, 100);
072    Dimension preferredSize;
073    
074    /** Creates a new instance of SVGIcon */
075    public SVGIcon()
076    {
077    }
078    
079    public void addPropertyChangeListener(PropertyChangeListener p)
080    {
081        changes.addPropertyChangeListener(p);
082    }
083    
084    public void removePropertyChangeListener(PropertyChangeListener p)
085    {
086        changes.removePropertyChangeListener(p);
087    }
088    
089    /**
090     * @return height of this icon
091     */
092    public int getIconHeight()
093    {
094        if (scaleToFit && preferredSize != null)
095        {
096            return preferredSize.height;
097        }
098        
099        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
100        if (diagram == null)
101        {
102            return 0;
103        }
104        return (int)diagram.getHeight();
105    }
106    
107    /**
108     * @return width of this icon
109     */
110    public int getIconWidth()
111    {
112        if (scaleToFit && preferredSize != null)
113        {
114            return preferredSize.width;
115        }
116        
117        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
118        if (diagram == null)
119        {
120            return 0;
121        }
122        return (int)diagram.getWidth();
123    }
124    
125    /**
126     * Draws the icon to the specified component.
127     * @param comp - Component to draw icon to.  This is ignored by SVGIcon, and can be set to null; only gg is used for drawing the icon
128     * @param gg - Graphics context to render SVG content to
129     * @param x - X coordinate to draw icon
130     * @param y - Y coordinate to draw icon
131     */
132    public void paintIcon(Component comp, Graphics gg, int x, int y)
133    {
134        //Copy graphics object so that 
135        Graphics2D g = (Graphics2D)gg.create();
136        paintIcon(comp, g, x, y);
137        g.dispose();
138    }
139    
140    private void paintIcon(Component comp, Graphics2D g, int x, int y)
141    {
142        Object oldAliasHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
143        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
144        
145        Object oldInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
146        switch (interpolation)
147        {
148            case INTERP_NEAREST_NEIGHBOR:
149                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
150                break;
151            case INTERP_BILINEAR:
152                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
153                break;
154            case INTERP_BICUBIC:
155                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
156                break;
157        }
158        
159        
160        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
161        if (diagram == null)
162        {
163            return;
164        }
165        
166        g.translate(x, y);
167        diagram.setIgnoringClipHeuristic(!clipToViewbox);
168        if (clipToViewbox)
169        {
170            g.setClip(new Rectangle2D.Float(0, 0, diagram.getWidth(), diagram.getHeight()));
171        }
172        
173        
174        
175        if (!scaleToFit)
176        {
177            try
178            {
179                diagram.render(g);
180                g.translate(-x, -y);
181                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
182            }
183            catch (Exception e)
184            {
185                throw new RuntimeException(e);
186            }
187            return;
188        }
189        
190        final int width = getIconWidth();
191        final int height = getIconHeight();
192//        int width = getWidth();
193//        int height = getHeight();
194        
195        if (width == 0 || height == 0)
196        {
197            return;
198        }
199        
200//        if (width == 0 || height == 0)
201//        {
202//           //Chances are we're rendering offscreen
203//            Dimension dim = getSize();
204//            width = dim.width;
205//            height = dim.height;
206//            return;
207//        }
208        
209//        g.setClip(0, 0, width, height);
210        
211        
212        final Rectangle2D.Double rect = new Rectangle2D.Double();
213        diagram.getViewRect(rect);
214        
215        scaleXform.setToScale(width / rect.width, height / rect.height);
216        
217        AffineTransform oldXform = g.getTransform();
218        g.transform(scaleXform);
219        
220        try
221        {
222            diagram.render(g);
223        }
224        catch (SVGException e)
225        {
226            throw new RuntimeException(e);
227        }
228        
229        g.setTransform(oldXform);
230        
231        
232        g.translate(-x, -y);
233        
234        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
235        if (oldInterpolationHint != null)
236        {
237            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldInterpolationHint);
238        }
239    }
240    
241    /**
242     * @return the universe this icon draws it's SVGDiagrams from
243     */
244    public SVGUniverse getSvgUniverse()
245    {
246        return svgUniverse;
247    }
248    
249    public void setSvgUniverse(SVGUniverse svgUniverse)
250    {
251        SVGUniverse old = this.svgUniverse;
252        this.svgUniverse = svgUniverse;
253        changes.firePropertyChange("svgUniverse", old, svgUniverse);
254    }
255    
256    /**
257     * @return the uni of the document being displayed by this icon
258     */
259    public URI getSvgURI()
260    {
261        return svgURI;
262    }
263    
264    /**
265     * Loads an SVG document from a URI.
266     * @param svgURI - URI to load document from
267     */
268    public void setSvgURI(URI svgURI)
269    {
270        URI old = this.svgURI;
271        this.svgURI = svgURI;
272        
273        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
274        if (diagram != null)
275        {
276            Dimension size = getPreferredSize();
277            if (size == null)
278            {
279                size = new Dimension((int)diagram.getRoot().getDeviceWidth(), (int)diagram.getRoot().getDeviceHeight());
280            }
281            diagram.setDeviceViewport(new Rectangle(0, 0, size.width, size.height));
282        }
283        
284        changes.firePropertyChange("svgURI", old, svgURI);
285    }
286    
287    /**
288     * Loads an SVG document from the classpath.  This function is equivilant to
289     * setSvgURI(new URI(getClass().getResource(resourcePath).toString());
290     * @param resourcePath - resource to load
291     */
292    public void setSvgResourcePath(String resourcePath)
293    {
294        URI old = this.svgURI;
295        
296        try
297        {
298            svgURI = new URI(getClass().getResource(resourcePath).toString());
299            changes.firePropertyChange("svgURI", old, svgURI);
300            
301            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
302            if (diagram != null)
303            {
304                diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
305            }
306            
307        }
308        catch (Exception e)
309        {
310            svgURI = old;
311        }
312    }
313    
314    /**
315     * If this SVG document has a viewbox, if scaleToFit is set, will scale the viewbox to match the
316     * preferred size of this icon
317     */
318    public boolean isScaleToFit()
319    {
320        return scaleToFit;
321    }
322    
323    public void setScaleToFit(boolean scaleToFit)
324    {
325        boolean old = this.scaleToFit;
326        this.scaleToFit = scaleToFit;
327        changes.firePropertyChange("scaleToFit", old, scaleToFit);
328    }
329    
330    public Dimension getPreferredSize()
331    {
332        if (preferredSize == null)
333        {
334            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
335            if (diagram != null)
336            {
337                //preferredSize = new Dimension((int)diagram.getWidth(), (int)diagram.getHeight());
338                setPreferredSize(new Dimension((int)diagram.getWidth(), (int)diagram.getHeight()));
339            }
340        }
341        
342        return new Dimension(preferredSize);
343    }
344    
345    public void setPreferredSize(Dimension preferredSize)
346    {
347        Dimension old = this.preferredSize;
348        this.preferredSize = preferredSize;
349        
350        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
351        if (diagram != null)
352        {
353            diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
354        }
355        
356        changes.firePropertyChange("preferredSize", old, preferredSize);
357    }
358    
359    
360    /**
361     * @return true if antiAliasing is turned on.
362     * @deprecated
363     */
364    public boolean getUseAntiAlias()
365    {
366        return getAntiAlias();
367    }
368    
369    /**
370     * @param antiAlias true to use antiAliasing.
371     * @deprecated
372     */
373    public void setUseAntiAlias(boolean antiAlias)
374    {
375        setAntiAlias(antiAlias);
376    }
377    
378    /**
379     * @return true if antiAliasing is turned on.
380     */
381    public boolean getAntiAlias()
382    {
383        return antiAlias;
384    }
385    
386    /**
387     * @param antiAlias true to use antiAliasing.
388     */
389    public void setAntiAlias(boolean antiAlias)
390    {
391        boolean old = this.antiAlias;
392        this.antiAlias = antiAlias;
393        changes.firePropertyChange("antiAlias", old, antiAlias);
394    }
395    
396    /**
397     * @return interpolation used in rescaling images
398     */
399    public int getInterpolation()
400    {
401        return interpolation;
402    }
403    
404    /**
405     * @param interpolation Interpolation value used in rescaling images.
406     * Should be one of
407     *    INTERP_NEAREST_NEIGHBOR - Fastest, one pixel resampling, poor quality
408     *    INTERP_BILINEAR - four pixel resampling
409     *    INTERP_BICUBIC - Slowest, nine pixel resampling, best quality
410     */
411    public void setInterpolation(int interpolation)
412    {
413        int old = this.interpolation;
414        this.interpolation = interpolation;
415        changes.firePropertyChange("interpolation", old, interpolation);
416    }
417    
418    /**
419     * clipToViewbox will set a clip box equivilant to the SVG's viewbox before
420     * rendering.
421     */
422    public boolean isClipToViewbox()
423    {
424        return clipToViewbox;
425    }
426    
427    public void setClipToViewbox(boolean clipToViewbox)
428    {
429        this.clipToViewbox = clipToViewbox;
430    }
431    
432}