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 January 26, 2004, 5:21 PM
035 */
036
037package com.kitfox.svg;
038
039import com.kitfox.svg.Marker.MarkerLayout;
040import com.kitfox.svg.Marker.MarkerPos;
041import com.kitfox.svg.xml.StyleAttribute;
042import java.awt.AlphaComposite;
043import java.awt.BasicStroke;
044import java.awt.Color;
045import java.awt.Composite;
046import java.awt.Graphics2D;
047import java.awt.Paint;
048import java.awt.Shape;
049import java.awt.geom.AffineTransform;
050import java.awt.geom.Point2D;
051import java.awt.geom.Rectangle2D;
052import java.net.URI;
053import java.util.ArrayList;
054import java.util.List;
055
056
057
058/**
059 * Parent of shape objects
060 *
061 * @author Mark McKay
062 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
063 */
064abstract public class ShapeElement extends RenderableElement 
065{
066
067    /**
068     * This is necessary to get text elements to render the stroke the correct
069     * width.  It is an alternative to producing new font glyph sets at different
070     * sizes.
071     */
072    protected float strokeWidthScalar = 1f;
073
074    /** Creates a new instance of ShapeElement */
075    public ShapeElement() {
076    }
077
078    abstract public void render(java.awt.Graphics2D g) throws SVGException;
079
080    /*
081    protected void setStrokeWidthScalar(float strokeWidthScalar)
082    {
083        this.strokeWidthScalar = strokeWidthScalar;
084    }
085     */
086
087    void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
088    {
089//        StyleAttribute styleAttrib = new StyleAttribute();
090//        if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
091        if ((boundingBox ? getBoundingBox() : getShape()).contains(point))
092        {
093            retVec.add(getPath(null));
094        }
095    }
096
097    void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
098    {
099        StyleAttribute styleAttrib = new StyleAttribute();
100//        if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
101        if (ltw.createTransformedShape((boundingBox ? getBoundingBox() : getShape())).intersects(pickArea))
102        {
103            retVec.add(getPath(null));
104        }
105    }
106
107    private Paint handleCurrentColor(StyleAttribute styleAttrib) throws SVGException
108    {
109        if (styleAttrib.getStringValue().equals("currentColor"))
110        {
111            StyleAttribute currentColorAttrib = new StyleAttribute();
112            if (getStyle(currentColorAttrib.setName("color")))
113            {
114                if (!currentColorAttrib.getStringValue().equals("none"))
115                {
116                    return currentColorAttrib.getColorValue();
117                }
118            }
119            return null;
120        }
121        else
122        {
123            return styleAttrib.getColorValue();
124        }
125    }
126
127    protected void renderShape(Graphics2D g, Shape shape) throws SVGException
128    {
129//g.setColor(Color.green);
130
131        StyleAttribute styleAttrib = new StyleAttribute();
132        
133        //Don't process if not visible
134        if (getStyle(styleAttrib.setName("visibility")))
135        {
136            if (!styleAttrib.getStringValue().equals("visible")) return;
137        }
138
139        if (getStyle(styleAttrib.setName("display")))
140        {
141            if (styleAttrib.getStringValue().equals("none")) return;
142        }
143
144        //None, solid color, gradient, pattern
145        Paint fillPaint = Color.black;  //Default to black.  Must be explicitly set to none for no fill.
146        if (getStyle(styleAttrib.setName("fill")))
147        {
148            if (styleAttrib.getStringValue().equals("none")) fillPaint = null;
149            else
150            {
151                fillPaint = handleCurrentColor(styleAttrib);
152                if (fillPaint == null)
153                {
154                    URI uri = styleAttrib.getURIValue(getXMLBase());
155                    if (uri != null)
156                    {
157                        Rectangle2D bounds = shape.getBounds2D();
158                        AffineTransform xform = g.getTransform();
159
160                        SVGElement ele = diagram.getUniverse().getElement(uri);
161                        if (ele != null)
162                        {
163                            fillPaint = ((FillElement)ele).getPaint(bounds, xform);
164                        }
165                    }
166                }
167            }
168        }
169
170        //Default opacity
171        float opacity = 1f;
172        if (getStyle(styleAttrib.setName("opacity")))
173        {
174            opacity = styleAttrib.getRatioValue();
175        }
176        
177        float fillOpacity = opacity;
178        if (getStyle(styleAttrib.setName("fill-opacity")))
179        {
180            fillOpacity *= styleAttrib.getRatioValue();
181        }
182
183
184        Paint strokePaint = null;  //Default is to stroke with none
185        if (getStyle(styleAttrib.setName("stroke")))
186        {
187            if (styleAttrib.getStringValue().equals("none")) strokePaint = null;
188            else
189            {
190                strokePaint = handleCurrentColor(styleAttrib);
191                if (strokePaint == null)
192                {
193                    URI uri = styleAttrib.getURIValue(getXMLBase());
194                    if (uri != null)
195                    {
196                        Rectangle2D bounds = shape.getBounds2D();
197                        AffineTransform xform = g.getTransform();
198
199                        SVGElement ele = diagram.getUniverse().getElement(uri);
200                        if (ele != null)
201                        {
202                            strokePaint = ((FillElement)ele).getPaint(bounds, xform);
203                        }
204                    }
205                }
206            }
207        }
208
209        float[] strokeDashArray = null;
210        if (getStyle(styleAttrib.setName("stroke-dasharray")))
211        {
212            strokeDashArray = styleAttrib.getFloatList();
213            if (strokeDashArray.length == 0) strokeDashArray = null;
214        }
215
216        float strokeDashOffset = 0f;
217        if (getStyle(styleAttrib.setName("stroke-dashoffset")))
218        {
219            strokeDashOffset = styleAttrib.getFloatValueWithUnits();
220        }
221
222        int strokeLinecap = BasicStroke.CAP_BUTT;
223        if (getStyle(styleAttrib.setName("stroke-linecap")))
224        {
225            String val = styleAttrib.getStringValue();
226            if (val.equals("round"))
227            {
228                strokeLinecap = BasicStroke.CAP_ROUND;
229            }
230            else if (val.equals("square"))
231            {
232                strokeLinecap = BasicStroke.CAP_SQUARE;
233            }
234        }
235
236        int strokeLinejoin = BasicStroke.JOIN_MITER;
237        if (getStyle(styleAttrib.setName("stroke-linejoin")))
238        {
239            String val = styleAttrib.getStringValue();
240            if (val.equals("round"))
241            {
242                strokeLinejoin = BasicStroke.JOIN_ROUND;
243            }
244            else if (val.equals("bevel"))
245            {
246                strokeLinejoin = BasicStroke.JOIN_BEVEL;
247            }
248        }
249
250        float strokeMiterLimit = 4f;
251        if (getStyle(styleAttrib.setName("stroke-miterlimit")))
252        {
253            strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1);
254        }
255
256        float strokeOpacity = opacity;
257        if (getStyle(styleAttrib.setName("stroke-opacity")))
258        {
259            strokeOpacity *= styleAttrib.getRatioValue();
260        }
261
262        float strokeWidth = 1f;
263        if (getStyle(styleAttrib.setName("stroke-width")))
264        {
265            strokeWidth = styleAttrib.getFloatValueWithUnits();
266        }
267//        if (strokeWidthScalar != 1f)
268//        {
269            strokeWidth *= strokeWidthScalar;
270//        }
271
272        Marker markerStart = null;
273        if (getStyle(styleAttrib.setName("marker-start")))
274        {
275            if (!styleAttrib.getStringValue().equals("none"))
276            {
277                URI uri = styleAttrib.getURIValue(getXMLBase());
278                markerStart = (Marker)diagram.getUniverse().getElement(uri);
279            }
280        }
281
282        Marker markerMid = null;
283        if (getStyle(styleAttrib.setName("marker-mid")))
284        {
285            if (!styleAttrib.getStringValue().equals("none"))
286            {
287                URI uri = styleAttrib.getURIValue(getXMLBase());
288                markerMid = (Marker)diagram.getUniverse().getElement(uri);
289            }
290        }
291
292        Marker markerEnd = null;
293        if (getStyle(styleAttrib.setName("marker-end")))
294        {
295            if (!styleAttrib.getStringValue().equals("none"))
296            {
297                URI uri = styleAttrib.getURIValue(getXMLBase());
298                markerEnd = (Marker)diagram.getUniverse().getElement(uri);
299            }
300        }
301
302
303        //Draw the shape
304        if (fillPaint != null && fillOpacity != 0f)
305        {
306            if (fillOpacity <= 0)
307            {
308                //Do nothing
309            }
310            else if (fillOpacity < 1f)
311            {
312                Composite cachedComposite = g.getComposite();
313                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity));
314
315                g.setPaint(fillPaint);
316                g.fill(shape);
317            
318                g.setComposite(cachedComposite);
319            }
320            else
321            {
322                g.setPaint(fillPaint);
323                g.fill(shape);
324            }
325        }
326
327
328        if (strokePaint != null && strokeOpacity != 0f)
329        {
330            BasicStroke stroke;
331            if (strokeDashArray == null)
332            {
333                stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit);
334            }
335            else
336            {
337                stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset);
338            }
339
340            Shape strokeShape;
341            AffineTransform cacheXform = g.getTransform();
342            if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
343            {
344                strokeShape = cacheXform.createTransformedShape(shape);
345                strokeShape = stroke.createStrokedShape(strokeShape);
346            }
347            else
348            {
349                strokeShape = stroke.createStrokedShape(shape);
350            }
351
352            if (strokeOpacity <= 0)
353            {
354                //Do nothing
355            }
356            else
357            {
358                Composite cachedComposite = g.getComposite();
359
360                if (strokeOpacity < 1f)
361                {
362                    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity));
363                }
364
365                if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
366                {
367                    //Set to identity
368                    g.setTransform(new AffineTransform());
369                }
370
371                g.setPaint(strokePaint);
372                g.fill(strokeShape);
373
374                if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
375                {
376                    //Set to identity
377                    g.setTransform(cacheXform);
378                }
379
380                if (strokeOpacity < 1f)
381                {
382                    g.setComposite(cachedComposite);
383                }
384            }
385        }
386
387        if (markerStart != null || markerMid != null || markerEnd != null)
388        {
389            MarkerLayout layout = new MarkerLayout();
390            layout.layout(shape);
391            
392            ArrayList list = layout.getMarkerList();
393            for (int i = 0; i < list.size(); ++i)
394            {
395                MarkerPos pos = (MarkerPos)list.get(i);
396
397                switch (pos.type)
398                {
399                    case Marker.MARKER_START:
400                        if (markerStart != null)
401                        {
402                            markerStart.render(g, pos, strokeWidth);
403                        }
404                        break;
405                    case Marker.MARKER_MID:
406                        if (markerMid != null)
407                        {
408                            markerMid.render(g, pos, strokeWidth);
409                        }
410                        break;
411                    case Marker.MARKER_END:
412                        if (markerEnd != null)
413                        {
414                            markerEnd.render(g, pos, strokeWidth);
415                        }
416                        break;
417                }
418            }
419        }
420    }
421    
422    abstract public Shape getShape();
423
424    protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException
425    {
426        StyleAttribute styleAttrib = new StyleAttribute();
427        if (!getStyle(styleAttrib.setName("stroke"))) return rect;
428
429        double strokeWidth = 1;
430        if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue();
431
432        rect.setRect(
433            rect.getX() - strokeWidth / 2,
434            rect.getY() - strokeWidth / 2,
435            rect.getWidth() + strokeWidth,
436            rect.getHeight() + strokeWidth);
437
438        return rect;
439    }
440
441}