001package armyc2.c2sd.renderer.utilities;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007
008import android.graphics.*;
009import android.graphics.Bitmap.Config;
010import android.graphics.drawable.shapes.ArcShape;
011import android.util.FloatMath;
012import android.util.Log;
013
014public class SVGPath 
015{
016    private String _ID = null;
017    private String _strPath = null;
018    private static String _regex1 = "(?=[M,m,Z,z,L,l,H,h,V,v,C,c,S,s,Q,q,T,t,A,a])";
019    private static String _regex2 = "(?=[M,m,L,l,H,h,V,v,C,c,S,s,Q,q,T,t,A,a])";
020    private static char[] svgCommands = { 'M', 'm', 'Z', 'z', 'L', 'l', 'H', 'h', 'V', 'v', 'C', 'c', 'S', 's', 'Q', 'q', 'T', 't', 'A', 'a' };
021
022
023    private Path _path = null;
024
025    public String getID()
026    {
027        return _ID;
028    }
029
030    /**
031     * Returns bounds of the core symbol
032     * */
033    public RectF getBounds()
034    {
035        RectF bounds = new RectF();
036        _path.computeBounds(bounds,true);
037        return bounds;
038        
039    }
040
041    /*
042     * Returns bounds of the symbol when it's being outlined.
043     * */
044    public RectF getBounds(float outlineWidth)
045    {
046        RectF bounds = new RectF();
047        _path.computeBounds(bounds,true);
048        bounds = new RectF(bounds.left - outlineWidth, bounds.top - outlineWidth, bounds.right + outlineWidth, bounds.bottom + outlineWidth);
049        return bounds;
050    }
051
052    @SuppressWarnings("unused")
053        private SVGPath()
054    {
055    }
056
057    public SVGPath(SVGPath path)
058    {
059        _path = new Path(path._path);
060        _ID = path._ID;
061        _strPath = path._strPath.substring(0);
062    }
063
064    public SVGPath(String unicodeHex, String path)
065    {
066        _ID = String.valueOf(Integer.parseInt(unicodeHex,  16));
067        _strPath = path;
068
069        _path = new Path();
070        
071        parsePath();
072
073    }
074
075    /*public override object Clone()
076    {
077        SVGPath clone = new SVGPath();
078        clone._path = (GraphicsPath)_path.Clone();
079        clone._ID = _ID;
080        clone._strPath = (String)_strPath.Clone();
081        return clone;
082    }*/
083
084    private void parsePath()
085    {
086        String delimiter = " ";
087                
088        String[] commands = _strPath.split(_regex1);
089        String[] values = null;
090        float[] points = new float[7];
091        PointF[] pointFs = new PointF[4];
092        PointF lastPoint = new PointF(0, 0);
093        PointF lastControlPoint = new PointF(0, 0);
094        //PointF firstPoint = new PointF(0, 0);
095        //PointF firstPoint = new PointF(0, 0);
096
097        try
098        {
099
100            for (int i = 0; i < commands.length; i++)
101            {
102
103                String strCommand = commands[i];
104                char action = (strCommand != null && strCommand.length() > 0) ? strCommand.charAt(0) : ' ';
105                values = strCommand.split(delimiter);
106
107                if (action == 'M')
108                {
109                    points[0] = Float.valueOf(values[0].substring(1));
110                    points[1] = -Float.valueOf(values[1]);
111                    _path.moveTo(points[0], points[1]);
112                    lastPoint.set(points[0], points[1]);
113                    
114                    //_path.StartFigure();
115                    
116                }
117                else if (action == 'm')
118                {
119                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
120                    points[1] = -Float.valueOf(values[1]) + lastPoint.y;
121                    _path.moveTo(points[0], points[1]);
122                    lastPoint.set(points[0], points[1]);
123                    //_path.StartFigure();
124                }
125                else if (action == 'L')
126                {
127                    points[0] = Float.valueOf(values[0].substring(1));
128                    points[1] = -Float.valueOf(values[1]);
129                    _path.lineTo(points[0], points[1]);
130                    lastPoint.set(points[0], points[1]);
131
132                }
133                else if (action == 'l')
134                {
135                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
136                    points[1] = -Float.valueOf(values[1]) + lastPoint.y;
137                    _path.lineTo(points[0], points[1]);
138                    lastPoint.set(points[0], points[1]);
139                }
140                else if (action == 'H')
141                {
142                    points[0] = Float.valueOf(values[0].substring(1));
143                    _path.lineTo(points[0], lastPoint.y);
144                    lastPoint.set(points[0], lastPoint.y);
145
146                }
147                else if (action == 'h')
148                {
149                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
150                    _path.lineTo(points[0], lastPoint.y);
151                    lastPoint.set(points[0], lastPoint.y);
152                }
153                else if (action == 'V')
154                {
155                    points[0] = -Float.valueOf(values[0].substring(1));
156                    _path.lineTo(lastPoint.x, points[0]);
157                    lastPoint.set(lastPoint.x, points[0]);
158
159                }
160                else if (action == 'v')
161                {
162                    points[0] = -Float.valueOf(values[0].substring(1)) + lastPoint.y;
163                    _path.lineTo(lastPoint.x, points[0]);
164                    lastPoint.set(lastPoint.x, points[0]);
165                }
166                else if (action == 'C')//cubic bezier, 2 control points
167                {
168                    points[0] = Float.valueOf(values[0].substring(1));
169                    points[1] = -Float.valueOf(values[1]);
170                    points[2] = Float.valueOf(values[2]);
171                    points[3] = -Float.valueOf(values[3]);
172                    points[4] = Float.valueOf(values[4]);
173                    points[5] = -Float.valueOf(values[5]);
174
175
176                    _path.cubicTo(points[0], points[1], points[2], points[3], points[4], points[5]);
177                    
178
179                    lastPoint.set(points[4], points[5]);
180                    lastControlPoint.set(points[2], points[3]);
181                }
182                else if (action == 'c')
183                {
184                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
185                    points[1] = -Float.valueOf(values[1]) + lastPoint.y;
186                    points[2] = Float.valueOf(values[2]) + lastPoint.x;
187                    points[3] = -Float.valueOf(values[3]) + lastPoint.y;
188                    points[4] = Float.valueOf(values[4]) + lastPoint.x;
189                    points[5] = -Float.valueOf(values[5]) + lastPoint.y;
190
191                    pointFs[0] = lastPoint;
192                    pointFs[1] = new PointF(points[0], points[1]);
193                    pointFs[2] = new PointF(points[2], points[3]);
194                    pointFs[3] = new PointF(points[4], points[5]);
195                    //_path.AddBezier(pointFs[0], pointFs[1], pointFs[2], pointFs[3]);
196
197                    _path.cubicTo(points[0], points[1], points[2], points[3], points[4], points[5]);
198
199                    //SvgCubicCurveSegment sccs = new SvgCubicCurveSegment(_path.getLastPoint(), pointFs[1], pointFs[2], pointFs[3]);
200                    //sccs.AddToPath(_path);
201                    //lastPoint = new PointF(points[4], points[5]);
202                    lastPoint.set(points[2], points[3]);
203                    lastControlPoint.set(points[2], points[3]);
204
205                }
206                else if (action == 'S')
207                {
208                    points[0] = Float.valueOf(values[0].substring(1));
209                    points[1] = -Float.valueOf(values[1]);
210                    points[2] = Float.valueOf(values[2]);
211                    points[3] = -Float.valueOf(values[3]);
212
213                    pointFs[0] = lastPoint;
214                    pointFs[1] = mirrorControlPoint(lastControlPoint, lastPoint);
215                    pointFs[2] = new PointF(points[0], points[1]);
216                    pointFs[3] = new PointF(points[2], points[3]);
217                    
218                    _path.cubicTo(pointFs[1].x,pointFs[1].y, points[0], points[1], points[2], points[3]);
219
220                    lastPoint.set(points[2], points[3]);
221                    lastControlPoint.set(points[0], points[1]);
222                }
223                else if (action == 's')
224                {
225                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
226                    points[1] = -Float.valueOf(values[1]) + lastPoint.y;
227                    points[2] = Float.valueOf(values[2]) + lastPoint.x;
228                    points[3] = -Float.valueOf(values[3]) + lastPoint.y;
229
230                    pointFs[0] = lastPoint;
231                    pointFs[1] = mirrorControlPoint(lastControlPoint, lastPoint);
232
233                    _path.cubicTo(pointFs[1].x,pointFs[1].y, points[0], points[1], points[2], points[3]);
234
235                    lastPoint.set(points[2], points[3]);
236                    lastControlPoint.set(points[0], points[1]);
237                }
238                else if (action == 'Q')//quadratic bezier, 1 control point
239                {
240                    points[0] = Float.valueOf(values[0].substring(1));
241                    points[1] = -Float.valueOf(values[1]);
242                    points[2] = Float.valueOf(values[2]);
243                    points[3] = -Float.valueOf(values[3]);
244
245                    _path.quadTo(points[0], points[1], points[2], points[3]);
246
247                    lastPoint.set(points[2], points[3]);
248
249                    lastControlPoint.set(points[0], points[1]);
250                }
251                else if (action == 'q')
252                {
253                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
254                    points[1] = -Float.valueOf(values[1]) + lastPoint.y;
255                    points[2] = Float.valueOf(values[2]) + lastPoint.x;
256                    points[3] = -Float.valueOf(values[3]) + lastPoint.y;
257
258                    _path.quadTo(points[0], points[1], points[2], points[3]);
259
260
261                    lastPoint.set(points[2], points[3]);
262
263                    lastControlPoint.set(points[0], points[1]);
264                }
265                else if (action == 'T')
266                {
267                    points[0] = Float.valueOf(values[0].substring(1));
268                    points[1] = -Float.valueOf(values[1]);
269
270
271                    PointF QP1 = mirrorControlPoint(lastControlPoint, lastPoint);
272
273                    _path.quadTo(QP1.x, QP1.y, points[0], points[1]);
274
275
276                    lastPoint.set(points[0], points[1]);
277
278                    lastControlPoint = QP1;
279                }
280                else if (action == 't')
281                {
282                    points[0] = Float.valueOf(values[0].substring(1)) + lastPoint.x;
283                    points[1] = -Float.valueOf(values[1]) + lastPoint.y;
284
285                    //convert quadratic to bezier
286                    PointF QP1 = mirrorControlPoint(lastControlPoint, lastPoint);
287
288                    _path.quadTo(QP1.x, QP1.y, points[0], points[1]);
289
290                    //SvgQuadraticCurveSegment qcs = new SvgQuadraticCurveSegment(QP0, QP1, QP2);
291                    //qcs.AddToPath(_path);
292                    lastPoint.set(points[0], points[1]);
293
294                    lastControlPoint = QP1;
295                }
296                else if (action == 'A')
297                {
298                    points[0] = Float.valueOf(values[0].substring(1));
299                    points[1] = Float.valueOf(values[1]);
300                    points[2] = Float.valueOf(values[2]);
301                    points[3] = Float.valueOf(values[3]);
302                    points[4] = Float.valueOf(values[4]);
303                    points[5] = Float.valueOf(values[5]);
304                    points[6] = -Float.valueOf(values[6]);
305
306                    drawArc(_path,lastPoint.x, lastPoint.y,points[0],points[1],points[2],points[3],points[4],(int)points[5],(int)points[6]);
307                    
308                    lastPoint.set(points[5], points[6]);
309                    lastControlPoint.set(points[2], points[3]);
310                    
311                }
312                else if (action == 'a')
313                {
314                    points[0] = Float.valueOf(values[0].substring(1));
315                    points[1] = Float.valueOf(values[1]);
316                    points[2] = Float.valueOf(values[2]);
317                    points[3] = Float.valueOf(values[3]);
318                    points[4] = Float.valueOf(values[4]);
319                    points[5] = Float.valueOf(values[5] + lastPoint.x);
320                    points[6] = -Float.valueOf(values[6] + lastPoint.y);
321
322                    drawArc(_path,lastPoint.x, lastPoint.y,points[0],points[1],points[2],points[3],points[4],(int)points[5],(int)points[6]);
323                    
324                    lastPoint.set(points[5], points[6]);
325                    lastControlPoint.set(points[2], points[3]);
326
327                }
328                else if (action == 'Z' || action == 'z')
329                {
330                    _path.close();
331                }
332
333                //Matrix verticalFlip = new Matrix();
334                //verticalFlip.Scale(1, -1);
335                //_path.Transform(verticalFlip);
336            
337
338            }
339        }
340        catch (Exception exc)
341        {
342            //ErrorLogger.LogException("SVGPath", "parsePath", exc);
343                Log.e("SVGPath.parsePath", exc.getMessage());
344        }
345
346    }
347    
348    private PointF mirrorControlPoint(PointF cp, PointF endPoint)
349    {
350
351        float xOffset = endPoint.x - cp.x;
352        float yOffset = endPoint.y - cp.y;
353
354        PointF mirror = new PointF(endPoint.x + xOffset, endPoint.y + yOffset);
355
356        return mirror;
357    }
358    
359    private static double angle(double x1, double y1, double x2, double y2) 
360    {
361        return Math.toDegrees(Math.atan2(x1, y1) - Math.atan2(x2, y2)) % 360;
362        }
363    
364    private static final RectF arcRectf = new RectF();
365    private static final Matrix arcMatrix = new Matrix();
366    private static final Matrix arcMatrix2 = new Matrix();
367    private static void drawArc(Path p, float lastX, float lastY, float x, float y, float rx, float ry, float theta,
368    int largeArc, int sweepArc) 
369    {
370            // Log.d("drawArc", "from (" + lastX + "," + lastY + ") to (" + x + ","+ y + ") r=(" + rx + "," + ry +
371            // ") theta=" + theta + " flags="+ largeArc + "," + sweepArc);
372            // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
373            if (rx == 0 || ry == 0) 
374            {
375                    p.lineTo(x, y);
376                    return;
377            }
378            if (x == lastX && y == lastY) 
379            {
380                return; // nothing to draw
381            }
382            rx = Math.abs(rx);
383            ry = Math.abs(ry);
384            final double thrad = theta * Math.PI / 180;
385            final double st = Math.sin(thrad);
386            final double ct = Math.cos(thrad);
387            final double xc = (lastX - x) / 2;
388            final double yc = (lastY - y) / 2;
389            final double x1t = ct * xc + st * yc;
390            final double y1t = -st * xc + ct * yc;
391            final double x1ts = x1t * x1t;
392            final double y1ts = y1t * y1t;
393            double rxs = rx * rx;
394            double rys = ry * ry;
395            double lambda = (x1ts / rxs + y1ts / rys) * 1.001f; // add 0.1% to be sure that no out of range occurs due to
396            // limited precision
397            if (lambda > 1) 
398            {
399                        double lambdasr = Math.sqrt(lambda);
400                rx *= lambdasr;
401                ry *= lambdasr;
402                rxs = rx * rx;
403                rys = ry * ry;
404            }
405            final double R =
406            Math.sqrt((rxs * rys - rxs * y1ts - rys * x1ts) / (rxs * y1ts + rys * x1ts))
407            * ((largeArc == sweepArc) ? -1 : 1);
408            final double cxt = R * rx * y1t / ry;
409            final double cyt = -R * ry * x1t / rx;
410            final double cx = ct * cxt - st * cyt + (lastX + x) / 2;
411            final double cy = st * cxt + ct * cyt + (lastY + y) / 2;
412            final double th1 = angle(1, 0, (x1t - cxt) / rx, (y1t - cyt) / ry);
413            double dth = angle((x1t - cxt) / rx, (y1t - cyt) / ry, (-x1t - cxt) / rx, (-y1t - cyt) / ry);
414            if (sweepArc == 0 && dth > 0) 
415            {
416                dth -= 360;
417            } 
418            else if (sweepArc != 0 && dth < 0) 
419            {
420                dth += 360;
421            }
422            // draw
423            if ((theta % 360) == 0) 
424            {
425                // no rotate and translate need
426                arcRectf.set((float)(cx - rx), (float)(cy - ry), (float)(cx + rx), (float)(cy + ry));
427                p.arcTo(arcRectf, (float)th1, (float)dth);
428            } 
429            else 
430            {
431                // this is the hard and slow part :-)
432                arcRectf.set(-rx, -ry, rx, ry);
433                arcMatrix.reset();
434                arcMatrix.postRotate(theta);
435                arcMatrix.postTranslate((float)cx, (float)cy);
436                arcMatrix.invert(arcMatrix2);
437                p.transform(arcMatrix2);
438                p.arcTo(arcRectf, (float)th1, (float)dth);
439                p.transform(arcMatrix);
440            }
441    }
442    
443    public Matrix TransformToFitDimensions(int width, int height)
444    {
445        RectF rect = new RectF(); 
446        _path.computeBounds(rect, true);
447        Matrix m = new Matrix();
448        Matrix mScale = new Matrix();
449        Matrix mTranslate = new Matrix();
450        
451        float sx = width / rect.width();
452        float sy = height / rect.height();
453        if (sx < sy)
454        {
455            mScale.setScale(sx, sx);
456            m.setScale(sx, sx);
457        }
458        else
459        {
460            mScale.setScale(sy, sy);
461            m.setScale(sy, sy);
462        }
463
464        _path.transform(mScale);
465        
466        _path.computeBounds(rect, true);//(testMatrix, testPen);
467
468        float transx = 0;
469        float transy = 0;
470        if (rect.left < 0)
471            transx = rect.left * -1.0f;
472        if (rect.top < 0)
473            transy = rect.top * -1.0f;
474        mTranslate.setTranslate(transx, transy);
475        m.postTranslate(transx, transy);
476        _path.transform(mTranslate);
477        
478        
479        return m;
480    }
481    
482    public void Transform(Matrix m)
483    {
484        _path.transform(m);
485    }
486    
487    public void Draw(Canvas c, Color lineColor, float lineWidth, Color fillColor, Matrix m)
488    {
489                                
490        if (m != null)
491            _path.transform(m);
492        if (lineColor != null)
493        {
494                Paint strokePaint = new Paint();
495                strokePaint.setStyle(Paint.Style.STROKE);
496                strokePaint.setColor(lineColor.toARGB());
497                strokePaint.setAntiAlias(true);
498            c.drawPath(_path, strokePaint);
499        }
500
501        if (fillColor != null)
502        {
503                Paint fillPaint = new Paint();
504                fillPaint.setStyle(Paint.Style.FILL);
505                fillPaint.setColor(fillColor.toARGB());
506                fillPaint.setAntiAlias(true);
507            c.drawPath(_path, fillPaint);
508        }
509    }
510
511    /**
512     * Draws SVG to fit into a image of the specified dimensions
513     * */
514    public Bitmap Draw(int width, int height, Color lineColor, Color fillColor)
515    {
516        Bitmap bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888);
517                Canvas c = new Canvas(bmp);
518                Bitmap foo = null;
519
520        RectF rect = new RectF(); 
521                _path.computeBounds(rect, true);
522        Matrix m = new Matrix();
523
524        float sx = width / rect.width();
525        float sy = height / rect.height();
526        if (sx < sy)
527            m.postScale(sx, sx);
528        else
529            m.postScale(sy, sy);
530
531        _path.transform(m);
532        _path.computeBounds(rect, true);
533        m = new Matrix();
534        float transx = 0;
535        float transy = 0;
536        if (rect.left < 0)
537            transx = rect.left * -1.0f;
538        if (rect.top < 0)
539            transy = rect.top * -1.0f;
540        m.postTranslate(transx, transy);
541        //m.Translate(300,300);
542
543        _path.transform(m);
544        _path.computeBounds(rect, true);
545
546        //Console.WriteLine(rect.ToString());
547        
548        if (lineColor != null)
549        {
550                Paint strokePaint = new Paint();
551                strokePaint.setStyle(Paint.Style.STROKE);
552                strokePaint.setColor(lineColor.toARGB());
553                strokePaint.setAntiAlias(true);
554            c.drawPath(_path, strokePaint);
555        }
556
557        if (fillColor != null)
558        {
559                Paint fillPaint = new Paint();
560                fillPaint.setStyle(Paint.Style.FILL);
561                fillPaint.setColor(fillColor.toARGB());
562                fillPaint.setAntiAlias(true);
563            c.drawPath(_path, fillPaint);
564        }
565        
566        return bmp;
567/*
568        c.DrawPath(_path, new Paint.);
569        Color c = Color.FromArgb(128, 0, 255, 255);
570        Pen p = new Pen(c);
571        Brush b = new SolidBrush(c);
572        g.FillPath(b, _gp);
573        g.DrawRectangle(new Pen(Color.Green), 0, 0, width - 1, height - 1);
574        foo = (Image)bmp;
575        return foo;//*/
576    }
577
578
579}
580
581//*/