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 = 1;
059    float markerHeight = 1;
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        build();
199        
200        //Marker properties do not change
201        return changeState;
202    }
203    
204    //--------------------------------
205    public static final int MARKER_START = 0;
206    public static final int MARKER_MID = 1;
207    public static final int MARKER_END = 2;
208
209    public static class MarkerPos
210    {
211
212        int type;
213        double x;
214        double y;
215        double dx;
216        double dy;
217
218        public MarkerPos(int type, double x, double y, double dx, double dy)
219        {
220            this.type = type;
221            this.x = x;
222            this.y = y;
223            this.dx = dx;
224            this.dy = dy;
225        }
226    }
227
228    public static class MarkerLayout
229    {
230
231        private ArrayList markerList = new ArrayList();
232        boolean started = false;
233
234        public void layout(Shape shape)
235        {
236            double px = 0;
237            double py = 0;
238            double[] coords = new double[6];
239            for (PathIterator it = shape.getPathIterator(null);
240                !it.isDone(); it.next())
241            {
242                switch (it.currentSegment(coords))
243                {
244                    case PathIterator.SEG_MOVETO:
245                        px = coords[0];
246                        py = coords[1];
247                        started = false;
248                        break;
249                    case PathIterator.SEG_CLOSE:
250                        started = false;
251                        break;
252                    case PathIterator.SEG_LINETO:
253                    {
254                        double x = coords[0];
255                        double y = coords[1];
256                        markerIn(px, py, x - px, y - py);
257                        markerOut(x, y, x - px, y - py);
258                        px = x;
259                        py = y;
260                        break;
261                    }
262                    case PathIterator.SEG_QUADTO:
263                    {
264                        double k0x = coords[0];
265                        double k0y = coords[1];
266                        double x = coords[2];
267                        double y = coords[3];
268
269
270                        //Best in tangent
271                        if (px != k0x || py != k0y)
272                        {
273                            markerIn(px, py, k0x - px, k0y - py);
274                        } else
275                        {
276                            markerIn(px, py, x - px, y - py);
277                        }
278
279                        //Best out tangent
280                        if (x != k0x || y != k0y)
281                        {
282                            markerOut(x, y, x - k0x, y - k0y);
283                        } else
284                        {
285                            markerOut(x, y, x - px, y - py);
286                        }
287
288                        markerIn(px, py, k0x - px, k0y - py);
289                        markerOut(x, y, x - k0x, y - k0y);
290                        px = x;
291                        py = y;
292                        break;
293                    }
294                    case PathIterator.SEG_CUBICTO:
295                    {
296                        double k0x = coords[0];
297                        double k0y = coords[1];
298                        double k1x = coords[2];
299                        double k1y = coords[3];
300                        double x = coords[4];
301                        double y = coords[5];
302
303                        //Best in tangent
304                        if (px != k0x || py != k0y)
305                        {
306                            markerIn(px, py, k0x - px, k0y - py);
307                        } else if (px != k1x || py != k1y)
308                        {
309                            markerIn(px, py, k1x - px, k1y - py);
310                        } else
311                        {
312                            markerIn(px, py, x - px, y - py);
313                        }
314
315                        //Best out tangent
316                        if (x != k1x || y != k1y)
317                        {
318                            markerOut(x, y, x - k1x, y - k1y);
319                        } else if (x != k0x || y != k0y)
320                        {
321                            markerOut(x, y, x - k0x, y - k0y);
322                        } else
323                        {
324                            markerOut(x, y, x - px, y - py);
325                        }
326                        px = x;
327                        py = y;
328                        break;
329                    }
330                }
331            }
332
333            for (int i = 1; i < markerList.size(); ++i)
334            {
335                MarkerPos prev = (MarkerPos) markerList.get(i - 1);
336                MarkerPos cur = (MarkerPos) markerList.get(i);
337
338                if (cur.type == MARKER_START)
339                {
340                    prev.type = MARKER_END;
341                }
342            }
343            MarkerPos last = (MarkerPos) markerList.get(markerList.size() - 1);
344            last.type = MARKER_END;
345        }
346
347        private void markerIn(double x, double y, double dx, double dy)
348        {
349            if (started == false)
350            {
351                started = true;
352                markerList.add(new MarkerPos(MARKER_START, x, y, dx, dy));
353            }
354        }
355
356        private void markerOut(double x, double y, double dx, double dy)
357        {
358            markerList.add(new MarkerPos(MARKER_MID, x, y, dx, dy));
359        }
360
361        /**
362         * @return the markerList
363         */
364        public ArrayList getMarkerList()
365        {
366            return markerList;
367        }
368    }
369}