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                            try {
164                                fillPaint = ((FillElement)ele).getPaint(bounds, xform);
165                            } catch (IllegalArgumentException e) {
166                                throw new SVGException(e);
167                            }
168                        }
169                    }
170                }
171            }
172        }
173
174        //Default opacity
175        float opacity = 1f;
176        if (getStyle(styleAttrib.setName("opacity")))
177        {
178            opacity = styleAttrib.getRatioValue();
179        }
180        
181        float fillOpacity = opacity;
182        if (getStyle(styleAttrib.setName("fill-opacity")))
183        {
184            fillOpacity *= styleAttrib.getRatioValue();
185        }
186
187
188        Paint strokePaint = null;  //Default is to stroke with none
189        if (getStyle(styleAttrib.setName("stroke")))
190        {
191            if (styleAttrib.getStringValue().equals("none")) strokePaint = null;
192            else
193            {
194                strokePaint = handleCurrentColor(styleAttrib);
195                if (strokePaint == null)
196                {
197                    URI uri = styleAttrib.getURIValue(getXMLBase());
198                    if (uri != null)
199                    {
200                        Rectangle2D bounds = shape.getBounds2D();
201                        AffineTransform xform = g.getTransform();
202
203                        SVGElement ele = diagram.getUniverse().getElement(uri);
204                        if (ele != null)
205                        {
206                            strokePaint = ((FillElement)ele).getPaint(bounds, xform);
207                        }
208                    }
209                }
210            }
211        }
212
213        float[] strokeDashArray = null;
214        if (getStyle(styleAttrib.setName("stroke-dasharray")))
215        {
216            strokeDashArray = styleAttrib.getFloatList();
217            if (strokeDashArray.length == 0) strokeDashArray = null;
218        }
219
220        float strokeDashOffset = 0f;
221        if (getStyle(styleAttrib.setName("stroke-dashoffset")))
222        {
223            strokeDashOffset = styleAttrib.getFloatValueWithUnits();
224        }
225
226        int strokeLinecap = BasicStroke.CAP_BUTT;
227        if (getStyle(styleAttrib.setName("stroke-linecap")))
228        {
229            String val = styleAttrib.getStringValue();
230            if (val.equals("round"))
231            {
232                strokeLinecap = BasicStroke.CAP_ROUND;
233            }
234            else if (val.equals("square"))
235            {
236                strokeLinecap = BasicStroke.CAP_SQUARE;
237            }
238        }
239
240        int strokeLinejoin = BasicStroke.JOIN_MITER;
241        if (getStyle(styleAttrib.setName("stroke-linejoin")))
242        {
243            String val = styleAttrib.getStringValue();
244            if (val.equals("round"))
245            {
246                strokeLinejoin = BasicStroke.JOIN_ROUND;
247            }
248            else if (val.equals("bevel"))
249            {
250                strokeLinejoin = BasicStroke.JOIN_BEVEL;
251            }
252        }
253
254        float strokeMiterLimit = 4f;
255        if (getStyle(styleAttrib.setName("stroke-miterlimit")))
256        {
257            strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1);
258        }
259
260        float strokeOpacity = opacity;
261        if (getStyle(styleAttrib.setName("stroke-opacity")))
262        {
263            strokeOpacity *= styleAttrib.getRatioValue();
264        }
265
266        float strokeWidth = 1f;
267        if (getStyle(styleAttrib.setName("stroke-width")))
268        {
269            strokeWidth = styleAttrib.getFloatValueWithUnits();
270        }
271//        if (strokeWidthScalar != 1f)
272//        {
273            strokeWidth *= strokeWidthScalar;
274//        }
275
276        Marker markerStart = null;
277        if (getStyle(styleAttrib.setName("marker-start")))
278        {
279            if (!styleAttrib.getStringValue().equals("none"))
280            {
281                URI uri = styleAttrib.getURIValue(getXMLBase());
282                markerStart = (Marker)diagram.getUniverse().getElement(uri);
283            }
284        }
285
286        Marker markerMid = null;
287        if (getStyle(styleAttrib.setName("marker-mid")))
288        {
289            if (!styleAttrib.getStringValue().equals("none"))
290            {
291                URI uri = styleAttrib.getURIValue(getXMLBase());
292                markerMid = (Marker)diagram.getUniverse().getElement(uri);
293            }
294        }
295
296        Marker markerEnd = null;
297        if (getStyle(styleAttrib.setName("marker-end")))
298        {
299            if (!styleAttrib.getStringValue().equals("none"))
300            {
301                URI uri = styleAttrib.getURIValue(getXMLBase());
302                markerEnd = (Marker)diagram.getUniverse().getElement(uri);
303            }
304        }
305
306
307        //Draw the shape
308        if (fillPaint != null && fillOpacity != 0f)
309        {
310            if (fillOpacity <= 0)
311            {
312                //Do nothing
313            }
314            else if (fillOpacity < 1f)
315            {
316                Composite cachedComposite = g.getComposite();
317                g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity));
318
319                g.setPaint(fillPaint);
320                g.fill(shape);
321            
322                g.setComposite(cachedComposite);
323            }
324            else
325            {
326                g.setPaint(fillPaint);
327                g.fill(shape);
328            }
329        }
330
331
332        if (strokePaint != null && strokeOpacity != 0f)
333        {
334            BasicStroke stroke;
335            if (strokeDashArray == null)
336            {
337                stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit);
338            }
339            else
340            {
341                stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset);
342            }
343
344            Shape strokeShape;
345            AffineTransform cacheXform = g.getTransform();
346            if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
347            {
348                strokeShape = cacheXform.createTransformedShape(shape);
349                strokeShape = stroke.createStrokedShape(strokeShape);
350            }
351            else
352            {
353                strokeShape = stroke.createStrokedShape(shape);
354            }
355
356            if (strokeOpacity <= 0)
357            {
358                //Do nothing
359            }
360            else
361            {
362                Composite cachedComposite = g.getComposite();
363
364                if (strokeOpacity < 1f)
365                {
366                    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity));
367                }
368
369                if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
370                {
371                    //Set to identity
372                    g.setTransform(new AffineTransform());
373                }
374
375                g.setPaint(strokePaint);
376                g.fill(strokeShape);
377
378                if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
379                {
380                    //Set to identity
381                    g.setTransform(cacheXform);
382                }
383
384                if (strokeOpacity < 1f)
385                {
386                    g.setComposite(cachedComposite);
387                }
388            }
389        }
390
391        if (markerStart != null || markerMid != null || markerEnd != null)
392        {
393            MarkerLayout layout = new MarkerLayout();
394            layout.layout(shape);
395            
396            ArrayList list = layout.getMarkerList();
397            for (int i = 0; i < list.size(); ++i)
398            {
399                MarkerPos pos = (MarkerPos)list.get(i);
400
401                switch (pos.type)
402                {
403                    case Marker.MARKER_START:
404                        if (markerStart != null)
405                        {
406                            markerStart.render(g, pos, strokeWidth);
407                        }
408                        break;
409                    case Marker.MARKER_MID:
410                        if (markerMid != null)
411                        {
412                            markerMid.render(g, pos, strokeWidth);
413                        }
414                        break;
415                    case Marker.MARKER_END:
416                        if (markerEnd != null)
417                        {
418                            markerEnd.render(g, pos, strokeWidth);
419                        }
420                        break;
421                }
422            }
423        }
424    }
425    
426    abstract public Shape getShape();
427
428    protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException
429    {
430        StyleAttribute styleAttrib = new StyleAttribute();
431        if (!getStyle(styleAttrib.setName("stroke"))) return rect;
432
433        double strokeWidth = 1;
434        if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue();
435
436        rect.setRect(
437            rect.getX() - strokeWidth / 2,
438            rect.getY() - strokeWidth / 2,
439            rect.getWidth() + strokeWidth,
440            rect.getHeight() + strokeWidth);
441
442        return rect;
443    }
444
445}