generate iconFont
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
6.7 KiB

'use strict';
var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0 };
var SPECIAL_SPACES = [
0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF
];
function isSpace(ch) {
return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) || // Line terminators
// White spaces
(ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
(ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0);
}
function isCommand(code) {
/*eslint-disable no-bitwise*/
switch (code | 0x20) {
case 0x6D/* m */:
case 0x7A/* z */:
case 0x6C/* l */:
case 0x68/* h */:
case 0x76/* v */:
case 0x63/* c */:
case 0x73/* s */:
case 0x71/* q */:
case 0x74/* t */:
case 0x61/* a */:
case 0x72/* r */:
return true;
}
return false;
}
function isArc(code) {
return (code | 0x20) === 0x61;
}
function isDigit(code) {
return (code >= 48 && code <= 57); // 0..9
}
function isDigitStart(code) {
return (code >= 48 && code <= 57) || /* 0..9 */
code === 0x2B || /* + */
code === 0x2D || /* - */
code === 0x2E; /* . */
}
function State(path) {
this.index = 0;
this.path = path;
this.max = path.length;
this.result = [];
this.param = 0.0;
this.err = '';
this.segmentStart = 0;
this.data = [];
}
function skipSpaces(state) {
while (state.index < state.max && isSpace(state.path.charCodeAt(state.index))) {
state.index++;
}
}
function scanFlag(state) {
var ch = state.path.charCodeAt(state.index);
if (ch === 0x30/* 0 */) {
state.param = 0;
state.index++;
return;
}
if (ch === 0x31/* 1 */) {
state.param = 1;
state.index++;
return;
}
state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')';
}
function scanParam(state) {
var start = state.index,
index = start,
max = state.max,
zeroFirst = false,
hasCeiling = false,
hasDecimal = false,
hasDot = false,
ch;
if (index >= max) {
state.err = 'SvgPath: missed param (at pos ' + index + ')';
return;
}
ch = state.path.charCodeAt(index);
if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
index++;
ch = (index < max) ? state.path.charCodeAt(index) : 0;
}
// This logic is shamelessly borrowed from Esprima
// https://github.com/ariya/esprimas
//
if (!isDigit(ch) && ch !== 0x2E/* . */) {
state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
return;
}
if (ch !== 0x2E/* . */) {
zeroFirst = (ch === 0x30/* 0 */);
index++;
ch = (index < max) ? state.path.charCodeAt(index) : 0;
if (zeroFirst && index < max) {
// decimal number starts with '0' such as '09' is illegal.
if (ch && isDigit(ch)) {
state.err = 'SvgPath: numbers started with `0` such as `09` are illegal (at pos ' + start + ')';
return;
}
}
while (index < max && isDigit(state.path.charCodeAt(index))) {
index++;
hasCeiling = true;
}
ch = (index < max) ? state.path.charCodeAt(index) : 0;
}
if (ch === 0x2E/* . */) {
hasDot = true;
index++;
while (isDigit(state.path.charCodeAt(index))) {
index++;
hasDecimal = true;
}
ch = (index < max) ? state.path.charCodeAt(index) : 0;
}
if (ch === 0x65/* e */ || ch === 0x45/* E */) {
if (hasDot && !hasCeiling && !hasDecimal) {
state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
return;
}
index++;
ch = (index < max) ? state.path.charCodeAt(index) : 0;
if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
index++;
}
if (index < max && isDigit(state.path.charCodeAt(index))) {
while (index < max && isDigit(state.path.charCodeAt(index))) {
index++;
}
} else {
state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
return;
}
}
state.index = index;
state.param = parseFloat(state.path.slice(start, index)) + 0.0;
}
function finalizeSegment(state) {
var cmd, cmdLC;
// Process duplicated commands (without comand name)
// This logic is shamelessly borrowed from Raphael
// https://github.com/DmitryBaranovskiy/raphael/
//
cmd = state.path[state.segmentStart];
cmdLC = cmd.toLowerCase();
var params = state.data;
if (cmdLC === 'm' && params.length > 2) {
state.result.push([ cmd, params[0], params[1] ]);
params = params.slice(2);
cmdLC = 'l';
cmd = (cmd === 'm') ? 'l' : 'L';
}
if (cmdLC === 'r') {
state.result.push([ cmd ].concat(params));
} else {
while (params.length >= paramCounts[cmdLC]) {
state.result.push([ cmd ].concat(params.splice(0, paramCounts[cmdLC])));
if (!paramCounts[cmdLC]) {
break;
}
}
}
}
function scanSegment(state) {
var max = state.max,
cmdCode, is_arc, comma_found, need_params, i;
state.segmentStart = state.index;
cmdCode = state.path.charCodeAt(state.index);
is_arc = isArc(cmdCode);
if (!isCommand(cmdCode)) {
state.err = 'SvgPath: bad command ' + state.path[state.index] + ' (at pos ' + state.index + ')';
return;
}
need_params = paramCounts[state.path[state.index].toLowerCase()];
state.index++;
skipSpaces(state);
state.data = [];
if (!need_params) {
// Z
finalizeSegment(state);
return;
}
comma_found = false;
for (;;) {
for (i = need_params; i > 0; i--) {
if (is_arc && (i === 3 || i === 4)) scanFlag(state);
else scanParam(state);
if (state.err.length) {
finalizeSegment(state);
return;
}
state.data.push(state.param);
skipSpaces(state);
comma_found = false;
if (state.index < max && state.path.charCodeAt(state.index) === 0x2C/* , */) {
state.index++;
skipSpaces(state);
comma_found = true;
}
}
// after ',' param is mandatory
if (comma_found) {
continue;
}
if (state.index >= state.max) {
break;
}
// Stop on next segment
if (!isDigitStart(state.path.charCodeAt(state.index))) {
break;
}
}
finalizeSegment(state);
}
/* Returns array of segments:
*
* [
* [ command, coord1, coord2, ... ]
* ]
*/
module.exports = function pathParse(svgPath) {
var state = new State(svgPath);
var max = state.max;
skipSpaces(state);
while (state.index < max && !state.err.length) {
scanSegment(state);
}
if (state.result.length) {
if ('mM'.indexOf(state.result[0][0]) < 0) {
state.err = 'SvgPath: string should start with `M` or `m`';
state.result = [];
} else {
state.result[0][0] = 'M';
}
}
return {
err: state.err,
segments: state.result
};
};