<html>
<head>
<div style="height:0">

<div id="test1">
computeDelta c1=(0,1 1,6 1,0 2,0) t1=0.0166500365 scale1=1 c2=(0,1 0,2 1,0 6,1) t2=0.126935168 scale2=1
cubicTangent t=0.0166500365 tangent=(-2.85263545,-12.6745554 2.95089079,15.1559166) pt=(0.0491276698 1.24068063) dxy=(2.90176312 13.915236)
cubicTangent t=0.126935168 tangent=(-0.852150487,0.242871519 0.961097194,2.2532568) pt=(0.0544733534 1.24806416) dxy=(0.90662384 1.00519264)
cubicDelta tangent=(-0.00039510851,-0.00189471984 0.0495227783,1.24257535) intersectLen=0.00193547772 tangentLen=14.2145708 scale=0.00390625 result=0.00404241153
cubicDelta tangent=(0.00495057512,0.00548880522 0.0495227783,1.24257535) intersectLen=0.00739156118 tangentLen=1.35365395 scale=0.00390625 result=0.00936670107
</div>

<div id="test2">
computeDelta c1=(0,1 0,2 1,0 6,1) t1=0.121215914 scale1=0.0187334021 c2=(0,1 1,6 1,0 2,0) t2=0.0167515231 scale2=0.00808482306
cubicTangent t=0.121215914 tangent=(-0.810112087,0.159501524 0.908958243,2.32468734) pt=(0.0494230781 1.24209443) dxy=(0.859535165 1.08259291)
cubicTangent t=0.0167515231 tangent=(-2.85175241,-12.6666182 2.95059667,15.1508033) pt=(0.0494221303 1.24209251) dxy=(2.90117454 13.9087108)
cubicDelta tangent=(7.4284882e-07,9.35625319e-07 0.0494223352,1.2420935) intersectLen=1.19466276e-06 tangentLen=1.38231983 scale=7.31773521e-05 result=7.40415969e-05
cubicDelta tangent=(-2.04951629e-07,-9.82572016e-07 0.0494223352,1.2420935) intersectLen=1.00371955e-06 tangentLen=14.2080628 scale=3.15813401e-05 result=3.16519844e-05
</div>

<div id="test3">
computeDelta c1=(0,1 1,6 1,0 2,0) t1=0.0167458976 scale1=6.33039689e-05 c2=(0,1 0,2 1,0 6,1) t2=0.121141872 scale2=0.000148083194
cubicTangent t=0.0167458976 tangent=(-2.85180136,-12.6670582 2.95061297,15.1510867) pt=(0.0494058095 1.24201427) dxy=(2.90120716 13.9090724)
cubicTangent t=0.121141872 tangent=(-0.809569955,0.158411583 0.908288874,2.32561689) pt=(0.0493594591 1.24201424) dxy=(0.858929414 1.08360265)
cubicDelta tangent=(-1.65436799e-05,-7.93143093e-05 0.0494223532,1.24209358) intersectLen=8.1021312e-05 tangentLen=14.2084235 scale=2.47281129e-07 result=5.94962466e-06
cubicDelta tangent=(-6.28940702e-05,-7.93454971e-05 0.0494223532,1.24209358) intersectLen=0.000101249059 tangentLen=1.38273441 scale=5.78449976e-07 result=7.38022436e-05
</div>

</div>

<script type="text/javascript">

var testDivs = [
    test3,
    test2,
    test1,
];

var scale, columns, rows, xStart, yStart;

var ticks = 10;
var at_x = 13 + 0.5;
var at_y = 23 + 0.5;
var decimal_places = 3;
var tests = [];
var testTitles = [];
var testIndex = 0;
var ctx;
var minScale = 1;
var subscale = 1;
var curveT = -1;
var drawCubics = true;
var drawQuads = true;
var drawControlLines = true;
var drawT = true;

var xmin, xmax, ymin, ymax;

function strs_to_nums(strs) {
    var result = [];
    for (var idx in strs) {
        var str = strs[idx];
        var num = parseFloat(str);
        if (isNaN(num)) {
            result.push(str);
        } else {
            result.push(num);
        }
    }
    return result;
}

function construct_regexp(pattern) {
    var escape = pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
    escape = escape.replace(/PT_VAL/g, "(-?\\d+\\.?\\d*e?-?\\d*),(-?\\d+\\.?\\d*e?-?\\d*)");
    escape = escape.replace(/T_VAL/g, "(-?\\d+\\.?\\d*e?-?\\d*)");
    escape = escape.replace(/IDX/g, "(\\d+)");
    escape = escape.replace(/OPT/g, "(\\?|-?\\d+)");
    return new RegExp(escape, 'i');
}

var COMPUTE_DELTA = 1;
var CUBIC_TANGENT = 2;
var CUBIC_DATA = 3;

var DELTA_C1_X1 = 1;
var DELTA_C1_Y1 = 2;
var DELTA_C1_X2 = 3;
var DELTA_C1_Y2 = 4;
var DELTA_C1_X3 = 5;
var DELTA_C1_Y3 = 6;
var DELTA_C1_X4 = 7;
var DELTA_C1_Y4 = 8;
var DELTA_T1 = 9;
var DELTA_SCALE1 = 10;
var DELTA_C2_X1 = 11;
var DELTA_C2_Y1 = 12;
var DELTA_C2_X2 = 13;
var DELTA_C2_Y2 = 14;
var DELTA_C2_X3 = 15;
var DELTA_C2_Y3 = 16;
var DELTA_C2_X4 = 17;
var DELTA_C2_Y4 = 18;
var DELTA_T2 = 19;
var DELTA_SCALE2 = 20;

var TANGENT_T = 1;
var TANGENT_TANGENT_X1 = 2;
var TANGENT_TANGENT_Y1 = 3;
var TANGENT_TANGENT_X2 = 4;
var TANGENT_TANGENT_Y2 = 5;
var TANGENT_PT_X = 6;
var TANGENT_PT_Y = 7;
var TANGENT_DXY_X = 8;
var TANGENT_DXY_Y = 9;

var CUBIC_TANGENT_X1 = 1;
var CUBIC_TANGENT_Y1 = 2;
var CUBIC_TANGENT_X2 = 3;
var CUBIC_TANGENT_Y2 = 4;
var CUBIC_INTERSECTION_LEN = 5;
var CUBIC_TANGENT_LEN = 6;
var CUBIC_SCALE = 7;
var CUBIC_RESULT = 8;

function parse(test, title) {
    var compute_delta = construct_regexp(" c1=(PT_VAL PT_VAL PT_VAL PT_VAL)"
        + " t1=T_VAL scale1=T_VAL c2=(PT_VAL PT_VAL PT_VAL PT_VAL) t2=T_VAL scale2=T_VAL");
    var cubic_tangent = construct_regexp(" t=T_VAL tangent=(PT_VAL PT_VAL)"
        + " pt=(T_VAL T_VAL) dxy=(T_VAL T_VAL)");
    var cubic_data = construct_regexp(" tangent=(PT_VAL PT_VAL)"
        + " intersectLen=T_VAL tangentLen=T_VAL scale=T_VAL result=T_VAL");

    var cStrs = test.split("computeDelta");
    var data = [];
    for (var cs in cStrs) {
        var str = cStrs[cs];
        if (str == "\n") {
            continue;
        }
        var tStrs = str.split("cubicTangent");
        for (var ts in tStrs) {
            str = tStrs[ts];
            if (str == "\n") {
                continue;
            }
            var dStrs = str.split("cubicDelta");
            var dataStrs;
            for (var ds in dStrs) {
                str = dStrs[ds];
                if (str == "\n") {
                    continue;
                }
                var lineMatch, lineStrs;
                if (compute_delta.test(str)) {
                    lineMatch = COMPUTE_DELTA;
                    lineStrs = compute_delta.exec(str);
                } else if (cubic_tangent.test(str)) {
                    lineMatch = CUBIC_TANGENT;
                    lineStrs = cubic_tangent.exec(str);
                } else if (cubic_data.test(str)) {
                    lineMatch = CUBIC_DATA;
                    lineStrs = cubic_data.exec(str);
                } else {
                    continue;
                }
                var line = strs_to_nums(lineStrs);
                data.push(lineMatch);
                data.push(line);
            }
        }
    }
    if (data.length >= 1) {
        tests.push(data);
        testTitles.push(title);
    }
}

function init(test) {
    var canvas = document.getElementById('canvas');
    if (!canvas.getContext) return;
    canvas.width = window.innerWidth - at_x;
    canvas.height = window.innerHeight - at_y;
    ctx = canvas.getContext('2d');
    xmin = Infinity;
    xmax = -Infinity;
    ymin = Infinity;
    ymax = -Infinity;
    var scanType = -1;
    for (var scansStr in test) {
        var scans = parseInt(scansStr);
        var scan = test[scans];
        if (scanType == -1) {
            scanType = scan;
            continue;
        }
        if (scanType == CUBIC_TANGENT) {
            for (var idx = TANGENT_TANGENT_X1; idx < TANGENT_PT_X; idx += 2) {
                xmin = Math.min(xmin, scan[idx]);
                xmax = Math.max(xmax, scan[idx]);
                ymin = Math.min(ymin, scan[idx + 1]);
                ymax = Math.max(ymax, scan[idx + 1]);
            }
        }
        scanType = -1;
    }
    var testW = xmax - xmin;
    var testH = ymax - ymin;
    subscale = 1;
    if (testW > 1e10 || testH > 1e10) {
        return;
    }
    while (testW * subscale < 0.1 && testH * subscale < 0.1) {
        subscale *= 10;
    }
    while (testW * subscale > 10 && testH * subscale > 10) {
        subscale /= 10;
    }
    calcFromScale();
}

function calcFromScale() {
    xStart = Math.floor(xmin * subscale) / subscale;
    yStart = Math.floor(ymin * subscale) / subscale;
    var xEnd = Math.ceil(xmin * subscale) / subscale;
    var yEnd = Math.ceil(ymin * subscale) / subscale;
    var cCelsW = Math.floor(ctx.canvas.width / 10);
    var cCelsH = Math.floor(ctx.canvas.height / 10);
    var testW = xEnd - xStart;
    var testH = yEnd - yStart; 
    var scaleWH = 1;
    while (cCelsW > testW * scaleWH * 10 && cCelsH > testH * scaleWH * 10) {
        scaleWH *= 10;
    }
    while (cCelsW * 10 < testW * scaleWH && cCelsH * 10 < testH * scaleWH) {
        scaleWH /= 10;
    }
    
    columns = Math.ceil(xmax * subscale) - Math.floor(xmin * subscale) + 1;
    rows = Math.ceil(ymax * subscale) - Math.floor(ymin * subscale) + 1;
    
    var hscale = ctx.canvas.width / columns / ticks;
    var vscale = ctx.canvas.height / rows / ticks;
    minScale = Math.floor(Math.min(hscale, vscale));
    scale = minScale * subscale;
}

function drawPoint(px, py, xoffset, yoffset, unit) {
    var label = px.toFixed(decimal_places) + ", " + py.toFixed(decimal_places);
    var _px = px * unit + xoffset;
    var _py = py * unit + yoffset;
    ctx.beginPath();
    ctx.arc(_px, _py, 3, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fill();
    ctx.fillText(label, _px + 5, _py);
}

function drawTPt(scan, cIdx, tIdx, xoffset, yoffset, unit) {
    var t = scan[tIdx];
    var one_t = 1 - t;
    var one_t2 = one_t * one_t;
    var a = one_t2 * one_t;
    var b = 3 * one_t2 * t;
    var t2 = t * t;
    var c = 3 * one_t * t2;
    var d = t2 * t;
    var x = a * scan[cIdx + 0] + b * scan[cIdx + 2] + c * scan[cIdx + 4] + d * scan[cIdx + 6];
    var y = a * scan[cIdx + 1] + b * scan[cIdx + 3] + c * scan[cIdx + 5] + d * scan[cIdx + 7];
    drawPoint(x, y, xoffset, yoffset, unit);
}

function draw(test, title, scale) {
    ctx.fillStyle = "rgba(0,0,0, 0.1)";
    ctx.font = "normal 50px Arial";
    ctx.fillText(title, 50, 50);
    ctx.font = "normal 10px Arial";

    var unit = scale * ticks;
    ctx.lineWidth = 1;
    var i;
    for (i = 0; i <= rows * ticks; ++i) {
        ctx.strokeStyle = (i % ticks) != 0 ? "rgb(200,200,200)" : "black";
        ctx.beginPath();
        ctx.moveTo(at_x + 0, at_y + i * minScale);
        ctx.lineTo(at_x + ticks * columns * minScale, at_y + i * minScale);
        ctx.stroke();
    }
    for (i = 0; i <= columns * ticks; ++i) {
        ctx.strokeStyle = (i % ticks) != 0 ? "rgb(200,200,200)" : "black";
        ctx.beginPath();
        ctx.moveTo(at_x + i * minScale, at_y + 0);
        ctx.lineTo(at_x + i * minScale, at_y + ticks * rows * minScale);
        ctx.stroke();
    }
 
    var xoffset = xStart * -unit + at_x;
    var yoffset = yStart * -unit + at_y;

    ctx.fillStyle = "rgb(40,80,60)"
    for (i = 0; i <= columns; i += 1)
    {
        num = xStart + i / subscale; 
        ctx.fillText(num.toFixed(decimal_places), xoffset + num * unit - 5, 10);
    }
    for (i = 0; i <= rows; i += 1)
    {
        num = yStart + i / subscale; 
        ctx.fillText(num.toFixed(decimal_places), 0, yoffset + num * unit + 0);
    }
    var scanType = -1;
    var partIndex = 0;
    for (var scans in test) {
        var scan = test[scans];
        if (scanType == -1) {
            scanType = scan;
            continue;
        }
        partIndex++;
        if (scanType == COMPUTE_DELTA) {
            ctx.beginPath();
            ctx.moveTo(xoffset + scan[DELTA_C1_X1] * unit, yoffset + scan[DELTA_C1_Y1] * unit);
            ctx.bezierCurveTo(
                xoffset + scan[DELTA_C1_X2] * unit, yoffset + scan[DELTA_C1_Y2] * unit,
                xoffset + scan[DELTA_C1_X3] * unit, yoffset + scan[DELTA_C1_Y3] * unit,
                xoffset + scan[DELTA_C1_X4] * unit, yoffset + scan[DELTA_C1_Y4] * unit);
            ctx.strokeStyle = "red"; // "rgba(0,0,0, 1.0)";
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(xoffset + scan[DELTA_C2_X1] * unit, yoffset + scan[DELTA_C2_Y1] * unit);
            ctx.bezierCurveTo(
                xoffset + scan[DELTA_C2_X2] * unit, yoffset + scan[DELTA_C2_Y2] * unit,
                xoffset + scan[DELTA_C2_X3] * unit, yoffset + scan[DELTA_C2_Y3] * unit,
                xoffset + scan[DELTA_C2_X4] * unit, yoffset + scan[DELTA_C2_Y4] * unit);
            ctx.strokeStyle = "blue"; // "rgba(0,0,0, 1.0)";
            ctx.stroke();
        }
        if (scanType == COMPUTE_DELTA && drawControlLines) {
            ctx.beginPath();
            ctx.moveTo(xoffset + scan[DELTA_C1_X1] * unit, yoffset + scan[DELTA_C1_Y1] * unit);
            ctx.lineTo(xoffset + scan[DELTA_C1_X2] * unit, yoffset + scan[DELTA_C1_Y2] * unit);
            ctx.lineTo(xoffset + scan[DELTA_C1_X3] * unit, yoffset + scan[DELTA_C1_Y3] * unit);
            ctx.lineTo(xoffset + scan[DELTA_C1_X4] * unit, yoffset + scan[DELTA_C1_Y4] * unit);
            ctx.strokeStyle = "rgba(0,0,0, 0.3)";
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(xoffset + scan[DELTA_C2_X1] * unit, yoffset + scan[DELTA_C2_Y1] * unit);
            ctx.lineTo(xoffset + scan[DELTA_C2_X2] * unit, yoffset + scan[DELTA_C2_Y2] * unit);
            ctx.lineTo(xoffset + scan[DELTA_C2_X3] * unit, yoffset + scan[DELTA_C2_Y3] * unit);
            ctx.lineTo(xoffset + scan[DELTA_C2_X4] * unit, yoffset + scan[DELTA_C2_Y4] * unit);
            ctx.strokeStyle = "rgba(0,0,0, 0.3)";
            ctx.stroke();
        }
        if (scanType == COMPUTE_DELTA && drawT) {
            drawTPt(scan, DELTA_C1_X1, DELTA_T1, xoffset, yoffset, unit);
            drawTPt(scan, DELTA_C2_X1, DELTA_T2, xoffset, yoffset, unit);
            var num = "c1=" + scan[DELTA_T1].toFixed(decimal_places)
                    + " c2=" + scan[DELTA_T2].toFixed(decimal_places);
            ctx.beginPath();
            ctx.rect(200,10,200,10);
            ctx.fillStyle="white";
            ctx.fill();
            ctx.fillStyle="black";
            ctx.fillText(num, 230, 18);
        }
        if (scanType == CUBIC_TANGENT) {
            ctx.beginPath();
            ctx.moveTo(xoffset + scan[TANGENT_TANGENT_X1] * unit, yoffset + scan[TANGENT_TANGENT_Y1] * unit);
            ctx.lineTo(xoffset + scan[TANGENT_TANGENT_X2] * unit, yoffset + scan[TANGENT_TANGENT_Y2] * unit);
            ctx.strokeStyle = partIndex > 2 ? "rgba(0,0,255, 0.7)" : "rgba(255,0,0, 0.7)";
            ctx.stroke();
        }
        scanType = -1;
    }
}

function drawTop() {
    init(tests[testIndex]);
    redraw();
}

function redraw() {
    ctx.beginPath();
    ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.fillStyle="white";
    ctx.fill();
    draw(tests[testIndex], testTitles[testIndex], scale);
}

function doKeyPress(evt) {
    var char = String.fromCharCode(evt.charCode);
    switch (char) {
    case 'c':
        drawCubics ^= true;
        redraw();
        break;
    case 'd':
        decimal_places++;
        redraw();
        break;
    case 'D':
        decimal_places--;
        if (decimal_places < 1) {
            decimal_places = 1;
        }
        redraw();
        break;
    case 'l':
        drawControlLines ^= true;
        redraw();
        break;
    case 'N':
        testIndex += 9;
    case 'n':
        if (++testIndex >= tests.length)
            testIndex = 0;
        mouseX = Infinity;
        drawTop();
        break;
    case 'P':
        testIndex -= 9;
    case 'p':
        if (--testIndex < 0)
            testIndex = tests.length - 1;
        mouseX = Infinity;
        drawTop();
        break;
    case 'q':
        drawQuads ^= true;
        redraw();
        break;
    case 't':
        drawT ^= true;
        redraw();
        break;
    case 'x':
        drawCubics ^= true;
        drawQuads ^= true;
        redraw();
        break;
    case '-':
    case '_':
        subscale /= 2;
        calcFromScale();
        redraw();
        break;
    case '+':
    case '=':
        subscale *= 2;
        calcFromScale();
        redraw();
        break;
    }
}

/*
    var e = window.event;
	var tgt = e.target || e.srcElement;
    var min = tgt.offsetTop + Math.ceil(at_y);
    var max = min + ticks * rows * minScale;
    curveT = (e.clientY - min) / (max - min);
    redraw();
}
*/

function calcXY() {
    var e = window.event;
	var tgt = e.target || e.srcElement;
    var left = tgt.offsetLeft;
    var top = tgt.offsetTop;
    var unit = scale * ticks;
    mouseX = (e.clientX - left - Math.ceil(at_x) + 1) / unit + xStart;
    mouseY = (e.clientY - top - Math.ceil(at_y)) / unit + yStart;
}

function handleMouseOver() {
 /*   calcXY();
    var num = mouseX.toFixed(decimal_places) + ", " + mouseY.toFixed(decimal_places);
    ctx.beginPath();
    ctx.rect(30,10,200,10);
    ctx.fillStyle="white";
    ctx.fill();
    ctx.fillStyle="black";
    ctx.fillText(num, 30, 18);
*/
}

function start() {
    for (i = 0; i < testDivs.length; ++i) {
        var title = testDivs[i].id.toString();
        var str = testDivs[i].firstChild.data;
        parse(str, title);
    }
    drawTop();
    window.addEventListener('keypress', doKeyPress, true);
    window.onresize = function() {
        drawTop();
    }
}

function handleMouseClick() {
    start();
}

function startx() {
}

</script>
</head>

<body onLoad="startx();">
<canvas id="canvas" width="750" height="500"
    onmousemove="handleMouseOver()"
    onclick="handleMouseClick()"
    ></canvas >
</body>
</html>