001    /*
002     * ShapeElement.java
003     *
004     *
005     *  The Salamander Project - 2D and 3D graphics libraries in Java
006     *  Copyright (C) 2004 Mark McKay
007     *
008     *  This library is free software; you can redistribute it and/or
009     *  modify it under the terms of the GNU Lesser General Public
010     *  License as published by the Free Software Foundation; either
011     *  version 2.1 of the License, or (at your option) any later version.
012     *
013     *  This library is distributed in the hope that it will be useful,
014     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
015     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016     *  Lesser General Public License for more details.
017     *
018     *  You should have received a copy of the GNU Lesser General Public
019     *  License along with this library; if not, write to the Free Software
020     *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021     *
022     *  Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
023     *  projects can be found at http://www.kitfox.com
024     *
025     * Created on January 26, 2004, 5:21 PM
026     */
027    
028    package com.kitfox.svg;
029    
030    import com.kitfox.svg.Marker.MarkerLayout;
031    import com.kitfox.svg.Marker.MarkerPos;
032    import com.kitfox.svg.xml.StyleAttribute;
033    import java.awt.AlphaComposite;
034    import java.awt.BasicStroke;
035    import java.awt.Color;
036    import java.awt.Composite;
037    import java.awt.Graphics2D;
038    import java.awt.Paint;
039    import java.awt.Shape;
040    import java.awt.geom.AffineTransform;
041    import java.awt.geom.Point2D;
042    import java.awt.geom.Rectangle2D;
043    import java.net.URI;
044    import java.util.ArrayList;
045    import java.util.List;
046    
047    
048    
049    /**
050     * Parent of shape objects
051     *
052     * @author Mark McKay
053     * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
054     */
055    abstract public class ShapeElement extends RenderableElement 
056    {
057    
058        /**
059         * This is necessary to get text elements to render the stroke the correct
060         * width.  It is an alternative to producing new font glyph sets at different
061         * sizes.
062         */
063        protected float strokeWidthScalar = 1f;
064    
065        /** Creates a new instance of ShapeElement */
066        public ShapeElement() {
067        }
068    
069        abstract public void render(java.awt.Graphics2D g) throws SVGException;
070    
071        /*
072        protected void setStrokeWidthScalar(float strokeWidthScalar)
073        {
074            this.strokeWidthScalar = strokeWidthScalar;
075        }
076         */
077    
078        void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
079        {
080            StyleAttribute styleAttrib = new StyleAttribute();
081    //        if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
082            if ((boundingBox ? getBoundingBox() : getShape()).contains(point))
083            {
084                retVec.add(getPath(null));
085            }
086        }
087    
088        void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
089        {
090            StyleAttribute styleAttrib = new StyleAttribute();
091    //        if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
092            if (ltw.createTransformedShape((boundingBox ? getBoundingBox() : getShape())).intersects(pickArea))
093            {
094                retVec.add(getPath(null));
095            }
096        }
097    
098        private Paint handleCurrentColor(StyleAttribute styleAttrib) throws SVGException
099        {
100            if (styleAttrib.getStringValue().equals("currentColor"))
101            {
102                StyleAttribute currentColorAttrib = new StyleAttribute();
103                if (getStyle(currentColorAttrib.setName("color")))
104                {
105                    if (!currentColorAttrib.getStringValue().equals("none"))
106                    {
107                        return currentColorAttrib.getColorValue();
108                    }
109                }
110                return null;
111            }
112            else
113            {
114                return styleAttrib.getColorValue();
115            }
116        }
117    
118        protected void renderShape(Graphics2D g, Shape shape) throws SVGException
119        {
120    //g.setColor(Color.green);
121    
122            StyleAttribute styleAttrib = new StyleAttribute();
123            
124            //Don't process if not visible
125            if (getStyle(styleAttrib.setName("visibility")))
126            {
127                if (!styleAttrib.getStringValue().equals("visible")) return;
128            }
129    
130            if (getStyle(styleAttrib.setName("display")))
131            {
132                if (styleAttrib.getStringValue().equals("none")) return;
133            }
134    
135            //None, solid color, gradient, pattern
136            Paint fillPaint = Color.black;  //Default to black.  Must be explicitly set to none for no fill.
137            if (getStyle(styleAttrib.setName("fill")))
138            {
139                if (styleAttrib.getStringValue().equals("none")) fillPaint = null;
140                else
141                {
142                    fillPaint = handleCurrentColor(styleAttrib);
143                    if (fillPaint == null)
144                    {
145                        URI uri = styleAttrib.getURIValue(getXMLBase());
146                        if (uri != null)
147                        {
148                            Rectangle2D bounds = shape.getBounds2D();
149                            AffineTransform xform = g.getTransform();
150    
151                            SVGElement ele = diagram.getUniverse().getElement(uri);
152                            if (ele != null)
153                            {
154                                fillPaint = ((FillElement)ele).getPaint(bounds, xform);
155                            }
156                        }
157                    }
158                }
159            }
160    
161            //Default opacity
162            float opacity = 1f;
163            if (getStyle(styleAttrib.setName("opacity")))
164            {
165                opacity = styleAttrib.getRatioValue();
166            }
167            
168            float fillOpacity = opacity;
169            if (getStyle(styleAttrib.setName("fill-opacity")))
170            {
171                fillOpacity *= styleAttrib.getRatioValue();
172            }
173    
174    
175            Paint strokePaint = null;  //Default is to stroke with none
176            if (getStyle(styleAttrib.setName("stroke")))
177            {
178                if (styleAttrib.getStringValue().equals("none")) strokePaint = null;
179                else
180                {
181                    strokePaint = handleCurrentColor(styleAttrib);
182                    if (strokePaint == null)
183                    {
184                        URI uri = styleAttrib.getURIValue(getXMLBase());
185                        if (uri != null)
186                        {
187                            Rectangle2D bounds = shape.getBounds2D();
188                            AffineTransform xform = g.getTransform();
189    
190                            SVGElement ele = diagram.getUniverse().getElement(uri);
191                            if (ele != null)
192                            {
193                                strokePaint = ((FillElement)ele).getPaint(bounds, xform);
194                            }
195                        }
196                    }
197                }
198            }
199    
200            float[] strokeDashArray = null;
201            if (getStyle(styleAttrib.setName("stroke-dasharray")))
202            {
203                strokeDashArray = styleAttrib.getFloatList();
204                if (strokeDashArray.length == 0) strokeDashArray = null;
205            }
206    
207            float strokeDashOffset = 0f;
208            if (getStyle(styleAttrib.setName("stroke-dashoffset")))
209            {
210                strokeDashOffset = styleAttrib.getFloatValueWithUnits();
211            }
212    
213            int strokeLinecap = BasicStroke.CAP_BUTT;
214            if (getStyle(styleAttrib.setName("stroke-linecap")))
215            {
216                String val = styleAttrib.getStringValue();
217                if (val.equals("round"))
218                {
219                    strokeLinecap = BasicStroke.CAP_ROUND;
220                }
221                else if (val.equals("square"))
222                {
223                    strokeLinecap = BasicStroke.CAP_SQUARE;
224                }
225            }
226    
227            int strokeLinejoin = BasicStroke.JOIN_MITER;
228            if (getStyle(styleAttrib.setName("stroke-linejoin")))
229            {
230                String val = styleAttrib.getStringValue();
231                if (val.equals("round"))
232                {
233                    strokeLinejoin = BasicStroke.JOIN_ROUND;
234                }
235                else if (val.equals("bevel"))
236                {
237                    strokeLinejoin = BasicStroke.JOIN_BEVEL;
238                }
239            }
240    
241            float strokeMiterLimit = 4f;
242            if (getStyle(styleAttrib.setName("stroke-miterlimit")))
243            {
244                strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1);
245            }
246    
247            float strokeOpacity = opacity;
248            if (getStyle(styleAttrib.setName("stroke-opacity")))
249            {
250                strokeOpacity *= styleAttrib.getRatioValue();
251            }
252    
253            float strokeWidth = 1f;
254            if (getStyle(styleAttrib.setName("stroke-width")))
255            {
256                strokeWidth = styleAttrib.getFloatValueWithUnits();
257            }
258    //        if (strokeWidthScalar != 1f)
259    //        {
260                strokeWidth *= strokeWidthScalar;
261    //        }
262    
263            Marker markerStart = null;
264            if (getStyle(styleAttrib.setName("marker-start")))
265            {
266                if (!styleAttrib.getStringValue().equals("none"))
267                {
268                    URI uri = styleAttrib.getURIValue(getXMLBase());
269                    markerStart = (Marker)diagram.getUniverse().getElement(uri);
270                }
271            }
272    
273            Marker markerMid = null;
274            if (getStyle(styleAttrib.setName("marker-mid")))
275            {
276                if (!styleAttrib.getStringValue().equals("none"))
277                {
278                    URI uri = styleAttrib.getURIValue(getXMLBase());
279                    markerMid = (Marker)diagram.getUniverse().getElement(uri);
280                }
281            }
282    
283            Marker markerEnd = null;
284            if (getStyle(styleAttrib.setName("marker-end")))
285            {
286                if (!styleAttrib.getStringValue().equals("none"))
287                {
288                    URI uri = styleAttrib.getURIValue(getXMLBase());
289                    markerEnd = (Marker)diagram.getUniverse().getElement(uri);
290                }
291            }
292    
293    
294            //Draw the shape
295            if (fillPaint != null && fillOpacity != 0f)
296            {
297                if (fillOpacity <= 0)
298                {
299                    //Do nothing
300                }
301                else if (fillOpacity < 1f)
302                {
303                    Composite cachedComposite = g.getComposite();
304                    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity));
305    
306                    g.setPaint(fillPaint);
307                    g.fill(shape);
308                
309                    g.setComposite(cachedComposite);
310                }
311                else
312                {
313                    g.setPaint(fillPaint);
314                    g.fill(shape);
315                }
316            }
317    
318    
319            if (strokePaint != null && strokeOpacity != 0f)
320            {
321                BasicStroke stroke;
322                if (strokeDashArray == null)
323                {
324                    stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit);
325                }
326                else
327                {
328                    stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset);
329                }
330    
331                Shape strokeShape = stroke.createStrokedShape(shape);
332    
333                if (strokeOpacity <= 0)
334                {
335                    //Do nothing
336                }
337                else if (strokeOpacity < 1f)
338                {
339                    Composite cachedComposite = g.getComposite();
340                    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity));
341    
342                    g.setPaint(strokePaint);
343                    g.fill(strokeShape);
344    
345                    g.setComposite(cachedComposite);
346                }
347                else
348                {
349                    g.setPaint(strokePaint);
350                    g.fill(strokeShape);
351                }
352            }
353    
354            if (markerStart != null || markerMid != null || markerEnd != null)
355            {
356                MarkerLayout layout = new MarkerLayout();
357                layout.layout(shape);
358                
359                ArrayList list = layout.getMarkerList();
360                for (int i = 0; i < list.size(); ++i)
361                {
362                    MarkerPos pos = (MarkerPos)list.get(i);
363    
364                    switch (pos.type)
365                    {
366                        case Marker.MARKER_START:
367                            if (markerStart != null)
368                            {
369                                markerStart.render(g, pos, strokeWidth);
370                            }
371                            break;
372                        case Marker.MARKER_MID:
373                            if (markerMid != null)
374                            {
375                                markerMid.render(g, pos, strokeWidth);
376                            }
377                            break;
378                        case Marker.MARKER_END:
379                            if (markerEnd != null)
380                            {
381                                markerEnd.render(g, pos, strokeWidth);
382                            }
383                            break;
384                    }
385                }
386            }
387        }
388        
389        abstract public Shape getShape();
390    
391        protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException
392        {
393            StyleAttribute styleAttrib = new StyleAttribute();
394            if (!getStyle(styleAttrib.setName("stroke"))) return rect;
395    
396            double strokeWidth = 1;
397            if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue();
398    
399            rect.setRect(
400                rect.getX() - strokeWidth / 2,
401                rect.getY() - strokeWidth / 2,
402                rect.getWidth() + strokeWidth,
403                rect.getHeight() + strokeWidth);
404    
405            return rect;
406        }
407    
408    }