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 }