Skip to content

Instantly share code, notes, and snippets.

@spacejack
Created April 5, 2020 02:39
Show Gist options
  • Save spacejack/25ec6fe532d0868174add72d37361657 to your computer and use it in GitHub Desktop.
Save spacejack/25ec6fe532d0868174add72d37361657 to your computer and use it in GitHub Desktop.
SVGPath parser/serializer sketch
/** Expected number of values for each command type */
const CMD_LENGTHS = {
a: 7, c: 6, C: 6, h: 1, l: 2, m: 2, M: 2, q: 4, s: 4, S: 4, t: 2, v: 1, z: 0
}
type CmdType = keyof typeof CMD_LENGTHS
export interface PathSeg {
/** Command type string */
cmd: CmdType
/** Values array */
v: number[]
}
export function PathSeg (cmd: string, v: number[]): PathSeg {
const n = CMD_LENGTHS[cmd as CmdType]
if (n == null) {
console.warn('Unrecognized command type in path: ' + cmd)
} else {
if (n !== v.length) {
console.warn(`Unexpected value count ${v.length} for command ${cmd}`)
}
}
return {cmd: cmd as CmdType, v}
}
export namespace PathSeg {
export function clone(src: PathSeg) {
return {
cmd: src.cmd,
v: src.v.slice()
}
}
}
/** Parse the vertices & control points from a svg path d attribute */
export function dParse (path: string): PathSeg[] {
const rx = /[a-z]/ig
const indices: number[] = []
for (let match = rx.exec(path); !!match; match = rx.exec(path)) {
indices.push(match.index)
}
const lenm1 = indices.length - 1
return indices.map((idx, i) => {
const idx2 = i < lenm1 ? indices[i + 1] : path.length
const cmd = path[idx]
const str = path.slice(idx + 1, idx2)
return PathSeg(cmd, parseValues(str))
})
}
/** Parse the values from a path segment string. This string should not include commands. */
function parseValues (str: string) {
const values: number[] = []
let nstr = ''
for (let i = 0; i < str.length; ++i) {
const c = str[i]
if ((c < '0' || c > '9') && c !== '.') {
if (nstr) {
values.push(parseReal(nstr))
nstr = c === '-' ? c : ''
} else {
if (c === '-') {
nstr = c
}
}
} else {
nstr += c
}
}
if (nstr.length > 0) {
values.push(parseReal(nstr))
}
return values
}
/** Parse a real number or throw an Error */
function parseReal (str: string) {
const n = Number(str)
if (!Number.isFinite(n)) {
throw new Error('Failed to parse number from: ' + str)
}
return n
}
/** Given an array of `PathPart`s, return an SVGPath `d` attribute string */
export function dSerialize (seg: PathSeg[]) {
return seg.reduce((d, p) => {
if (d.length > 0) d += ' '
d = d + `${p.cmd}${p.v.join(',')}`
return d
}, '')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment