V.normalizePathData(pathData)

Convert provided SVG path data string into a normalized path data string. The normalization uses a restricted subset of path commands; all segments are translated into lineto, curveto, moveto, and closepath segments. Relative path commands are changed into their absolute counterparts, and chaining of coordinates is disallowed.

The function will always return a valid path data string; if an input string cannot be normalized, 'M 0 0' is returned.

Command translations:

V.normalizePathData('M 10 10 H 20') === 'M 10 10 L 20 10';
V.normalizePathData('M 10 10 V 20') === 'M 10 10 L 10 20';
V.normalizePathData('M 10 20 C 10 10 25 10 25 20 S 40 30 40 20') === 'M 10 20 C 10 10 25 10 25 20 C 25 30 40 30 40 20';
V.normalizePathData('M 20 20 Q 40 0 60 20') === 'M 20 20 C 33.33333333333333 6.666666666666666 46.666666666666664 6.666666666666666 60 20';
V.normalizePathData('M 20 20 Q 40 0 60 20 T 100 20') === 'M 20 20 C 33.33333333333333 6.666666666666666 46.666666666666664 6.666666666666666 60 20 C 73.33333333333333 33.33333333333333 86.66666666666666 33.33333333333333 100 20';
V.normalizePathData('M 30 15 A 15 15 0 0 0 15 30') === 'M 30 15 C 21.715728752538098 15.000000000000002 14.999999999999998 21.715728752538098 15 30';

Relative-to-absolute normalizations:

V.normalizePathData('m 10 10') === 'M 10 10';
V.normalizePathData('M 10 10 m 10 10') === 'M 10 10 M 20 20';
V.normalizePathData('M 10 10 l 10 10') === 'M 10 10 L 20 20';
V.normalizePathData('M 10 10 c 0 10 10 10 10 0') === 'M 10 10 C 10 20 20 20 20 10';
V.normalizePathData('M 10 10 z') === 'M 10 10 Z'

Transcription of chained coordinates:

V.normalizePathData('M 10 10 20 20') === 'M 10 10 L 20 20';
V.normalizePathData('M 10 10 L 20 20 30 30') === 'M 10 10 L 20 20 L 30 30';
V.normalizePathData('M 10 10 C 10 20 20 20 20 10 20 0 30 0 30 10') === 'M 10 10 C 10 20 20 20 20 10 C 20 0 30 0 30 10';

Edge cases:

V.normalizePathData('L 10 10') === 'M 0 0 L 10 10';
V.normalizePathData('C 0 10 10 10 10 0') === 'M 0 0 C 0 10 10 10 10 0';
V.normalizePathData('Z') === 'M 0 0 Z';

V.normalizePathData('M 10 10 Z L 20 20') === 'M 10 10 Z L 20 20';
V.normalizePathData('M 10 10 Z C 10 20 20 20 20 10') === 'M 10 10 Z C 10 20 20 20 20 10';
V.normalizePathData('M 10 10 Z Z') === 'M 10 10 Z Z';

V.normalizePathData('') === 'M 0 0'; // empty string
V.normalizePathData('X') === 'M 0 0'; // invalid command

V.normalizePathData('M') === 'M 0 0'; // no arguments for a command that needs them
V.normalizePathData('M 10') === 'M 0 0'; // too few arguments
V.normalizePathData('M 10 10 20') === 'M 10 10'; // too many arguments

V.normalizePathData('X M 10 10') === 'M 10 10'; // mixing invalid and valid commands
V.normalizePathData('X M 10 10 X L 20 20') === 'M 10 10 L 20 20'; // invalid commands interspersed with valid commands