/** * 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 // Copyright (c) Gavin Kistner // http://phrogz.net/js/_ReuseLicense.txt // Modifications: tidy formatting and naming 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; } } }; })();