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