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 */
034package com.kitfox.svg;
035
036import com.kitfox.svg.xml.StyleAttribute;
037import java.awt.Graphics2D;
038import java.awt.Rectangle;
039import java.awt.Shape;
040import java.awt.geom.AffineTransform;
041import java.awt.geom.PathIterator;
042import java.awt.geom.Rectangle2D;
043import java.util.ArrayList;
044
045/**
046 *
047 * @author kitfox
048 */
049public class Marker extends Group
050{
051    public static final String TAG_NAME = "marker";
052    
053    AffineTransform viewXform;
054    AffineTransform markerXform;
055    Rectangle2D viewBox;
056    float refX;
057    float refY;
058    float markerWidth = 3;
059    float markerHeight = 3;
060    float orient = Float.NaN;
061    boolean markerUnitsStrokeWidth = true; //if set to false 'userSpaceOnUse' is assumed
062
063    public String getTagName()
064    {
065        return TAG_NAME;
066    }
067
068    protected void build() throws SVGException
069    {
070        super.build();
071
072        StyleAttribute sty = new StyleAttribute();
073
074        if (getPres(sty.setName("refX")))
075        {
076            refX = sty.getFloatValueWithUnits();
077        }
078        if (getPres(sty.setName("refY")))
079        {
080            refY = sty.getFloatValueWithUnits();
081        }
082        if (getPres(sty.setName("markerWidth")))
083        {
084            markerWidth = sty.getFloatValueWithUnits();
085        }
086        if (getPres(sty.setName("markerHeight")))
087        {
088            markerHeight = sty.getFloatValueWithUnits();
089        }
090
091        if (getPres(sty.setName("orient")))
092        {
093            if ("auto".equals(sty.getStringValue()))
094            {
095                orient = Float.NaN;
096            } else
097            {
098                orient = sty.getFloatValue();
099            }
100        }
101
102        if (getPres(sty.setName("viewBox")))
103        {
104            float[] dim = sty.getFloatList();
105            viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
106        }
107
108        if (viewBox == null)
109        {
110            viewBox = new Rectangle(0, 0, 1, 1);
111        }
112
113        if (getPres(sty.setName("markerUnits")))
114        {
115            String markerUnits = sty.getStringValue();
116            if (markerUnits != null && markerUnits.equals("userSpaceOnUse"))
117            {
118                markerUnitsStrokeWidth = false;
119            }
120        }
121
122        //Transform pattern onto unit square
123        viewXform = new AffineTransform();
124        viewXform.scale(1.0 / viewBox.getWidth(), 1.0 / viewBox.getHeight());
125        viewXform.translate(-viewBox.getX(), -viewBox.getY());
126
127        markerXform = new AffineTransform();
128        markerXform.scale(markerWidth, markerHeight);
129        markerXform.concatenate(viewXform);
130        markerXform.translate(-refX, -refY);
131    }
132
133    protected boolean outsideClip(Graphics2D g) throws SVGException
134    {
135        Shape clip = g.getClip();
136        Rectangle2D rect = super.getBoundingBox();
137        if (clip == null || clip.intersects(rect))
138        {
139            return false;
140        }
141
142        return true;
143
144    }
145
146    public void render(Graphics2D g) throws SVGException
147    {
148        AffineTransform oldXform = g.getTransform();
149        g.transform(markerXform);
150
151        super.render(g);
152
153        g.setTransform(oldXform);
154    }
155
156    public void render(Graphics2D g, MarkerPos pos, float strokeWidth) throws SVGException
157    {
158        AffineTransform cacheXform = g.getTransform();
159
160        g.translate(pos.x, pos.y);
161        if (markerUnitsStrokeWidth)
162        {
163            g.scale(strokeWidth, strokeWidth);
164        }
165
166        g.rotate(Math.atan2(pos.dy, pos.dx));
167
168        g.transform(markerXform);
169
170        super.render(g);
171
172        g.setTransform(cacheXform);
173    }
174
175    public Shape getShape()
176    {
177        Shape shape = super.getShape();
178        return markerXform.createTransformedShape(shape);
179    }
180
181    public Rectangle2D getBoundingBox() throws SVGException
182    {
183        Rectangle2D rect = super.getBoundingBox();
184        return markerXform.createTransformedShape(rect).getBounds2D();
185    }
186
187    /**
188     * Updates all attributes in this diagram associated with a time event. Ie,
189     * all attributes with track information.
190     *
191     * @return - true if this node has changed state as a result of the time
192     * update
193     */
194    public boolean updateTime(double curTime) throws SVGException
195    {
196        boolean changeState = super.updateTime(curTime);
197
198        //Marker properties do not change
199        return changeState;
200    }
201    
202    //--------------------------------
203    public static final int MARKER_START = 0;
204    public static final int MARKER_MID = 1;
205    public static final int MARKER_END = 2;
206
207    public static class MarkerPos
208    {
209
210        int type;
211        double x;
212        double y;
213        double dx;
214        double dy;
215
216        public MarkerPos(int type, double x, double y, double dx, double dy)
217        {
218            this.type = type;
219            this.x = x;
220            this.y = y;
221            this.dx = dx;
222            this.dy = dy;
223        }
224    }
225
226    public static class MarkerLayout
227    {
228
229        private ArrayList markerList = new ArrayList();
230        boolean started = false;
231
232        public void layout(Shape shape)
233        {
234            double px = 0;
235            double py = 0;
236            double[] coords = new double[6];
237            for (PathIterator it = shape.getPathIterator(null);
238                !it.isDone(); it.next())
239            {
240                switch (it.currentSegment(coords))
241                {
242                    case PathIterator.SEG_MOVETO:
243                        px = coords[0];
244                        py = coords[1];
245                        started = false;
246                        break;
247                    case PathIterator.SEG_CLOSE:
248                        started = false;
249                        break;
250                    case PathIterator.SEG_LINETO:
251                    {
252                        double x = coords[0];
253                        double y = coords[1];
254                        markerIn(px, py, x - px, y - py);
255                        markerOut(x, y, x - px, y - py);
256                        px = x;
257                        py = y;
258                        break;
259                    }
260                    case PathIterator.SEG_QUADTO:
261                    {
262                        double k0x = coords[0];
263                        double k0y = coords[1];
264                        double x = coords[2];
265                        double y = coords[3];
266
267
268                        //Best in tangent
269                        if (px != k0x || py != k0y)
270                        {
271                            markerIn(px, py, k0x - px, k0y - py);
272                        } else
273                        {
274                            markerIn(px, py, x - px, y - py);
275                        }
276
277                        //Best out tangent
278                        if (x != k0x || y != k0y)
279                        {
280                            markerOut(x, y, x - k0x, y - k0y);
281                        } else
282                        {
283                            markerOut(x, y, x - px, y - py);
284                        }
285
286                        markerIn(px, py, k0x - px, k0y - py);
287                        markerOut(x, y, x - k0x, y - k0y);
288                        px = x;
289                        py = y;
290                        break;
291                    }
292                    case PathIterator.SEG_CUBICTO:
293                    {
294                        double k0x = coords[0];
295                        double k0y = coords[1];
296                        double k1x = coords[2];
297                        double k1y = coords[3];
298                        double x = coords[4];
299                        double y = coords[5];
300
301                        //Best in tangent
302                        if (px != k0x || py != k0y)
303                        {
304                            markerIn(px, py, k0x - px, k0y - py);
305                        } else if (px != k1x || py != k1y)
306                        {
307                            markerIn(px, py, k1x - px, k1y - py);
308                        } else
309                        {
310                            markerIn(px, py, x - px, y - py);
311                        }
312
313                        //Best out tangent
314                        if (x != k1x || y != k1y)
315                        {
316                            markerOut(x, y, x - k1x, y - k1y);
317                        } else if (x != k0x || y != k0y)
318                        {
319                            markerOut(x, y, x - k0x, y - k0y);
320                        } else
321                        {
322                            markerOut(x, y, x - px, y - py);
323                        }
324                        px = x;
325                        py = y;
326                        break;
327                    }
328                }
329            }
330
331            for (int i = 1; i < markerList.size(); ++i)
332            {
333                MarkerPos prev = (MarkerPos) markerList.get(i - 1);
334                MarkerPos cur = (MarkerPos) markerList.get(i);
335
336                if (cur.type == MARKER_START)
337                {
338                    prev.type = MARKER_END;
339                }
340            }
341            MarkerPos last = (MarkerPos) markerList.get(markerList.size() - 1);
342            last.type = MARKER_END;
343        }
344
345        private void markerIn(double x, double y, double dx, double dy)
346        {
347            if (started == false)
348            {
349                started = true;
350                markerList.add(new MarkerPos(MARKER_START, x, y, dx, dy));
351            }
352        }
353
354        private void markerOut(double x, double y, double dx, double dy)
355        {
356            markerList.add(new MarkerPos(MARKER_MID, x, y, dx, dy));
357        }
358
359        /**
360         * @return the markerList
361         */
362        public ArrayList getMarkerList()
363        {
364            return markerList;
365        }
366    }
367}