Unpack an SVG path string into different curves and lines  https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
function splitSegments(polygon) {
    if (typeof polygon !== "string") {
        throw new Error("Polygon should be a path string");
    }

    var start = null;
    var position = null;
    var result = [];
    var input = polygon;

    function stripWhitespace() {
        polygon = polygon.trim();
    }

    function readCharSeq(n) {
        var c = polygon.charCodeAt(n);
        while (c >= 48 && c <= 57) {
            n++;
            c = polygon.charCodeAt(n);
        }
        return n;
    }

    function readNumber() {
        stripWhitespace();

        var start = 0;
        var end = 0;
        if (polygon[start] === ",") {
            start++;
            end++;
        }

        if (polygon[start] === "-") {
            end++;
        }

        end = readCharSeq(end);
        if (polygon[end] === ".") {
            end++;
            end = readCharSeq(end);
        }

        var s = polygon.substring(start, end);
        if (s !== "") {
            var num = toFloat(s);
            polygon = polygon.substring(end);
            if (polygon.length && polygon[0].toLowerCase() === "e") {
                var f = 1;
                var expEnd = 0;
                if (polygon.length > 1 && polygon[1] === "-") {
                    f = -1;
                    expEnd = readCharSeq(2);
                } else {
                    expEnd = readCharSeq(1);
                }
                var exp = toFloat(polygon.substring(1, expEnd));
                if (mathAbs(exp) > 0) {
                    num *= mathPow(10, exp);
                }
                polygon = polygon.substring(expEnd);
            }
            return num;
        } else {
            throw new Error("Expected number: " + polygon);
        }
    }

    function readNumbers(n, fn) {
        stripWhitespace();
        var index = 0;
        var c = polygon.charCodeAt(0);
        while ((c >= 48 && c <= 57) || c === 44 || c === 45) {
            var numbers = [];
            for (var i = 0; i < n; i++) {
                numbers.push(readNumber());
            }
            fn(numbers, index);

            stripWhitespace();
            c = polygon.charCodeAt(0);
            index++;
        }
    }

    function readCoords(n, fn) {
        readNumbers(n * 2, function (numbers, index) {
            var coords = [];
            for (var i = 0; i < n; i++) {
                coords.push(numbers.splice(0, 2));
            }
            fn(coords, index);
        });
    }

    function pushType(itemType, offset) {
        return function (c) {
            if (offset) {
                c = c.map(function (c) {
                    return [x(c) + x(offset), y(c) + y(offset)];
                });
            }
            c.unshift(position);
            result.push({
                type: itemType,
                coords: c,
            });
            position = c[c.length - 1];
        };
    }

    function calculateCubicControlPoints(coords) {
        return [coords[0], [
            x(coords[0]) + 2.0 / 3.0 * (x(coords[1]) - x(coords[0])),
            y(coords[0]) + 2.0 / 3.0 * (y(coords[1]) - y(coords[0])),
        ], [
            x(coords[2]) + 2.0 / 3.0 * (x(coords[1]) - x(coords[2])),
            y(coords[2]) + 2.0 / 3.0 * (y(coords[1]) - y(coords[2])),
        ], coords[2],
        ];
    }

    function calculateBezierControlPoint() {
        var lastBezier = result[result.length - 1];
        var controlPoint = null;
        if (!lastBezier || lastBezier.type !== bezier3Type) {
            controlPoint = position;
        } else {
            // Calculate the mirror point of the last control point
            var lastPoint = lastBezier.coords[2];
            var xOffset = x(position) - x(lastPoint);
            var yOffset = y(position) - y(lastPoint);

            controlPoint = [x(position) + xOffset, y(position) + yOffset];
        }

        return controlPoint;
    }

    function handleArcSegment(relative) {
        readNumbers(7, function (numbers) {
            var c2 = coordAdd(numbers.slice(5, 7), relative);
            var args = [position].concat(numbers.slice(0, 5)).concat([c2]);
            var curve = arcToCurve.apply(null, args);
            for (var i = 0; i < curve.length; i++) {
                pushType(bezier3Type)(curve[i]);
            }
        });
    }

    function readSegment() {
        stripWhitespace();
        if (polygon === "") {
            return;
        }

        var operator = polygon[0];
        polygon = polygon.substring(1);

        var pushLine = pushType(lineType);
        var origin = [0, 0];

        switch (operator) {
            case "M":
                readCoords(1, function (c, i) {
                    if (i === 0) {
                        position = c[0];
                        if (!start) {
                            start = position;
                        }
                    } else {
                        pushType(lineType)(c);
                    }
                });
                break;
            case "m":
                readCoords(1, function (c, i) {
                    if (i === 0) {
                        if (!position) {
                            position = c[0];
                        } else {
                            position = coordAdd(c, position);
                        }

                        if (!start) {
                            start = position;
                        }
                    } else {
                        var c0 = c[0];
                        pushType(lineType)([coordAdd(c0, position)]);
                    }
                });
                break;
            case "C":
                readCoords(3, pushType(bezier3Type));
                break;
            case "c":
                readCoords(3, pushType(bezier3Type, position));
                break;
            case "Q":
                readCoords(2, function (coords) {
                    coords.unshift(position);
                    coords = calculateCubicControlPoints(coords);
                    coords.shift();
                    pushType(bezier3Type)(coords);
                });
                break;
            case "q":
                readCoords(2, function (coords) {
                    coords = coords.map(function (c) { return coordAdd(c, position); });
                    coords.unshift(position);
                    coords = calculateCubicControlPoints(coords);
                    coords.shift();
                    pushType(bezier3Type)(coords);
                });
                break;
            case "S":
                readCoords(2, function (coords) {
                    var controlPoint = calculateBezierControlPoint();
                    coords.unshift(controlPoint);
                    pushType(bezier3Type)(coords);
                });
                break;
            case "s":
                readCoords(2, function (coords) {
                    var controlPoint = calculateBezierControlPoint();
                    coords = coords.map(function (c) { return coordAdd(c, position); });
                    coords.unshift(controlPoint);
                    pushType(bezier3Type)(coords);
                });
                break;
            case "A":
                handleArcSegment(origin);
                break;
            case "a":
                handleArcSegment(position);
                break;
            case "L":
                readCoords(1, pushType(lineType));
                break;
            case "l":
                readCoords(1, function (c) {
                    pushLine([[x(c[0]) + x(position), y(c[0]) + y(position)]]);
                });
                break;
            case "H":
                pushType(lineType)([[readNumber(), y(position)]]);
                break;
            case "h":
                pushType(lineType, position)([[readNumber(), 0]]);
                break;
            case "V":
                pushType(lineType)([[x(position), readNumber()]]);
                break;
            case "v":
                pushType(lineType, position)([[0, readNumber()]]);
                break;
            case "Z":
            case "z":
                if (!coordEqual(position, start)) {
                    pushType(lineType)([start]);
                }
                break;
            default:
                throw new Error("Unknown operator: " + operator + " for polygon '" + input + "'");
        }
    }

    while (polygon.length > 0) {
        readSegment();
    }

    // Remove zero-length lines
    for (var i = 0; i < result.length; i++) {
        var segment = result[i];
        if (segment.type === lineType && coordEqual(segment.coords[0], segment.coords[1])) {
            result.splice(i, 1);
            i--;
        }
    }

    return result;
}