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