Update rive-cpp to 2.0 version
[platform/core/uifw/rive-tizen.git] / submodule / skia / tools / pathops_sorter.htm
1 <!DOCTYPE html>
2
3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
4 <head>
5     <meta charset="utf-8" />
6     <title></title>
7 <div style="height:0">
8
9     <div id="cubics">\r
10 {{{fX=124.70011901855469 fY=9.3718261718750000 } {fX=124.66775026544929 fY=9.3744316215161234 } {fX=124.63530969619751 fY=9.3770743012428284 }{fX=124.60282897949219 fY=9.3797206878662109 } id=10      \r
11 {{{fX=124.70011901855469 fY=9.3718004226684570 } {fX=124.66775026544929 fY=9.3744058723095804 } {fX=124.63530969619751 fY=9.3770485520362854 } {fX=124.60282897949219 fY=9.3796949386596680 } id=1\r
12 {{{fX=124.70011901855469 fY=9.3718004226684570 } {fX=124.66786243087600 fY=9.3743968522034287 } {fX=124.63553249625420 fY=9.3770303056986286 } {fX=124.60316467285156 fY=9.3796672821044922 } id=2\r
13     </div>
14
15     </div>
16
17 <script type="text/javascript">
18
19     var testDivs = [
20     cubics,
21     ];
22
23     var decimal_places = 3;
24
25     var tests = [];
26     var testTitles = [];
27     var testIndex = 0;
28     var ctx;
29
30     var subscale = 1;
31     var xmin, xmax, ymin, ymax;
32     var hscale, vscale;
33     var hinitScale, vinitScale;
34     var uniformScale = true;
35     var mouseX, mouseY;
36     var mouseDown = false;
37     var srcLeft, srcTop;
38     var screenWidth, screenHeight;
39     var drawnPts;
40     var curveT = 0;
41     var curveW = -1;
42
43     var lastX, lastY;
44     var activeCurve = [];
45     var activePt;
46     var ids = [];
47
48     var focus_on_selection = 0;
49     var draw_t = false;
50     var draw_w = false;
51     var draw_closest_t = false;
52     var draw_cubic_red = false;
53     var draw_derivative = false;
54     var draw_endpoints = 2;
55     var draw_id = 0;
56     var draw_midpoint = 0;
57     var draw_mouse_xy = false;
58     var draw_order = false;
59     var draw_point_xy = false;
60     var draw_ray_intersect = false;
61     var draw_quarterpoint = 0;
62     var draw_tangents = 1;
63     var draw_sortpoint = 0;
64     var retina_scale = !!window.devicePixelRatio;
65
66     function parse(test, title) {
67         var curveStrs = test.split("{{");
68         var pattern = /-?\d+\.*\d*e?-?\d*/g;
69         var curves = [];
70         for (var c in curveStrs) {
71             var curveStr = curveStrs[c];
72             var idPart = curveStr.split("id=");
73             var id = -1;
74             if (idPart.length == 2) {
75                 id = parseInt(idPart[1]);
76                 curveStr = idPart[0];
77             }
78             var points = curveStr.match(pattern);
79             var pts = [];
80             for (var wd in points) {
81                 var num = parseFloat(points[wd]);
82                 if (isNaN(num)) continue;
83                 pts.push(num);
84             }
85             if (pts.length > 2) {
86                 curves.push(pts);
87             }
88             if (id >= 0) {
89                 ids.push(id);
90                 ids.push(pts);
91             }
92         }
93         if (curves.length >= 1) {
94             tests.push(curves);
95             testTitles.push(title);
96         }
97     }
98
99     function init(test) {
100         var canvas = document.getElementById('canvas');
101         if (!canvas.getContext) return;
102         ctx = canvas.getContext('2d');
103         var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
104         var unscaledWidth = window.innerWidth - 20;
105         var unscaledHeight = window.innerHeight - 20;
106         screenWidth = unscaledWidth;
107         screenHeight = unscaledHeight;
108         canvas.width = unscaledWidth * resScale;
109         canvas.height = unscaledHeight * resScale;
110         canvas.style.width = unscaledWidth + 'px';
111         canvas.style.height = unscaledHeight + 'px';
112         if (resScale != 1) {
113             ctx.scale(resScale, resScale);
114         }
115         xmin = Infinity;
116         xmax = -Infinity;
117         ymin = Infinity;
118         ymax = -Infinity;
119         for (var curves in test) {
120             var curve = test[curves];
121             var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
122             for (var idx = 0; idx < last; idx += 2) {
123                 xmin = Math.min(xmin, curve[idx]);
124                 xmax = Math.max(xmax, curve[idx]);
125                 ymin = Math.min(ymin, curve[idx + 1]);
126                 ymax = Math.max(ymax, curve[idx + 1]);
127             }
128         }
129         xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
130         var testW = xmax - xmin;
131         var testH = ymax - ymin;
132         subscale = 1;
133         while (testW * subscale < 0.1 && testH * subscale < 0.1) {
134             subscale *= 10;
135         }
136         while (testW * subscale > 10 && testH * subscale > 10) {
137             subscale /= 10;
138         }
139         setScale(xmin, xmax, ymin, ymax);
140         mouseX = (screenWidth / 2) / hscale + srcLeft;
141         mouseY = (screenHeight / 2) / vscale + srcTop;
142         hinitScale = hscale;
143         vinitScale = vscale;
144     }
145
146     function setScale(x0, x1, y0, y1) {
147         var srcWidth = x1 - x0;
148         var srcHeight = y1 - y0;
149         var usableWidth = screenWidth;
150         var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
151         var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
152         usableWidth -= (xDigits + yDigits) * 10;
153         usableWidth -= decimal_places * 10;
154         hscale = usableWidth / srcWidth;
155         vscale = screenHeight / srcHeight;
156         if (uniformScale) {
157             hscale = Math.min(hscale, vscale);
158             vscale = hscale;
159         }
160         var hinvScale = 1 / hscale;
161         var vinvScale = 1 / vscale;
162         var sxmin = x0 - hinvScale * 5;
163         var symin = y0 - vinvScale * 10;
164         var sxmax = x1 + hinvScale * (6 * decimal_places + 10);
165         var symax = y1 + vinvScale * 10;
166         srcWidth = sxmax - sxmin;
167         srcHeight = symax - symin;
168         hscale = usableWidth / srcWidth;
169         vscale = screenHeight / srcHeight;
170         if (uniformScale) {
171             hscale = Math.min(hscale, vscale);
172             vscale = hscale;
173         }
174         srcLeft = sxmin;
175         srcTop = symin;
176     }
177
178 function dxy_at_t(curve, t) {
179     var dxy = {};
180     if (curve.length == 6) {
181         var a = t - 1;
182         var b = 1 - 2 * t;
183         var c = t;
184         dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
185         dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
186     } else if (curve.length == 7) {
187         var p20x = curve[4] - curve[0];
188         var p20y = curve[5] - curve[1];
189         var p10xw = (curve[2] - curve[0]) * curve[6];
190         var p10yw = (curve[3] - curve[1]) * curve[6];
191         var coeff0x = curve[6] * p20x - p20x;
192         var coeff0y = curve[6] * p20y - p20y;
193         var coeff1x = p20x - 2 * p10xw;
194         var coeff1y = p20y - 2 * p10yw;
195         dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
196         dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
197     } else if (curve.length == 8) {
198         var one_t = 1 - t;
199         var a = curve[0];
200         var b = curve[2];
201         var c = curve[4];
202         var d = curve[6];
203         dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
204         a = curve[1];
205         b = curve[3];
206         c = curve[5];
207         d = curve[7];
208         dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
209     }
210     return dxy;
211 }
212
213     var flt_epsilon = 1.19209290E-07;
214
215     function approximately_zero(A) {
216         return Math.abs(A) < flt_epsilon;
217     }
218
219     function approximately_zero_inverse(A) {
220         return Math.abs(A) > (1 / flt_epsilon);
221     }
222
223     function quad_real_roots(A, B, C) {
224         var s = [];
225         var p = B / (2 * A);
226         var q = C / A;
227         if (approximately_zero(A) && (approximately_zero_inverse(p)
228                 || approximately_zero_inverse(q))) {
229             if (approximately_zero(B)) {
230                 if (C == 0) {
231                     s[0] = 0;
232                 }
233                 return s;
234             }
235             s[0] = -C / B;
236             return s;
237         }
238         /* normal form: x^2 + px + q = 0 */
239         var p2 = p * p;
240         if (!approximately_zero(p2 - q) && p2 < q) {
241             return s;
242         }
243         var sqrt_D = 0;
244         if (p2 > q) {
245             sqrt_D = Math.sqrt(p2 - q);
246         }
247         s[0] = sqrt_D - p;
248         var flip = -sqrt_D - p;
249         if (!approximately_zero(s[0] - flip)) {
250             s[1] = flip;
251         }
252         return s;
253     }
254
255     function cubic_real_roots(A, B, C, D) {
256         if (approximately_zero(A)) {  // we're just a quadratic
257             return quad_real_roots(B, C, D);
258         }
259         if (approximately_zero(D)) {  // 0 is one root
260             var s = quad_real_roots(A, B, C);
261             for (var i = 0; i < s.length; ++i) {
262                 if (approximately_zero(s[i])) {
263                     return s;
264                 }
265             }
266             s.push(0);
267             return s;
268         }
269         if (approximately_zero(A + B + C + D)) {  // 1 is one root
270             var s = quad_real_roots(A, A + B, -D);
271             for (var i = 0; i < s.length; ++i) {
272                 if (approximately_zero(s[i] - 1)) {
273                     return s;
274                 }
275             }
276             s.push(1);
277             return s;
278         }
279         var a, b, c;
280         var invA = 1 / A;
281         a = B * invA;
282         b = C * invA;
283         c = D * invA;
284         var a2 = a * a;
285         var Q = (a2 - b * 3) / 9;
286         var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
287         var R2 = R * R;
288         var Q3 = Q * Q * Q;
289         var R2MinusQ3 = R2 - Q3;
290         var adiv3 = a / 3;
291         var r;
292         var roots = [];
293         if (R2MinusQ3 < 0) {   // we have 3 real roots
294             var theta = Math.acos(R / Math.sqrt(Q3));
295             var neg2RootQ = -2 * Math.sqrt(Q);
296             r = neg2RootQ * Math.cos(theta / 3) - adiv3;
297             roots.push(r);
298             r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
299             if (!approximately_zero(roots[0] - r)) {
300                 roots.push(r);
301             }
302             r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
303             if (!approximately_zero(roots[0] - r) && (roots.length == 1
304                         || !approximately_zero(roots[1] - r))) {
305                 roots.push(r);
306             }
307         } else {  // we have 1 real root
308             var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
309             var A = Math.abs(R) + sqrtR2MinusQ3;
310             A = Math.pow(A, 1/3);
311             if (R > 0) {
312                 A = -A;
313             }
314             if (A != 0) {
315                 A += Q / A;
316             }
317             r = A - adiv3;
318             roots.push(r);
319             if (approximately_zero(R2 - Q3)) {
320                 r = -A / 2 - adiv3;
321                 if (!approximately_zero(roots[0] - r)) {
322                     roots.push(r);
323                 }
324             }
325         }
326         return roots;
327     }
328
329     function approximately_zero_or_more(tValue) {
330         return tValue >= -flt_epsilon;
331     }
332
333     function approximately_one_or_less(tValue) {
334         return tValue <= 1 + flt_epsilon;
335     }
336
337     function approximately_less_than_zero(tValue) {
338         return tValue < flt_epsilon;
339     }
340
341     function approximately_greater_than_one(tValue) {
342         return tValue > 1 - flt_epsilon;
343     }
344
345     function add_valid_ts(s) {
346         var t = [];
347     nextRoot:
348         for (var index = 0; index < s.length; ++index) {
349             var tValue = s[index];
350             if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
351                 if (approximately_less_than_zero(tValue)) {
352                     tValue = 0;
353                 } else if (approximately_greater_than_one(tValue)) {
354                     tValue = 1;
355                 }
356                 for (var idx2 = 0; idx2 < t.length; ++idx2) {
357                     if (approximately_zero(t[idx2] - tValue)) {
358                         continue nextRoot;
359                     }
360                 }
361                 t.push(tValue);
362             }
363         }
364         return t;
365     }
366
367     function quad_roots(A, B, C) {
368         var s = quad_real_roots(A, B, C);
369         var foundRoots = add_valid_ts(s);
370         return foundRoots;
371     }
372
373     function cubic_roots(A, B, C, D) {
374         var s = cubic_real_roots(A, B, C, D);
375         var foundRoots = add_valid_ts(s);
376         return foundRoots;
377     }
378
379     function ray_curve_intersect(startPt, endPt, curve) {
380         var adj = endPt[0] - startPt[0];
381         var opp = endPt[1] - startPt[1];
382         var r = [];
383         var len = (curve.length == 7 ? 6 : curve.length) / 2;
384         for (var n = 0; n < len; ++n) {
385             r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
386         }
387         if (curve.length == 6) {
388             var A = r[2];
389             var B = r[1];
390             var C = r[0];
391             A += C - 2 * B;  // A = a - 2*b + c
392             B -= C;  // B = -(b - c)
393             return quad_roots(A, 2 * B, C);
394         }
395         if (curve.length == 7) {
396             var A = r[2];
397             var B = r[1] * curve[6];
398             var C = r[0];
399             A += C - 2 * B;  // A = a - 2*b + c
400             B -= C;  // B = -(b - c)
401             return quad_roots(A, 2 * B, C);
402         }
403         var A = r[3];       // d
404         var B = r[2] * 3;   // 3*c
405         var C = r[1] * 3;   // 3*b
406         var D = r[0];       // a
407         A -= D - C + B;     // A =   -a + 3*b - 3*c + d
408         B += 3 * D - 2 * C; // B =  3*a - 6*b + 3*c
409         C -= 3 * D;         // C = -3*a + 3*b
410         return cubic_roots(A, B, C, D);
411     }
412
413     function x_at_t(curve, t) {
414         var one_t = 1 - t;
415         if (curve.length == 4) {
416             return one_t * curve[0] + t * curve[2];
417         }
418         var one_t2 = one_t * one_t;
419         var t2 = t * t;
420         if (curve.length == 6) {
421             return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
422         }
423         if (curve.length == 7) {
424             var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
425                     + t2 * curve[4];
426             var denom = one_t2            + 2 * one_t * t            * curve[6]
427                     + t2;
428             return numer / denom;
429         }
430         var a = one_t2 * one_t;
431         var b = 3 * one_t2 * t;
432         var c = 3 * one_t * t2;
433         var d = t2 * t;
434         return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
435     }
436
437     function y_at_t(curve, t) {
438         var one_t = 1 - t;
439         if (curve.length == 4) {
440             return one_t * curve[1] + t * curve[3];
441         }
442         var one_t2 = one_t * one_t;
443         var t2 = t * t;
444         if (curve.length == 6) {
445             return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
446         }
447         if (curve.length == 7) {
448             var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
449                     + t2 * curve[5];
450             var denom = one_t2            + 2 * one_t * t            * curve[6]
451                     + t2;
452             return numer / denom;
453         }
454         var a = one_t2 * one_t;
455         var b = 3 * one_t2 * t;
456         var c = 3 * one_t * t2;
457         var d = t2 * t;
458         return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
459     }
460
461     function drawPointAtT(curve) {
462         var x = x_at_t(curve, curveT);
463         var y = y_at_t(curve, curveT);
464         drawPoint(x, y, false);
465     }
466
467     function drawLine(x1, y1, x2, y2) {
468         ctx.beginPath();
469         ctx.moveTo((x1 - srcLeft) * hscale,
470                 (y1 - srcTop) * vscale);
471         ctx.lineTo((x2 - srcLeft) * hscale,
472                 (y2 - srcTop) * vscale);
473         ctx.stroke();
474     }
475
476     function drawPoint(px, py, xend) {
477         for (var pts = 0; pts < drawnPts.length; pts += 2) {
478             var x = drawnPts[pts];
479             var y = drawnPts[pts + 1];
480             if (px == x && py == y) {
481                 return;
482             }
483         }
484         drawnPts.push(px);
485         drawnPts.push(py);
486         var _px = (px - srcLeft) * hscale;
487         var _py = (py - srcTop) * vscale;
488         ctx.beginPath();
489         if (xend) {
490             ctx.moveTo(_px - 3, _py - 3);
491             ctx.lineTo(_px + 3, _py + 3);
492             ctx.moveTo(_px - 3, _py + 3);
493             ctx.lineTo(_px + 3, _py - 3);
494         } else {
495             ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
496             ctx.closePath();
497         }
498         ctx.stroke();
499         if (draw_point_xy) {
500             var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
501             ctx.font = "normal 10px Arial";
502             ctx.textAlign = "left";
503             ctx.fillStyle = "black";
504             ctx.fillText(label, _px + 5, _py);
505         }
506     }
507
508     function drawPointSolid(px, py) {
509         drawPoint(px, py, false);
510         ctx.fillStyle = "rgba(0,0,0, 0.4)";
511         ctx.fill();
512     }
513
514     function crossPt(origin, pt1, pt2) {
515         return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
516               - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
517     }
518
519     // may not work well for cubics
520     function curveClosestT(curve, x, y) {
521         var closest = -1;
522         var closestDist = Infinity;
523         var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
524         for (var i = 0; i < 16; ++i) {
525             var testX = x_at_t(curve, i / 16);
526             l = Math.min(testX, l);
527             r = Math.max(testX, r);
528             var testY = y_at_t(curve, i / 16);
529             t = Math.min(testY, t);
530             b = Math.max(testY, b);
531             var dx = testX - x;
532             var dy = testY - y;
533             var dist = dx * dx + dy * dy;
534             if (closestDist > dist) {
535                 closestDist = dist;
536                 closest = i;
537             }
538         }
539         var boundsX = r - l;
540         var boundsY = b - t;
541         var boundsDist = boundsX * boundsX + boundsY * boundsY;
542         if (closestDist > boundsDist) {
543             return -1;
544         }
545         console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
546                 + " t = " + closest / 16);
547         return closest / 16;
548     }
549
550     var kMaxConicToQuadPOW2 = 5;
551
552     function computeQuadPOW2(curve, tol) {
553         var a = curve[6] - 1;
554         var k = a / (4 * (2 + a));
555         var x = k * (curve[0] - 2 * curve[2] + curve[4]);
556         var y = k * (curve[1] - 2 * curve[3] + curve[5]);
557
558         var error = Math.sqrt(x * x + y * y);
559         var pow2;
560         for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
561             if (error <= tol) {
562                 break;
563             }
564             error *= 0.25;
565         }
566         return pow2;
567     }
568
569     function subdivide_w_value(w) {
570         return Math.sqrt(0.5 + w * 0.5);
571     }
572
573     function chop(curve, part1, part2) {
574         var w = curve[6];
575         var scale = 1 / (1 + w);
576         part1[0] = curve[0];
577         part1[1] = curve[1];
578         part1[2] = (curve[0] + curve[2] * w) * scale;
579         part1[3] = (curve[1] + curve[3] * w) * scale;
580         part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
581         part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
582         part2[2] = (curve[2] * w + curve[4]) * scale;
583         part2[3] = (curve[3] * w + curve[5]) * scale;
584         part2[4] = curve[4];
585         part2[5] = curve[5];
586         part1[6] = part2[6] = subdivide_w_value(w);
587     }
588
589     function subdivide(curve, level, pts) {
590         if (0 == level) {
591             pts.push(curve[2]);
592             pts.push(curve[3]);
593             pts.push(curve[4]);
594             pts.push(curve[5]);
595         } else {
596             var part1 = [], part2 = [];
597             chop(curve, part1, part2);
598             --level;
599             subdivide(part1, level, pts);
600             subdivide(part2, level, pts);
601         }
602     }
603
604     function chopIntoQuadsPOW2(curve, pow2, pts) {
605         subdivide(curve, pow2, pts);
606         return 1 << pow2;
607     }
608
609     function drawConic(curve, srcLeft, srcTop, hscale, vscale) {
610         var tol = 1 / Math.min(hscale, vscale);
611         var pow2 = computeQuadPOW2(curve, tol);
612         var pts = [];
613         chopIntoQuadsPOW2(curve, pow2, pts);
614         for (var i = 0; i < pts.length; i += 4) {
615             ctx.quadraticCurveTo(
616                 (pts[i + 0] - srcLeft) * hscale, (pts[i + 1] - srcTop) * vscale,
617                 (pts[i + 2] - srcLeft) * hscale, (pts[i + 3] - srcTop) * vscale);
618         }
619     }
620
621     function draw(test, title) {
622         ctx.font = "normal 50px Arial";
623         ctx.textAlign = "left";
624         ctx.fillStyle = "rgba(0,0,0, 0.1)";
625         ctx.fillText(title, 50, 50);
626         ctx.font = "normal 10px Arial";
627         //  ctx.lineWidth = "1.001"; "0.999";
628         var hullStarts = [];
629         var hullEnds = [];
630         var midSpokes = [];
631         var midDist = [];
632         var origin = [];
633         var shortSpokes = [];
634         var shortDist = [];
635         var sweeps = [];
636         drawnPts = [];
637         for (var curves in test) {
638             var curve = test[curves];
639             origin.push(curve[0]);
640             origin.push(curve[1]);
641             var startPt = [];
642             startPt.push(curve[2]);
643             startPt.push(curve[3]);
644             hullStarts.push(startPt);
645             var endPt = [];
646             if (curve.length == 4) {
647                 endPt.push(curve[2]);
648                 endPt.push(curve[3]);
649             } else if (curve.length == 6 || curve.length == 7) {
650                 endPt.push(curve[4]);
651                 endPt.push(curve[5]);
652             } else if (curve.length == 8) {
653                 endPt.push(curve[6]);
654                 endPt.push(curve[7]);
655             }
656             hullEnds.push(endPt);
657             var sweep = crossPt(origin, startPt, endPt);
658             sweeps.push(sweep);
659             var midPt = [];
660             midPt.push(x_at_t(curve, 0.5));
661             midPt.push(y_at_t(curve, 0.5));
662             midSpokes.push(midPt);
663             var shortPt = [];
664             shortPt.push(x_at_t(curve, 0.25));
665             shortPt.push(y_at_t(curve, 0.25));
666             shortSpokes.push(shortPt);
667             var dx = midPt[0] - origin[0];
668             var dy = midPt[1] - origin[1];
669             var dist = Math.sqrt(dx * dx + dy * dy);
670             midDist.push(dist);
671             dx = shortPt[0] - origin[0];
672             dy = shortPt[1] - origin[1];
673             dist = Math.sqrt(dx * dx + dy * dy);
674             shortDist.push(dist);
675         }
676         var intersect = [];
677         var useIntersect = false;
678         var maxWidth = Math.max(xmax - xmin, ymax - ymin);
679         for (var curves in test) {
680             var curve = test[curves];
681             if (curve.length >= 6 && curve.length <= 8) {
682                 var opp = curves == 0 || curves == 1 ? 0 : 1;
683                 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
684                 intersect.push(sects);
685                 if (sects.length > 1) {
686                     var intersection = sects[0];
687                     if (intersection == 0) {
688                         intersection = sects[1];
689                     }
690                     var ix = x_at_t(curve, intersection) - origin[0];
691                     var iy = y_at_t(curve, intersection) - origin[1];
692                     var ex = hullEnds[opp][0] - origin[0];
693                     var ey = hullEnds[opp][1] - origin[1];
694                     if (ix * ex >= 0 && iy * ey >= 0) {
695                         var iDist = Math.sqrt(ix * ix + iy * iy);
696                         var eDist = Math.sqrt(ex * ex + ey * ey);
697                         var delta = Math.abs(iDist - eDist) / maxWidth;
698                         if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
699                             useIntersect ^= true;
700                         }
701                     }
702                 }
703             }
704         }
705         var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
706         var firstInside;
707         if (useIntersect) {
708             var sect1 = intersect[0].length > 1;
709             var sIndex = sect1 ? 0 : 1;
710             var sects = intersect[sIndex];
711             var intersection = sects[0];
712             if (intersection == 0) {
713                 intersection = sects[1];
714             }
715             var curve = test[sIndex];
716             var ix = x_at_t(curve, intersection) - origin[0];
717             var iy = y_at_t(curve, intersection) - origin[1];
718             var opp = sect1 ? 1 : 0;
719             var ex = hullEnds[opp][0] - origin[0];
720             var ey = hullEnds[opp][1] - origin[1];
721             var iDist = ix * ix + iy * iy;
722             var eDist = ex * ex + ey * ey;
723             firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
724 //            console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
725  //                   + " sweeps[0]=" + sweeps[0]);
726         } else {
727  //           console.log("midLeft=" + midLeft);
728             firstInside = midLeft != 0;
729         }
730         var shorter = midDist[1] < midDist[0];
731         var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
732                 : crossPt(origin, midSpokes[0], shortSpokes[1]);
733         var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
734         var disallowShort = midLeft == startCross && midLeft == sweeps[0]
735                     && midLeft == sweeps[1];
736
737   //      console.log("midLeft=" + midLeft + " startCross=" + startCross);
738         var intersectIndex = 0;
739         for (var curves in test) {
740             var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
741             if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
742                 continue;
743             }
744             ctx.lineWidth = 1;
745             if (draw_tangents != 0) {
746                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
747                     ctx.strokeStyle = "rgba(255,0,0, 0.3)";
748                 } else {
749                     ctx.strokeStyle = "rgba(0,0,255, 0.3)";
750                 }
751                 drawLine(curve[0], curve[1], curve[2], curve[3]);
752                 if (draw_tangents != 2) {
753                     if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
754                     if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
755                 }
756                 if (draw_tangents != 1) {
757                     if (curve.length == 6 || curve.length == 7) {
758                         drawLine(curve[0], curve[1], curve[4], curve[5]);
759                     }
760                     if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
761                 }
762             }
763             ctx.beginPath();
764             ctx.moveTo((curve[0] - srcLeft) * hscale, (curve[1] - srcTop) * vscale);
765             if (curve.length == 4) {
766                 ctx.lineTo((curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale);
767             } else if (curve.length == 6) {
768                 ctx.quadraticCurveTo(
769                     (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
770                     (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale);
771             } else if (curve.length == 7) {
772                 drawConic(curve, srcLeft, srcTop, hscale, vscale);
773             } else {
774                 ctx.bezierCurveTo(
775                     (curve[2] - srcLeft) * hscale, (curve[3] - srcTop) * vscale,
776                     (curve[4] - srcLeft) * hscale, (curve[5] - srcTop) * vscale,
777                     (curve[6] - srcLeft) * hscale, (curve[7] - srcTop) * vscale);
778             }
779             if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
780                 ctx.strokeStyle = "rgba(255,0,0, 1)";
781             } else {
782                 ctx.strokeStyle = "rgba(0,0,255, 1)";
783             }
784             ctx.stroke();
785             if (draw_endpoints > 0) {
786                 drawPoint(curve[0], curve[1], false);
787                 if (draw_endpoints > 1 || curve.length == 4) {
788                     drawPoint(curve[2], curve[3], curve.length == 4 && draw_endpoints == 3);
789                 }
790                 if (curve.length == 6 || curve.length == 7 ||
791                         (draw_endpoints > 1 && curve.length == 8)) {
792                     drawPoint(curve[4], curve[5], (curve.length == 6 || curve.length == 7) && draw_endpoints == 3);
793                 }
794                 if (curve.length == 8) {
795                     drawPoint(curve[6], curve[7], curve.length == 8 && draw_endpoints == 3);
796                 }
797             }
798             if (draw_midpoint != 0) {
799                 if ((curves == 0) == (midLeft == 0)) {
800                     ctx.strokeStyle = "rgba(0,180,127, 0.6)";
801                 } else {
802                     ctx.strokeStyle = "rgba(127,0,127, 0.6)";
803                 }
804                 var midX = x_at_t(curve, 0.5);
805                 var midY = y_at_t(curve, 0.5);
806                 drawPointSolid(midX, midY);
807                 if (draw_midpoint > 1) {
808                     drawLine(curve[0], curve[1], midX, midY);
809                 }
810             }
811             if (draw_quarterpoint != 0) {
812                 if ((curves == 0) == (shortLeft == 0)) {
813                     ctx.strokeStyle = "rgba(0,191,63, 0.6)";
814                 } else {
815                     ctx.strokeStyle = "rgba(63,0,191, 0.6)";
816                 }
817                 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
818                 var midX = x_at_t(curve, midT);
819                 var midY = y_at_t(curve, midT);
820                 drawPointSolid(midX, midY);
821                 if (draw_quarterpoint > 1) {
822                     drawLine(curve[0], curve[1], midX, midY);
823                 }
824             }
825             if (draw_sortpoint != 0) {
826                 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
827                     ctx.strokeStyle = "rgba(0,155,37, 0.6)";
828                 } else {
829                     ctx.strokeStyle = "rgba(37,0,155, 0.6)";
830                 }
831                 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
832                 console.log("curves=" + curves + " disallowShort=" + disallowShort
833                         + " midLeft=" + midLeft + " shortLeft=" + shortLeft
834                         + " shorter=" + shorter + " midT=" + midT);
835                 var midX = x_at_t(curve, midT);
836                 var midY = y_at_t(curve, midT);
837                 drawPointSolid(midX, midY);
838                 if (draw_sortpoint > 1) {
839                     drawLine(curve[0], curve[1], midX, midY);
840                 }
841             }
842             if (draw_ray_intersect != 0) {
843                 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
844                 if (curve.length >= 6 && curve.length <= 8) {
845                     var intersections = intersect[intersectIndex];
846                     for (var i in intersections) {
847                         var intersection = intersections[i];
848                         var x = x_at_t(curve, intersection);
849                         var y = y_at_t(curve, intersection);
850                         drawPointSolid(x, y);
851                         if (draw_ray_intersect > 1) {
852                             drawLine(curve[0], curve[1], x, y);
853                         }
854                     }
855                 }
856                 ++intersectIndex;
857             }
858             if (draw_order) {
859                 var px = x_at_t(curve, 0.75);
860                 var py = y_at_t(curve, 0.75);
861                 var _px = (px - srcLeft) * hscale;
862                 var _py = (py - srcTop) * vscale;
863                 ctx.beginPath();
864                 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
865                 ctx.closePath();
866                 ctx.fillStyle = "white";
867                 ctx.fill();
868                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
869                     ctx.strokeStyle = "rgba(255,0,0, 1)";
870                     ctx.fillStyle = "rgba(255,0,0, 1)";
871                 } else {
872                     ctx.strokeStyle = "rgba(0,0,255, 1)";
873                     ctx.fillStyle = "rgba(0,0,255, 1)";
874                 }
875                 ctx.stroke();
876                 ctx.font = "normal 16px Arial";
877                 ctx.textAlign = "center";
878                 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
879             }
880             if (draw_closest_t) {
881                 var t = curveClosestT(curve, mouseX, mouseY);
882                 if (t >= 0) {
883                     var x = x_at_t(curve, t);
884                     var y = y_at_t(curve, t);
885                     drawPointSolid(x, y);
886                 }
887             }
888             if (!approximately_zero(hscale - hinitScale)) {
889                 ctx.font = "normal 20px Arial";
890                 ctx.fillStyle = "rgba(0,0,0, 0.3)";
891                 ctx.textAlign = "right";
892                 var scaleTextOffset = hscale != vscale ? -25 : -5;
893                 ctx.fillText(hscale.toFixed(decimal_places) + 'x',
894                         screenWidth - 10, screenHeight - scaleTextOffset);
895                 if (hscale != vscale) {
896                     ctx.fillText(vscale.toFixed(decimal_places) + 'y',
897                             screenWidth - 10, screenHeight - 5);
898                 }
899             }
900             if (draw_t) {
901                 drawPointAtT(curve);
902             }
903             if (draw_id != 0) {
904                 var id = -1;
905                 for (var i = 0; i < ids.length; i += 2) {
906                     if (ids[i + 1] == curve) {
907                         id = ids[i];
908                         break;
909                     }
910                 }
911                 if (id >= 0) {
912                     var px = x_at_t(curve, 0.5);
913                     var py = y_at_t(curve, 0.5);
914                     var _px = (px - srcLeft) * hscale;
915                     var _py = (py - srcTop) * vscale;
916                     ctx.beginPath();
917                     ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
918                     ctx.closePath();
919                     ctx.fillStyle = "white";
920                     ctx.fill();
921                     ctx.strokeStyle = "rgba(255,0,0, 1)";
922                     ctx.fillStyle = "rgba(255,0,0, 1)";
923                     ctx.stroke();
924                     ctx.font = "normal 16px Arial";
925                     ctx.textAlign = "center";
926                     ctx.fillText(id, _px, _py + 5);
927                 }
928             }
929         }
930         if (draw_t) {
931             drawCurveTControl();
932         }
933         if (draw_w) {
934             drawCurveWControl();
935         }
936     }
937
938     function drawCurveTControl() {
939         ctx.lineWidth = 2;
940         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
941         ctx.beginPath();
942         ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
943         ctx.stroke();
944         var ty = 40 + curveT * (screenHeight - 80);
945         ctx.beginPath();
946         ctx.moveTo(screenWidth - 80, ty);
947         ctx.lineTo(screenWidth - 85, ty - 5);
948         ctx.lineTo(screenWidth - 85, ty + 5);
949         ctx.lineTo(screenWidth - 80, ty);
950         ctx.fillStyle = "rgba(0,0,0, 0.6)";
951         ctx.fill();
952         var num = curveT.toFixed(decimal_places);
953         ctx.font = "normal 10px Arial";
954         ctx.textAlign = "left";
955         ctx.fillText(num, screenWidth - 78, ty);
956     }
957
958     function drawCurveWControl() {
959         var w = -1;
960         var choice = 0;
961         for (var curves in tests[testIndex]) {
962             var curve = tests[testIndex][curves];
963             if (curve.length != 7) {
964                 continue;
965             }
966             if (choice == curveW) {
967                 w = curve[6];
968                 break;
969             }
970             ++choice;
971         }
972         if (w < 0) {
973             return;
974         }
975         ctx.lineWidth = 2;
976         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
977         ctx.beginPath();
978         ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
979         ctx.stroke();
980         var ty = 40 + w * (screenHeight - 80);
981         ctx.beginPath();
982         ctx.moveTo(screenWidth - 40, ty);
983         ctx.lineTo(screenWidth - 45, ty - 5);
984         ctx.lineTo(screenWidth - 45, ty + 5);
985         ctx.lineTo(screenWidth - 40, ty);
986         ctx.fillStyle = "rgba(0,0,0, 0.6)";
987         ctx.fill();
988         var num = w.toFixed(decimal_places);
989         ctx.font = "normal 10px Arial";
990         ctx.textAlign = "left";
991         ctx.fillText(num, screenWidth - 38, ty);
992     }
993
994     function ptInTControl() {
995         var e = window.event;
996         var tgt = e.target || e.srcElement;
997         var left = tgt.offsetLeft;
998         var top = tgt.offsetTop;
999         var x = (e.clientX - left);
1000         var y = (e.clientY - top);
1001         if (x < screenWidth - 80 || x > screenWidth - 50) {
1002             return false;
1003         }
1004         if (y < 40 || y > screenHeight - 80) {
1005             return false;
1006         }
1007         curveT = (y - 40) / (screenHeight - 120);
1008         if (curveT < 0 || curveT > 1) {
1009             throw "stop execution";
1010         }
1011         return true;
1012     }
1013
1014     function ptInWControl() {
1015         var e = window.event;
1016         var tgt = e.target || e.srcElement;
1017         var left = tgt.offsetLeft;
1018         var top = tgt.offsetTop;
1019         var x = (e.clientX - left);
1020         var y = (e.clientY - top);
1021         if (x < screenWidth - 40 || x > screenWidth - 10) {
1022             return false;
1023         }
1024         if (y < 40 || y > screenHeight - 80) {
1025             return false;
1026         }
1027         var w = (y - 40) / (screenHeight - 120);
1028         if (w < 0 || w > 1) {
1029             throw "stop execution";
1030         }
1031         var choice = 0;
1032         for (var curves in tests[testIndex]) {
1033             var curve = tests[testIndex][curves];
1034             if (curve.length != 7) {
1035                 continue;
1036             }
1037             if (choice == curveW) {
1038                 curve[6] = w;
1039                 break;
1040             }
1041             ++choice;
1042         }
1043         return true;
1044     }
1045
1046     function drawTop() {
1047         init(tests[testIndex]);
1048         redraw();
1049     }
1050
1051     function redraw() {
1052         if (focus_on_selection > 0) {
1053             var focusXmin = focusYmin = Infinity;
1054             var focusXmax = focusYmax = -Infinity;
1055             var choice = 0;
1056             for (var curves in tests[testIndex]) {
1057                 if (++choice != focus_on_selection) {
1058                     continue;
1059                 }
1060                 var curve = tests[testIndex][curves];
1061                 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
1062                 for (var idx = 0; idx < last; idx += 2) {
1063                     focusXmin = Math.min(focusXmin, curve[idx]);
1064                     focusXmax = Math.max(focusXmax, curve[idx]);
1065                     focusYmin = Math.min(focusYmin, curve[idx + 1]);
1066                     focusYmax = Math.max(focusYmax, curve[idx + 1]);
1067                 }
1068             }
1069             focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1070             if (focusXmin < focusXmax && focusYmin < focusYmax) {
1071                 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1072             }
1073         }
1074         ctx.beginPath();
1075         ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1076         ctx.fillStyle = "white";
1077         ctx.fill();
1078         draw(tests[testIndex], testTitles[testIndex]);
1079     }
1080
1081     function doKeyPress(evt) {
1082         var char = String.fromCharCode(evt.charCode);
1083         var focusWasOn = false;
1084         switch (char) {
1085             case '0':
1086             case '1':
1087             case '2':
1088             case '3':
1089             case '4':
1090             case '5':
1091             case '6':
1092             case '7':
1093             case '8':
1094             case '9':
1095                 decimal_places = char - '0';
1096                 redraw();
1097                 break;
1098             case '-':
1099                 focusWasOn = focus_on_selection;
1100                 if (focusWasOn) {
1101                     focus_on_selection = false;
1102                     hscale /= 1.2;
1103                     vscale /= 1.2;
1104                 } else {
1105                     hscale /= 2;
1106                     vscale /= 2;
1107                 }
1108                 calcLeftTop();
1109                 redraw();
1110                 focus_on_selection = focusWasOn;
1111                 break;
1112             case '=':
1113             case '+':
1114                 focusWasOn = focus_on_selection;
1115                 if (focusWasOn) {
1116                     focus_on_selection = false;
1117                     hscale *= 1.2;
1118                     vscale *= 1.2;
1119                 } else {
1120                     hscale *= 2;
1121                     vscale *= 2;
1122                 }
1123                 calcLeftTop();
1124                 redraw();
1125                 focus_on_selection = focusWasOn;
1126                 break;
1127             case 'b':
1128                 draw_cubic_red ^= true;
1129                 redraw();
1130                 break;
1131             case 'c':
1132                 drawTop();
1133                 break;
1134             case 'd':
1135                 var test = tests[testIndex];
1136                 var testClone = [];
1137                 for (var curves in test) {
1138                     var c = test[curves];
1139                     var cClone = [];
1140                     for (var index = 0; index < c.length; ++index) {
1141                         cClone.push(c[index]);
1142                     }
1143                     testClone.push(cClone);
1144                 }
1145                 tests.push(testClone);
1146                 testTitles.push(testTitles[testIndex] + " copy");
1147                 testIndex = tests.length - 1;
1148                 redraw();
1149                 break;
1150             case 'e':
1151                 draw_endpoints = (draw_endpoints + 1) % 4;
1152                 redraw();
1153                 break;
1154             case 'f':
1155                 draw_derivative ^= true;
1156                 redraw();
1157                 break;
1158             case 'g':
1159                 hscale *= 1.2;
1160                 calcLeftTop();
1161                 redraw();
1162                 break;
1163             case 'G':
1164                 hscale /= 1.2;
1165                 calcLeftTop();
1166                 redraw();
1167                 break;
1168             case 'h':
1169                 vscale *= 1.2;
1170                 calcLeftTop();
1171                 redraw();
1172                 break;
1173             case 'H':
1174                 vscale /= 1.2;
1175                 calcLeftTop();
1176                 redraw();
1177                 break;
1178             case 'i':
1179                 draw_ray_intersect = (draw_ray_intersect + 1) % 3;
1180                 redraw();
1181                 break;
1182             case 'l':
1183                 var test = tests[testIndex];
1184                 console.log("<div id=\"" + testTitles[testIndex] + "\" >");
1185                 for (var curves in test) {
1186                     var c = test[curves];
1187                     var s = "{{";
1188                     for (var i = 0; i < c.length; i += 2) {
1189                         s += "{";
1190                         s += c[i] + "," + c[i + 1];
1191                         s += "}";
1192                         if (i + 2 < c.length) {
1193                             s += ", ";
1194                         }
1195                     }
1196                     console.log(s + "}},");
1197                 }
1198                 console.log("</div>");
1199                 break;
1200             case 'm':
1201                 draw_midpoint = (draw_midpoint + 1) % 3;
1202                 redraw();
1203                 break;
1204             case 'N':
1205                 testIndex += 9;
1206             case 'n':
1207                 testIndex = (testIndex + 1) % tests.length;
1208                 drawTop();
1209                 break;
1210             case 'o':
1211                 draw_order ^= true;
1212                 redraw();
1213                 break;
1214             case 'P':
1215                 testIndex -= 9;
1216             case 'p':
1217                 if (--testIndex < 0)
1218                     testIndex = tests.length - 1;
1219                 drawTop();
1220                 break;
1221             case 'q':
1222                 draw_quarterpoint = (draw_quarterpoint + 1) % 3;
1223                 redraw();
1224                 break;
1225             case 'r':
1226                 for (var i = 0; i < testDivs.length; ++i) {
1227                     var title = testDivs[i].id.toString();
1228                     if (title == testTitles[testIndex]) {
1229                         var str = testDivs[i].firstChild.data;
1230                         parse(str, title);
1231                         var original = tests.pop();
1232                         testTitles.pop();
1233                         tests[testIndex] = original;
1234                         break;
1235                     }
1236                 }
1237                 redraw();
1238                 break;
1239             case 's':
1240                 draw_sortpoint = (draw_sortpoint + 1) % 3;
1241                 redraw();
1242                 break;
1243             case 't':
1244                 draw_t ^= true;
1245                 redraw();
1246                 break;
1247             case 'u':
1248                 draw_closest_t ^= true;
1249                 redraw();
1250                 break;
1251             case 'v':
1252                 draw_tangents = (draw_tangents + 1) % 4;
1253                 redraw();
1254                 break;
1255             case 'w':
1256                 ++curveW;
1257                 var choice = 0;
1258                 draw_w = false;
1259                 for (var curves in tests[testIndex]) {
1260                     var curve = tests[testIndex][curves];
1261                     if (curve.length != 7) {
1262                         continue;
1263                     }
1264                     if (choice == curveW) {
1265                         draw_w = true;
1266                         break;
1267                     }
1268                     ++choice;
1269                 }
1270                 if (!draw_w) {
1271                     curveW = -1;
1272                 }
1273                 redraw();
1274                 break;
1275             case 'x':
1276                 draw_point_xy ^= true;
1277                 redraw();
1278                 break;
1279             case 'y':
1280                 draw_mouse_xy ^= true;
1281                 redraw();
1282                 break;
1283             case '\\':
1284                 retina_scale ^= true;
1285                 drawTop();
1286                 break;
1287             case '`':
1288                 ++focus_on_selection;
1289                 if (focus_on_selection >= tests[testIndex].length) {
1290                     focus_on_selection = 0;
1291                 }
1292                 setScale(xmin, xmax, ymin, ymax);
1293                 redraw();
1294                 break;
1295             case '.':
1296                 draw_id = (draw_id + 1) % 3;
1297                 redraw();
1298                 break;
1299         }
1300     }
1301
1302     function doKeyDown(evt) {
1303         var char = evt.keyCode;
1304         var preventDefault = false;
1305         switch (char) {
1306             case 37: // left arrow
1307                 if (evt.shiftKey) {
1308                     testIndex -= 9;
1309                 }
1310                 if (--testIndex < 0)
1311                     testIndex = tests.length - 1;
1312                 if (evt.ctrlKey) {
1313                     redraw();
1314                 } else {
1315                     drawTop();
1316                 }
1317                 preventDefault = true;
1318                 break;
1319             case 39: // right arrow
1320                 if (evt.shiftKey) {
1321                     testIndex += 9;
1322                 }
1323                 if (++testIndex >= tests.length)
1324                     testIndex = 0;
1325                 if (evt.ctrlKey) {
1326                     redraw();
1327                 } else {
1328                     drawTop();
1329                 }
1330                 preventDefault = true;
1331                 break;
1332         }
1333         if (preventDefault) {
1334             evt.preventDefault();
1335             return false;
1336         }
1337         return true;
1338     }
1339
1340     function calcXY() {
1341         var e = window.event;
1342         var tgt = e.target || e.srcElement;
1343         var left = tgt.offsetLeft;
1344         var top = tgt.offsetTop;
1345         mouseX = (e.clientX - left) / hscale + srcLeft;
1346         mouseY = (e.clientY - top) / vscale + srcTop;
1347     }
1348
1349     function calcLeftTop() {
1350         srcLeft = mouseX - screenWidth / 2 / hscale;
1351         srcTop = mouseY - screenHeight / 2 / vscale;
1352     }
1353
1354     function handleMouseClick() {
1355         if ((!draw_t || !ptInTControl()) && (!draw_w || !ptInWControl())) {
1356             calcXY();
1357         } else {
1358             redraw();
1359         }
1360     }
1361
1362     function initDown() {
1363         var test = tests[testIndex];
1364         var bestDistance = 1000000;
1365         activePt = -1;
1366         for (var curves in test) {
1367             var testCurve = test[curves];
1368             if (testCurve.length != 4 && (testCurve.length < 6 || testCurve.length > 8)) {
1369                 continue;
1370             }
1371             var testMax = testCurve.length == 7 ? 6 : testCurve.length;
1372             for (var i = 0; i < testMax; i += 2) {
1373                 var testX = testCurve[i];
1374                 var testY = testCurve[i + 1];
1375                 var dx = testX - mouseX;
1376                 var dy = testY - mouseY;
1377                 var dist = dx * dx + dy * dy;
1378                 if (dist > bestDistance) {
1379                     continue;
1380                 }
1381                 activeCurve = testCurve;
1382                 activePt = i;
1383                 bestDistance = dist;
1384             }
1385         }
1386         if (activePt >= 0) {
1387             lastX = mouseX;
1388             lastY = mouseY;
1389         }
1390     }
1391
1392     function handleMouseOver() {
1393         calcXY();
1394         if (draw_mouse_xy) {
1395             var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
1396             ctx.beginPath();
1397             ctx.rect(300, 100, num.length * 6, 10);
1398             ctx.fillStyle = "white";
1399             ctx.fill();
1400             ctx.font = "normal 10px Arial";
1401             ctx.fillStyle = "black";
1402             ctx.textAlign = "left";
1403             ctx.fillText(num, 300, 108);
1404         }
1405         if (!mouseDown) {
1406             activePt = -1;
1407             return;
1408         }
1409         if (activePt < 0) {
1410             initDown();
1411             return;
1412         }
1413         var deltaX = mouseX - lastX;
1414         var deltaY = mouseY - lastY;
1415         lastX = mouseX;
1416         lastY = mouseY;
1417         if (activePt == 0) {
1418             var test = tests[testIndex];
1419             for (var curves in test) {
1420                 var testCurve = test[curves];
1421                 testCurve[0] += deltaX;
1422                 testCurve[1] += deltaY;
1423             }
1424         } else {
1425             activeCurve[activePt] += deltaX;
1426             activeCurve[activePt + 1] += deltaY;
1427         }
1428         redraw();
1429     }
1430
1431     function start() {
1432         for (var i = 0; i < testDivs.length; ++i) {
1433             var title = testDivs[i].id.toString();
1434             var str = testDivs[i].firstChild.data;
1435             parse(str, title);
1436         }
1437         drawTop();
1438         window.addEventListener('keypress', doKeyPress, true);
1439         window.addEventListener('keydown', doKeyDown, true);
1440         window.onresize = function () {
1441             drawTop();
1442         }
1443     }
1444
1445 </script>
1446 </head>
1447
1448 <body onLoad="start();">
1449
1450 <canvas id="canvas" width="750" height="500"
1451     onmousedown="mouseDown = true"
1452     onmouseup="mouseDown = false"
1453     onmousemove="handleMouseOver()"
1454     onclick="handleMouseClick()"
1455     ></canvas >
1456 </body>
1457 </html>