UNPKG

roughjs-es5

Version:

Create graphics using HTML Canvas or SVG with a hand-drawn, sketchy, appearance.

581 lines (543 loc) 18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var PathToken = function () { function PathToken(type, text) { _classCallCheck(this, PathToken); this.type = type; this.text = text; } _createClass(PathToken, [{ key: "isType", value: function isType(type) { return this.type === type; } }]); return PathToken; }(); var ParsedPath = function () { function ParsedPath(d) { _classCallCheck(this, ParsedPath); this.PARAMS = { A: ["rx", "ry", "x-axis-rotation", "large-arc-flag", "sweep-flag", "x", "y"], a: ["rx", "ry", "x-axis-rotation", "large-arc-flag", "sweep-flag", "x", "y"], C: ["x1", "y1", "x2", "y2", "x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], H: ["x"], h: ["x"], L: ["x", "y"], l: ["x", "y"], M: ["x", "y"], m: ["x", "y"], Q: ["x1", "y1", "x", "y"], q: ["x1", "y1", "x", "y"], S: ["x2", "y2", "x", "y"], s: ["x2", "y2", "x", "y"], T: ["x", "y"], t: ["x", "y"], V: ["y"], v: ["y"], Z: [], z: [] }; this.COMMAND = 0; this.NUMBER = 1; this.EOD = 2; this.segments = []; this.d = d || ""; this.parseData(d); this.processPoints(); } _createClass(ParsedPath, [{ key: "loadFromSegments", value: function loadFromSegments(segments) { this.segments = segments; this.processPoints(); } }, { key: "processPoints", value: function processPoints() { var first = null, prev = null, currentPoint = [0, 0]; for (var i = 0; i < this.segments.length; i++) { var s = this.segments[i]; switch (s.key) { case 'M': case 'L': case 'T': s.point = [s.data[0], s.data[1]]; break; case 'm': case 'l': case 't': s.point = [s.data[0] + currentPoint[0], s.data[1] + currentPoint[1]]; break; case 'H': s.point = [s.data[0], currentPoint[1]]; break; case 'h': s.point = [s.data[0] + currentPoint[0], currentPoint[1]]; break; case 'V': s.point = [currentPoint[0], s.data[0]]; break; case 'v': s.point = [currentPoint[0], s.data[0] + currentPoint[1]]; break; case 'z': case 'Z': if (first) { s.point = [first[0], first[1]]; } break; case 'C': s.point = [s.data[4], s.data[5]]; break; case 'c': s.point = [s.data[4] + currentPoint[0], s.data[5] + currentPoint[1]]; break; case 'S': s.point = [s.data[2], s.data[3]]; break; case 's': s.point = [s.data[2] + currentPoint[0], s.data[3] + currentPoint[1]]; break; case 'Q': s.point = [s.data[2], s.data[3]]; break; case 'q': s.point = [s.data[2] + currentPoint[0], s.data[3] + currentPoint[1]]; break; case 'A': s.point = [s.data[5], s.data[6]]; break; case 'a': s.point = [s.data[5] + currentPoint[0], s.data[6] + currentPoint[1]]; break; } if (s.key === 'm' || s.key === 'M') { first = null; } if (s.point) { currentPoint = s.point; if (!first) { first = s.point; } } if (s.key === 'z' || s.key === 'Z') { first = null; } prev = s; } } }, { key: "parseData", value: function parseData(d) { var tokens = this.tokenize(d); var index = 0; var token = tokens[index]; var mode = "BOD"; this.segments = new Array(); while (!token.isType(this.EOD)) { var param_length; var params = new Array(); if (mode == "BOD") { if (token.text == "M" || token.text == "m") { index++; param_length = this.PARAMS[token.text].length; mode = token.text; } else { return this.parseData('M0,0' + d); } } else { if (token.isType(this.NUMBER)) { param_length = this.PARAMS[mode].length; } else { index++; param_length = this.PARAMS[token.text].length; mode = token.text; } } if (index + param_length < tokens.length) { for (var i = index; i < index + param_length; i++) { var number = tokens[i]; if (number.isType(this.NUMBER)) { params[params.length] = number.text; } else { console.error("Parameter type is not a number: " + mode + "," + number.text); return; } } var segment; if (this.PARAMS[mode]) { segment = { key: mode, data: params }; } else { console.error("Unsupported segment type: " + mode); return; } this.segments.push(segment); index += param_length; token = tokens[index]; if (mode == "M") mode = "L"; if (mode == "m") mode = "l"; } else { console.error("Path data ended before all parameters were found"); } } } }, { key: "tokenize", value: function tokenize(d) { var tokens = new Array(); while (d != "") { if (d.match(/^([ \t\r\n,]+)/)) { d = d.substr(RegExp.$1.length); } else if (d.match(/^([aAcChHlLmMqQsStTvVzZ])/)) { tokens[tokens.length] = new PathToken(this.COMMAND, RegExp.$1); d = d.substr(RegExp.$1.length); } else if (d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/)) { tokens[tokens.length] = new PathToken(this.NUMBER, parseFloat(RegExp.$1)); d = d.substr(RegExp.$1.length); } else { console.error("Unrecognized segment command: " + d); return null; } } tokens[tokens.length] = new PathToken(this.EOD, null); return tokens; } }, { key: "closed", get: function get() { if (typeof this._closed === 'undefined') { this._closed = false; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = this.segments[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var s = _step.value; if (s.key.toLowerCase() === 'z') { this._closed = true; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } return this._closed; } }]); return ParsedPath; }(); var RoughPath = exports.RoughPath = function () { function RoughPath(d) { _classCallCheck(this, RoughPath); this.d = d; this.parsed = new ParsedPath(d); this._position = [0, 0]; this.bezierReflectionPoint = null; this.quadReflectionPoint = null; this._first = null; } _createClass(RoughPath, [{ key: "setPosition", value: function setPosition(x, y) { this._position = [x, y]; if (!this._first) { this._first = [x, y]; } } }, { key: "segments", get: function get() { return this.parsed.segments; } }, { key: "closed", get: function get() { return this.parsed.closed; } }, { key: "linearPoints", get: function get() { if (!this._linearPoints) { var lp = []; var points = []; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = this.parsed.segments[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var s = _step2.value; var key = s.key.toLowerCase(); if (key === 'm' || key === 'z') { if (points.length) { lp.push(points); points = []; } if (key === 'z') { continue; } } if (s.point) { points.push(s.point); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } if (points.length) { lp.push(points); points = []; } this._linearPoints = lp; } return this._linearPoints; } }, { key: "first", get: function get() { return this._first; }, set: function set(v) { this._first = v; } }, { key: "position", get: function get() { return this._position; } }, { key: "x", get: function get() { return this._position[0]; } }, { key: "y", get: function get() { return this._position[1]; } }]); return RoughPath; }(); var RoughArcConverter = exports.RoughArcConverter = function () { // Algorithm as described in https://www.w3.org/TR/SVG/implnote.html // Code adapted from nsSVGPathDataParser.cpp in Mozilla // https://hg.mozilla.org/mozilla-central/file/17156fbebbc8/content/svg/content/src/nsSVGPathDataParser.cpp#l887 function RoughArcConverter(from, to, radii, angle, largeArcFlag, sweepFlag) { _classCallCheck(this, RoughArcConverter); var radPerDeg = Math.PI / 180; this._segIndex = 0; this._numSegs = 0; if (from[0] == to[0] && from[1] == to[1]) { return; } this._rx = Math.abs(radii[0]); this._ry = Math.abs(radii[1]); this._sinPhi = Math.sin(angle * radPerDeg); this._cosPhi = Math.cos(angle * radPerDeg); var x1dash = this._cosPhi * (from[0] - to[0]) / 2.0 + this._sinPhi * (from[1] - to[1]) / 2.0; var y1dash = -this._sinPhi * (from[0] - to[0]) / 2.0 + this._cosPhi * (from[1] - to[1]) / 2.0; var root; var numerator = this._rx * this._rx * this._ry * this._ry - this._rx * this._rx * y1dash * y1dash - this._ry * this._ry * x1dash * x1dash; if (numerator < 0) { var s = Math.sqrt(1 - numerator / (this._rx * this._rx * this._ry * this._ry)); this._rx = s; this._ry = s; root = 0; } else { root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) * Math.sqrt(numerator / (this._rx * this._rx * y1dash * y1dash + this._ry * this._ry * x1dash * x1dash)); } var cxdash = root * this._rx * y1dash / this._ry; var cydash = -root * this._ry * x1dash / this._rx; this._C = [0, 0]; this._C[0] = this._cosPhi * cxdash - this._sinPhi * cydash + (from[0] + to[0]) / 2.0; this._C[1] = this._sinPhi * cxdash + this._cosPhi * cydash + (from[1] + to[1]) / 2.0; this._theta = this.calculateVectorAngle(1.0, 0.0, (x1dash - cxdash) / this._rx, (y1dash - cydash) / this._ry); var dtheta = this.calculateVectorAngle((x1dash - cxdash) / this._rx, (y1dash - cydash) / this._ry, (-x1dash - cxdash) / this._rx, (-y1dash - cydash) / this._ry); if (!sweepFlag && dtheta > 0) { dtheta -= 2 * Math.PI; } else if (sweepFlag && dtheta < 0) { dtheta += 2 * Math.PI; } this._numSegs = Math.ceil(Math.abs(dtheta / (Math.PI / 2))); this._delta = dtheta / this._numSegs; this._T = 8 / 3 * Math.sin(this._delta / 4) * Math.sin(this._delta / 4) / Math.sin(this._delta / 2); this._from = from; } _createClass(RoughArcConverter, [{ key: "getNextSegment", value: function getNextSegment() { var cp1, cp2, to; if (this._segIndex == this._numSegs) { return null; } var cosTheta1 = Math.cos(this._theta); var sinTheta1 = Math.sin(this._theta); var theta2 = this._theta + this._delta; var cosTheta2 = Math.cos(theta2); var sinTheta2 = Math.sin(theta2); to = [this._cosPhi * this._rx * cosTheta2 - this._sinPhi * this._ry * sinTheta2 + this._C[0], this._sinPhi * this._rx * cosTheta2 + this._cosPhi * this._ry * sinTheta2 + this._C[1]]; cp1 = [this._from[0] + this._T * (-this._cosPhi * this._rx * sinTheta1 - this._sinPhi * this._ry * cosTheta1), this._from[1] + this._T * (-this._sinPhi * this._rx * sinTheta1 + this._cosPhi * this._ry * cosTheta1)]; cp2 = [to[0] + this._T * (this._cosPhi * this._rx * sinTheta2 + this._sinPhi * this._ry * cosTheta2), to[1] + this._T * (this._sinPhi * this._rx * sinTheta2 - this._cosPhi * this._ry * cosTheta2)]; this._theta = theta2; this._from = [to[0], to[1]]; this._segIndex++; return { cp1: cp1, cp2: cp2, to: to }; } }, { key: "calculateVectorAngle", value: function calculateVectorAngle(ux, uy, vx, vy) { var ta = Math.atan2(uy, ux); var tb = Math.atan2(vy, vx); if (tb >= ta) return tb - ta; return 2 * Math.PI - (ta - tb); } }]); return RoughArcConverter; }(); var PathFitter = exports.PathFitter = function () { function PathFitter(sets, closed) { _classCallCheck(this, PathFitter); this.sets = sets; this.closed = closed; } _createClass(PathFitter, [{ key: "fit", value: function fit(simplification) { var outSets = []; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = this.sets[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var set = _step3.value; var length = set.length; var estLength = Math.floor(simplification * length); if (estLength < 5) { if (length <= 5) { continue; } estLength = 5; } outSets.push(this.reduce(set, estLength)); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } var d = ''; var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = outSets[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var _set = _step4.value; for (var i = 0; i < _set.length; i++) { var point = _set[i]; if (i === 0) { d += 'M' + point[0] + "," + point[1]; } else { d += 'L' + point[0] + "," + point[1]; } } if (this.closed) { d += 'z '; } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } return d; } }, { key: "distance", value: function distance(p1, p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); } }, { key: "reduce", value: function reduce(set, count) { if (set.length <= count) { return set; } var points = set.slice(0); while (points.length > count) { var areas = []; var minArea = -1; var minIndex = -1; for (var i = 1; i < points.length - 1; i++) { var a = this.distance(points[i - 1], points[i]); var b = this.distance(points[i], points[i + 1]); var c = this.distance(points[i - 1], points[i + 1]); var s = (a + b + c) / 2.0; var area = Math.sqrt(s * (s - a) * (s - b) * (s - c)); areas.push(area); if (minArea < 0 || area < minArea) { minArea = area; minIndex = i; } } if (minIndex > 0) { points.splice(minIndex, 1); } else { break; } } return points; } }]); return PathFitter; }();