// SVG Path Data String Parser and Formatter in pure MoonBit.
///|
fn is_digit(ch : Char) -> Bool {
ch >= '0' && ch <= '9'
}
///|
fn is_whitespace(ch : Char) -> Bool {
ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == ','
}
///|
struct PathTokenizer {
src : String
mut pos : Int
} derive(Debug)
///|
fn PathTokenizer::new(src : String) -> PathTokenizer {
{ src, pos: 0 }
}
///|
fn PathTokenizer::skip_whitespace(self : PathTokenizer) -> Unit {
while self.pos < self.src.length() &&
is_whitespace(char_at(self.src, self.pos)) {
self.pos = self.pos + 1
}
}
///|
fn PathTokenizer::next_char(self : PathTokenizer) -> Char? {
self.skip_whitespace()
if self.pos >= self.src.length() {
None
} else {
let ch = char_at(self.src, self.pos)
self.pos = self.pos + 1
Some(ch)
}
}
///|
fn PathTokenizer::parse_number(self : PathTokenizer) -> Double {
self.skip_whitespace()
let start = self.pos
if self.pos < self.src.length() {
let c = char_at(self.src, self.pos)
if c == '-' || c == '+' {
self.pos = self.pos + 1
}
}
let mut has_dot = false
while self.pos < self.src.length() {
let ch = char_at(self.src, self.pos)
if is_digit(ch) {
self.pos = self.pos + 1
} else if ch == '.' && !has_dot {
has_dot = true
self.pos = self.pos + 1
} else {
break
}
}
let sub = self.src[start:self.pos].to_owned()
if sub.is_empty() {
0.0
} else {
let mut sign = 1.0
let mut idx = 0
if char_at(sub, 0) == '-' {
sign = -1.0
idx = 1
} else if char_at(sub, 0) == '+' {
idx = 1
}
let mut int_part = 0.0
let mut frac_part = 0.0
let mut frac_div = 1.0
let mut in_frac = false
while idx < sub.length() {
let ch = char_at(sub, idx)
if ch == '.' {
in_frac = true
} else if is_digit(ch) {
let d = (ch.to_int() - '0'.to_int()).to_double()
if !in_frac {
int_part = int_part * 10.0 + d
} else {
frac_div = frac_div * 10.0
frac_part = frac_part + d / frac_div
}
}
idx = idx + 1
}
sign * (int_part + frac_part)
}
}
///|
pub fn parse_svg_path(path_str : String) -> Path {
let tok = PathTokenizer::new(path_str)
let builder = PathBuilder::new()
let mut curr = @math.Vec2::zero()
let mut start_p = @math.Vec2::zero()
while true {
match tok.next_char() {
None => break
Some(cmd) =>
match cmd {
'M' => {
let x = tok.parse_number()
let y = tok.parse_number()
curr = @math.Vec2::new(x, y)
start_p = curr
ignore(builder.move_to(curr))
}
'm' => {
let dx = tok.parse_number()
let dy = tok.parse_number()
curr = @math.Vec2::new(curr.x + dx, curr.y + dy)
start_p = curr
ignore(builder.move_to(curr))
}
'L' => {
let x = tok.parse_number()
let y = tok.parse_number()
curr = @math.Vec2::new(x, y)
ignore(builder.line_to(curr))
}
'l' => {
let dx = tok.parse_number()
let dy = tok.parse_number()
curr = @math.Vec2::new(curr.x + dx, curr.y + dy)
ignore(builder.line_to(curr))
}
'H' => {
let x = tok.parse_number()
curr = @math.Vec2::new(x, curr.y)
ignore(builder.line_to(curr))
}
'h' => {
let dx = tok.parse_number()
curr = @math.Vec2::new(curr.x + dx, curr.y)
ignore(builder.line_to(curr))
}
'V' => {
let y = tok.parse_number()
curr = @math.Vec2::new(curr.x, y)
ignore(builder.line_to(curr))
}
'v' => {
let dy = tok.parse_number()
curr = @math.Vec2::new(curr.x, curr.y + dy)
ignore(builder.line_to(curr))
}
'C' => {
let x1 = tok.parse_number()
let y1 = tok.parse_number()
let x2 = tok.parse_number()
let y2 = tok.parse_number()
let x = tok.parse_number()
let y = tok.parse_number()
let c1 = @math.Vec2::new(x1, y1)
let c2 = @math.Vec2::new(x2, y2)
curr = @math.Vec2::new(x, y)
ignore(builder.cubic_to(c1, c2, curr))
}
'c' => {
let dx1 = tok.parse_number()
let dy1 = tok.parse_number()
let dx2 = tok.parse_number()
let dy2 = tok.parse_number()
let dx = tok.parse_number()
let dy = tok.parse_number()
let c1 = @math.Vec2::new(curr.x + dx1, curr.y + dy1)
let c2 = @math.Vec2::new(curr.x + dx2, curr.y + dy2)
curr = @math.Vec2::new(curr.x + dx, curr.y + dy)
ignore(builder.cubic_to(c1, c2, curr))
}
'Q' => {
let x1 = tok.parse_number()
let y1 = tok.parse_number()
let x = tok.parse_number()
let y = tok.parse_number()
let c1 = @math.Vec2::new(x1, y1)
curr = @math.Vec2::new(x, y)
ignore(builder.quad_to(c1, curr))
}
'q' => {
let dx1 = tok.parse_number()
let dy1 = tok.parse_number()
let dx = tok.parse_number()
let dy = tok.parse_number()
let c1 = @math.Vec2::new(curr.x + dx1, curr.y + dy1)
curr = @math.Vec2::new(curr.x + dx, curr.y + dy)
ignore(builder.quad_to(c1, curr))
}
'Z' | 'z' => {
ignore(builder.close_path())
curr = start_p
}
_ => ()
}
}
}
builder.build()
}
///|
pub fn format_svg_path(path : Path) -> String {
Show::to_string(path)
}