mirror of
https://github.com/svg/svgo.git
synced 2025-07-29 20:21:14 +03:00
Check path intersection while merging.
Fixes #253, resolves #260, fixes #298.
This commit is contained in:
288
plugins/_path.js
288
plugins/_path.js
@ -9,7 +9,8 @@ var regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/,
|
|||||||
referencesProps = collections.referencesProps,
|
referencesProps = collections.referencesProps,
|
||||||
defaultStrokeWidth = collections.attrsGroupsDefaults.presentation['stroke-width'],
|
defaultStrokeWidth = collections.attrsGroupsDefaults.presentation['stroke-width'],
|
||||||
cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
|
cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
|
||||||
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero;
|
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
|
||||||
|
prevCtrlPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert path string to JS representation.
|
* Convert path string to JS representation.
|
||||||
@ -311,7 +312,6 @@ exports.applyTransforms = function(elem, path, applyTransformsStroked, floatPrec
|
|||||||
elem.removeAttr('transform');
|
elem.removeAttr('transform');
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -590,6 +590,290 @@ function set(dest, source) {
|
|||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if two paths have an intersection by checking convex hulls
|
||||||
|
* collision using Gilbert-Johnson-Keerthi distance algorithm
|
||||||
|
* http://entropyinteractive.com/2011/04/gjk-algorithm/
|
||||||
|
*
|
||||||
|
* @param {Array} path1 JS path representation
|
||||||
|
* @param {Array} path2 JS path representation
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
exports.interesects = function(path1, path2) {
|
||||||
|
if (path1.length < 3 || path2.length < 3) return false; // nothing to fill
|
||||||
|
|
||||||
|
// Collect points of every subpath.
|
||||||
|
var points1 = relative2absolute(path1).reduce(gatherPoints, []),
|
||||||
|
points2 = relative2absolute(path2).reduce(gatherPoints, []);
|
||||||
|
|
||||||
|
// Axis-aligned bounding box check.
|
||||||
|
if (points1.maxX <= points2.minX || points2.maxX <= points1.minX ||
|
||||||
|
points1.maxY <= points2.minY || points2.maxY <= points1.minY ||
|
||||||
|
points1.every(function (set1) {
|
||||||
|
return points2.every(function (set2) {
|
||||||
|
return set1[set1.maxX][0] <= set2[set2.minX][0] ||
|
||||||
|
set2[set2.maxX][0] <= set1[set1.minX][0] ||
|
||||||
|
set1[set1.maxY][1] <= set2[set2.minY][1] ||
|
||||||
|
set2[set2.maxY][1] <= set1[set1.minY][1];
|
||||||
|
});
|
||||||
|
})
|
||||||
|
) return false;
|
||||||
|
|
||||||
|
// Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
|
||||||
|
var hullNest1 = points1.map(convexHull),
|
||||||
|
hullNest2 = points2.map(convexHull);
|
||||||
|
|
||||||
|
// Check intersection of every subpath of the first path with every subpath of the second.
|
||||||
|
return hullNest1.some(function(hull1) {
|
||||||
|
if (hull1.length < 3) return false;
|
||||||
|
|
||||||
|
return hullNest2.some(function(hull2) {
|
||||||
|
if (hull2.length < 3) return false;
|
||||||
|
|
||||||
|
var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
|
||||||
|
direction = minus(simplex[0]); // set the direction to point towards the origin
|
||||||
|
|
||||||
|
var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
|
||||||
|
while (true) {
|
||||||
|
if (iterations-- == 0) {
|
||||||
|
console.error('Error: infinite loop while processing mergePaths plugin.');
|
||||||
|
return true; // true is the safe value that means “do nothing with paths”
|
||||||
|
}
|
||||||
|
// add a new point
|
||||||
|
simplex.push(getSupport(hull1, hull2, direction));
|
||||||
|
// see if the new point was on the correct side of the origin
|
||||||
|
if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
|
||||||
|
// process the simplex
|
||||||
|
if (processSimplex(simplex, direction)) return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getSupport(a, b, direction) {
|
||||||
|
return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute farthest polygon point in particular direction.
|
||||||
|
function supportPoint(polygon, direction) {
|
||||||
|
// Choose a quadrant to search in. In the worst case only one quadrant would be iterated.
|
||||||
|
var index = direction[1] >= 0 ?
|
||||||
|
direction[0] < 0 ? polygon.maxY : polygon.maxX : // [1, 0] lands right on maxX
|
||||||
|
direction[0] < 0 ? polygon.minX : polygon.minY,
|
||||||
|
max = dot(polygon[index], direction);
|
||||||
|
|
||||||
|
for (var i = index; i < polygon.length; i++) {
|
||||||
|
var value = dot(polygon[i], direction);
|
||||||
|
if (value >= max) {
|
||||||
|
index = i;
|
||||||
|
max = value;
|
||||||
|
} else break; // surely we've found the maximum since we've choosen a quadrant
|
||||||
|
}
|
||||||
|
return polygon[index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function processSimplex(simplex, direction) {
|
||||||
|
// we only need to handle to 1-simplex and 2-simplex
|
||||||
|
if (simplex.length == 2) { // 1-simplex
|
||||||
|
var a = simplex[1],
|
||||||
|
b = simplex[0],
|
||||||
|
AO = minus(simplex[1]),
|
||||||
|
AB = sub(b, a);
|
||||||
|
// AO is in the same direction as AB
|
||||||
|
if (dot(AO, AB) > 0) {
|
||||||
|
// get the vector perpendicular to AB facing O
|
||||||
|
set(direction, orth(AB, a));
|
||||||
|
} else {
|
||||||
|
set(direction, AO);
|
||||||
|
// only A remains in the simplex
|
||||||
|
simplex.shift();
|
||||||
|
}
|
||||||
|
} else { // 2-simplex
|
||||||
|
var a = simplex[2], // [a, b, c] = simplex
|
||||||
|
b = simplex[1],
|
||||||
|
c = simplex[0],
|
||||||
|
AB = sub(b, a),
|
||||||
|
AC = sub(c, a),
|
||||||
|
AO = minus(a),
|
||||||
|
ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
|
||||||
|
ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
|
||||||
|
|
||||||
|
if (dot(ACB, AO) > 0) {
|
||||||
|
if (dot(AB, AO) > 0) { // region 4
|
||||||
|
set(direction, ACB);
|
||||||
|
simplex.shift(); // simplex = [b, a]
|
||||||
|
} else { // region 5
|
||||||
|
set(direction, AO);
|
||||||
|
simplex.splice(0, 2); // simplex = [a]
|
||||||
|
}
|
||||||
|
} else if (dot(ABC, AO) > 0) {
|
||||||
|
if (dot(AC, AO) > 0) { // region 6
|
||||||
|
set(direction, ABC);
|
||||||
|
simplex.splice(1, 1); // simplex = [c, a]
|
||||||
|
} else { // region 5 (again)
|
||||||
|
set(direction, AO);
|
||||||
|
simplex.splice(0, 2); // simplex = [a]
|
||||||
|
}
|
||||||
|
} else // region 7
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minus(v) {
|
||||||
|
return [-v[0], -v[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function sub(v1, v2) {
|
||||||
|
return [v1[0] - v2[0], v1[1] - v2[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function dot(v1, v2) {
|
||||||
|
return v1[0] * v2[0] + v1[1] * v2[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function orth(v, from) {
|
||||||
|
var o = [-v[1], v[0]];
|
||||||
|
return dot(o, minus(from)) < 0 ? minus(o) : o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gatherPoints(points, item, index, path) {
|
||||||
|
|
||||||
|
var subPath = points.length && points[points.length - 1],
|
||||||
|
prev = index && path[index - 1],
|
||||||
|
basePoint = subPath.length && subPath[subPath.length - 1],
|
||||||
|
data = item.data,
|
||||||
|
ctrlPoint = basePoint;
|
||||||
|
|
||||||
|
switch (item.instruction) {
|
||||||
|
case 'M':
|
||||||
|
points.push(subPath = []);
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
addPoint(subPath, [data[0], basePoint[1]]);
|
||||||
|
break;
|
||||||
|
case 'V':
|
||||||
|
addPoint(subPath, [basePoint[0], data[0]]);
|
||||||
|
break;
|
||||||
|
case 'Q':
|
||||||
|
addPoint(subPath, data.slice(0, 2));
|
||||||
|
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
if (prev.instruction == 'Q' && prev.instruction == 'T') {
|
||||||
|
ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
|
||||||
|
addPoint(subPath, ctrlPoint);
|
||||||
|
prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
// Approximate quibic Bezier curve with middle points between control points
|
||||||
|
addPoint(subPath, [.5 * (basePoint[0] + data[0]), .5 * (basePoint[1] + data[1])]);
|
||||||
|
addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
|
||||||
|
addPoint(subPath, [.5 * (data[2] + data[4]), .5 * (data[3] + data[5])]);
|
||||||
|
prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
if (prev.instruction == 'C' && prev.instruction == 'S') {
|
||||||
|
addPoint(subPath, [basePoint[0] + .5 * prevCtrlPoint[0], basePoint[1] + .5 * prevCtrlPoint[1]]);
|
||||||
|
ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
|
||||||
|
}
|
||||||
|
addPoint(subPath, [.5 * (ctrlPoint[0] + data[0]), .5 * (ctrlPoint[1]+ data[1])]);
|
||||||
|
addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
|
||||||
|
prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
// Convert the arc to bezier curves and use the same approximation
|
||||||
|
var curves = a2c.apply(0, basePoint.concat(data));
|
||||||
|
for (var cData; (cData = curves.splice(0,6).map(toAbsolute)).length;) {
|
||||||
|
addPoint(subPath, [.5 * (basePoint[0] + cData[0]), .5 * (basePoint[1] + cData[1])]);
|
||||||
|
addPoint(subPath, [.5 * (cData[0] + cData[2]), .5 * (cData[1] + cData[3])]);
|
||||||
|
addPoint(subPath, [.5 * (cData[2] + cData[4]), .5 * (cData[3] + cData[5])]);
|
||||||
|
if (curves.length) addPoint(subPath, basePoint = cData.slice(-2));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Save final command coordinates
|
||||||
|
if (data && data.length >= 2) addPoint(subPath, data.slice(-2));
|
||||||
|
return points;
|
||||||
|
|
||||||
|
function toAbsolute(n, i) { return n + basePoint[i % 2] }
|
||||||
|
|
||||||
|
// Writes data about the extreme points on each axle
|
||||||
|
function addPoint(path, point) {
|
||||||
|
if (!path.length || point[1] > path[path.maxY][1]) {
|
||||||
|
path.maxY = path.length;
|
||||||
|
points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1];
|
||||||
|
}
|
||||||
|
if (!path.length || point[0] > path[path.maxX][0]) {
|
||||||
|
path.maxX = path.length;
|
||||||
|
points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0];
|
||||||
|
}
|
||||||
|
if (!path.length || point[1] < path[path.minY][1]) {
|
||||||
|
path.minY = path.length;
|
||||||
|
points.minY = points.length ? Math.min(point[1], points.minY) : point[1];
|
||||||
|
}
|
||||||
|
if (!path.length || point[0] < path[path.minX][0]) {
|
||||||
|
path.minX = path.length;
|
||||||
|
points.minX = points.length ? Math.min(point[0], points.minX) : point[0];
|
||||||
|
}
|
||||||
|
path.push(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm.
|
||||||
|
* http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
|
||||||
|
*
|
||||||
|
* @param points An array of [X, Y] coordinates
|
||||||
|
*/
|
||||||
|
function convexHull(points) {
|
||||||
|
points.sort(function(a, b) {
|
||||||
|
return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
var lower = [],
|
||||||
|
minY = 0;
|
||||||
|
for (var i = 0; i < points.length; i++) {
|
||||||
|
while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
|
||||||
|
lower.pop();
|
||||||
|
}
|
||||||
|
if (points[i][1] < points[minY][1]) {
|
||||||
|
minY = lower.length;
|
||||||
|
}
|
||||||
|
lower.push(points[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var upper = [],
|
||||||
|
maxY = points.length - 1;
|
||||||
|
for (var i = points.length ; i--;) {
|
||||||
|
while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
|
||||||
|
upper.pop();
|
||||||
|
}
|
||||||
|
if (points[i][1] > points[maxY][1]) {
|
||||||
|
maxY = upper.length;
|
||||||
|
}
|
||||||
|
upper.push(points[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// last points are equal to starting points of the other part
|
||||||
|
upper.pop();
|
||||||
|
lower.pop();
|
||||||
|
|
||||||
|
var hull = lower.concat(upper);
|
||||||
|
|
||||||
|
hull.minX = 0; // by sorting
|
||||||
|
hull.maxX = lower.length;
|
||||||
|
hull.minY = minY;
|
||||||
|
hull.maxY = (lower.length + maxY) % hull.length;
|
||||||
|
|
||||||
|
return hull;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cross(o, a, b) {
|
||||||
|
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
|
||||||
|
}
|
||||||
|
|
||||||
/* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
|
/* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
|
||||||
* Thanks to Dmitry Baranovskiy for his great work!
|
* Thanks to Dmitry Baranovskiy for his great work!
|
||||||
*/
|
*/
|
||||||
|
@ -11,7 +11,8 @@ exports.params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var path2js = require('./_path.js').path2js,
|
var path2js = require('./_path.js').path2js,
|
||||||
js2path = require('./_path.js').js2path;
|
js2path = require('./_path.js').js2path,
|
||||||
|
interesects = require('./_path.js').interesects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge multiple Paths into one.
|
* Merge multiple Paths into one.
|
||||||
@ -51,7 +52,7 @@ exports.fn = function(item, params) {
|
|||||||
prevPathJS = path2js(prevContentItem),
|
prevPathJS = path2js(prevContentItem),
|
||||||
curPathJS = path2js(contentItem);
|
curPathJS = path2js(contentItem);
|
||||||
|
|
||||||
if (equalData) {
|
if (equalData && !interesects(prevPathJS, curPathJS)) {
|
||||||
js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
|
js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<path d="M 30,30 z"/>
|
<path d="M 30,30 z"/>
|
||||||
<path d="M 30,30 z" fill="#f00"/>
|
<path d="M 30,30 z" fill="#f00"/>
|
||||||
<path d="M 40,40 z"/>
|
<path d="M 40,40 z"/>
|
||||||
<path d="m 50,50 z"/>
|
<path d="m 50,50 0,10 20,30 40,0"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
@@@
|
@@@
|
||||||
@ -13,5 +13,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M0 0zM10 10zM20 20l10 10M30 0c10 0 20 10 20 20M30 30z"/>
|
<path d="M0 0zM10 10zM20 20l10 10M30 0c10 0 20 10 20 20M30 30z"/>
|
||||||
<path d="M 30,30 z" fill="#f00"/>
|
<path d="M 30,30 z" fill="#f00"/>
|
||||||
<path d="M40 40zM50 50z"/>
|
<path d="M40 40zM50 50l0 10 20 30 40 0"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 494 B |
@ -13,6 +13,10 @@
|
|||||||
<path d="M 40,40" fill="#fff" stroke="#333"/>
|
<path d="M 40,40" fill="#fff" stroke="#333"/>
|
||||||
<path d="m 50,50" fill="#fff" stroke="#333"/>
|
<path d="m 50,50" fill="#fff" stroke="#333"/>
|
||||||
<path d="m 50,50 z" fill="#fff" stroke="#333"/>
|
<path d="m 50,50 z" fill="#fff" stroke="#333"/>
|
||||||
|
<path d="M0 0v100h100V0z" fill="red"/>
|
||||||
|
<path d="M200 0v100h100V0z" fill="red"/>
|
||||||
|
<path d="M0 0v100h100V0z" fill="blue"/>
|
||||||
|
<path d="M200 0v100h100V0zM0 200h100v100H0z" fill="blue"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
@@@
|
@@@
|
||||||
@ -22,4 +26,6 @@
|
|||||||
<path d="M 30,30 z" fill="#f00"/>
|
<path d="M 30,30 z" fill="#f00"/>
|
||||||
<path d="M40 40zM50 50zM40 40 50 50"/>
|
<path d="M40 40zM50 50zM40 40 50 50"/>
|
||||||
<path d="M40 40zM50 50zM40 40 50 50 50 50z" fill="#fff" stroke="#333"/>
|
<path d="M40 40zM50 50zM40 40 50 50 50 50z" fill="#fff" stroke="#333"/>
|
||||||
|
<path d="M0 0v100h100V0zM200 0v100h100V0z" fill="red"/>
|
||||||
|
<path d="M0 0v100h100V0zM200 0v100h100V0zM0 200h100v100H0z" fill="blue"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 925 B After Width: | Height: | Size: 1.2 KiB |
44
test/plugins/mergePaths.03.svg
Normal file
44
test/plugins/mergePaths.03.svg
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M30 0L0 40H60z"/>
|
||||||
|
<path d="M0 10H60L30 50z"/>
|
||||||
|
<path d="M0 0V50L50 0"/>
|
||||||
|
<path d="M0 60L50 10V60"/>
|
||||||
|
<g>
|
||||||
|
<path d="M100 0a50 50 0 0 1 0 100"/>
|
||||||
|
<path d="M25 25H75V75H25z"/>
|
||||||
|
<path d="M135 85H185V135H135z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M10 14H7v1h3v-1z"/>
|
||||||
|
<path d="M9 21H8v1h1v-1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M30 32.705V40h10.42L30 32.705z"/>
|
||||||
|
<path d="M46.25 34.928V30h-7.04l7.04 4.928z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M20 20H60L100 30"/>
|
||||||
|
<path d="M20 20L50 30H100"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
@@@
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M30 0L0 40H60z"/>
|
||||||
|
<path d="M0 10H60L30 50z"/>
|
||||||
|
<path d="M0 0V50L50 0M0 60L50 10V60"/>
|
||||||
|
<g>
|
||||||
|
<path d="M100 0a50 50 0 0 1 0 100M25 25H75V75H25z"/>
|
||||||
|
<path d="M135 85H185V135H135z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M10 14H7v1h3v-1zM9 21H8v1h1v-1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M30 32.705V40h10.42L30 32.705zM46.25 34.928V30h-7.04l7.04 4.928z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="M20 20H60L100 30M20 20L50 30H100"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
10
test/plugins/mergePaths.04.svg
Normal file
10
test/plugins/mergePaths.04.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M320 60c17.466-8.733 33.76-12.78 46.593-12.484 12.856.297 22.254 4.936 26.612 12.484 4.358 7.548 3.676 18.007-2.494 29.29-6.16 11.26-17.812 23.348-34.107 34.107-16.26 10.735-37.164 20.14-60.72 26.613C272.356 156.473 246.178 160 220 160c-26.18 0-52.357-3.527-75.882-9.99-23.557-6.472-44.462-15.878-60.72-26.613-16.296-10.76-27.95-22.846-34.11-34.108-6.17-11.283-6.85-21.742-2.493-29.29 4.358-7.548 13.756-12.187 26.612-12.484C86.24 47.22 102.535 51.266 120 60c17.426 8.713 36.024 22.114 53.407 39.28C190.767 116.42 206.91 137.33 220 160c13.09 22.67 23.124 47.106 29.29 70.71 6.173 23.638 8.48 46.445 7.313 65.893-1.17 19.49-5.812 35.627-12.485 46.592C237.432 354.18 228.716 360 220 360s-17.432-5.82-24.118-16.805c-6.673-10.965-11.315-27.1-12.485-46.592-1.167-19.448 1.14-42.255 7.314-65.892 6.166-23.604 16.2-48.04 29.29-70.71 13.09-22.67 29.233-43.58 46.593-60.72C283.976 82.113 302.573 68.712 320 60z"/>
|
||||||
|
<path d="M280 320l100-173.2h200l100 173.2-100 173.2h-200"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
@@@
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M320 60c17.466-8.733 33.76-12.78 46.593-12.484 12.856.297 22.254 4.936 26.612 12.484 4.358 7.548 3.676 18.007-2.494 29.29-6.16 11.26-17.812 23.348-34.107 34.107-16.26 10.735-37.164 20.14-60.72 26.613C272.356 156.473 246.178 160 220 160c-26.18 0-52.357-3.527-75.882-9.99-23.557-6.472-44.462-15.878-60.72-26.613-16.296-10.76-27.95-22.846-34.11-34.108-6.17-11.283-6.85-21.742-2.493-29.29 4.358-7.548 13.756-12.187 26.612-12.484C86.24 47.22 102.535 51.266 120 60c17.426 8.713 36.024 22.114 53.407 39.28C190.767 116.42 206.91 137.33 220 160c13.09 22.67 23.124 47.106 29.29 70.71 6.173 23.638 8.48 46.445 7.313 65.893-1.17 19.49-5.812 35.627-12.485 46.592C237.432 354.18 228.716 360 220 360s-17.432-5.82-24.118-16.805c-6.673-10.965-11.315-27.1-12.485-46.592-1.167-19.448 1.14-42.255 7.314-65.892 6.166-23.604 16.2-48.04 29.29-70.71 13.09-22.67 29.233-43.58 46.593-60.72C283.976 82.113 302.573 68.712 320 60zM280 320l100-173.2h200l100 173.2-100 173.2h-200"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
Reference in New Issue
Block a user