001    /*
002     * To change this template, choose Tools | Templates
003     * and open the template in the editor.
004     */
005    
006    package com.kitfox.svg;
007    
008    import com.kitfox.svg.xml.StyleAttribute;
009    import java.awt.Graphics2D;
010    import java.awt.Rectangle;
011    import java.awt.Shape;
012    import java.awt.geom.AffineTransform;
013    import java.awt.geom.PathIterator;
014    import java.awt.geom.Rectangle2D;
015    import java.util.ArrayList;
016    
017    /**
018     *
019     * @author kitfox
020     */
021    public class Marker extends Group
022    {
023        AffineTransform viewXform;
024        AffineTransform markerXform;
025        Rectangle2D viewBox;
026    
027        float refX;
028        float refY;
029        float markerWidth = 3;
030        float markerHeight = 3;
031        float orient = Float.NaN;
032        boolean markerUnitsStrokeWidth = true; //if set to false 'userSpaceOnUse' is assumed
033    
034        protected void build() throws SVGException
035        {
036            super.build();
037    
038            StyleAttribute sty = new StyleAttribute();
039    
040            if (getPres(sty.setName("refX"))) refX = sty.getFloatValueWithUnits();
041            if (getPres(sty.setName("refY"))) refY = sty.getFloatValueWithUnits();
042            if (getPres(sty.setName("markerWidth"))) markerWidth = sty.getFloatValueWithUnits();
043            if (getPres(sty.setName("markerHeight"))) markerHeight = sty.getFloatValueWithUnits();
044    
045            if (getPres(sty.setName("orient")))
046            {
047                if ("auto".equals(sty.getStringValue()))
048                {
049                    orient = Float.NaN;
050                }
051                else
052                {
053                    orient = sty.getFloatValue();
054                }
055            }
056    
057            if (getPres(sty.setName("viewBox")))
058            {
059                float[] dim = sty.getFloatList();
060                viewBox = new Rectangle2D.Float(dim[0], dim[1], dim[2], dim[3]);
061            }
062    
063            if (viewBox == null)
064            {
065                viewBox = new Rectangle(0, 0, 1, 1);
066            }
067    
068            if (getPres(sty.setName("markerUnits")))
069            {
070                String markerUnits = sty.getStringValue();
071                if (markerUnits != null && markerUnits.equals("userSpaceOnUse"))
072                {
073                    markerUnitsStrokeWidth = false;
074                }
075            }
076    
077            //Transform pattern onto unit square
078            viewXform = new AffineTransform();
079            viewXform.scale(1.0 / viewBox.getWidth(), 1.0 / viewBox.getHeight());
080            viewXform.translate(-viewBox.getX(), -viewBox.getY());
081    
082            markerXform = new AffineTransform();
083            markerXform.scale(markerWidth, markerHeight);
084            markerXform.concatenate(viewXform);
085            markerXform.translate(-refX, -refY);
086        }
087    
088        protected boolean outsideClip(Graphics2D g) throws SVGException
089        {
090            Shape clip = g.getClip();
091            Rectangle2D rect = super.getBoundingBox();
092            if (clip == null || clip.intersects(rect))
093            {
094                return false;
095            }
096    
097            return true;
098    
099        }
100    
101        public void render(Graphics2D g) throws SVGException
102        {
103            AffineTransform oldXform = g.getTransform();
104            g.transform(markerXform);
105    
106            super.render(g);
107    
108            g.setTransform(oldXform);
109        }
110    
111        public void render(Graphics2D g, MarkerPos pos, float strokeWidth) throws SVGException
112        {
113            AffineTransform cacheXform = g.getTransform();
114    
115            g.translate(pos.x, pos.y);
116            if (markerUnitsStrokeWidth)
117            {
118                g.scale(strokeWidth, strokeWidth);
119            }
120    
121            g.rotate(Math.atan2(pos.dy, pos.dx));
122    
123            g.transform(markerXform);
124    
125            super.render(g);
126    
127            g.setTransform(cacheXform);
128        }
129    
130        public Shape getShape()
131        {
132            Shape shape = super.getShape();
133            return markerXform.createTransformedShape(shape);
134        }
135    
136        public Rectangle2D getBoundingBox() throws SVGException
137        {
138            Rectangle2D rect = super.getBoundingBox();
139            return markerXform.createTransformedShape(rect).getBounds2D();
140        }
141    
142        /**
143         * Updates all attributes in this diagram associated with a time event.
144         * Ie, all attributes with track information.
145         * @return - true if this node has changed state as a result of the time
146         * update
147         */
148        public boolean updateTime(double curTime) throws SVGException
149        {
150            boolean changeState = super.updateTime(curTime);
151    
152            //Marker properties do not change
153            return changeState;
154        }
155    
156        //--------------------------------
157        public static final int MARKER_START = 0;
158        public static final int MARKER_MID = 1;
159        public static final int MARKER_END = 2;
160    
161        public static class MarkerPos
162        {
163            int type;
164            double x;
165            double y;
166            double dx;
167            double dy;
168    
169            public MarkerPos(int type, double x, double y, double dx, double dy)
170            {
171                this.type = type;
172                this.x = x;
173                this.y = y;
174                this.dx = dx;
175                this.dy = dy;
176            }
177        }
178    
179        public static class MarkerLayout
180        {
181            private ArrayList markerList = new ArrayList();
182            boolean started = false;
183    
184            public void layout(Shape shape)
185            {
186                double px = 0;
187                double py = 0;
188                double[] coords = new double[6];
189                for (PathIterator it = shape.getPathIterator(null);
190                        !it.isDone(); it.next())
191                {
192                    switch (it.currentSegment(coords))
193                    {
194                        case PathIterator.SEG_MOVETO:
195                            px = coords[0];
196                            py = coords[1];
197                            started = false;
198                            break;
199                        case PathIterator.SEG_CLOSE:
200                            started = false;
201                            break;
202                        case PathIterator.SEG_LINETO:
203                        {
204                            double x = coords[0];
205                            double y = coords[1];
206                            markerIn(px, py, x - px, y - py);
207                            markerOut(x, y, x - px, y - py);
208                            px = x;
209                            py = y;
210                            break;
211                        }
212                        case PathIterator.SEG_QUADTO:
213                        {
214                            double k0x = coords[0];
215                            double k0y = coords[1];
216                            double x = coords[2];
217                            double y = coords[3];
218                            markerIn(px, py, k0x - px, k0y - py);
219                            markerOut(x, y, x - k0x, y - k0y);
220                            px = x;
221                            py = y;
222                            break;
223                        }
224                        case PathIterator.SEG_CUBICTO:
225                        {
226                            double k0x = coords[0];
227                            double k0y = coords[1];
228                            double k1x = coords[2];
229                            double k1y = coords[3];
230                            double x = coords[4];
231                            double y = coords[5];
232                            markerIn(px, py, k0x - px, k0y - py);
233                            markerOut(x, y, x - k1x, y - k1y);
234                            px = x;
235                            py = y;
236                            break;
237                        }
238                    }
239                }
240    
241                for (int i = 1; i < markerList.size(); ++i)
242                {
243                    MarkerPos prev = (MarkerPos)markerList.get(i - 1);
244                    MarkerPos cur = (MarkerPos)markerList.get(i);
245    
246                    if (cur.type == MARKER_START)
247                    {
248                        prev.type = MARKER_END;
249                    }
250                }
251                MarkerPos last = (MarkerPos)markerList.get(markerList.size() - 1);
252                last.type = MARKER_END;
253            }
254    
255            private void markerIn(double x, double y, double dx, double dy)
256            {
257                if (started == false)
258                {
259                    started = true;
260                    markerList.add(new MarkerPos(MARKER_START, x, y, dx, dy));
261                }
262            }
263    
264            private void markerOut(double x, double y, double dx, double dy)
265            {
266                markerList.add(new MarkerPos(MARKER_MID, x, y, dx, dy));
267            }
268    
269            /**
270             * @return the markerList
271             */
272            public ArrayList getMarkerList()
273            {
274                return markerList;
275            }
276        }
277    }