| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8daa225 commit 49ffb16
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -518,7 +518,7 @@ protected void parsePath() { | |||
| 518 | 518 | c == 'S' || c == 's' || | |
| 519 | 519 | c == 'Q' || c == 'q' || // quadratic beziers | |
| 520 | 520 | c == 'T' || c == 't' || | |
| 521 | - // c == 'A' || c == 'a' || // elliptical arc | ||
| 521 | + c == 'A' || c == 'a' || // elliptical arc | ||
| 522 | 522 | c == 'Z' || c == 'z' || // closepath | |
| 523 | 523 | c == ',') { | |
| 524 | 524 | separate = true; | |
@@ -816,6 +816,40 @@ protected void parsePath() { | |||
| 816 | 816 | } | |
| 817 | 817 | break; | |
| 818 | 818 | ||
| 819 | + // A - elliptical arc to (absolute) | ||
| 820 | + case 'A': { | ||
| 821 | + float rx = PApplet.parseFloat(pathTokens[i + 1]); | ||
| 822 | + float ry = PApplet.parseFloat(pathTokens[i + 2]); | ||
| 823 | + float angle = PApplet.parseFloat(pathTokens[i + 3]); | ||
| 824 | + boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0; | ||
| 825 | + boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0; | ||
| 826 | + float endX = PApplet.parseFloat(pathTokens[i + 6]); | ||
| 827 | + float endY = PApplet.parseFloat(pathTokens[i + 7]); | ||
| 828 | + parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY); | ||
| 829 | + cx = endX; | ||
| 830 | + cy = endY; | ||
| 831 | + i += 8; | ||
| 832 | + prevCurve = true; | ||
| 833 | + } | ||
| 834 | + break; | ||
| 835 | + | ||
| 836 | + // a - elliptical arc to (relative) | ||
| 837 | + case 'a': { | ||
| 838 | + float rx = PApplet.parseFloat(pathTokens[i + 1]); | ||
| 839 | + float ry = PApplet.parseFloat(pathTokens[i + 2]); | ||
| 840 | + float angle = PApplet.parseFloat(pathTokens[i + 3]); | ||
| 841 | + boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0; | ||
| 842 | + boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0; | ||
| 843 | + float endX = cx + PApplet.parseFloat(pathTokens[i + 6]); | ||
| 844 | + float endY = cy + PApplet.parseFloat(pathTokens[i + 7]); | ||
| 845 | + parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY); | ||
| 846 | + cx = endX; | ||
| 847 | + cy = endY; | ||
| 848 | + i += 8; | ||
| 849 | + prevCurve = true; | ||
| 850 | + } | ||
| 851 | + break; | ||
| 852 | + | ||
| 819 | 853 | case 'Z': | |
| 820 | 854 | case 'z': | |
| 821 | 855 | // since closing the path, the 'current' point needs | |
@@ -924,6 +958,93 @@ private void parsePathQuadto(float cx, float cy, | |||
| 924 | 958 | } | |
| 925 | 959 | ||
| 926 | 960 | ||
| 961 | + // Approximates elliptical arc by several bezier segments. | ||
| 962 | + // Meets SVG standard requirements from: | ||
| 963 | + // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands | ||
| 964 | + // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes | ||
| 965 | + // Based on arc to bezier curve equations from: | ||
| 966 | + // http://www.spaceroots.org/documents/ellipse/node22.html | ||
| 967 | + private void parsePathArcto(float x1, float y1, | ||
| 968 | + float rx, float ry, | ||
| 969 | + float angle, | ||
| 970 | + boolean fa, boolean fs, | ||
| 971 | + float x2, float y2) { | ||
| 972 | + if (x1 == x2 && y1 == y2) return; | ||
| 973 | + if (rx == 0 || ry == 0) { parsePathLineto(x2, y2); return; } | ||
| 974 | + | ||
| 975 | + rx = PApplet.abs(rx); ry = PApplet.abs(ry); | ||
| 976 | + | ||
| 977 | + float phi = PApplet.radians(((angle % 360) + 360) % 360); | ||
| 978 | + float cosPhi = PApplet.cos(phi), sinPhi = PApplet.sin(phi); | ||
| 979 | + | ||
| 980 | + float x1r = ( cosPhi * (x1 - x2) + sinPhi * (y1 - y2)) / 2; | ||
| 981 | + float y1r = (-sinPhi * (x1 - x2) + cosPhi * (y1 - y2)) / 2; | ||
| 982 | + | ||
| 983 | + float cxr, cyr; | ||
| 984 | + { | ||
| 985 | + float A = (x1r*x1r) / (rx*rx) + (y1r*y1r) / (ry*ry); | ||
| 986 | + if (A > 1) { | ||
| 987 | + // No solution, scale ellipse up according to SVG standard | ||
| 988 | + float sqrtA = PApplet.sqrt(A); | ||
| 989 | + rx *= sqrtA; cxr = 0; | ||
| 990 | + ry *= sqrtA; cyr = 0; | ||
| 991 | + } else { | ||
| 992 | + float k = ((fa == fs) ? -1f : 1f) * | ||
| 993 | + PApplet.sqrt((rx*rx * ry*ry) / ((rx*rx * y1r*y1r) + (ry*ry * x1r*x1r)) - 1f); | ||
| 994 | + cxr = k * rx * y1r / ry; | ||
| 995 | + cyr = -k * ry * x1r / rx; | ||
| 996 | + } | ||
| 997 | + } | ||
| 998 | + | ||
| 999 | + float cx = cosPhi * cxr - sinPhi * cyr + (x1 + x2) / 2; | ||
| 1000 | + float cy = sinPhi * cxr + cosPhi * cyr + (y1 + y2) / 2; | ||
| 1001 | + | ||
| 1002 | + float phi1, phiDelta; | ||
| 1003 | + { | ||
| 1004 | + float sx = ( x1r - cxr) / rx, sy = ( y1r - cyr) / ry; | ||
| 1005 | + float tx = (-x1r - cxr) / rx, ty = (-y1r - cyr) / ry; | ||
| 1006 | + phi1 = PApplet.atan2(sy, sx); | ||
| 1007 | + phiDelta = (((PApplet.atan2(ty, tx) - phi1) % TWO_PI) + TWO_PI) % TWO_PI; | ||
| 1008 | + if (!fs) phiDelta -= TWO_PI; | ||
| 1009 | + } | ||
| 1010 | + | ||
| 1011 | + // One segment can not cover more that PI, less than PI/2 is | ||
| 1012 | + // recommended to avoid visible inaccuracies caused by rounding errors | ||
| 1013 | + int segmentCount = PApplet.ceil(PApplet.abs(phiDelta) / TWO_PI * 4); | ||
| 1014 | + | ||
| 1015 | + float inc = phiDelta / segmentCount; | ||
| 1016 | + float a = PApplet.sin(inc) * | ||
| 1017 | + (PApplet.sqrt(4 + 3 * PApplet.sq(PApplet.tan(inc / 2))) - 1) / 3; | ||
| 1018 | + | ||
| 1019 | + float sinPhi1 = PApplet.sin(phi1), cosPhi1 = PApplet.cos(phi1); | ||
| 1020 | + | ||
| 1021 | + float p1x = x1; | ||
| 1022 | + float p1y = y1; | ||
| 1023 | + float relq1x = a * (-rx * cosPhi * sinPhi1 - ry * sinPhi * cosPhi1); | ||
| 1024 | + float relq1y = a * (-rx * sinPhi * sinPhi1 + ry * cosPhi * cosPhi1); | ||
| 1025 | + | ||
| 1026 | + for (int i = 0; i < segmentCount; i++) { | ||
| 1027 | + float eta = phi1 + (i + 1) * inc; | ||
| 1028 | + float sinEta = PApplet.sin(eta), cosEta = PApplet.cos(eta); | ||
| 1029 | + | ||
| 1030 | + float p2x = cx + rx * cosPhi * cosEta - ry * sinPhi * sinEta; | ||
| 1031 | + float p2y = cy + rx * sinPhi * cosEta + ry * cosPhi * sinEta; | ||
| 1032 | + float relq2x = a * (-rx * cosPhi * sinEta - ry * sinPhi * cosEta); | ||
| 1033 | + float relq2y = a * (-rx * sinPhi * sinEta + ry * cosPhi * cosEta); | ||
| 1034 | + | ||
| 1035 | + if (i == segmentCount - 1) { p2x = x2; p2y = y2; } | ||
| 1036 | + | ||
| 1037 | + parsePathCode(BEZIER_VERTEX); | ||
| 1038 | + parsePathVertex(p1x + relq1x, p1y + relq1y); | ||
| 1039 | + parsePathVertex(p2x - relq2x, p2y - relq2y); | ||
| 1040 | + parsePathVertex(p2x, p2y); | ||
| 1041 | + | ||
| 1042 | + p1x = p2x; relq1x = relq2x; | ||
| 1043 | + p1y = p2y; relq1y = relq2y; | ||
| 1044 | + } | ||
| 1045 | + } | ||
| 1046 | + | ||
| 1047 | + | ||
| 927 | 1048 | /** | |
| 928 | 1049 | * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D | |
| 929 | 1050 | * is rotated relative to the SVG definition, so parameters are rearranged | |
| Back | FazBrowse Home | New Git URL |
0 commit comments