mirror of
https://github.com/lovasoa/whitebophir
synced 2024-11-10 06:24:17 +00:00
1119 lines
31 KiB
JavaScript
1119 lines
31 KiB
JavaScript
|
|
// @info
|
|
// Polyfill for SVG getPathData() and setPathData() methods. Based on:
|
|
// - SVGPathSeg polyfill by Philip Rogers (MIT License)
|
|
// https://github.com/progers/pathseg
|
|
// - SVGPathNormalizer by Tadahisa Motooka (MIT License)
|
|
// https://github.com/motooka/SVGPathNormalizer/tree/master/src
|
|
// - arcToCubicCurves() by Dmitry Baranovskiy (MIT License)
|
|
// https://github.com/DmitryBaranovskiy/raphael/blob/v2.1.1/raphael.core.js#L1837
|
|
// @author
|
|
// Jarosław Foksa
|
|
// @license
|
|
// MIT License
|
|
if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) {
|
|
(function () {
|
|
var commandsMap = {
|
|
"Z": "Z", "M": "M", "L": "L", "C": "C", "Q": "Q", "A": "A", "H": "H", "V": "V", "S": "S", "T": "T",
|
|
"z": "Z", "m": "m", "l": "l", "c": "c", "q": "q", "a": "a", "h": "h", "v": "v", "s": "s", "t": "t"
|
|
};
|
|
|
|
var Source = function (string) {
|
|
this._string = string;
|
|
this._currentIndex = 0;
|
|
this._endIndex = this._string.length;
|
|
this._prevCommand = null;
|
|
this._skipOptionalSpaces();
|
|
};
|
|
|
|
var isIE = window.navigator.userAgent.indexOf("MSIE ") !== -1;
|
|
|
|
Source.prototype = {
|
|
parseSegment: function () {
|
|
var char = this._string[this._currentIndex];
|
|
var command = commandsMap[char] ? commandsMap[char] : null;
|
|
|
|
if (command === null) {
|
|
// Possibly an implicit command. Not allowed if this is the first command.
|
|
if (this._prevCommand === null) {
|
|
return null;
|
|
}
|
|
|
|
// Check for remaining coordinates in the current command.
|
|
if (
|
|
(char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && this._prevCommand !== "Z"
|
|
) {
|
|
if (this._prevCommand === "M") {
|
|
command = "L";
|
|
}
|
|
else if (this._prevCommand === "m") {
|
|
command = "l";
|
|
}
|
|
else {
|
|
command = this._prevCommand;
|
|
}
|
|
}
|
|
else {
|
|
command = null;
|
|
}
|
|
|
|
if (command === null) {
|
|
return null;
|
|
}
|
|
}
|
|
else {
|
|
this._currentIndex += 1;
|
|
}
|
|
|
|
this._prevCommand = command;
|
|
|
|
var values = null;
|
|
var cmd = command.toUpperCase();
|
|
|
|
if (cmd === "H" || cmd === "V") {
|
|
values = [this._parseNumber()];
|
|
}
|
|
else if (cmd === "M" || cmd === "L" || cmd === "T") {
|
|
values = [this._parseNumber(), this._parseNumber()];
|
|
}
|
|
else if (cmd === "S" || cmd === "Q") {
|
|
values = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()];
|
|
}
|
|
else if (cmd === "C") {
|
|
values = [
|
|
this._parseNumber(),
|
|
this._parseNumber(),
|
|
this._parseNumber(),
|
|
this._parseNumber(),
|
|
this._parseNumber(),
|
|
this._parseNumber()
|
|
];
|
|
}
|
|
else if (cmd === "A") {
|
|
values = [
|
|
this._parseNumber(),
|
|
this._parseNumber(),
|
|
this._parseNumber(),
|
|
this._parseArcFlag(),
|
|
this._parseArcFlag(),
|
|
this._parseNumber(),
|
|
this._parseNumber()
|
|
];
|
|
}
|
|
else if (cmd === "Z") {
|
|
this._skipOptionalSpaces();
|
|
values = [];
|
|
}
|
|
|
|
if (values === null || values.indexOf(null) >= 0) {
|
|
// Unknown command or known command with invalid values
|
|
return null;
|
|
}
|
|
else {
|
|
return { type: command, values: values };
|
|
}
|
|
},
|
|
|
|
hasMoreData: function () {
|
|
return this._currentIndex < this._endIndex;
|
|
},
|
|
|
|
peekSegmentType: function () {
|
|
var char = this._string[this._currentIndex];
|
|
return commandsMap[char] ? commandsMap[char] : null;
|
|
},
|
|
|
|
initialCommandIsMoveTo: function () {
|
|
// If the path is empty it is still valid, so return true.
|
|
if (!this.hasMoreData()) {
|
|
return true;
|
|
}
|
|
|
|
var command = this.peekSegmentType();
|
|
// Path must start with moveTo.
|
|
return command === "M" || command === "m";
|
|
},
|
|
|
|
_isCurrentSpace: function () {
|
|
var char = this._string[this._currentIndex];
|
|
return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f");
|
|
},
|
|
|
|
_skipOptionalSpaces: function () {
|
|
while (this._currentIndex < this._endIndex && this._isCurrentSpace()) {
|
|
this._currentIndex += 1;
|
|
}
|
|
|
|
return this._currentIndex < this._endIndex;
|
|
},
|
|
|
|
_skipOptionalSpacesOrDelimiter: function () {
|
|
if (
|
|
this._currentIndex < this._endIndex &&
|
|
!this._isCurrentSpace() &&
|
|
this._string[this._currentIndex] !== ","
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (this._skipOptionalSpaces()) {
|
|
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") {
|
|
this._currentIndex += 1;
|
|
this._skipOptionalSpaces();
|
|
}
|
|
}
|
|
return this._currentIndex < this._endIndex;
|
|
},
|
|
|
|
// Parse a number from an SVG path. This very closely follows genericParseNumber(...) from
|
|
// Source/core/svg/SVGParserUtilities.cpp.
|
|
// Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
|
|
_parseNumber: function () {
|
|
var exponent = 0;
|
|
var integer = 0;
|
|
var frac = 1;
|
|
var decimal = 0;
|
|
var sign = 1;
|
|
var expsign = 1;
|
|
var startIndex = this._currentIndex;
|
|
|
|
this._skipOptionalSpaces();
|
|
|
|
// Read the sign.
|
|
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") {
|
|
this._currentIndex += 1;
|
|
}
|
|
else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") {
|
|
this._currentIndex += 1;
|
|
sign = -1;
|
|
}
|
|
|
|
if (
|
|
this._currentIndex === this._endIndex ||
|
|
(
|
|
(this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") &&
|
|
this._string[this._currentIndex] !== "."
|
|
)
|
|
) {
|
|
// The first character of a number must be one of [0-9+-.].
|
|
return null;
|
|
}
|
|
|
|
// Read the integer part, build right-to-left.
|
|
var startIntPartIndex = this._currentIndex;
|
|
|
|
while (
|
|
this._currentIndex < this._endIndex &&
|
|
this._string[this._currentIndex] >= "0" &&
|
|
this._string[this._currentIndex] <= "9"
|
|
) {
|
|
this._currentIndex += 1; // Advance to first non-digit.
|
|
}
|
|
|
|
if (this._currentIndex !== startIntPartIndex) {
|
|
var scanIntPartIndex = this._currentIndex - 1;
|
|
var multiplier = 1;
|
|
|
|
while (scanIntPartIndex >= startIntPartIndex) {
|
|
integer += multiplier * (this._string[scanIntPartIndex] - "0");
|
|
scanIntPartIndex -= 1;
|
|
multiplier *= 10;
|
|
}
|
|
}
|
|
|
|
// Read the decimals.
|
|
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") {
|
|
this._currentIndex += 1;
|
|
|
|
// There must be a least one digit following the .
|
|
if (
|
|
this._currentIndex >= this._endIndex ||
|
|
this._string[this._currentIndex] < "0" ||
|
|
this._string[this._currentIndex] > "9"
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
while (
|
|
this._currentIndex < this._endIndex &&
|
|
this._string[this._currentIndex] >= "0" &&
|
|
this._string[this._currentIndex] <= "9"
|
|
) {
|
|
frac *= 10;
|
|
decimal += (this._string.charAt(this._currentIndex) - "0") / frac;
|
|
this._currentIndex += 1;
|
|
}
|
|
}
|
|
|
|
// Read the exponent part.
|
|
if (
|
|
this._currentIndex !== startIndex &&
|
|
this._currentIndex + 1 < this._endIndex &&
|
|
(this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") &&
|
|
(this._string[this._currentIndex + 1] !== "x" && this._string[this._currentIndex + 1] !== "m")
|
|
) {
|
|
this._currentIndex += 1;
|
|
|
|
// Read the sign of the exponent.
|
|
if (this._string[this._currentIndex] === "+") {
|
|
this._currentIndex += 1;
|
|
}
|
|
else if (this._string[this._currentIndex] === "-") {
|
|
this._currentIndex += 1;
|
|
expsign = -1;
|
|
}
|
|
|
|
// There must be an exponent.
|
|
if (
|
|
this._currentIndex >= this._endIndex ||
|
|
this._string[this._currentIndex] < "0" ||
|
|
this._string[this._currentIndex] > "9"
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
while (
|
|
this._currentIndex < this._endIndex &&
|
|
this._string[this._currentIndex] >= "0" &&
|
|
this._string[this._currentIndex] <= "9"
|
|
) {
|
|
exponent *= 10;
|
|
exponent += (this._string[this._currentIndex] - "0");
|
|
this._currentIndex += 1;
|
|
}
|
|
}
|
|
|
|
var number = integer + decimal;
|
|
number *= sign;
|
|
|
|
if (exponent) {
|
|
number *= Math.pow(10, expsign * exponent);
|
|
}
|
|
|
|
if (startIndex === this._currentIndex) {
|
|
return null;
|
|
}
|
|
|
|
this._skipOptionalSpacesOrDelimiter();
|
|
|
|
return number;
|
|
},
|
|
|
|
_parseArcFlag: function () {
|
|
if (this._currentIndex >= this._endIndex) {
|
|
return null;
|
|
}
|
|
|
|
var flag = null;
|
|
var flagChar = this._string[this._currentIndex];
|
|
|
|
this._currentIndex += 1;
|
|
|
|
if (flagChar === "0") {
|
|
flag = 0;
|
|
}
|
|
else if (flagChar === "1") {
|
|
flag = 1;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
|
|
this._skipOptionalSpacesOrDelimiter();
|
|
return flag;
|
|
}
|
|
};
|
|
|
|
var parsePathDataString = function (string) {
|
|
if (!string || string.length === 0) return [];
|
|
|
|
var source = new Source(string);
|
|
var pathData = [];
|
|
|
|
if (source.initialCommandIsMoveTo()) {
|
|
while (source.hasMoreData()) {
|
|
var pathSeg = source.parseSegment();
|
|
|
|
if (pathSeg === null) {
|
|
break;
|
|
}
|
|
else {
|
|
pathData.push(pathSeg);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pathData;
|
|
}
|
|
|
|
var setAttribute = SVGPathElement.prototype.setAttribute;
|
|
var removeAttribute = SVGPathElement.prototype.removeAttribute;
|
|
|
|
var $cachedPathData = window.Symbol ? Symbol() : "__cachedPathData";
|
|
var $cachedNormalizedPathData = window.Symbol ? Symbol() : "__cachedNormalizedPathData";
|
|
|
|
// @info
|
|
// Get an array of corresponding cubic bezier curve parameters for given arc curve paramters.
|
|
var arcToCubicCurves = function (x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, _recursive) {
|
|
var degToRad = function (degrees) {
|
|
return (Math.PI * degrees) / 180;
|
|
};
|
|
|
|
var rotate = function (x, y, angleRad) {
|
|
var X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
|
|
var Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
|
|
return { x: X, y: Y };
|
|
};
|
|
|
|
var angleRad = degToRad(angle);
|
|
var params = [];
|
|
var f1, f2, cx, cy;
|
|
|
|
if (_recursive) {
|
|
f1 = _recursive[0];
|
|
f2 = _recursive[1];
|
|
cx = _recursive[2];
|
|
cy = _recursive[3];
|
|
}
|
|
else {
|
|
var p1 = rotate(x1, y1, -angleRad);
|
|
x1 = p1.x;
|
|
y1 = p1.y;
|
|
|
|
var p2 = rotate(x2, y2, -angleRad);
|
|
x2 = p2.x;
|
|
y2 = p2.y;
|
|
|
|
var x = (x1 - x2) / 2;
|
|
var y = (y1 - y2) / 2;
|
|
var h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);
|
|
|
|
if (h > 1) {
|
|
h = Math.sqrt(h);
|
|
r1 = h * r1;
|
|
r2 = h * r2;
|
|
}
|
|
|
|
var sign;
|
|
|
|
if (largeArcFlag === sweepFlag) {
|
|
sign = -1;
|
|
}
|
|
else {
|
|
sign = 1;
|
|
}
|
|
|
|
var r1Pow = r1 * r1;
|
|
var r2Pow = r2 * r2;
|
|
|
|
var left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
|
|
var right = r1Pow * y * y + r2Pow * x * x;
|
|
|
|
var k = sign * Math.sqrt(Math.abs(left / right));
|
|
|
|
cx = k * r1 * y / r2 + (x1 + x2) / 2;
|
|
cy = k * -r2 * x / r1 + (y1 + y2) / 2;
|
|
|
|
f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
|
|
f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
|
|
|
|
if (x1 < cx) {
|
|
f1 = Math.PI - f1;
|
|
}
|
|
if (x2 < cx) {
|
|
f2 = Math.PI - f2;
|
|
}
|
|
|
|
if (f1 < 0) {
|
|
f1 = Math.PI * 2 + f1;
|
|
}
|
|
if (f2 < 0) {
|
|
f2 = Math.PI * 2 + f2;
|
|
}
|
|
|
|
if (sweepFlag && f1 > f2) {
|
|
f1 = f1 - Math.PI * 2;
|
|
}
|
|
if (!sweepFlag && f2 > f1) {
|
|
f2 = f2 - Math.PI * 2;
|
|
}
|
|
}
|
|
|
|
var df = f2 - f1;
|
|
|
|
if (Math.abs(df) > (Math.PI * 120 / 180)) {
|
|
var f2old = f2;
|
|
var x2old = x2;
|
|
var y2old = y2;
|
|
|
|
if (sweepFlag && f2 > f1) {
|
|
f2 = f1 + (Math.PI * 120 / 180) * (1);
|
|
}
|
|
else {
|
|
f2 = f1 + (Math.PI * 120 / 180) * (-1);
|
|
}
|
|
|
|
x2 = cx + r1 * Math.cos(f2);
|
|
y2 = cy + r2 * Math.sin(f2);
|
|
params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
|
|
}
|
|
|
|
df = f2 - f1;
|
|
|
|
var c1 = Math.cos(f1);
|
|
var s1 = Math.sin(f1);
|
|
var c2 = Math.cos(f2);
|
|
var s2 = Math.sin(f2);
|
|
var t = Math.tan(df / 4);
|
|
var hx = 4 / 3 * r1 * t;
|
|
var hy = 4 / 3 * r2 * t;
|
|
|
|
var m1 = [x1, y1];
|
|
var m2 = [x1 + hx * s1, y1 - hy * c1];
|
|
var m3 = [x2 + hx * s2, y2 - hy * c2];
|
|
var m4 = [x2, y2];
|
|
|
|
m2[0] = 2 * m1[0] - m2[0];
|
|
m2[1] = 2 * m1[1] - m2[1];
|
|
|
|
if (_recursive) {
|
|
return [m2, m3, m4].concat(params);
|
|
}
|
|
else {
|
|
params = [m2, m3, m4].concat(params);
|
|
|
|
var curves = [];
|
|
|
|
for (var i = 0; i < params.length; i += 3) {
|
|
var r1 = rotate(params[i][0], params[i][1], angleRad);
|
|
var r2 = rotate(params[i + 1][0], params[i + 1][1], angleRad);
|
|
var r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);
|
|
curves.push([r1.x, r1.y, r2.x, r2.y, r3.x, r3.y]);
|
|
}
|
|
|
|
return curves;
|
|
}
|
|
};
|
|
|
|
var clonePathData = function (pathData) {
|
|
return pathData.map(function (seg) {
|
|
return { type: seg.type, values: Array.prototype.slice.call(seg.values) }
|
|
});
|
|
};
|
|
|
|
// @info
|
|
// Takes any path data, returns path data that consists only from absolute commands.
|
|
var absolutizePathData = function (pathData) {
|
|
var absolutizedPathData = [];
|
|
|
|
var currentX = null;
|
|
var currentY = null;
|
|
|
|
var subpathX = null;
|
|
var subpathY = null;
|
|
|
|
pathData.forEach(function (seg) {
|
|
var type = seg.type;
|
|
|
|
if (type === "M") {
|
|
var x = seg.values[0];
|
|
var y = seg.values[1];
|
|
|
|
absolutizedPathData.push({ type: "M", values: [x, y] });
|
|
|
|
subpathX = x;
|
|
subpathY = y;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "m") {
|
|
var x = currentX + seg.values[0];
|
|
var y = currentY + seg.values[1];
|
|
|
|
absolutizedPathData.push({ type: "M", values: [x, y] });
|
|
|
|
subpathX = x;
|
|
subpathY = y;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "L") {
|
|
var x = seg.values[0];
|
|
var y = seg.values[1];
|
|
|
|
absolutizedPathData.push({ type: "L", values: [x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "l") {
|
|
var x = currentX + seg.values[0];
|
|
var y = currentY + seg.values[1];
|
|
|
|
absolutizedPathData.push({ type: "L", values: [x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "C") {
|
|
var x1 = seg.values[0];
|
|
var y1 = seg.values[1];
|
|
var x2 = seg.values[2];
|
|
var y2 = seg.values[3];
|
|
var x = seg.values[4];
|
|
var y = seg.values[5];
|
|
|
|
absolutizedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "c") {
|
|
var x1 = currentX + seg.values[0];
|
|
var y1 = currentY + seg.values[1];
|
|
var x2 = currentX + seg.values[2];
|
|
var y2 = currentY + seg.values[3];
|
|
var x = currentX + seg.values[4];
|
|
var y = currentY + seg.values[5];
|
|
|
|
absolutizedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "Q") {
|
|
var x1 = seg.values[0];
|
|
var y1 = seg.values[1];
|
|
var x = seg.values[2];
|
|
var y = seg.values[3];
|
|
|
|
absolutizedPathData.push({ type: "Q", values: [x1, y1, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "q") {
|
|
var x1 = currentX + seg.values[0];
|
|
var y1 = currentY + seg.values[1];
|
|
var x = currentX + seg.values[2];
|
|
var y = currentY + seg.values[3];
|
|
|
|
absolutizedPathData.push({ type: "Q", values: [x1, y1, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "A") {
|
|
var x = seg.values[5];
|
|
var y = seg.values[6];
|
|
|
|
absolutizedPathData.push({
|
|
type: "A",
|
|
values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y]
|
|
});
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "a") {
|
|
var x = currentX + seg.values[5];
|
|
var y = currentY + seg.values[6];
|
|
|
|
absolutizedPathData.push({
|
|
type: "A",
|
|
values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y]
|
|
});
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "H") {
|
|
var x = seg.values[0];
|
|
absolutizedPathData.push({ type: "H", values: [x] });
|
|
currentX = x;
|
|
}
|
|
|
|
else if (type === "h") {
|
|
var x = currentX + seg.values[0];
|
|
absolutizedPathData.push({ type: "H", values: [x] });
|
|
currentX = x;
|
|
}
|
|
|
|
else if (type === "V") {
|
|
var y = seg.values[0];
|
|
absolutizedPathData.push({ type: "V", values: [y] });
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "v") {
|
|
var y = currentY + seg.values[0];
|
|
absolutizedPathData.push({ type: "V", values: [y] });
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "S") {
|
|
var x2 = seg.values[0];
|
|
var y2 = seg.values[1];
|
|
var x = seg.values[2];
|
|
var y = seg.values[3];
|
|
|
|
absolutizedPathData.push({ type: "S", values: [x2, y2, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "s") {
|
|
var x2 = currentX + seg.values[0];
|
|
var y2 = currentY + seg.values[1];
|
|
var x = currentX + seg.values[2];
|
|
var y = currentY + seg.values[3];
|
|
|
|
absolutizedPathData.push({ type: "S", values: [x2, y2, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "T") {
|
|
var x = seg.values[0];
|
|
var y = seg.values[1]
|
|
|
|
absolutizedPathData.push({ type: "T", values: [x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "t") {
|
|
var x = currentX + seg.values[0];
|
|
var y = currentY + seg.values[1]
|
|
|
|
absolutizedPathData.push({ type: "T", values: [x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (type === "Z" || type === "z") {
|
|
absolutizedPathData.push({ type: "Z", values: [] });
|
|
|
|
currentX = subpathX;
|
|
currentY = subpathY;
|
|
}
|
|
});
|
|
|
|
return absolutizedPathData;
|
|
};
|
|
|
|
// @info
|
|
// Takes path data that consists only from absolute commands, returns path data that consists only from
|
|
// "M", "L", "C" and "Z" commands.
|
|
var reducePathData = function (pathData) {
|
|
var reducedPathData = [];
|
|
var lastType = null;
|
|
|
|
var lastControlX = null;
|
|
var lastControlY = null;
|
|
|
|
var currentX = null;
|
|
var currentY = null;
|
|
|
|
var subpathX = null;
|
|
var subpathY = null;
|
|
|
|
pathData.forEach(function (seg) {
|
|
if (seg.type === "M") {
|
|
var x = seg.values[0];
|
|
var y = seg.values[1];
|
|
|
|
reducedPathData.push({ type: "M", values: [x, y] });
|
|
|
|
subpathX = x;
|
|
subpathY = y;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "C") {
|
|
var x1 = seg.values[0];
|
|
var y1 = seg.values[1];
|
|
var x2 = seg.values[2];
|
|
var y2 = seg.values[3];
|
|
var x = seg.values[4];
|
|
var y = seg.values[5];
|
|
|
|
reducedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
|
|
|
|
lastControlX = x2;
|
|
lastControlY = y2;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "L") {
|
|
var x = seg.values[0];
|
|
var y = seg.values[1];
|
|
|
|
reducedPathData.push({ type: "L", values: [x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "H") {
|
|
var x = seg.values[0];
|
|
|
|
reducedPathData.push({ type: "L", values: [x, currentY] });
|
|
|
|
currentX = x;
|
|
}
|
|
|
|
else if (seg.type === "V") {
|
|
var y = seg.values[0];
|
|
|
|
reducedPathData.push({ type: "L", values: [currentX, y] });
|
|
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "S") {
|
|
var x2 = seg.values[0];
|
|
var y2 = seg.values[1];
|
|
var x = seg.values[2];
|
|
var y = seg.values[3];
|
|
|
|
var cx1, cy1;
|
|
|
|
if (lastType === "C" || lastType === "S") {
|
|
cx1 = currentX + (currentX - lastControlX);
|
|
cy1 = currentY + (currentY - lastControlY);
|
|
}
|
|
else {
|
|
cx1 = currentX;
|
|
cy1 = currentY;
|
|
}
|
|
|
|
reducedPathData.push({ type: "C", values: [cx1, cy1, x2, y2, x, y] });
|
|
|
|
lastControlX = x2;
|
|
lastControlY = y2;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "T") {
|
|
var x = seg.values[0];
|
|
var y = seg.values[1];
|
|
|
|
var x1, y1;
|
|
|
|
if (lastType === "Q" || lastType === "T") {
|
|
x1 = currentX + (currentX - lastControlX);
|
|
y1 = currentY + (currentY - lastControlY);
|
|
}
|
|
else {
|
|
x1 = currentX;
|
|
y1 = currentY;
|
|
}
|
|
|
|
var cx1 = currentX + 2 * (x1 - currentX) / 3;
|
|
var cy1 = currentY + 2 * (y1 - currentY) / 3;
|
|
var cx2 = x + 2 * (x1 - x) / 3;
|
|
var cy2 = y + 2 * (y1 - y) / 3;
|
|
|
|
reducedPathData.push({ type: "C", values: [cx1, cy1, cx2, cy2, x, y] });
|
|
|
|
lastControlX = x1;
|
|
lastControlY = y1;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "Q") {
|
|
var x1 = seg.values[0];
|
|
var y1 = seg.values[1];
|
|
var x = seg.values[2];
|
|
var y = seg.values[3];
|
|
|
|
var cx1 = currentX + 2 * (x1 - currentX) / 3;
|
|
var cy1 = currentY + 2 * (y1 - currentY) / 3;
|
|
var cx2 = x + 2 * (x1 - x) / 3;
|
|
var cy2 = y + 2 * (y1 - y) / 3;
|
|
|
|
reducedPathData.push({ type: "C", values: [cx1, cy1, cx2, cy2, x, y] });
|
|
|
|
lastControlX = x1;
|
|
lastControlY = y1;
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
|
|
else if (seg.type === "A") {
|
|
var r1 = Math.abs(seg.values[0]);
|
|
var r2 = Math.abs(seg.values[1]);
|
|
var angle = seg.values[2];
|
|
var largeArcFlag = seg.values[3];
|
|
var sweepFlag = seg.values[4];
|
|
var x = seg.values[5];
|
|
var y = seg.values[6];
|
|
|
|
if (r1 === 0 || r2 === 0) {
|
|
reducedPathData.push({ type: "C", values: [currentX, currentY, x, y, x, y] });
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
else {
|
|
if (currentX !== x || currentY !== y) {
|
|
var curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
|
|
|
|
curves.forEach(function (curve) {
|
|
reducedPathData.push({ type: "C", values: curve });
|
|
});
|
|
|
|
currentX = x;
|
|
currentY = y;
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (seg.type === "Z") {
|
|
reducedPathData.push(seg);
|
|
|
|
currentX = subpathX;
|
|
currentY = subpathY;
|
|
}
|
|
|
|
lastType = seg.type;
|
|
});
|
|
|
|
return reducedPathData;
|
|
};
|
|
|
|
SVGPathElement.prototype.setAttribute = function (name, value) {
|
|
if (name === "d") {
|
|
this[$cachedPathData] = null;
|
|
this[$cachedNormalizedPathData] = null;
|
|
}
|
|
|
|
setAttribute.call(this, name, value);
|
|
};
|
|
|
|
SVGPathElement.prototype.removeAttribute = function (name, value) {
|
|
if (name === "d") {
|
|
this[$cachedPathData] = null;
|
|
this[$cachedNormalizedPathData] = null;
|
|
}
|
|
|
|
removeAttribute.call(this, name);
|
|
};
|
|
|
|
SVGPathElement.prototype.getPathData = function (options) {
|
|
if (options && options.normalize) {
|
|
if (this[$cachedNormalizedPathData]) {
|
|
return clonePathData(this[$cachedNormalizedPathData]);
|
|
}
|
|
else {
|
|
var pathData;
|
|
|
|
if (this[$cachedPathData]) {
|
|
pathData = clonePathData(this[$cachedPathData]);
|
|
}
|
|
else {
|
|
pathData = parsePathDataString(this.getAttribute("d") || "");
|
|
this[$cachedPathData] = clonePathData(pathData);
|
|
}
|
|
|
|
var normalizedPathData = reducePathData(absolutizePathData(pathData));
|
|
this[$cachedNormalizedPathData] = clonePathData(normalizedPathData);
|
|
return normalizedPathData;
|
|
}
|
|
}
|
|
else {
|
|
if (this[$cachedPathData]) {
|
|
return clonePathData(this[$cachedPathData]);
|
|
}
|
|
else {
|
|
var pathData = parsePathDataString(this.getAttribute("d") || "");
|
|
this[$cachedPathData] = clonePathData(pathData);
|
|
return pathData;
|
|
}
|
|
}
|
|
};
|
|
|
|
SVGPathElement.prototype.setPathData = function (pathData) {
|
|
if (pathData.length === 0) {
|
|
if (isIE) {
|
|
// @bugfix https://github.com/mbostock/d3/issues/1737
|
|
this.setAttribute("d", "");
|
|
}
|
|
else {
|
|
this.removeAttribute("d");
|
|
}
|
|
}
|
|
else {
|
|
var d = "";
|
|
|
|
for (var i = 0, l = pathData.length; i < l; i += 1) {
|
|
var seg = pathData[i];
|
|
|
|
if (i > 0) {
|
|
d += " ";
|
|
}
|
|
|
|
d += seg.type;
|
|
|
|
if (seg.values && seg.values.length > 0) {
|
|
d += " " + seg.values.join(" ");
|
|
}
|
|
}
|
|
|
|
this.setAttribute("d", d);
|
|
}
|
|
};
|
|
|
|
SVGRectElement.prototype.getPathData = function (options) {
|
|
var x = this.x.baseVal.value;
|
|
var y = this.y.baseVal.value;
|
|
var width = this.width.baseVal.value;
|
|
var height = this.height.baseVal.value;
|
|
var rx = this.hasAttribute("rx") ? this.rx.baseVal.value : this.ry.baseVal.value;
|
|
var ry = this.hasAttribute("ry") ? this.ry.baseVal.value : this.rx.baseVal.value;
|
|
|
|
if (rx > width / 2) {
|
|
rx = width / 2;
|
|
}
|
|
|
|
if (ry > height / 2) {
|
|
ry = height / 2;
|
|
}
|
|
|
|
var pathData = [
|
|
{ type: "M", values: [x + rx, y] },
|
|
{ type: "H", values: [x + width - rx] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
{ type: "V", values: [y + height - ry] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
{ type: "H", values: [x + rx] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
{ type: "V", values: [y + ry] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
{ type: "Z", values: [] }
|
|
];
|
|
|
|
// Get rid of redundant "A" segs when either rx or ry is 0
|
|
pathData = pathData.filter(function (s) {
|
|
return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0) ? false : true;
|
|
});
|
|
|
|
if (options && options.normalize === true) {
|
|
pathData = reducePathData(pathData);
|
|
}
|
|
|
|
return pathData;
|
|
};
|
|
|
|
SVGCircleElement.prototype.getPathData = function (options) {
|
|
var cx = this.cx.baseVal.value;
|
|
var cy = this.cy.baseVal.value;
|
|
var r = this.r.baseVal.value;
|
|
|
|
var pathData = [
|
|
{ type: "M", values: [cx + r, cy] },
|
|
{ type: "A", values: [r, r, 0, 0, 1, cx, cy + r] },
|
|
{ type: "A", values: [r, r, 0, 0, 1, cx - r, cy] },
|
|
{ type: "A", values: [r, r, 0, 0, 1, cx, cy - r] },
|
|
{ type: "A", values: [r, r, 0, 0, 1, cx + r, cy] },
|
|
{ type: "Z", values: [] }
|
|
];
|
|
|
|
if (options && options.normalize === true) {
|
|
pathData = reducePathData(pathData);
|
|
}
|
|
|
|
return pathData;
|
|
};
|
|
|
|
SVGEllipseElement.prototype.getPathData = function (options) {
|
|
var cx = this.cx.baseVal.value;
|
|
var cy = this.cy.baseVal.value;
|
|
var rx = this.rx.baseVal.value;
|
|
var ry = this.ry.baseVal.value;
|
|
|
|
var pathData = [
|
|
{ type: "M", values: [cx + rx, cy] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, cx, cy + ry] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, cx - rx, cy] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, cx, cy - ry] },
|
|
{ type: "A", values: [rx, ry, 0, 0, 1, cx + rx, cy] },
|
|
{ type: "Z", values: [] }
|
|
];
|
|
|
|
if (options && options.normalize === true) {
|
|
pathData = reducePathData(pathData);
|
|
}
|
|
|
|
return pathData;
|
|
};
|
|
|
|
SVGLineElement.prototype.getPathData = function () {
|
|
return [
|
|
{ type: "M", values: [this.x1.baseVal.value, this.y1.baseVal.value] },
|
|
{ type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] }
|
|
];
|
|
};
|
|
|
|
SVGPolylineElement.prototype.getPathData = function () {
|
|
var pathData = [];
|
|
|
|
for (var i = 0; i < this.points.numberOfItems; i += 1) {
|
|
var point = this.points.getItem(i);
|
|
|
|
pathData.push({
|
|
type: (i === 0 ? "M" : "L"),
|
|
values: [point.x, point.y]
|
|
});
|
|
}
|
|
|
|
return pathData;
|
|
};
|
|
|
|
SVGPolygonElement.prototype.getPathData = function () {
|
|
var pathData = [];
|
|
|
|
for (var i = 0; i < this.points.numberOfItems; i += 1) {
|
|
var point = this.points.getItem(i);
|
|
|
|
pathData.push({
|
|
type: (i === 0 ? "M" : "L"),
|
|
values: [point.x, point.y]
|
|
});
|
|
}
|
|
|
|
pathData.push({
|
|
type: "Z",
|
|
values: []
|
|
});
|
|
|
|
return pathData;
|
|
};
|
|
})();
|
|
}
|