001package armyc2.c5isr.renderer.utilities;
002
003import android.graphics.Point;
004import android.graphics.PointF;
005import android.graphics.Rect;
006import android.graphics.RectF;
007
008import java.util.ArrayList;
009
010public class SVGPath {
011
012    public static float ACTION_MOVE_TO = 0;
013    public static float ACTION_LINE_TO = 1;
014    public static float ACTION_CURVE_TO = 2;//cubic bezier curve
015    public static float ACTION_QUAD_TO = 3;//quadratic bezier curve
016    public static float ACTION_ARC_TO = 4;
017    public static float ACTION_ARC = 5;
018    public static float ACTION_DASHED_LINE_TO = 6;
019
020    private ArrayList<float[]> _actions = new ArrayList<>();
021    private String _dashArray = null;
022    private PointF _startPoint=null;
023    private PointF _endPoint=null;
024    private PointF _lastMoveTo = null;
025    private RectF _rectangle = null;
026    private String _method = null;//stroke,fill,fillPattern
027
028    public void setLineDash(String dashArray)
029    {
030        this._dashArray = dashArray;
031    }
032
033    public Rect getBounds()
034    {
035        if(this._rectangle != null)
036        {
037            return RectUtilities.makeRectFromRectF(this._rectangle);
038        }
039        else
040        {
041            return null;
042        }
043    }
044
045    public void shift(int x, int y)
046    {
047        int size = this._actions.size();
048        float[] temp = null;
049        RectUtilities.shift(this._rectangle,x,y);
050
051        for(int i=0; i<size;i++)
052        {
053            temp = this._actions.get(i);
054            if(temp[0]==ACTION_MOVE_TO)
055            {
056                temp[1] = temp[1] + x;
057                temp[2] = temp[2] + y;
058            }
059            else if(temp[0]==ACTION_LINE_TO)
060            {
061                temp[1] = temp[1] + x;
062                temp[2] = temp[2] + y;
063            }
064            else if(temp[0]==ACTION_CURVE_TO)
065            {
066                temp[1] = temp[1] + x;
067                temp[2] = temp[2] + y;
068                temp[3] = temp[3] + x;
069                temp[4] = temp[4] + y;
070                temp[5] = temp[5] + x;
071                temp[6] = temp[6] + y;
072            }
073            else if(temp[0]==ACTION_QUAD_TO)
074            {
075                temp[1] = temp[1] + x;
076                temp[2] = temp[2] + y;
077                temp[3] = temp[3] + x;
078                temp[4] = temp[4] + y;
079            }
080            else if(temp[0]==ACTION_ARC_TO)
081            {
082                temp[1] = temp[1] + x;
083                temp[2] = temp[2] + y;
084                temp[3] = temp[3] + x;
085                temp[4] = temp[4] + y;
086            }
087            else if(temp[0]==ACTION_ARC)
088            {
089                temp[1] = temp[1] + x;
090                temp[2] = temp[2] + y;
091            }
092        }
093
094        this._startPoint.offset(x,y);
095        this._endPoint.offset(x,y);
096        this._lastMoveTo.offset(x,y);
097    }
098
099    /**
100     * The number of this._actions on the path
101     */
102    public int getLength()
103    {
104        return this._actions.size();
105    };
106
107    /**
108     * Adds a point to the path by moving to the specified coordinates specified
109     * @param x
110     * @param y
111     */
112    public void moveTo(float x, float y)
113    {
114
115        if(this._actions.size() == 0)
116        {
117            this._rectangle = new RectF(x,y,1,1);
118            this._startPoint = new PointF(x,y);
119            this._endPoint = new PointF(x,y);
120            //curr_startPoint = new armyc2.c2sd.renderer.Point(x,y);
121            //curr_endPoint = new armyc2.c2sd.renderer.Point(x,y);
122        }
123        this._rectangle.union(x,y);
124        float[] actions = {ACTION_MOVE_TO,x,y};
125        this._actions.add(actions);
126        this._lastMoveTo = new PointF(x,y);
127        this._endPoint = new PointF(x,y);
128    }
129
130    /**
131     * Adds a point to the path by drawing a straight line from the current
132     * coordinates to the new specified coordinates specified
133     * @param x
134     * @param y
135     */
136    public void lineTo(float x, float y)
137    {
138
139        if(this._actions.size() == 0)
140        {
141            this.moveTo(0,0);
142        }
143        float[] actions = {ACTION_LINE_TO,x,y};
144        this._actions.add(actions);
145        this._rectangle.union(x,y);
146        this._endPoint = new PointF(x,y);
147    }
148
149    /**
150     * Adds a curved segment, defined by three new points, to the path by
151     * drawing a Bézier curve that intersects both the current coordinates
152     * and the specified coordinates (x,y), using the specified points
153     * (cp1x,xp1y) and (cp2x,cp2y) as Bézier control points.
154     * @param cp1x
155     * @param cp1y
156     * @param cp2x
157     * @param cp2y
158     * @param x
159     * @param y
160     */
161    public void bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y){
162
163        if(this._actions.size() == 0)
164        {
165            this.moveTo(0,0);
166        }
167        float[] actions = {ACTION_CURVE_TO,cp1x,cp1y,cp2x,cp2y,x,y};
168        this._actions.add(actions);
169        this._rectangle.union(cp1x,cp1y);
170        this._rectangle.union(cp2x,cp2y);
171        this._rectangle.union(x,y);
172        this._endPoint = new PointF(x,y);
173    }
174
175    /**
176     * Adds a curved segment, defined by two new points, to the path by
177     * drawing a Quadratic curve that intersects both the current
178     * coordinates and the specified coordinates (x,y), using the
179     * specified point (cpx,cpy) as a quadratic parametric control point.
180     * @param cpx
181     * @param cpy
182     * @param x
183     * @param y
184     * @returns
185     */
186    public void quadraticCurveTo(float cpx, float cpy, float x, float y)
187    {
188        if(this._actions.size() == 0)
189        {
190            this.moveTo(0,0);
191        }
192        float[] actions = {ACTION_QUAD_TO,cpx,cpy,x,y};
193        this._actions.add(actions);
194        this._rectangle.union(cpx,cpy);
195        this._rectangle.union(x,y);
196        this._endPoint = new PointF(x,y);
197    }
198
199    /**
200     * The arcTo() method creates an arc/curve between two tangents on the canvas.
201     * @param x1 The x-coordinate of the beginning of the arc
202     * @param y1 The y-coordinate of the beginning of the arc
203     * @param x2 The x-coordinate of the end of the arc
204     * @param y2 The y-coordinate of the end of the arc
205     * @param r The radius of the arc
206     * @returns
207     */
208    public void arcTo(float x1, float y1, float x2, float y2, float r)
209    {
210        if(this._actions.size() == 0)
211        {
212            this.moveTo(0,0);
213        }
214        float[] actions = {ACTION_ARC_TO,x1,y1,x2,y2};
215        this._actions.add(actions);
216        this._rectangle.union(x1,y1);
217        this._rectangle.union(x2,y2);
218        this._endPoint = new PointF(x2,y2);
219    }
220
221    /**
222     * The arc() method creates an arc/curve
223     * (use to create circles. or parts of circles).
224     * @param x The x-coordinate of the center of the circle
225     * @param y The y-coordinate of the center of the circle
226     * @param r The radius of the circle
227     * @param sAngle The starting angle, in degrees
228     * (0 is at the 3 -'clock position of the arc's circle)
229     * @param eAngle The ending angle, in degrees
230     * @param counterclockwise Optional. Specifies wheter the drawing
231     * should be counterclockwise or clockwise.  False=clockwise,
232     * true=counter-clockwise;
233     * @returns
234     */
235    public void arc(float x, float y, float r, float sAngle, float eAngle, boolean counterclockwise)
236    {
237/*
238        if(counterclockwise != true)
239        {
240            counterclockwise = false;
241        }
242
243        //degrees to radians
244        float sa = (float)(sAngle * (Math.PI / 180));
245        float ea = (float)(eAngle * (Math.PI / 180));
246
247
248        if(this._startPoint===null)
249        {
250            double sX = r * Math.cos(sa) + x;
251            double sY = r * Math.sin(sa) + y;
252            this._startPoint = new PointF((float)sX,(float)sY);
253            this._rectangle = new RectF((float)sX,(float)sY,1,1);
254        }
255
256        float[] actions = {ACTION_ARC,x,y,r,sa,ea,counterclockwise};
257        this._actions.add(actions);
258        this._rectangle.union(new Rectangle(x-r,y-r,r*2,r*2));
259
260        var newX = r * Math.cos(ea) + x;
261        var newY = r * Math.sin(ea) + y;
262        this._endPoint = new Point(newX,newY);
263        this.moveTo(newX,newY);//*/
264
265    }
266
267    /**
268     * Arc and ArcTo do not covert currently
269     */
270    public String toSVGElement(String stroke, float strokeWidth, String fill, float strokeOpacity, float fillOpacity, String lineCap)
271    {
272        int format = 1;
273
274
275        //context.beginPath();
276        int size = this._actions.size();
277        float[] temp = null;
278        String path = "";
279        String line = null;
280
281        try {
282            for (int i = 0; i < size; i++)
283            {
284                temp = this._actions.get(i);
285
286            /*if(path !== "")
287                path += " ";*/
288
289                if (temp[0] == ACTION_LINE_TO) {
290                    path += "L" + temp[1] + " " + temp[2];
291                    //context.lineTo(temp[1],temp[2]);
292                } else if (temp[0] == ACTION_MOVE_TO) {
293                    //context.moveTo(temp[1],temp[2]);
294
295                    if (i == 0 || this._method != "fillPattern") {
296                        path += "M" + temp[1] + " " + temp[2];
297                        //context.moveTo(temp[1],temp[2]);
298                    } else//no moves in a fill shape except maybe for the first one
299                    {
300                        path += "L" + temp[1] + " " + temp[2];
301                        //context.lineTo(temp[1],temp[2]);
302                    }//*/
303                } else if (temp[0] == ACTION_DASHED_LINE_TO) {
304                    path += "L" + temp[3] + " " + temp[4];
305                /*if(this._method === "stroke")
306                {
307                    context.dashedLineTo(temp[1],temp[2],temp[3],temp[4],temp[5]);
308                }
309                else //you don't dash a fill shape
310                {
311                    context.lineTo(temp[3],temp[4]);
312                }//*/
313                } else if (temp[0] == ACTION_CURVE_TO) {
314                    //C100 100 250 100 250 200
315                    path += "C" + temp[1] + " " + temp[2] + " " + temp[3] + " " + temp[4] + " " + temp[5] + " " + temp[6];
316                    //context.bezierCurveTo(temp[1],temp[2],temp[3],temp[4],temp[5],temp[6]);
317                } else if (temp[0] == ACTION_QUAD_TO) {
318                    path += "Q" + temp[1] + " " + temp[2] + " " + temp[3] + " " + temp[4];
319                    //context.quadraticCurveTo(temp[1],temp[2],temp[3],temp[4]);
320                } else if (temp[0] == ACTION_ARC_TO) {
321                    //path += "C" + temp[1] + " " + temp[2] + " " + temp[3] + " " + temp[4] + " " + temp[5];
322                    //context.arcTo(temp[1],temp[2],temp[3],temp[4],temp[5]);
323                } else if (temp[0] == ACTION_ARC) {
324                    //context.arc(temp[1],temp[2],temp[3],temp[4],temp[5],temp[6]);
325                }//*/
326            }
327            //TODO: generate path svg element
328            line = "<path d=\"" + path + "\"";
329
330            if (stroke != null) {
331
332                line += " stroke=\"" + stroke + "\"";
333            /*else
334                line += ' stroke="' + stroke.replace(/#/g,"&#35;") + '"';*/
335
336                if (strokeWidth > 0)
337                    line += " stroke-width=\"" + strokeWidth + '"';
338                else
339                    line += " stroke-width=\"2\"";
340
341                if (strokeOpacity != 1.0) {
342                    //stroke-opacity="0.4"
343                    line += " stroke-opacity=\"" + strokeOpacity + "\"";
344                }
345
346                if(lineCap != null &&
347                        (lineCap.equalsIgnoreCase("butt") ||
348                                lineCap.equalsIgnoreCase("round") ||
349                                lineCap.equalsIgnoreCase("square")))
350                {
351                    line += " stroke-linecap=\"" + lineCap + "\"";
352                }
353                else
354                    line += " stroke-linecap=\"round\"";
355            }
356
357            if (this._dashArray != null)
358                line += " stroke-dasharray=\"" + this._dashArray + "\"";
359
360            if (fill != null) {
361                if (fill.indexOf("url") == 0) {
362                    line += " fill=\"url(#fillPattern)\"";
363                    //line += ' fill="url(&#35;fillPattern)"';
364                } else {
365                    //line += ' fill="' + fill + '"';
366                    line += " fill=\"" + fill + '"';//text = text.replace(/\</g,"&gt;");
367                /*else
368                    line += ' fill="' + fill.replace(/#/g,"&#35;") + '"';//text = text.replace(/\</g,"&gt;");*/
369
370                    if (fillOpacity != 1.0) {
371                        //fill-opacity="0.4"
372                        line += " fill-opacity=\"" + fillOpacity + "\"";
373                    }
374                }
375
376            } else
377                line += " fill=\"none\"";
378
379            line += " />";
380        }
381        catch(Exception exc)
382        {
383            ErrorLogger.LogException("SVGPath", "toSVGElement", exc);
384            line = null;
385        }
386        return line;
387
388    }
389
390}