• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fontello / svgpath / 10803082942

28 Oct 2022 03:34PM UTC coverage: 100.0%. Remained the same
10803082942

push

github

puzrin
2.6.0 released

412 of 414 branches covered (99.52%)

638 of 638 relevant lines covered (100.0%)

19822.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/lib/path_parse.js
1
'use strict';
2

3

4
var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0 };
1✔
5

6
var SPECIAL_SPACES = [
1✔
7
  0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
8
  0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF
9
];
10

11
function isSpace(ch) {
12
  return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) || // Line terminators
397,458✔
13
    // White spaces
14
    (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
15
    (ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0);
16
}
17

18
function isCommand(code) {
19
  /*eslint-disable no-bitwise*/
20
  switch (code | 0x20) {
60,036✔
21
    case 0x6D/* m */:
22
    case 0x7A/* z */:
23
    case 0x6C/* l */:
24
    case 0x68/* h */:
25
    case 0x76/* v */:
26
    case 0x63/* c */:
27
    case 0x73/* s */:
28
    case 0x71/* q */:
29
    case 0x74/* t */:
30
    case 0x61/* a */:
31
    case 0x72/* r */:
32
      return true;
60,032✔
33
  }
34
  return false;
4✔
35
}
36

37
function isArc(code) {
38
  return (code | 0x20) === 0x61;
60,036✔
39
}
40

41
function isDigit(code) {
42
  return (code >= 48 && code <= 57);   // 0..9
727,911✔
43
}
44

45
function isDigitStart(code) {
46
  return (code >= 48 && code <= 57) || /* 0..9 */
84,371✔
47
          code === 0x2B || /* + */
48
          code === 0x2D || /* - */
49
          code === 0x2E;   /* . */
50
}
51

52

53
function State(path) {
54
  this.index  = 0;
2,179✔
55
  this.path   = path;
2,179✔
56
  this.max    = path.length;
2,179✔
57
  this.result = [];
2,179✔
58
  this.param  = 0.0;
2,179✔
59
  this.err    = '';
2,179✔
60
  this.segmentStart = 0;
2,179✔
61
  this.data   = [];
2,179✔
62
}
63

64
function skipSpaces(state) {
65
  while (state.index < state.max && isSpace(state.path.charCodeAt(state.index))) {
294,330✔
66
    state.index++;
105,293✔
67
  }
68
}
69

70

71
function scanFlag(state) {
72
  var ch = state.path.charCodeAt(state.index);
74✔
73

74
  if (ch === 0x30/* 0 */) {
74✔
75
    state.param = 0;
34✔
76
    state.index++;
34✔
77
    return;
34✔
78
  }
79

80
  if (ch === 0x31/* 1 */) {
40✔
81
    state.param = 1;
38✔
82
    state.index++;
38✔
83
    return;
38✔
84
  }
85

86
  state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')';
2✔
87
}
88

89

90
function scanParam(state) {
91
  var start = state.index,
232,028✔
92
      index = start,
232,028✔
93
      max = state.max,
232,028✔
94
      zeroFirst = false,
232,028✔
95
      hasCeiling = false,
232,028✔
96
      hasDecimal = false,
232,028✔
97
      hasDot = false,
232,028✔
98
      ch;
99

100
  if (index >= max) {
232,028✔
101
    state.err = 'SvgPath: missed param (at pos ' + index + ')';
4✔
102
    return;
4✔
103
  }
104
  ch = state.path.charCodeAt(index);
232,024✔
105

106
  if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
232,024✔
107
    index++;
96,773✔
108
    ch = (index < max) ? state.path.charCodeAt(index) : 0;
96,773✔
109
  }
110

111
  // This logic is shamelessly borrowed from Esprima
112
  // https://github.com/ariya/esprimas
113
  //
114
  if (!isDigit(ch) && ch !== 0x2E/* . */) {
232,024✔
115
    state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
2✔
116
    return;
2✔
117
  }
118

119
  if (ch !== 0x2E/* . */) {
232,022✔
120
    zeroFirst = (ch === 0x30/* 0 */);
232,006✔
121
    index++;
232,006✔
122

123
    ch = (index < max) ? state.path.charCodeAt(index) : 0;
232,006✔
124

125
    if (zeroFirst && index < max) {
232,006✔
126
      // decimal number starts with '0' such as '09' is illegal.
127
      if (ch && isDigit(ch)) {
32,960✔
128
        state.err = 'SvgPath: numbers started with `0` such as `09` are illegal (at pos ' + start + ')';
2✔
129
        return;
2✔
130
      }
131
    }
132

133
    while (index < max && isDigit(state.path.charCodeAt(index))) {
232,004✔
134
      index++;
230,723✔
135
      hasCeiling = true;
230,723✔
136
    }
137
    ch = (index < max) ? state.path.charCodeAt(index) : 0;
232,004✔
138
  }
139

140
  if (ch === 0x2E/* . */) {
232,020✔
141
    hasDot = true;
74✔
142
    index++;
74✔
143
    while (isDigit(state.path.charCodeAt(index))) {
74✔
144
      index++;
218✔
145
      hasDecimal = true;
218✔
146
    }
147
    ch = (index < max) ? state.path.charCodeAt(index) : 0;
74✔
148
  }
149

150
  if (ch === 0x65/* e */ || ch === 0x45/* E */) {
232,020✔
151
    if (hasDot && !hasCeiling && !hasDecimal) {
10✔
152
      state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
2✔
153
      return;
2✔
154
    }
155

156
    index++;
8✔
157

158
    ch = (index < max) ? state.path.charCodeAt(index) : 0;
8✔
159
    if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
8✔
160
      index++;
5✔
161
    }
162
    if (index < max && isDigit(state.path.charCodeAt(index))) {
8✔
163
      while (index < max && isDigit(state.path.charCodeAt(index))) {
6✔
164
        index++;
6✔
165
      }
166
    } else {
167
      state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')';
2✔
168
      return;
2✔
169
    }
170
  }
171

172
  state.index = index;
232,016✔
173
  state.param = parseFloat(state.path.slice(start, index)) + 0.0;
232,016✔
174
}
175

176

177
function finalizeSegment(state) {
178
  var cmd, cmdLC;
179

180
  // Process duplicated commands (without comand name)
181

182
  // This logic is shamelessly borrowed from Raphael
183
  // https://github.com/DmitryBaranovskiy/raphael/
184
  //
185
  cmd   = state.path[state.segmentStart];
60,032✔
186
  cmdLC = cmd.toLowerCase();
60,032✔
187

188
  var params = state.data;
60,032✔
189

190
  if (cmdLC === 'm' && params.length > 2) {
60,032✔
191
    state.result.push([ cmd, params[0], params[1] ]);
4✔
192
    params = params.slice(2);
4✔
193
    cmdLC = 'l';
4✔
194
    cmd = (cmd === 'm') ? 'l' : 'L';
4✔
195
  }
196

197
  if (cmdLC === 'r') {
60,032✔
198
    state.result.push([ cmd ].concat(params));
2✔
199
  } else {
200

201
    while (params.length >= paramCounts[cmdLC]) {
60,030✔
202
      state.result.push([ cmd ].concat(params.splice(0, paramCounts[cmdLC])));
91,041✔
203
      if (!paramCounts[cmdLC]) {
91,041✔
204
        break;
6,554✔
205
      }
206
    }
207
  }
208
}
209

210

211
function scanSegment(state) {
212
  var max = state.max,
60,036✔
213
      cmdCode, is_arc, comma_found, need_params, i;
214

215
  state.segmentStart = state.index;
60,036✔
216
  cmdCode = state.path.charCodeAt(state.index);
60,036✔
217
  is_arc = isArc(cmdCode);
60,036✔
218

219
  if (!isCommand(cmdCode)) {
60,036✔
220
    state.err = 'SvgPath: bad command ' + state.path[state.index] + ' (at pos ' + state.index + ')';
4✔
221
    return;
4✔
222
  }
223

224
  need_params = paramCounts[state.path[state.index].toLowerCase()];
60,032✔
225

226
  state.index++;
60,032✔
227
  skipSpaces(state);
60,032✔
228

229
  state.data = [];
60,032✔
230

231
  if (!need_params) {
60,032✔
232
    // Z
233
    finalizeSegment(state);
6,554✔
234
    return;
6,554✔
235
  }
236

237
  comma_found = false;
53,478✔
238

239
  for (;;) {
53,478✔
240
    for (i = need_params; i > 0; i--) {
84,507✔
241
      if (is_arc && (i === 3 || i === 4)) scanFlag(state);
232,102✔
242
      else scanParam(state);
232,028✔
243

244
      if (state.err.length) {
232,102✔
245
        finalizeSegment(state);
14✔
246
        return;
14✔
247
      }
248
      state.data.push(state.param);
232,088✔
249

250
      skipSpaces(state);
232,088✔
251
      comma_found = false;
232,088✔
252

253
      if (state.index < max && state.path.charCodeAt(state.index) === 0x2C/* , */) {
232,088✔
254
        state.index++;
31✔
255
        skipSpaces(state);
31✔
256
        comma_found = true;
31✔
257
      }
258
    }
259

260
    // after ',' param is mandatory
261
    if (comma_found) {
84,493✔
262
      continue;
2✔
263
    }
264

265
    if (state.index >= state.max) {
84,491✔
266
      break;
120✔
267
    }
268

269
    // Stop on next segment
270
    if (!isDigitStart(state.path.charCodeAt(state.index))) {
84,371✔
271
      break;
53,344✔
272
    }
273
  }
274

275
  finalizeSegment(state);
53,464✔
276
}
277

278

279
/* Returns array of segments:
280
 *
281
 * [
282
 *   [ command, coord1, coord2, ... ]
283
 * ]
284
 */
285
module.exports = function pathParse(svgPath) {
1✔
286
  var state = new State(svgPath);
2,179✔
287
  var max = state.max;
2,179✔
288

289
  skipSpaces(state);
2,179✔
290

291
  while (state.index < max && !state.err.length) {
2,179✔
292
    scanSegment(state);
60,036✔
293
  }
294

295
  if (state.result.length) {
2,179✔
296
    if ('mM'.indexOf(state.result[0][0]) < 0) {
2,170✔
297
      state.err = 'SvgPath: string should start with `M` or `m`';
2✔
298
      state.result = [];
2✔
299
    } else {
300
      state.result[0][0] = 'M';
2,168✔
301
    }
302
  }
303

304
  return {
2,179✔
305
    err: state.err,
306
    segments: state.result
307
  };
308
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc