Fix UB in SkDivBits
[platform/upstream/libSkiaSharp.git] / 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="sect0">
10 {{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
11 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
12 </div>
13
14 <div id="sect1">
15 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
16 {{{{306.58801299999999, -227.983994}, {212.46499600000001, -262.24200400000001}, {95.551200899999998, 58.976398500000002}}}, 0.707107008f} id=1
17 {{{{377.21899400000001, -141.98100299999999}, {237.77799285476553, -166.56830755921084}, {134.08399674208422, -155.06258330544892}}}, 0.788580656f} id=2
18 {{{{134.08399674208422, -155.06258330544892}, {30.390000629402859, -143.55685905168704}, {23.185499199999999, -102.697998}}}, 0.923879623f} id=4
19 </div>
20
21 <div id="sect2">
22 {{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
23 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
24 {{{{205.78973252799028, -158.12538713371103}, {143.97848953841861, -74.076645245042371}, {95.551200899999998, 58.976398500000002}}}, 0.923879623f} id=3
25 {{{{377.21899400000001, -141.98100299999999}, {237.77799285476553, -166.56830755921084}, {134.08399674208422, -155.06258330544892}}}, 0.788580656f} id=2
26 </div>
27
28 <div id="sect3">
29 {{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
30 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
31 {{{{205.78973252799028, -158.12538713371103}, {143.97848953841861, -74.076645245042371}, {95.551200899999998, 58.976398500000002}}}, 0.923879623f} id=3
32 {{{{252.08225670812539, -156.90491625851064}, {185.93099479842493, -160.81544543232982}, {134.08399674208422, -155.06258330544892}}}, 0.835816324f} id=6
33 </div>
34
35 <div id="sect4">
36 {{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
37 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
38 {{{{205.78973252799028, -158.12538713371103}, {174.88411103320448, -116.10101618937664}, {145.19509369736275, -56.857102571363754}}}, 0.871667147f} id=3
39 {{{{252.08225670812539, -156.90491625851064}, {185.93099479842493, -160.81544543232982}, {134.08399674208422, -155.06258330544892}}}, 0.835816324f} id=6
40 </div>
41
42 <div id="sect5">
43 {{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
44 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
45 {{{{205.78973252799028, -158.12538713371103}, {174.88411103320448, -116.10101618937664}, {145.19509369736275, -56.857102571363754}}}, 0.871667147f} id=3
46 {{{{252.08225670812539, -156.90491625851064}, {219.70109133058406, -158.81912754088933}, {190.17095392508796, -158.38373974664466}}}, 0.858306944f} id=6
47 </div>
48
49 <div id="sect6">
50 {{{{306.588013,-227.983994}, {212.464996,-262.242004}, {95.5512009,58.9763985}}}, 0.707107008f},
51 {{{{377.218994,-141.981003}, {40.578701,-201.339996}, {23.1854992,-102.697998}}}, 0.707107008f},
52 {{{{205.78973252799028, -158.12538713371103}, {190.33692178059735, -137.11320166154385}, {174.87004877564593, -111.2132534799228}}}, 0.858117759f} id=3
53 {{{{252.08225670812539, -156.90491625851064}, {219.70109133058406, -158.81912754088933}, {190.17095392508796, -158.38373974664466}}}, 0.858306944f} id=6
54 </div>
55
56 </div>
57
58 <script type="text/javascript">
59
60 var testDivs = [
61 sect0,
62 sect1,
63 sect2,
64 sect3,
65 sect4,
66 sect5,
67 sect6,
68 ];
69
70     var decimal_places = 3;
71
72     var tests = [];
73     var testTitles = [];
74     var testIndex = 0;
75     var ctx;
76
77     var subscale = 1;
78     var xmin, xmax, ymin, ymax;
79     var scale;
80     var initScale;
81     var mouseX, mouseY;
82     var mouseDown = false;
83     var srcLeft, srcTop;
84     var screenWidth, screenHeight;
85     var drawnPts;
86     var curveT = 0;
87     var curveW = -1;
88
89     var lastX, lastY;
90     var activeCurve = [];
91     var activePt;
92     var ids = [];
93
94     var focus_on_selection = 0;
95     var draw_t = false;
96     var draw_w = false;
97     var draw_closest_t = false;
98     var draw_cubic_red = false;
99     var draw_derivative = false;
100     var draw_endpoints = 2;
101     var draw_id = 0;
102     var draw_midpoint = 0;
103     var draw_mouse_xy = false;
104     var draw_order = false;
105     var draw_point_xy = false;
106     var draw_ray_intersect = false;
107     var draw_quarterpoint = 0;
108     var draw_tangents = 1;
109     var draw_sortpoint = 0;
110     var retina_scale = !!window.devicePixelRatio;
111
112     function parse(test, title) {
113         var curveStrs = test.split("{{");
114         var pattern = /-?\d+\.*\d*e?-?\d*/g;
115         var curves = [];
116         for (var c in curveStrs) {
117             var curveStr = curveStrs[c];
118             var idPart = curveStr.split("id=");
119             var id = -1;
120             if (idPart.length == 2) {
121                 id = parseInt(idPart[1]);
122                 curveStr = idPart[0];
123             }
124             var points = curveStr.match(pattern);
125             var pts = [];
126             for (var wd in points) {
127                 var num = parseFloat(points[wd]);
128                 if (isNaN(num)) continue;
129                 pts.push(num);
130             }
131             if (pts.length > 2) {
132                 curves.push(pts);
133             }
134             if (id >= 0) {
135                 ids.push(id);
136                 ids.push(pts);
137             }
138         }
139         if (curves.length >= 1) {
140             tests.push(curves);
141             testTitles.push(title);
142         }
143     }
144
145     function init(test) {
146         var canvas = document.getElementById('canvas');
147         if (!canvas.getContext) return;
148         ctx = canvas.getContext('2d');
149         var resScale = retina_scale && window.devicePixelRatio ? window.devicePixelRatio : 1;
150         var unscaledWidth = window.innerWidth - 20;
151         var unscaledHeight = window.innerHeight - 20;
152         screenWidth = unscaledWidth;
153         screenHeight = unscaledHeight;
154         canvas.width = unscaledWidth * resScale;
155         canvas.height = unscaledHeight * resScale;
156         canvas.style.width = unscaledWidth + 'px';
157         canvas.style.height = unscaledHeight + 'px';
158         if (resScale != 1) {
159             ctx.scale(resScale, resScale);
160         }
161         xmin = Infinity;
162         xmax = -Infinity;
163         ymin = Infinity;
164         ymax = -Infinity;
165         for (var curves in test) {
166             var curve = test[curves];
167             var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
168             for (var idx = 0; idx < last; idx += 2) {
169                 xmin = Math.min(xmin, curve[idx]);
170                 xmax = Math.max(xmax, curve[idx]);
171                 ymin = Math.min(ymin, curve[idx + 1]);
172                 ymax = Math.max(ymax, curve[idx + 1]);
173             }
174         }
175         xmin -= Math.min(1, Math.max(xmax - xmin, ymax - ymin));
176         var testW = xmax - xmin;
177         var testH = ymax - ymin;
178         subscale = 1;
179         while (testW * subscale < 0.1 && testH * subscale < 0.1) {
180             subscale *= 10;
181         }
182         while (testW * subscale > 10 && testH * subscale > 10) {
183             subscale /= 10;
184         }
185         setScale(xmin, xmax, ymin, ymax);
186         mouseX = (screenWidth / 2) / scale + srcLeft;
187         mouseY = (screenHeight / 2) / scale + srcTop;
188         initScale = scale;
189     }
190
191     function setScale(x0, x1, y0, y1) {
192         var srcWidth = x1 - x0;
193         var srcHeight = y1 - y0;
194         var usableWidth = screenWidth;
195         var xDigits = Math.ceil(Math.log(Math.abs(xmax)) / Math.log(10));
196         var yDigits = Math.ceil(Math.log(Math.abs(ymax)) / Math.log(10));
197         usableWidth -= (xDigits + yDigits) * 10;
198         usableWidth -= decimal_places * 10;
199         var hscale = usableWidth / srcWidth;
200         var vscale = screenHeight / srcHeight;
201         scale = Math.min(hscale, vscale);
202         var invScale = 1 / scale;
203         var sxmin = x0 - invScale * 5;
204         var symin = y0 - invScale * 10;
205         var sxmax = x1 + invScale * (6 * decimal_places + 10);
206         var symax = y1 + invScale * 10;
207         srcWidth = sxmax - sxmin;
208         srcHeight = symax - symin;
209         hscale = usableWidth / srcWidth;
210         vscale = screenHeight / srcHeight;
211         scale = Math.min(hscale, vscale);
212         srcLeft = sxmin;
213         srcTop = symin;
214     }
215
216 function dxy_at_t(curve, t) {
217     var dxy = {};
218     if (curve.length == 6) {
219         var a = t - 1;
220         var b = 1 - 2 * t;
221         var c = t;
222         dxy.x = a * curve[0] + b * curve[2] + c * curve[4];
223         dxy.y = a * curve[1] + b * curve[3] + c * curve[5];
224     } else if (curve.length == 7) {
225         var p20x = curve[4] - curve[0];
226         var p20y = curve[5] - curve[1];
227         var p10xw = (curve[2] - curve[0]) * curve[6];
228         var p10yw = (curve[3] - curve[1]) * curve[6];
229         var coeff0x = curve[6] * p20x - p20x;
230         var coeff0y = curve[6] * p20y - p20y;
231         var coeff1x = p20x - 2 * p10xw;
232         var coeff1y = p20y - 2 * p10yw;
233         dxy.x = t * (t * coeff0x + coeff1x) + p10xw;
234         dxy.y = t * (t * coeff0y + coeff1y) + p10yw;
235     } else if (curve.length == 8) {
236         var one_t = 1 - t;
237         var a = curve[0];
238         var b = curve[2];
239         var c = curve[4];
240         var d = curve[6];
241         dxy.x = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
242         a = curve[1];
243         b = curve[3];
244         c = curve[5];
245         d = curve[7];
246         dxy.y = 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t);
247     }
248     return dxy;
249 }
250
251     var flt_epsilon = 1.19209290E-07;
252
253     function approximately_zero(A) {
254         return Math.abs(A) < flt_epsilon;
255     }
256
257     function approximately_zero_inverse(A) {
258         return Math.abs(A) > (1 / flt_epsilon);
259     }
260
261     function quad_real_roots(A, B, C) {
262         var s = [];
263         var p = B / (2 * A);
264         var q = C / A;
265         if (approximately_zero(A) && (approximately_zero_inverse(p)
266                 || approximately_zero_inverse(q))) {
267             if (approximately_zero(B)) {
268                 if (C == 0) {
269                     s[0] = 0;
270                 }
271                 return s;
272             }
273             s[0] = -C / B;
274             return s;
275         }
276         /* normal form: x^2 + px + q = 0 */
277         var p2 = p * p;
278         if (!approximately_zero(p2 - q) && p2 < q) {
279             return s;
280         }
281         var sqrt_D = 0;
282         if (p2 > q) {
283             sqrt_D = Math.sqrt(p2 - q);
284         }
285         s[0] = sqrt_D - p;
286         var flip = -sqrt_D - p;
287         if (!approximately_zero(s[0] - flip)) {
288             s[1] = flip;
289         }
290         return s;
291     }
292
293     function cubic_real_roots(A, B, C, D) {
294         if (approximately_zero(A)) {  // we're just a quadratic
295             return quad_real_roots(B, C, D);
296         }
297         if (approximately_zero(D)) {  // 0 is one root
298             var s = quad_real_roots(A, B, C);
299             for (var i = 0; i < s.length; ++i) {
300                 if (approximately_zero(s[i])) {
301                     return s;
302                 }
303             }
304             s.push(0);
305             return s;
306         }
307         if (approximately_zero(A + B + C + D)) {  // 1 is one root
308             var s = quad_real_roots(A, A + B, -D);
309             for (var i = 0; i < s.length; ++i) {
310                 if (approximately_zero(s[i] - 1)) {
311                     return s;
312                 }
313             }
314             s.push(1);
315             return s;
316         }
317         var a, b, c;
318         var invA = 1 / A;
319         a = B * invA;
320         b = C * invA;
321         c = D * invA;
322         var a2 = a * a;
323         var Q = (a2 - b * 3) / 9;
324         var R = (2 * a2 * a - 9 * a * b + 27 * c) / 54;
325         var R2 = R * R;
326         var Q3 = Q * Q * Q;
327         var R2MinusQ3 = R2 - Q3;
328         var adiv3 = a / 3;
329         var r;
330         var roots = [];
331         if (R2MinusQ3 < 0) {   // we have 3 real roots
332             var theta = Math.acos(R / Math.sqrt(Q3));
333             var neg2RootQ = -2 * Math.sqrt(Q);
334             r = neg2RootQ * Math.cos(theta / 3) - adiv3;
335             roots.push(r);
336             r = neg2RootQ * Math.cos((theta + 2 * Math.PI) / 3) - adiv3;
337             if (!approximately_zero(roots[0] - r)) {
338                 roots.push(r);
339             }
340             r = neg2RootQ * Math.cos((theta - 2 * Math.PI) / 3) - adiv3;
341             if (!approximately_zero(roots[0] - r) && (roots.length == 1
342                         || !approximately_zero(roots[1] - r))) {
343                 roots.push(r);
344             }
345         } else {  // we have 1 real root
346             var sqrtR2MinusQ3 = Math.sqrt(R2MinusQ3);
347             var A = Math.abs(R) + sqrtR2MinusQ3;
348             A = Math.pow(A, 1/3);
349             if (R > 0) {
350                 A = -A;
351             }
352             if (A != 0) {
353                 A += Q / A;
354             }
355             r = A - adiv3;
356             roots.push(r);
357             if (approximately_zero(R2 - Q3)) {
358                 r = -A / 2 - adiv3;
359                 if (!approximately_zero(roots[0] - r)) {
360                     roots.push(r);
361                 }
362             }
363         }
364         return roots;
365     }
366
367     function approximately_zero_or_more(tValue) {
368         return tValue >= -flt_epsilon;
369     }
370
371     function approximately_one_or_less(tValue) {
372         return tValue <= 1 + flt_epsilon;
373     }
374
375     function approximately_less_than_zero(tValue) {
376         return tValue < flt_epsilon;
377     }
378
379     function approximately_greater_than_one(tValue) {
380         return tValue > 1 - flt_epsilon;
381     }
382
383     function add_valid_ts(s) {
384         var t = [];
385     nextRoot:
386         for (var index = 0; index < s.length; ++index) {
387             var tValue = s[index];
388             if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) {
389                 if (approximately_less_than_zero(tValue)) {
390                     tValue = 0;
391                 } else if (approximately_greater_than_one(tValue)) {
392                     tValue = 1;
393                 }
394                 for (var idx2 = 0; idx2 < t.length; ++idx2) {
395                     if (approximately_zero(t[idx2] - tValue)) {
396                         continue nextRoot;
397                     }
398                 }
399                 t.push(tValue);
400             }
401         }
402         return t;
403     }
404
405     function quad_roots(A, B, C) {
406         var s = quad_real_roots(A, B, C);
407         var foundRoots = add_valid_ts(s);
408         return foundRoots;
409     }
410
411     function cubic_roots(A, B, C, D) {
412         var s = cubic_real_roots(A, B, C, D);
413         var foundRoots = add_valid_ts(s);
414         return foundRoots;
415     }
416
417     function ray_curve_intersect(startPt, endPt, curve) {
418         var adj = endPt[0] - startPt[0];
419         var opp = endPt[1] - startPt[1];
420         var r = [];
421         var len = (curve.length == 7 ? 6 : curve.length) / 2;
422         for (var n = 0; n < len; ++n) {
423             r[n] = (curve[n * 2 + 1] - startPt[1]) * adj - (curve[n * 2] - startPt[0]) * opp;
424         }
425         if (curve.length == 6) {
426             var A = r[2];
427             var B = r[1];
428             var C = r[0];
429             A += C - 2 * B;  // A = a - 2*b + c
430             B -= C;  // B = -(b - c)
431             return quad_roots(A, 2 * B, C);
432         }
433         if (curve.length == 7) {
434             var A = r[2];
435             var B = r[1] * curve[6];
436             var C = r[0];
437             A += C - 2 * B;  // A = a - 2*b + c
438             B -= C;  // B = -(b - c)
439             return quad_roots(A, 2 * B, C);
440         }
441         var A = r[3];       // d
442         var B = r[2] * 3;   // 3*c
443         var C = r[1] * 3;   // 3*b
444         var D = r[0];       // a
445         A -= D - C + B;     // A =   -a + 3*b - 3*c + d
446         B += 3 * D - 2 * C; // B =  3*a - 6*b + 3*c
447         C -= 3 * D;         // C = -3*a + 3*b
448         return cubic_roots(A, B, C, D);
449     }
450
451     function x_at_t(curve, t) {
452         var one_t = 1 - t;
453         if (curve.length == 4) {
454             return one_t * curve[0] + t * curve[2];
455         }
456         var one_t2 = one_t * one_t;
457         var t2 = t * t;
458         if (curve.length == 6) {
459             return one_t2 * curve[0] + 2 * one_t * t * curve[2] + t2 * curve[4];
460         }
461         if (curve.length == 7) {
462             var numer = one_t2 * curve[0] + 2 * one_t * t * curve[2] * curve[6]
463                     + t2 * curve[4];
464             var denom = one_t2            + 2 * one_t * t            * curve[6]
465                     + t2;
466             return numer / denom;
467         }
468         var a = one_t2 * one_t;
469         var b = 3 * one_t2 * t;
470         var c = 3 * one_t * t2;
471         var d = t2 * t;
472         return a * curve[0] + b * curve[2] + c * curve[4] + d * curve[6];
473     }
474
475     function y_at_t(curve, t) {
476         var one_t = 1 - t;
477         if (curve.length == 4) {
478             return one_t * curve[1] + t * curve[3];
479         }
480         var one_t2 = one_t * one_t;
481         var t2 = t * t;
482         if (curve.length == 6) {
483             return one_t2 * curve[1] + 2 * one_t * t * curve[3] + t2 * curve[5];
484         }
485         if (curve.length == 7) {
486             var numer = one_t2 * curve[1] + 2 * one_t * t * curve[3] * curve[6]
487                     + t2 * curve[5];
488             var denom = one_t2            + 2 * one_t * t            * curve[6]
489                     + t2;
490             return numer / denom;
491         }
492         var a = one_t2 * one_t;
493         var b = 3 * one_t2 * t;
494         var c = 3 * one_t * t2;
495         var d = t2 * t;
496         return a * curve[1] + b * curve[3] + c * curve[5] + d * curve[7];
497     }
498
499     function drawPointAtT(curve) {
500         var x = x_at_t(curve, curveT);
501         var y = y_at_t(curve, curveT);
502         drawPoint(x, y);
503     }
504
505     function drawLine(x1, y1, x2, y2) {
506         ctx.beginPath();
507         ctx.moveTo((x1 - srcLeft) * scale,
508                 (y1 - srcTop) * scale);
509         ctx.lineTo((x2 - srcLeft) * scale,
510                 (y2 - srcTop) * scale);
511         ctx.stroke();
512     }
513
514     function drawPoint(px, py) {
515         for (var pts = 0; pts < drawnPts.length; pts += 2) {
516             var x = drawnPts[pts];
517             var y = drawnPts[pts + 1];
518             if (px == x && py == y) {
519                 return;
520             }
521         }
522         drawnPts.push(px);
523         drawnPts.push(py);
524         var _px = (px - srcLeft) * scale;
525         var _py = (py - srcTop) * scale;
526         ctx.beginPath();
527         ctx.arc(_px, _py, 3, 0, Math.PI * 2, true);
528         ctx.closePath();
529         ctx.stroke();
530         if (draw_point_xy) {
531             var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
532             ctx.font = "normal 10px Arial";
533             ctx.textAlign = "left";
534             ctx.fillStyle = "black";
535             ctx.fillText(label, _px + 5, _py);
536         }
537     }
538
539     function drawPointSolid(px, py) {
540         drawPoint(px, py);
541         ctx.fillStyle = "rgba(0,0,0, 0.4)";
542         ctx.fill();
543     }
544
545     function crossPt(origin, pt1, pt2) {
546         return ((pt1[0] - origin[0]) * (pt2[1] - origin[1])
547               - (pt1[1] - origin[1]) * (pt2[0] - origin[0])) > 0 ? 0 : 1;
548     }
549
550     // may not work well for cubics
551     function curveClosestT(curve, x, y) {
552         var closest = -1;
553         var closestDist = Infinity;
554         var l = Infinity, t = Infinity, r = -Infinity, b = -Infinity;
555         for (var i = 0; i < 16; ++i) {
556             var testX = x_at_t(curve, i / 16);
557             l = Math.min(testX, l);
558             r = Math.max(testX, r);
559             var testY = y_at_t(curve, i / 16);
560             t = Math.min(testY, t);
561             b = Math.max(testY, b);
562             var dx = testX - x;
563             var dy = testY - y;
564             var dist = dx * dx + dy * dy;
565             if (closestDist > dist) {
566                 closestDist = dist;
567                 closest = i;
568             }
569         }
570         var boundsX = r - l;
571         var boundsY = b - t;
572         var boundsDist = boundsX * boundsX + boundsY * boundsY;
573         if (closestDist > boundsDist) {
574             return -1;
575         }
576         console.log("closestDist = " + closestDist + " boundsDist = " + boundsDist
577                 + " t = " + closest / 16);
578         return closest / 16;
579     }
580
581     var kMaxConicToQuadPOW2 = 5;
582
583     function computeQuadPOW2(curve, tol) {
584         var a = curve[6] - 1;
585         var k = a / (4 * (2 + a));
586         var x = k * (curve[0] - 2 * curve[2] + curve[4]);
587         var y = k * (curve[1] - 2 * curve[3] + curve[5]);
588
589         var error = Math.sqrt(x * x + y * y);
590         var pow2;
591         for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) {
592             if (error <= tol) {
593                 break;
594             }
595             error *= 0.25;
596         }
597         return pow2;
598     }
599
600     function subdivide_w_value(w) {
601         return Math.sqrt(0.5 + w * 0.5);
602     }
603
604     function chop(curve, part1, part2) {
605         var w = curve[6];
606         var scale = 1 / (1 + w);
607         part1[0] = curve[0];
608         part1[1] = curve[1];
609         part1[2] = (curve[0] + curve[2] * w) * scale;
610         part1[3] = (curve[1] + curve[3] * w) * scale;
611         part1[4] = part2[0] = (curve[0] + (curve[2] * w) * 2 + curve[4]) * scale * 0.5;
612         part1[5] = part2[1] = (curve[1] + (curve[3] * w) * 2 + curve[5]) * scale * 0.5;
613         part2[2] = (curve[2] * w + curve[4]) * scale;
614         part2[3] = (curve[3] * w + curve[5]) * scale;
615         part2[4] = curve[4];
616         part2[5] = curve[5];
617         part1[6] = part2[6] = subdivide_w_value(w);
618     }
619
620     function subdivide(curve, level, pts) {
621         if (0 == level) {
622             pts.push(curve[2]);
623             pts.push(curve[3]);
624             pts.push(curve[4]);
625             pts.push(curve[5]);
626         } else {
627             var part1 = [], part2 = [];
628             chop(curve, part1, part2);
629             --level;
630             subdivide(part1, level, pts);
631             subdivide(part2, level, pts);
632         }
633     }
634
635     function chopIntoQuadsPOW2(curve, pow2, pts) {
636         subdivide(curve, pow2, pts);
637         return 1 << pow2;
638     }
639
640     function drawConic(curve, srcLeft, srcTop, scale) {
641         var tol = 1 / scale;
642         var pow2 = computeQuadPOW2(curve, tol);
643         var pts = [];
644         chopIntoQuadsPOW2(curve, pow2, pts);
645         for (var i = 0; i < pts.length; i += 4) {
646             ctx.quadraticCurveTo(
647                 (pts[i + 0] - srcLeft) * scale, (pts[i + 1] - srcTop) * scale,
648                 (pts[i + 2] - srcLeft) * scale, (pts[i + 3] - srcTop) * scale);
649         }
650     }
651
652     function draw(test, title) {
653         ctx.font = "normal 50px Arial";
654         ctx.textAlign = "left";
655         ctx.fillStyle = "rgba(0,0,0, 0.1)";
656         ctx.fillText(title, 50, 50);
657         ctx.font = "normal 10px Arial";
658         //  ctx.lineWidth = "1.001"; "0.999";
659         var hullStarts = [];
660         var hullEnds = [];
661         var midSpokes = [];
662         var midDist = [];
663         var origin = [];
664         var shortSpokes = [];
665         var shortDist = [];
666         var sweeps = [];
667         drawnPts = [];
668         for (var curves in test) {
669             var curve = test[curves];
670             origin.push(curve[0]);
671             origin.push(curve[1]);
672             var startPt = [];
673             startPt.push(curve[2]);
674             startPt.push(curve[3]);
675             hullStarts.push(startPt);
676             var endPt = [];
677             if (curve.length == 4) {
678                 endPt.push(curve[2]);
679                 endPt.push(curve[3]);
680             } else if (curve.length == 6 || curve.length == 7) {
681                 endPt.push(curve[4]);
682                 endPt.push(curve[5]);
683             } else if (curve.length == 8) {
684                 endPt.push(curve[6]);
685                 endPt.push(curve[7]);
686             }
687             hullEnds.push(endPt);
688             var sweep = crossPt(origin, startPt, endPt);
689             sweeps.push(sweep);
690             var midPt = [];
691             midPt.push(x_at_t(curve, 0.5));
692             midPt.push(y_at_t(curve, 0.5));
693             midSpokes.push(midPt);
694             var shortPt = [];
695             shortPt.push(x_at_t(curve, 0.25));
696             shortPt.push(y_at_t(curve, 0.25));
697             shortSpokes.push(shortPt);
698             var dx = midPt[0] - origin[0];
699             var dy = midPt[1] - origin[1];
700             var dist = Math.sqrt(dx * dx + dy * dy);
701             midDist.push(dist);
702             dx = shortPt[0] - origin[0];
703             dy = shortPt[1] - origin[1];
704             dist = Math.sqrt(dx * dx + dy * dy);
705             shortDist.push(dist);
706         }
707         var intersect = [];
708         var useIntersect = false;
709         var maxWidth = Math.max(xmax - xmin, ymax - ymin);
710         for (var curves in test) {
711             var curve = test[curves];
712             if (curve.length >= 6 && curve.length <= 8) {
713                 var opp = curves == 0 || curves == 1 ? 0 : 1;
714                 var sects = ray_curve_intersect(origin, hullEnds[opp], curve);
715                 intersect.push(sects);
716                 if (sects.length > 1) {
717                     var intersection = sects[0];
718                     if (intersection == 0) {
719                         intersection = sects[1];
720                     }
721                     var ix = x_at_t(curve, intersection) - origin[0];
722                     var iy = y_at_t(curve, intersection) - origin[1];
723                     var ex = hullEnds[opp][0] - origin[0];
724                     var ey = hullEnds[opp][1] - origin[1];
725                     if (ix * ex >= 0 && iy * ey >= 0) {
726                         var iDist = Math.sqrt(ix * ix + iy * iy);
727                         var eDist = Math.sqrt(ex * ex + ey * ey);
728                         var delta = Math.abs(iDist - eDist) / maxWidth;
729                         if (delta > (curve.length != 8 ? 1e-5 : 1e-4)) {
730                             useIntersect ^= true;
731                         }
732                     }
733                 }
734             }
735         }
736         var midLeft = curves != 0 ? crossPt(origin, midSpokes[0], midSpokes[1]) : 0;
737         var firstInside;
738         if (useIntersect) {
739             var sect1 = intersect[0].length > 1;
740             var sIndex = sect1 ? 0 : 1;
741             var sects = intersect[sIndex];
742             var intersection = sects[0];
743             if (intersection == 0) {
744                 intersection = sects[1];
745             }
746             var curve = test[sIndex];
747             var ix = x_at_t(curve, intersection) - origin[0];
748             var iy = y_at_t(curve, intersection) - origin[1];
749             var opp = sect1 ? 1 : 0;
750             var ex = hullEnds[opp][0] - origin[0];
751             var ey = hullEnds[opp][1] - origin[1];
752             var iDist = ix * ix + iy * iy;
753             var eDist = ex * ex + ey * ey;
754             firstInside = (iDist > eDist) ^ (sIndex == 0) ^ sweeps[0];
755 //            console.log("iDist=" + iDist + " eDist=" + eDist + " sIndex=" + sIndex
756  //                   + " sweeps[0]=" + sweeps[0]);
757         } else {
758  //           console.log("midLeft=" + midLeft);
759             firstInside = midLeft != 0;
760         }
761         var shorter = midDist[1] < midDist[0];
762         var shortLeft = shorter ? crossPt(origin, shortSpokes[0], midSpokes[1])
763                 : crossPt(origin, midSpokes[0], shortSpokes[1]);
764         var startCross = crossPt(origin, hullStarts[0], hullStarts[1]);
765         var disallowShort = midLeft == startCross && midLeft == sweeps[0]
766                     && midLeft == sweeps[1];
767
768   //      console.log("midLeft=" + midLeft + " startCross=" + startCross);
769         var intersectIndex = 0;
770         for (var curves in test) {
771             var curve = test[draw_id != 2 ? curves : test.length - curves - 1];
772             if (curve.length != 4 && curve.length != 6 && curve.length != 7 && curve.length != 8) {
773                 continue;
774             }
775             ctx.lineWidth = 1;
776             if (draw_tangents != 0) {
777                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
778                     ctx.strokeStyle = "rgba(255,0,0, 0.3)";
779                 } else {
780                     ctx.strokeStyle = "rgba(0,0,255, 0.3)";
781                 }
782                 drawLine(curve[0], curve[1], curve[2], curve[3]);
783                 if (draw_tangents != 2) {
784                     if (curve.length > 4) drawLine(curve[2], curve[3], curve[4], curve[5]);
785                     if (curve.length == 8) drawLine(curve[4], curve[5], curve[6], curve[7]);
786                 }
787                 if (draw_tangents != 1) {
788                     if (curve.length == 6 || curve.length == 7) {
789                         drawLine(curve[0], curve[1], curve[4], curve[5]);
790                     }
791                     if (curve.length == 8) drawLine(curve[0], curve[1], curve[6], curve[7]);
792                 }
793             }
794             ctx.beginPath();
795             ctx.moveTo((curve[0] - srcLeft) * scale, (curve[1] - srcTop) * scale);
796             if (curve.length == 4) {
797                 ctx.lineTo((curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale);
798             } else if (curve.length == 6) {
799                 ctx.quadraticCurveTo(
800                     (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
801                     (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale);
802             } else if (curve.length == 7) {
803                 drawConic(curve, srcLeft, srcTop, scale);
804             } else {
805                 ctx.bezierCurveTo(
806                     (curve[2] - srcLeft) * scale, (curve[3] - srcTop) * scale,
807                     (curve[4] - srcLeft) * scale, (curve[5] - srcTop) * scale,
808                     (curve[6] - srcLeft) * scale, (curve[7] - srcTop) * scale);
809             }
810             if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
811                 ctx.strokeStyle = "rgba(255,0,0, 1)";
812             } else {
813                 ctx.strokeStyle = "rgba(0,0,255, 1)";
814             }
815             ctx.stroke();
816             if (draw_endpoints > 0) {
817                 drawPoint(curve[0], curve[1]);
818                 if (draw_endpoints > 1 || curve.length == 4) {
819                     drawPoint(curve[2], curve[3]);
820                 }
821                 if (curve.length == 6 || curve.length == 7 ||
822                         (draw_endpoints > 1 && curve.length == 8)) {
823                     drawPoint(curve[4], curve[5]);
824                 }
825                 if (curve.length == 8) drawPoint(curve[6], curve[7]);
826             }
827             if (draw_midpoint != 0) {
828                 if ((curves == 0) == (midLeft == 0)) {
829                     ctx.strokeStyle = "rgba(0,180,127, 0.6)";
830                 } else {
831                     ctx.strokeStyle = "rgba(127,0,127, 0.6)";
832                 }
833                 var midX = x_at_t(curve, 0.5);
834                 var midY = y_at_t(curve, 0.5);
835                 drawPointSolid(midX, midY);
836                 if (draw_midpoint > 1) {
837                     drawLine(curve[0], curve[1], midX, midY);
838                 }
839             }
840             if (draw_quarterpoint != 0) {
841                 if ((curves == 0) == (shortLeft == 0)) {
842                     ctx.strokeStyle = "rgba(0,191,63, 0.6)";
843                 } else {
844                     ctx.strokeStyle = "rgba(63,0,191, 0.6)";
845                 }
846                 var midT = (curves == 0) == shorter ? 0.25 : 0.5;
847                 var midX = x_at_t(curve, midT);
848                 var midY = y_at_t(curve, midT);
849                 drawPointSolid(midX, midY);
850                 if (draw_quarterpoint > 1) {
851                     drawLine(curve[0], curve[1], midX, midY);
852                 }
853             }
854             if (draw_sortpoint != 0) {
855                 if ((curves == 0) == ((disallowShort == -1 ? midLeft : shortLeft) == 0)) {
856                     ctx.strokeStyle = "rgba(0,155,37, 0.6)";
857                 } else {
858                     ctx.strokeStyle = "rgba(37,0,155, 0.6)";
859                 }
860                 var midT = (curves == 0) == shorter && disallowShort != curves ? 0.25 : 0.5;
861                 console.log("curves=" + curves + " disallowShort=" + disallowShort
862                         + " midLeft=" + midLeft + " shortLeft=" + shortLeft
863                         + " shorter=" + shorter + " midT=" + midT);
864                 var midX = x_at_t(curve, midT);
865                 var midY = y_at_t(curve, midT);
866                 drawPointSolid(midX, midY);
867                 if (draw_sortpoint > 1) {
868                     drawLine(curve[0], curve[1], midX, midY);
869                 }
870             }
871             if (draw_ray_intersect != 0) {
872                 ctx.strokeStyle = "rgba(75,45,199, 0.6)";
873                 if (curve.length >= 6 && curve.length <= 8) {
874                     var intersections = intersect[intersectIndex];
875                     for (var i in intersections) {
876                         var intersection = intersections[i];
877                         var x = x_at_t(curve, intersection);
878                         var y = y_at_t(curve, intersection);
879                         drawPointSolid(x, y);
880                         if (draw_ray_intersect > 1) {
881                             drawLine(curve[0], curve[1], x, y);
882                         }
883                     }
884                 }
885                 ++intersectIndex;
886             }
887             if (draw_order) {
888                 var px = x_at_t(curve, 0.75);
889                 var py = y_at_t(curve, 0.75);
890                 var _px = (px - srcLeft) * scale;
891                 var _py = (py - srcTop) * scale;
892                 ctx.beginPath();
893                 ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
894                 ctx.closePath();
895                 ctx.fillStyle = "white";
896                 ctx.fill();
897                 if (draw_cubic_red ? curve.length == 8 : firstInside == curves) {
898                     ctx.strokeStyle = "rgba(255,0,0, 1)";
899                     ctx.fillStyle = "rgba(255,0,0, 1)";
900                 } else {
901                     ctx.strokeStyle = "rgba(0,0,255, 1)";
902                     ctx.fillStyle = "rgba(0,0,255, 1)";
903                 }
904                 ctx.stroke();
905                 ctx.font = "normal 16px Arial";
906                 ctx.textAlign = "center";
907                 ctx.fillText(parseInt(curves) + 1, _px, _py + 5);
908             }
909             if (draw_closest_t) {
910                 var t = curveClosestT(curve, mouseX, mouseY);
911                 if (t >= 0) {
912                     var x = x_at_t(curve, t);
913                     var y = y_at_t(curve, t);
914                     drawPointSolid(x, y);
915                 }
916             }
917             if (!approximately_zero(scale - initScale)) {
918                 ctx.font = "normal 20px Arial";
919                 ctx.fillStyle = "rgba(0,0,0, 0.3)";
920                 ctx.textAlign = "right";
921                 ctx.fillText(scale.toFixed(decimal_places) + 'x',
922                         screenWidth - 10, screenHeight - 5);
923             }
924             if (draw_t) {
925                 drawPointAtT(curve);
926             }
927             if (draw_id != 0) {
928                 var id = -1;
929                 for (var i = 0; i < ids.length; i += 2) {
930                     if (ids[i + 1] == curve) {
931                         id = ids[i];
932                         break;
933                     }
934                 }
935                 if (id >= 0) {
936                     var px = x_at_t(curve, 0.5);
937                     var py = y_at_t(curve, 0.5);
938                     var _px = (px - srcLeft) * scale;
939                     var _py = (py - srcTop) * scale;
940                     ctx.beginPath();
941                     ctx.arc(_px, _py, 15, 0, Math.PI * 2, true);
942                     ctx.closePath();
943                     ctx.fillStyle = "white";
944                     ctx.fill();
945                     ctx.strokeStyle = "rgba(255,0,0, 1)";
946                     ctx.fillStyle = "rgba(255,0,0, 1)";
947                     ctx.stroke();
948                     ctx.font = "normal 16px Arial";
949                     ctx.textAlign = "center";
950                     ctx.fillText(id, _px, _py + 5);
951                 }
952             }
953         }
954         if (draw_t) {
955             drawCurveTControl();
956         }
957         if (draw_w) {
958             drawCurveWControl();
959         }
960     }
961
962     function drawCurveTControl() {
963         ctx.lineWidth = 2;
964         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
965         ctx.beginPath();
966         ctx.rect(screenWidth - 80, 40, 28, screenHeight - 80);
967         ctx.stroke();
968         var ty = 40 + curveT * (screenHeight - 80);
969         ctx.beginPath();
970         ctx.moveTo(screenWidth - 80, ty);
971         ctx.lineTo(screenWidth - 85, ty - 5);
972         ctx.lineTo(screenWidth - 85, ty + 5);
973         ctx.lineTo(screenWidth - 80, ty);
974         ctx.fillStyle = "rgba(0,0,0, 0.6)";
975         ctx.fill();
976         var num = curveT.toFixed(decimal_places);
977         ctx.font = "normal 10px Arial";
978         ctx.textAlign = "left";
979         ctx.fillText(num, screenWidth - 78, ty);
980     }
981
982     function drawCurveWControl() {
983         var w = -1;
984         var choice = 0;
985         for (var curves in tests[testIndex]) {
986             var curve = tests[testIndex][curves];
987             if (curve.length != 7) {
988                 continue;
989             }
990             if (choice == curveW) {
991                 w = curve[6];
992                 break;
993             }
994             ++choice;
995         }
996         if (w < 0) {
997             return;
998         }
999         ctx.lineWidth = 2;
1000         ctx.strokeStyle = "rgba(0,0,0, 0.3)";
1001         ctx.beginPath();
1002         ctx.rect(screenWidth - 40, 40, 28, screenHeight - 80);
1003         ctx.stroke();
1004         var ty = 40 + w * (screenHeight - 80);
1005         ctx.beginPath();
1006         ctx.moveTo(screenWidth - 40, ty);
1007         ctx.lineTo(screenWidth - 45, ty - 5);
1008         ctx.lineTo(screenWidth - 45, ty + 5);
1009         ctx.lineTo(screenWidth - 40, ty);
1010         ctx.fillStyle = "rgba(0,0,0, 0.6)";
1011         ctx.fill();
1012         var num = w.toFixed(decimal_places);
1013         ctx.font = "normal 10px Arial";
1014         ctx.textAlign = "left";
1015         ctx.fillText(num, screenWidth - 38, ty);
1016     }
1017
1018     function ptInTControl() {
1019         var e = window.event;
1020         var tgt = e.target || e.srcElement;
1021         var left = tgt.offsetLeft;
1022         var top = tgt.offsetTop;
1023         var x = (e.clientX - left);
1024         var y = (e.clientY - top);
1025         if (x < screenWidth - 80 || x > screenWidth - 50) {
1026             return false;
1027         }
1028         if (y < 40 || y > screenHeight - 80) {
1029             return false;
1030         }
1031         curveT = (y - 40) / (screenHeight - 120);
1032         if (curveT < 0 || curveT > 1) {
1033             throw "stop execution";
1034         }
1035         return true;
1036     }
1037
1038     function ptInWControl() {
1039         var e = window.event;
1040         var tgt = e.target || e.srcElement;
1041         var left = tgt.offsetLeft;
1042         var top = tgt.offsetTop;
1043         var x = (e.clientX - left);
1044         var y = (e.clientY - top);
1045         if (x < screenWidth - 40 || x > screenWidth - 10) {
1046             return false;
1047         }
1048         if (y < 40 || y > screenHeight - 80) {
1049             return false;
1050         }
1051         var w = (y - 40) / (screenHeight - 120);
1052         if (w < 0 || w > 1) {
1053             throw "stop execution";
1054         }
1055         var choice = 0;
1056         for (var curves in tests[testIndex]) {
1057             var curve = tests[testIndex][curves];
1058             if (curve.length != 7) {
1059                 continue;
1060             }
1061             if (choice == curveW) {
1062                 curve[6] = w;
1063                 break;
1064             }
1065             ++choice;
1066         }
1067         return true;
1068     }
1069
1070     function drawTop() {
1071         init(tests[testIndex]);
1072         redraw();
1073     }
1074
1075     function redraw() {
1076         if (focus_on_selection > 0) {
1077             var focusXmin = focusYmin = Infinity;
1078             var focusXmax = focusYmax = -Infinity;
1079             var choice = 0;
1080             for (var curves in tests[testIndex]) {
1081                 if (++choice != focus_on_selection) {
1082                     continue;
1083                 }
1084                 var curve = tests[testIndex][curves];
1085                 var last = curve.length - (curve.length % 2 == 1 ? 1 : 0);
1086                 for (var idx = 0; idx < last; idx += 2) {
1087                     focusXmin = Math.min(focusXmin, curve[idx]);
1088                     focusXmax = Math.max(focusXmax, curve[idx]);
1089                     focusYmin = Math.min(focusYmin, curve[idx + 1]);
1090                     focusYmax = Math.max(focusYmax, curve[idx + 1]);
1091                 }
1092             }
1093             focusXmin -= Math.min(1, Math.max(focusXmax - focusXmin, focusYmax - focusYmin));
1094             if (focusXmin < focusXmax && focusYmin < focusYmax) {
1095                 setScale(focusXmin, focusXmax, focusYmin, focusYmax);
1096             }
1097         }
1098         ctx.beginPath();
1099         ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
1100         ctx.fillStyle = "white";
1101         ctx.fill();
1102         draw(tests[testIndex], testTitles[testIndex]);
1103     }
1104
1105     function doKeyPress(evt) {
1106         var char = String.fromCharCode(evt.charCode);
1107         var focusWasOn = false;
1108         switch (char) {
1109             case '0':
1110             case '1':
1111             case '2':
1112             case '3':
1113             case '4':
1114             case '5':
1115             case '6':
1116             case '7':
1117             case '8':
1118             case '9':
1119                 decimal_places = char - '0';
1120                 redraw();
1121                 break;
1122             case '-':
1123                 focusWasOn = focus_on_selection;
1124                 if (focusWasOn) {
1125                     focus_on_selection = false;
1126                     scale /= 1.2;
1127                 } else {
1128                     scale /= 2;
1129                 }
1130                 calcLeftTop();
1131                 redraw();
1132                 focus_on_selection = focusWasOn;
1133                 break;
1134             case '=':
1135             case '+':
1136                 focusWasOn = focus_on_selection;
1137                 if (focusWasOn) {
1138                     focus_on_selection = false;
1139                     scale *= 1.2;
1140                 } else {
1141                     scale *= 2;
1142                 }
1143                 calcLeftTop();
1144                 redraw();
1145                 focus_on_selection = focusWasOn;
1146                 break;
1147             case 'b':
1148                 draw_cubic_red ^= true;
1149                 redraw();
1150                 break;
1151             case 'c':
1152                 drawTop();
1153                 break;
1154             case 'd':
1155                 var test = tests[testIndex];
1156                 var testClone = [];
1157                 for (var curves in test) {
1158                     var c = test[curves];
1159                     var cClone = [];
1160                     for (var index = 0; index < c.length; ++index) {
1161                         cClone.push(c[index]);
1162                     }
1163                     testClone.push(cClone);
1164                 }
1165                 tests.push(testClone);
1166                 testTitles.push(testTitles[testIndex] + " copy");
1167                 testIndex = tests.length - 1;
1168                 redraw();
1169                 break;
1170             case 'e':
1171                 draw_endpoints = (draw_endpoints + 1) % 3;
1172                 redraw();
1173                 break;
1174             case 'f':
1175                 draw_derivative ^= true;
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) / scale + srcLeft;
1346         mouseY = (e.clientY - top) / scale + srcTop;
1347     }
1348
1349     function calcLeftTop() {
1350         srcLeft = mouseX - screenWidth / 2 / scale;
1351         srcTop = mouseY - screenHeight / 2 / scale;
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>