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//*/