2017-03-08 23:53:53 +00:00
|
|
|
/**
|
|
|
|
* The `Matter.Svg` module contains methods for converting SVG images into an array of vector points.
|
|
|
|
*
|
|
|
|
* To use this module you also need the SVGPathSeg polyfill: https://github.com/progers/pathseg
|
|
|
|
*
|
|
|
|
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
|
|
|
|
*
|
|
|
|
* @class Svg
|
|
|
|
*/
|
|
|
|
|
|
|
|
var Svg = {};
|
|
|
|
|
|
|
|
module.exports = Svg;
|
|
|
|
|
|
|
|
var Bounds = require('../geometry/Bounds');
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts an SVG path into an array of vector points.
|
|
|
|
* If the input path forms a concave shape, you must decompose the result into convex parts before use.
|
|
|
|
* See `Bodies.fromVertices` which provides support for this.
|
|
|
|
* Note that this function is not guaranteed to support complex paths (such as those with holes).
|
|
|
|
* @method pathToVertices
|
|
|
|
* @param {SVGPathElement} path
|
|
|
|
* @param {Number} [sampleLength=15]
|
|
|
|
* @return {Vector[]} points
|
|
|
|
*/
|
|
|
|
Svg.pathToVertices = function(path, sampleLength) {
|
|
|
|
// https://github.com/wout/svg.topoly.js/blob/master/svg.topoly.js
|
|
|
|
var i, il, total, point, segment, segments,
|
|
|
|
segmentsQueue, lastSegment,
|
|
|
|
lastPoint, segmentIndex, points = [],
|
|
|
|
lx, ly, length = 0, x = 0, y = 0;
|
|
|
|
|
|
|
|
sampleLength = sampleLength || 15;
|
|
|
|
|
|
|
|
var addPoint = function(px, py, pathSegType) {
|
|
|
|
// all odd-numbered path types are relative except PATHSEG_CLOSEPATH (1)
|
|
|
|
var isRelative = pathSegType % 2 === 1 && pathSegType > 1;
|
|
|
|
|
|
|
|
// when the last point doesn't equal the current point add the current point
|
|
|
|
if (!lastPoint || px != lastPoint.x || py != lastPoint.y) {
|
|
|
|
if (lastPoint && isRelative) {
|
|
|
|
lx = lastPoint.x;
|
|
|
|
ly = lastPoint.y;
|
|
|
|
} else {
|
|
|
|
lx = 0;
|
|
|
|
ly = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
var point = {
|
|
|
|
x: lx + px,
|
|
|
|
y: ly + py
|
|
|
|
};
|
|
|
|
|
|
|
|
// set last point
|
|
|
|
if (isRelative || !lastPoint) {
|
|
|
|
lastPoint = point;
|
|
|
|
}
|
|
|
|
|
|
|
|
points.push(point);
|
|
|
|
|
|
|
|
x = lx + px;
|
|
|
|
y = ly + py;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var addSegmentPoint = function(segment) {
|
|
|
|
var segType = segment.pathSegTypeAsLetter.toUpperCase();
|
|
|
|
|
|
|
|
// skip path ends
|
|
|
|
if (segType === 'Z')
|
|
|
|
return;
|
|
|
|
|
|
|
|
// map segment to x and y
|
|
|
|
switch (segType) {
|
|
|
|
|
|
|
|
case 'M':
|
|
|
|
case 'L':
|
|
|
|
case 'T':
|
|
|
|
case 'C':
|
|
|
|
case 'S':
|
|
|
|
case 'Q':
|
|
|
|
x = segment.x;
|
|
|
|
y = segment.y;
|
|
|
|
break;
|
|
|
|
case 'H':
|
|
|
|
x = segment.x;
|
|
|
|
break;
|
|
|
|
case 'V':
|
|
|
|
y = segment.y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
addPoint(x, y, segment.pathSegType);
|
|
|
|
};
|
|
|
|
|
|
|
|
// ensure path is absolute
|
|
|
|
_svgPathToAbsolute(path);
|
|
|
|
|
|
|
|
// get total length
|
|
|
|
total = path.getTotalLength();
|
|
|
|
|
|
|
|
// queue segments
|
|
|
|
segments = [];
|
|
|
|
for (i = 0; i < path.pathSegList.numberOfItems; i += 1)
|
|
|
|
segments.push(path.pathSegList.getItem(i));
|
|
|
|
|
|
|
|
segmentsQueue = segments.concat();
|
|
|
|
|
|
|
|
// sample through path
|
|
|
|
while (length < total) {
|
|
|
|
// get segment at position
|
|
|
|
segmentIndex = path.getPathSegAtLength(length);
|
|
|
|
segment = segments[segmentIndex];
|
|
|
|
|
|
|
|
// new segment
|
|
|
|
if (segment != lastSegment) {
|
|
|
|
while (segmentsQueue.length && segmentsQueue[0] != segment)
|
|
|
|
addSegmentPoint(segmentsQueue.shift());
|
|
|
|
|
|
|
|
lastSegment = segment;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add points in between when curving
|
|
|
|
// TODO: adaptive sampling
|
|
|
|
switch (segment.pathSegTypeAsLetter.toUpperCase()) {
|
|
|
|
|
|
|
|
case 'C':
|
|
|
|
case 'T':
|
|
|
|
case 'S':
|
|
|
|
case 'Q':
|
|
|
|
case 'A':
|
|
|
|
point = path.getPointAtLength(length);
|
|
|
|
addPoint(point.x, point.y, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// increment by sample value
|
|
|
|
length += sampleLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add remaining segments not passed by sampling
|
|
|
|
for (i = 0, il = segmentsQueue.length; i < il; ++i)
|
|
|
|
addSegmentPoint(segmentsQueue[i]);
|
|
|
|
|
|
|
|
return points;
|
|
|
|
};
|
|
|
|
|
|
|
|
var _svgPathToAbsolute = function(path) {
|
|
|
|
// http://phrogz.net/convert-svg-path-to-all-absolute-commands
|
2017-11-17 15:29:37 +00:00
|
|
|
// Copyright (c) Gavin Kistner
|
|
|
|
// http://phrogz.net/js/_ReuseLicense.txt
|
|
|
|
// Modifications: tidy formatting and naming
|
2017-03-08 23:53:53 +00:00
|
|
|
var x0, y0, x1, y1, x2, y2, segs = path.pathSegList,
|
|
|
|
x = 0, y = 0, len = segs.numberOfItems;
|
|
|
|
|
|
|
|
for (var i = 0; i < len; ++i) {
|
|
|
|
var seg = segs.getItem(i),
|
|
|
|
segType = seg.pathSegTypeAsLetter;
|
|
|
|
|
|
|
|
if (/[MLHVCSQTA]/.test(segType)) {
|
|
|
|
if ('x' in seg) x = seg.x;
|
|
|
|
if ('y' in seg) y = seg.y;
|
|
|
|
} else {
|
|
|
|
if ('x1' in seg) x1 = x + seg.x1;
|
|
|
|
if ('x2' in seg) x2 = x + seg.x2;
|
|
|
|
if ('y1' in seg) y1 = y + seg.y1;
|
|
|
|
if ('y2' in seg) y2 = y + seg.y2;
|
|
|
|
if ('x' in seg) x += seg.x;
|
|
|
|
if ('y' in seg) y += seg.y;
|
|
|
|
|
|
|
|
switch (segType) {
|
|
|
|
|
|
|
|
case 'm':
|
|
|
|
segs.replaceItem(path.createSVGPathSegMovetoAbs(x, y), i);
|
|
|
|
break;
|
|
|
|
case 'l':
|
|
|
|
segs.replaceItem(path.createSVGPathSegLinetoAbs(x, y), i);
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
segs.replaceItem(path.createSVGPathSegLinetoHorizontalAbs(x), i);
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
segs.replaceItem(path.createSVGPathSegLinetoVerticalAbs(y), i);
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
segs.replaceItem(path.createSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2), i);
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
segs.replaceItem(path.createSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2), i);
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
segs.replaceItem(path.createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1), i);
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
segs.replaceItem(path.createSVGPathSegCurvetoQuadraticSmoothAbs(x, y), i);
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
segs.replaceItem(path.createSVGPathSegArcAbs(x, y, seg.r1, seg.r2, seg.angle, seg.largeArcFlag, seg.sweepFlag), i);
|
|
|
|
break;
|
|
|
|
case 'z':
|
|
|
|
case 'Z':
|
|
|
|
x = x0;
|
|
|
|
y = y0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (segType == 'M' || segType == 'm') {
|
|
|
|
x0 = x;
|
|
|
|
y0 = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
})();
|