fonteditor-core
Version:
fonts (ttf, woff, woff2, eot, svg, otf) parse, write, transform, glyph adjust.
530 lines (454 loc) • 12.8 kB
JavaScript
/**
* @file svg path转换为轮廓
* @author mengke01(kekee000@gmail.com)
*/
import bezierCubic2Q2 from '../../math/bezierCubic2Q2';
import getArc from '../../graphics/getArc';
import parseParams from './parseParams';
/**
* 三次贝塞尔曲线,转二次贝塞尔曲线
*
* @param {Array} cubicList 三次曲线数组
* @param {Array} contour 当前解析后的轮廓数组
* @return {Array} 当前解析后的轮廓数组
*/
function cubic2Points(cubicList, contour) {
let i;
let l;
const q2List = [];
cubicList.forEach(c => {
const list = bezierCubic2Q2(c[0], c[1], c[2], c[3]);
for (i = 0, l = list.length; i < l; i++) {
q2List.push(list[i]);
}
});
let q2;
let prevq2;
for (i = 0, l = q2List.length; i < l; i++) {
q2 = q2List[i];
if (i === 0) {
contour.push({
x: q2[1].x,
y: q2[1].y
});
contour.push({
x: q2[2].x,
y: q2[2].y,
onCurve: true
});
}
else {
prevq2 = q2List[i - 1];
// 检查是否存在切线点
if (
prevq2[1].x + q2[1].x === 2 * q2[0].x
&& prevq2[1].y + q2[1].y === 2 * q2[0].y
) {
contour.pop();
}
contour.push({
x: q2[1].x,
y: q2[1].y
});
contour.push({
x: q2[2].x,
y: q2[2].y,
onCurve: true
});
}
}
contour.push({
x: q2[2].x,
y: q2[2].y,
onCurve: true
});
return contour;
}
/**
* svg 命令数组转轮廓
*
* @param {Array} segments svg 命令数组
* @return {Array} 轮廓数组
*/
function segments2Contours(segments) {
// 解析segments
const contours = [];
let contour = [];
let prevX = 0;
let prevY = 0;
let segment;
let args;
let cmd;
let relative;
let q;
let ql;
let px;
let py;
let cubicList;
let p1;
let p2;
let c1;
let c2;
let prevCubicC1; // 三次贝塞尔曲线前一个控制点,用于绘制`s`命令
for (let i = 0, l = segments.length; i < l; i++) {
segment = segments[i];
cmd = segment.cmd;
relative = segment.relative;
args = segment.args;
if (args && !args.length && cmd !== 'Z') {
console.warn('`' + cmd + '` command args empty!');
continue;
}
if (cmd === 'Z') {
contours.push(contour);
contour = [];
}
else if (cmd === 'M' || cmd === 'L') {
if (args.length % 2) {
throw new Error('`M` command error:' + args.join(','));
}
// 这里可能会连续绘制,最后一个是终点
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
for (q = 0, ql = args.length; q < ql; q += 2) {
if (relative) {
px += args[q];
py += args[q + 1];
}
else {
px = args[q];
py = args[q + 1];
}
contour.push({
x: px,
y: py,
onCurve: true
});
}
prevX = px;
prevY = py;
}
else if (cmd === 'H') {
if (relative) {
prevX += args[0];
}
else {
prevX = args[0];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
else if (cmd === 'V') {
if (relative) {
prevY += args[0];
}
else {
prevY = args[0];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
// 二次贝塞尔
else if (cmd === 'Q') {
// 这里可能会连续绘制,最后一个是终点
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
for (q = 0, ql = args.length; q < ql; q += 4) {
contour.push({
x: px + args[q],
y: py + args[q + 1]
});
contour.push({
x: px + args[q + 2],
y: py + args[q + 3],
onCurve: true
});
if (relative) {
px += args[q + 2];
py += args[q + 3];
}
else {
px = 0;
py = 0;
}
}
if (relative) {
prevX = px;
prevY = py;
}
else {
prevX = args[ql - 2];
prevY = args[ql - 1];
}
}
// 二次贝塞尔平滑
else if (cmd === 'T') {
// 这里需要移除上一个曲线的终点
let last = contour.pop();
let pc = contour[contour.length - 1];
if (!pc) {
pc = last;
}
contour.push(pc = {
x: 2 * last.x - pc.x,
y: 2 * last.y - pc.y
});
px = prevX;
py = prevY;
for (q = 0, ql = args.length - 2; q < ql; q += 2) {
if (relative) {
px += args[q];
py += args[q + 1];
}
else {
px = args[q];
py = args[q + 1];
}
last = {
x: px,
y: py
};
contour.push(pc = {
x: 2 * last.x - pc.x,
y: 2 * last.y - pc.y
});
}
if (relative) {
prevX = px + args[ql];
prevY = py + args[ql + 1];
}
else {
prevX = args[ql];
prevY = args[ql + 1];
}
contour.push({
x: prevX,
y: prevY,
onCurve: true
});
}
// 三次贝塞尔
else if (cmd === 'C') {
if (args.length % 6) {
throw new Error('`C` command params error:' + args.join(','));
}
// 这里可能会连续绘制,最后一个是终点
cubicList = [];
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
p1 = {
x: prevX,
y: prevY
};
for (q = 0, ql = args.length; q < ql; q += 6) {
c1 = {
x: px + args[q],
y: py + args[q + 1]
};
c2 = {
x: px + args[q + 2],
y: py + args[q + 3]
};
p2 = {
x: px + args[q + 4],
y: py + args[q + 5]
};
cubicList.push([p1, c1, c2, p2]);
p1 = p2;
if (relative) {
px += args[q + 4];
py += args[q + 5];
}
else {
px = 0;
py = 0;
}
}
if (relative) {
prevX = px;
prevY = py;
}
else {
prevX = args[ql - 2];
prevY = args[ql - 1];
}
cubic2Points(cubicList, contour);
prevCubicC1 = cubicList[cubicList.length - 1][2];
}
// 三次贝塞尔平滑
else if (cmd === 'S') {
if (args.length % 4) {
throw new Error('`S` command params error:' + args.join(','));
}
// 这里可能会连续绘制,最后一个是终点
cubicList = [];
if (relative) {
px = prevX;
py = prevY;
}
else {
px = 0;
py = 0;
}
// 这里需要移除上一个曲线的终点
p1 = contour.pop();
if (!prevCubicC1) {
prevCubicC1 = p1;
}
c1 = {
x: 2 * p1.x - prevCubicC1.x,
y: 2 * p1.y - prevCubicC1.y
};
for (q = 0, ql = args.length; q < ql; q += 4) {
c2 = {
x: px + args[q],
y: py + args[q + 1]
};
p2 = {
x: px + args[q + 2],
y: py + args[q + 3]
};
cubicList.push([p1, c1, c2, p2]);
p1 = p2;
c1 = {
x: 2 * p1.x - c2.x,
y: 2 * p1.y - c2.y
};
if (relative) {
px += args[q + 2];
py += args[q + 3];
}
else {
px = 0;
py = 0;
}
}
if (relative) {
prevX = px;
prevY = py;
}
else {
prevX = args[ql - 2];
prevY = args[ql - 1];
}
cubic2Points(cubicList, contour);
prevCubicC1 = cubicList[cubicList.length - 1][2];
}
// 求弧度, rx, ry, angle, largeArc, sweep, ex, ey
else if (cmd === 'A') {
if (args.length % 7) {
throw new Error('arc command params error:' + args.join(','));
}
for (q = 0, ql = args.length; q < ql; q += 7) {
let ex = args[q + 5];
let ey = args[q + 6];
if (relative) {
ex = prevX + ex;
ey = prevY + ey;
}
const path = getArc(
args[q], args[q + 1],
args[q + 2], args[q + 3], args[q + 4],
{x: prevX, y: prevY},
{x: ex, y: ey}
);
if (path && path.length > 1) {
for (let r = 1, rl = path.length; r < rl; r++) {
contour.push(path[r]);
}
}
prevX = ex;
prevY = ey;
}
}
}
return contours;
}
/**
* svg path转轮廓
*
* @param {string} path svg的path字符串
* @return {Array} 转换后的轮廓
*/
export default function path2contours(path) {
if (!path || !path.length) {
return null;
}
path = path.trim();
// 修正头部不为`m`的情况
if (path[0] !== 'M' && path[0] !== 'm') {
path = 'M 0 0' + path;
}
// 修复中间没有结束符`z`的情况
path = path.replace(/(\d+)\s*(m|$)/gi, '$1z$2');
// 获取segments
const segments = [];
let cmd;
let relative = false;
let lastIndex;
let args;
for (let i = 0, l = path.length; i < l; i++) {
const c = path[i].toUpperCase();
const r = c !== path[i];
switch (c) {
case 'M':
/* jshint -W086 */
if (i === 0) {
cmd = c;
lastIndex = 1;
break;
}
// eslint-disable-next-line no-fallthrough
case 'Q':
case 'T':
case 'C':
case 'S':
case 'H':
case 'V':
case 'L':
case 'A':
case 'Z':
if (cmd === 'Z') {
segments.push({cmd: 'Z'});
}
else {
args = path.slice(lastIndex, i);
segments.push({
cmd,
relative,
args: parseParams(args)
});
}
cmd = c;
relative = r;
lastIndex = i + 1;
break;
}
}
segments.push({cmd: 'Z'});
return segments2Contours(segments);
}