以编程方式减去 SVG 路径
Subtracting SVG paths programmatically
我正在尝试找到一种从另一个 SVG 路径中减去 SVG 路径的方法,类似于反向剪辑蒙版。我不能使用过滤器,因为我需要找到复合路径与其他路径的交点。 Illustrator 使用 'minus front' 探路者工具执行此操作,如下所示:
红色方块减去前的路径:
<rect class="cls-1" x="0.5" y="0.5" width="184.93" height="178.08"/>
减法后:
<polygon class="cls-1" points="112.83 52.55 185.43 52.55 185.43 0.5 0.5 0.5 0.5 178.58 112.83 178.58 112.83 52.55"/>
我需要它来处理所有类型的形状,包括曲线。如果重要,输入的 SVG 将全部转换为通用路径。
一般来说这是一个不平凡的问题。
如果您可以接受将形状光栅化为像素,在那里执行布尔运算,然后使用行进方块 + 简化将结果矢量化,则可以轻松解决(小代码)。
代替计算精确*几何结果的已知算法非常复杂,难以在保持快速的同时正确实施。
Clipper is an easy to use library to perform this kind of computation in C++, with ports to Javascript.
请注意,困难在于正确处理边缘情况(例如,当输入线部分重叠或顶点恰好落在一条线上并且结果包括 zero-area 部分时)。
仅对清楚交叉的情况进行推理的代码更容易编写,但不幸的是,当这些边缘情况确实发生时,可能会产生宏观上错误的结果。
浮点数学太难以预测,无法用于这些计算...请参阅 https://hal.inria.fr/inria-00344310/document 以详细讨论使用浮点数学进行精确几何计算时会出现的问题类型.
即使是一个“简单”的方程式,比如判断三个点是否共线的方程式,clock-wise 或 counter-clokwise 在使用浮点数学计算时也会表现得很疯狂......(来自论文的图片)
(*) 整数坐标的两条线段的交点坐标,一般不能用double-precision数精确表示;因此,除非您使用支持有理数的数字库,否则结果仍将是一个近似值。
您可能需要 paper.js 来完成这项任务。
以下示例还使用了 Jarek Foksa's pathData polyfill.
paper.js 例子
var svg = document.querySelector("#svgSubtract");
// set auto ids for processing
function setAutoIDs(svg) {
let svgtEls = svg.querySelectorAll(
"path, polygon, rect, circle, line, text, g"
);
svgtEls.forEach(function(el, i) {
if (!el.getAttribute("id")) {
el.id = el.nodeName + "-" + i;
}
});
}
setAutoIDs(svg);
function shapesToPath(svg) {
let els = svg.querySelectorAll('rect, circle, polygon');
els.forEach(function(el, i) {
let className = el.getAttribute('class');
let id = el.id;
let d = el.getAttribute('d');
let fill = el.getAttribute('fill');
let pathData = el.getPathData({
normalize: true
});
let pathTmp = document.createElementNS("http://www.w3.org/2000/svg", 'path');
pathTmp.id = id;
pathTmp.setAttribute('class', className);
pathTmp.setAttribute('fill', fill);
pathTmp.setPathData(pathData);
svg.insertBefore(pathTmp, el);
el.remove();
})
};
shapesToPath(svg);
function subtract(svg) {
// init paper.js and add mandatory canvas
canvas = document.createElement('canvas');
canvas.id = "canvasPaper";
canvas.setAttribute('style', 'display:none')
document.body.appendChild(canvas);
paper.setup("canvasPaper");
let all = paper.project.importSVG(svg, function(item, i) {
let items = item.getItems();
// remove first item not containing path data
items.shift();
// get id names for selecting svg elements after processing
let ids = Object.keys(item._namedChildren);
if (items.length) {
let lastEl = items[items.length - 1];
// subtract paper.js objects
let subtracted = items[0].subtract(lastEl);
// convert subtracted paper.js object to svg pathData
let subtractedData = subtracted
.exportSVG({
precision: 3
})
.getAttribute("d");
let svgElFirst = svg.querySelector('#' + ids[0]);
let svgElLast = svg.querySelector('#' + ids[ids.length - 1]);
// overwrite original svg path
svgElFirst.setAttribute("d", subtractedData);
// delete subtracted svg path
svgElLast.remove();
}
});
}
svg {
display: inline-block;
width: 25%;
border: 1px solid #ccc
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
<p>
<button type="button" onclick="subtract(svg)">Subtract Path </button>
</p>
<svg id="svgSubtract" viewBox="0 0 100 100">
<rect class="cls-1" x="0" y="0" width="80" height="80" fill="red" />
<path d="M87.9,78.7C87.9,84,86,88,82.2,91c-3.8,2.9-8.9,4.4-15.4,4.4c-7,0-12.5-0.9-16.2-2.7v-6.7c2.4,1,5.1,1.8,8,2.4
c2.9,0.6,5.7,0.9,8.5,0.9c4.6,0,8.1-0.9,10.4-2.6c2.3-1.7,3.5-4.2,3.5-7.3c0-2.1-0.4-3.7-1.2-5.1c-0.8-1.3-2.2-2.5-4.1-3.6
c-1.9-1.1-4.9-2.4-8.8-3.8c-5.5-2-9.5-4.3-11.8-7c-2.4-2.7-3.6-6.2-3.6-10.6c0-4.6,1.7-8.2,5.2-10.9c3.4-2.7,8-4.1,13.6-4.1
c5.9,0,11.3,1.1,16.3,3.2l-2.2,6c-4.9-2.1-9.7-3.1-14.3-3.1c-3.7,0-6.5,0.8-8.6,2.4c-2.1,1.6-3.1,3.8-3.1,6.6
c0,2.1,0.4,3.7,1.1,5.1c0.8,1.3,2,2.5,3.8,3.6c1.8,1.1,4.6,2.3,8.3,3.6c6.2,2.2,10.5,4.6,12.9,7.1C86.7,71.4,87.9,74.7,87.9,78.7z"
/>
</svg>
路径规范化(使用 getPathData() polyfill)
我们需要转换 svg 图元 (<rect>
, <circle>
, <polygon>
)
到 <path>
个元素——至少在使用 paper.js Boolean operations.
时
本机创建为 paper.js 个对象的形状不需要此步骤。
pathData polyfill 提供了 normalizing svg elements.
的方法
此规范化将输出一个 d
属性(对于每个选定的 svg 子元素)仅包含 简化的立方路径命令集(M、C、L、Z) – 全部基于绝对坐标.
例2(多个元素相减)
const svg = document.querySelector("#svgSubtract");
const btnDownload = document.querySelector("#btnDownload");
const decimals = 1;
// set auto ids for processing
function setAutoIDs(svg) {
let svgtEls = svg.querySelectorAll(
"path, polygon, rect, circle, line, text, g"
);
svgtEls.forEach(function(el, i) {
if (!el.getAttribute("id")) {
el.id = el.nodeName + "-" + i;
}
});
}
setAutoIDs(svg);
function shapesToPathMerged(svg) {
let els = svg.querySelectorAll('path, rect, circle, polygon, ellipse ');
let pathsCombinedData = '';
let className = els[1].getAttribute('class');
let id = els[1].id;
let d = els[1].getAttribute('d');
let fill = els[1].getAttribute('fill');
els.forEach(function(el, i) {
let pathData = el.getPathData({
normalize: true
});
if (i == 0 && el.nodeName.toLowerCase() != 'path') {
let firstTmp = document.createElementNS("http://www.w3.org/2000/svg", 'path');
let firstClassName = els[1].getAttribute('class');
let firstId = el.id;
let firstFill = el.getAttribute('fill');
firstTmp.setPathData(pathData);
firstTmp.id = firstId;
firstTmp.setAttribute('class', firstClassName);
firstTmp.setAttribute('fill', firstFill);
svg.insertBefore(firstTmp, el);
el.remove();
}
if (i > 0) {
pathData.forEach(function(command, c) {
pathsCombinedData += ' ' + command['type'] + '' + command['values'].join(' ');
});
el.remove();
}
})
let pathTmp = document.createElementNS("http://www.w3.org/2000/svg", 'path');
pathTmp.id = id;
pathTmp.setAttribute('class', className);
pathTmp.setAttribute('fill', fill);
pathTmp.setAttribute('d', pathsCombinedData);
svg.insertBefore(pathTmp, els[0].nextElementSibling);
};
shapesToPathMerged(svg);
function subtract(svg) {
// init paper.js and add mandatory canvas
canvas = document.createElement('canvas');
canvas.id = "canvasPaper";
canvas.setAttribute('style', 'display:none')
document.body.appendChild(canvas);
paper.setup("canvasPaper");
let all = paper.project.importSVG(svg, function(item, i) {
let items = item.getItems();
// remove first item not containing path data
items.shift();
// get id names for selecting svg elements after processing
let ids = Object.keys(item._namedChildren);
if (items.length) {
let lastEl = items[items.length - 1];
// subtract paper.js objects
let subtracted = items[0].subtract(lastEl);
// convert subtracted paper.js object to svg pathData
let subtractedData = subtracted
.exportSVG({
precision: decimals
})
.getAttribute("d");
let svgElFirst = svg.querySelector('#' + ids[0]);
let svgElLast = svg.querySelector('#' + ids[ids.length - 1]);
// overwrite original svg path
svgElFirst.setAttribute("d", subtractedData);
// delete subtracted svg path
svgElLast.remove();
}
});
// get data URL
getdataURL(svg)
}
function getdataURL(svg) {
let markup = svg.outerHTML;
markupOpt = 'data:image/svg+xml;utf8,' + markup.replaceAll('"', '\'').
replaceAll('\t', '').
replaceAll('\n', '').
replaceAll('\r', '').
replaceAll('></path>', '/>').
replaceAll('<', '%3C').
replaceAll('>', '%3E').
replaceAll('#', '%23').
replaceAll(',', ' ').
replaceAll(' -', '-').
replace(/ +(?= )/g, '');
let btn = document.createElement('a');
btn.href = markupOpt;
btn.innerText = 'Download Svg';
btn.setAttribute('download', 'subtracted.svg');
document.body.insertAdjacentElement('afterbegin', btn);
return markupOpt;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<p>
<button type="button" onclick="subtract(svg)">Subtract Path </button>
</p>
<svg id="svgSubtract" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect class="cls-1" x="0" y="0" width="80" height="80" fill="red" />
<path id="s"
d="M87.9,78.7C87.9,84,86,88,82.2,91c-3.8,2.9-8.9,4.4-15.4,4.4c-7,0-12.5-0.9-16.2-2.7v-6.7c2.4,1,5.1,1.8,8,2.4
c2.9,0.6,5.7,0.9,8.5,0.9c4.6,0,8.1-0.9,10.4-2.6c2.3-1.7,3.5-4.2,3.5-7.3c0-2.1-0.4-3.7-1.2-5.1c-0.8-1.3-2.2-2.5-4.1-3.6
c-1.9-1.1-4.9-2.4-8.8-3.8c-5.5-2-9.5-4.3-11.8-7c-2.4-2.7-3.6-6.2-3.6-10.6c0-4.6,1.7-8.2,5.2-10.9c3.4-2.7,8-4.1,13.6-4.1
c5.9,0,11.3,1.1,16.3,3.2l-2.2,6c-4.9-2.1-9.7-3.1-14.3-3.1c-3.7,0-6.5,0.8-8.6,2.4c-2.1,1.6-3.1,3.8-3.1,6.6
c0,2.1,0.4,3.7,1.1,5.1c0.8,1.3,2,2.5,3.8,3.6c1.8,1.1,4.6,2.3,8.3,3.6c6.2,2.2,10.5,4.6,12.9,7.1C86.7,71.4,87.9,74.7,87.9,78.7z" />
<path id="o" d="M30.2,22.4c0,8.3-5.8,12-11.2,12c-6.1,0-10.8-4.5-10.8-11.6c0-7.5,4.9-12,11.2-12C25.9,10.8,30.2,15.5,30.2,22.4z
M12.4,22.6c0,4.9,2.8,8.7,6.8,8.7c3.9,0,6.8-3.7,6.8-8.7c0-3.8-1.9-8.7-6.7-8.7C14.5,13.8,12.4,18.3,12.4,22.6z" />
<circle cx="50%" cy="50%" r="10%"></circle>
</svg>
我正在尝试找到一种从另一个 SVG 路径中减去 SVG 路径的方法,类似于反向剪辑蒙版。我不能使用过滤器,因为我需要找到复合路径与其他路径的交点。 Illustrator 使用 'minus front' 探路者工具执行此操作,如下所示:
红色方块减去前的路径:
<rect class="cls-1" x="0.5" y="0.5" width="184.93" height="178.08"/>
减法后:
<polygon class="cls-1" points="112.83 52.55 185.43 52.55 185.43 0.5 0.5 0.5 0.5 178.58 112.83 178.58 112.83 52.55"/>
我需要它来处理所有类型的形状,包括曲线。如果重要,输入的 SVG 将全部转换为通用路径。
一般来说这是一个不平凡的问题。
如果您可以接受将形状光栅化为像素,在那里执行布尔运算,然后使用行进方块 + 简化将结果矢量化,则可以轻松解决(小代码)。
代替计算精确*几何结果的已知算法非常复杂,难以在保持快速的同时正确实施。
Clipper is an easy to use library to perform this kind of computation in C++, with ports to Javascript.
请注意,困难在于正确处理边缘情况(例如,当输入线部分重叠或顶点恰好落在一条线上并且结果包括 zero-area 部分时)。
仅对清楚交叉的情况进行推理的代码更容易编写,但不幸的是,当这些边缘情况确实发生时,可能会产生宏观上错误的结果。
浮点数学太难以预测,无法用于这些计算...请参阅 https://hal.inria.fr/inria-00344310/document 以详细讨论使用浮点数学进行精确几何计算时会出现的问题类型.
即使是一个“简单”的方程式,比如判断三个点是否共线的方程式,clock-wise 或 counter-clokwise 在使用浮点数学计算时也会表现得很疯狂......(来自论文的图片)
(*) 整数坐标的两条线段的交点坐标,一般不能用double-precision数精确表示;因此,除非您使用支持有理数的数字库,否则结果仍将是一个近似值。
您可能需要 paper.js 来完成这项任务。
以下示例还使用了 Jarek Foksa's pathData polyfill.
paper.js 例子
var svg = document.querySelector("#svgSubtract");
// set auto ids for processing
function setAutoIDs(svg) {
let svgtEls = svg.querySelectorAll(
"path, polygon, rect, circle, line, text, g"
);
svgtEls.forEach(function(el, i) {
if (!el.getAttribute("id")) {
el.id = el.nodeName + "-" + i;
}
});
}
setAutoIDs(svg);
function shapesToPath(svg) {
let els = svg.querySelectorAll('rect, circle, polygon');
els.forEach(function(el, i) {
let className = el.getAttribute('class');
let id = el.id;
let d = el.getAttribute('d');
let fill = el.getAttribute('fill');
let pathData = el.getPathData({
normalize: true
});
let pathTmp = document.createElementNS("http://www.w3.org/2000/svg", 'path');
pathTmp.id = id;
pathTmp.setAttribute('class', className);
pathTmp.setAttribute('fill', fill);
pathTmp.setPathData(pathData);
svg.insertBefore(pathTmp, el);
el.remove();
})
};
shapesToPath(svg);
function subtract(svg) {
// init paper.js and add mandatory canvas
canvas = document.createElement('canvas');
canvas.id = "canvasPaper";
canvas.setAttribute('style', 'display:none')
document.body.appendChild(canvas);
paper.setup("canvasPaper");
let all = paper.project.importSVG(svg, function(item, i) {
let items = item.getItems();
// remove first item not containing path data
items.shift();
// get id names for selecting svg elements after processing
let ids = Object.keys(item._namedChildren);
if (items.length) {
let lastEl = items[items.length - 1];
// subtract paper.js objects
let subtracted = items[0].subtract(lastEl);
// convert subtracted paper.js object to svg pathData
let subtractedData = subtracted
.exportSVG({
precision: 3
})
.getAttribute("d");
let svgElFirst = svg.querySelector('#' + ids[0]);
let svgElLast = svg.querySelector('#' + ids[ids.length - 1]);
// overwrite original svg path
svgElFirst.setAttribute("d", subtractedData);
// delete subtracted svg path
svgElLast.remove();
}
});
}
svg {
display: inline-block;
width: 25%;
border: 1px solid #ccc
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
<p>
<button type="button" onclick="subtract(svg)">Subtract Path </button>
</p>
<svg id="svgSubtract" viewBox="0 0 100 100">
<rect class="cls-1" x="0" y="0" width="80" height="80" fill="red" />
<path d="M87.9,78.7C87.9,84,86,88,82.2,91c-3.8,2.9-8.9,4.4-15.4,4.4c-7,0-12.5-0.9-16.2-2.7v-6.7c2.4,1,5.1,1.8,8,2.4
c2.9,0.6,5.7,0.9,8.5,0.9c4.6,0,8.1-0.9,10.4-2.6c2.3-1.7,3.5-4.2,3.5-7.3c0-2.1-0.4-3.7-1.2-5.1c-0.8-1.3-2.2-2.5-4.1-3.6
c-1.9-1.1-4.9-2.4-8.8-3.8c-5.5-2-9.5-4.3-11.8-7c-2.4-2.7-3.6-6.2-3.6-10.6c0-4.6,1.7-8.2,5.2-10.9c3.4-2.7,8-4.1,13.6-4.1
c5.9,0,11.3,1.1,16.3,3.2l-2.2,6c-4.9-2.1-9.7-3.1-14.3-3.1c-3.7,0-6.5,0.8-8.6,2.4c-2.1,1.6-3.1,3.8-3.1,6.6
c0,2.1,0.4,3.7,1.1,5.1c0.8,1.3,2,2.5,3.8,3.6c1.8,1.1,4.6,2.3,8.3,3.6c6.2,2.2,10.5,4.6,12.9,7.1C86.7,71.4,87.9,74.7,87.9,78.7z"
/>
</svg>
路径规范化(使用 getPathData() polyfill)
我们需要转换 svg 图元 (<rect>
, <circle>
, <polygon>
)
到 <path>
个元素——至少在使用 paper.js Boolean operations.
时
本机创建为 paper.js 个对象的形状不需要此步骤。
pathData polyfill 提供了 normalizing svg elements.
的方法
此规范化将输出一个 d
属性(对于每个选定的 svg 子元素)仅包含 简化的立方路径命令集(M、C、L、Z) – 全部基于绝对坐标.
例2(多个元素相减)
const svg = document.querySelector("#svgSubtract");
const btnDownload = document.querySelector("#btnDownload");
const decimals = 1;
// set auto ids for processing
function setAutoIDs(svg) {
let svgtEls = svg.querySelectorAll(
"path, polygon, rect, circle, line, text, g"
);
svgtEls.forEach(function(el, i) {
if (!el.getAttribute("id")) {
el.id = el.nodeName + "-" + i;
}
});
}
setAutoIDs(svg);
function shapesToPathMerged(svg) {
let els = svg.querySelectorAll('path, rect, circle, polygon, ellipse ');
let pathsCombinedData = '';
let className = els[1].getAttribute('class');
let id = els[1].id;
let d = els[1].getAttribute('d');
let fill = els[1].getAttribute('fill');
els.forEach(function(el, i) {
let pathData = el.getPathData({
normalize: true
});
if (i == 0 && el.nodeName.toLowerCase() != 'path') {
let firstTmp = document.createElementNS("http://www.w3.org/2000/svg", 'path');
let firstClassName = els[1].getAttribute('class');
let firstId = el.id;
let firstFill = el.getAttribute('fill');
firstTmp.setPathData(pathData);
firstTmp.id = firstId;
firstTmp.setAttribute('class', firstClassName);
firstTmp.setAttribute('fill', firstFill);
svg.insertBefore(firstTmp, el);
el.remove();
}
if (i > 0) {
pathData.forEach(function(command, c) {
pathsCombinedData += ' ' + command['type'] + '' + command['values'].join(' ');
});
el.remove();
}
})
let pathTmp = document.createElementNS("http://www.w3.org/2000/svg", 'path');
pathTmp.id = id;
pathTmp.setAttribute('class', className);
pathTmp.setAttribute('fill', fill);
pathTmp.setAttribute('d', pathsCombinedData);
svg.insertBefore(pathTmp, els[0].nextElementSibling);
};
shapesToPathMerged(svg);
function subtract(svg) {
// init paper.js and add mandatory canvas
canvas = document.createElement('canvas');
canvas.id = "canvasPaper";
canvas.setAttribute('style', 'display:none')
document.body.appendChild(canvas);
paper.setup("canvasPaper");
let all = paper.project.importSVG(svg, function(item, i) {
let items = item.getItems();
// remove first item not containing path data
items.shift();
// get id names for selecting svg elements after processing
let ids = Object.keys(item._namedChildren);
if (items.length) {
let lastEl = items[items.length - 1];
// subtract paper.js objects
let subtracted = items[0].subtract(lastEl);
// convert subtracted paper.js object to svg pathData
let subtractedData = subtracted
.exportSVG({
precision: decimals
})
.getAttribute("d");
let svgElFirst = svg.querySelector('#' + ids[0]);
let svgElLast = svg.querySelector('#' + ids[ids.length - 1]);
// overwrite original svg path
svgElFirst.setAttribute("d", subtractedData);
// delete subtracted svg path
svgElLast.remove();
}
});
// get data URL
getdataURL(svg)
}
function getdataURL(svg) {
let markup = svg.outerHTML;
markupOpt = 'data:image/svg+xml;utf8,' + markup.replaceAll('"', '\'').
replaceAll('\t', '').
replaceAll('\n', '').
replaceAll('\r', '').
replaceAll('></path>', '/>').
replaceAll('<', '%3C').
replaceAll('>', '%3E').
replaceAll('#', '%23').
replaceAll(',', ' ').
replaceAll(' -', '-').
replace(/ +(?= )/g, '');
let btn = document.createElement('a');
btn.href = markupOpt;
btn.innerText = 'Download Svg';
btn.setAttribute('download', 'subtracted.svg');
document.body.insertAdjacentElement('afterbegin', btn);
return markupOpt;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<p>
<button type="button" onclick="subtract(svg)">Subtract Path </button>
</p>
<svg id="svgSubtract" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect class="cls-1" x="0" y="0" width="80" height="80" fill="red" />
<path id="s"
d="M87.9,78.7C87.9,84,86,88,82.2,91c-3.8,2.9-8.9,4.4-15.4,4.4c-7,0-12.5-0.9-16.2-2.7v-6.7c2.4,1,5.1,1.8,8,2.4
c2.9,0.6,5.7,0.9,8.5,0.9c4.6,0,8.1-0.9,10.4-2.6c2.3-1.7,3.5-4.2,3.5-7.3c0-2.1-0.4-3.7-1.2-5.1c-0.8-1.3-2.2-2.5-4.1-3.6
c-1.9-1.1-4.9-2.4-8.8-3.8c-5.5-2-9.5-4.3-11.8-7c-2.4-2.7-3.6-6.2-3.6-10.6c0-4.6,1.7-8.2,5.2-10.9c3.4-2.7,8-4.1,13.6-4.1
c5.9,0,11.3,1.1,16.3,3.2l-2.2,6c-4.9-2.1-9.7-3.1-14.3-3.1c-3.7,0-6.5,0.8-8.6,2.4c-2.1,1.6-3.1,3.8-3.1,6.6
c0,2.1,0.4,3.7,1.1,5.1c0.8,1.3,2,2.5,3.8,3.6c1.8,1.1,4.6,2.3,8.3,3.6c6.2,2.2,10.5,4.6,12.9,7.1C86.7,71.4,87.9,74.7,87.9,78.7z" />
<path id="o" d="M30.2,22.4c0,8.3-5.8,12-11.2,12c-6.1,0-10.8-4.5-10.8-11.6c0-7.5,4.9-12,11.2-12C25.9,10.8,30.2,15.5,30.2,22.4z
M12.4,22.6c0,4.9,2.8,8.7,6.8,8.7c3.9,0,6.8-3.7,6.8-8.7c0-3.8-1.9-8.7-6.7-8.7C14.5,13.8,12.4,18.3,12.4,22.6z" />
<circle cx="50%" cy="50%" r="10%"></circle>
</svg>