#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![doc(test(attr(deny(warnings))))]
#[cfg(doctest)]
::doc_comment::doctest!("../README.md");
#[cfg(test)]
mod tests;
use std::iter::Peekable;
use std::str::Chars;
mod errors;
pub use errors::SyntaxError;
static COMMAND_CHARACTERS: [char; 20] = [
'M', 'm', 'V', 'v', 'H', 'h', 'L', 'l', 'Z', 'z', 'C', 'c', 'S', 's', 'Q', 'q',
'T', 't', 'A', 'a',
];
static WSP_COMMA_CHARACTERS: [char; 6] = [' ', '\t', '\n', '\x0C', '\r', ','];
type Coordinate = (Option<SVGPathCSTNode>, SVGPathCSTNode, f64);
#[derive(Debug, PartialEq, Clone)]
pub enum SVGPathCommand {
MovetoUpper,
MovetoLower,
LinetoUpper,
LinetoLower,
HorizontalUpper,
HorizontalLower,
VerticalUpper,
VerticalLower,
ClosepathUpper,
ClosepathLower,
CurvetoUpper,
CurvetoLower,
SmoothCurvetoUpper,
SmoothCurvetoLower,
ArcUpper,
ArcLower,
QuadraticUpper,
QuadraticLower,
SmoothQuadraticUpper,
SmoothQuadraticLower,
}
impl SVGPathCommand {
pub fn capacity(&self) -> usize {
match self {
SVGPathCommand::MovetoUpper => 2,
SVGPathCommand::MovetoLower => 2,
SVGPathCommand::LinetoUpper => 2,
SVGPathCommand::LinetoLower => 2,
SVGPathCommand::HorizontalUpper => 1,
SVGPathCommand::HorizontalLower => 1,
SVGPathCommand::VerticalUpper => 1,
SVGPathCommand::VerticalLower => 1,
SVGPathCommand::ClosepathUpper => 0,
SVGPathCommand::ClosepathLower => 0,
SVGPathCommand::CurvetoUpper => 6,
SVGPathCommand::CurvetoLower => 6,
SVGPathCommand::SmoothCurvetoUpper => 4,
SVGPathCommand::SmoothCurvetoLower => 4,
SVGPathCommand::ArcUpper => 7,
SVGPathCommand::ArcLower => 7,
SVGPathCommand::QuadraticUpper => 4,
SVGPathCommand::QuadraticLower => 4,
SVGPathCommand::SmoothQuadraticUpper => 2,
SVGPathCommand::SmoothQuadraticLower => 2,
}
}
pub fn to_char(&self) -> char {
match self {
SVGPathCommand::MovetoUpper => 'M',
SVGPathCommand::MovetoLower => 'm',
SVGPathCommand::LinetoUpper => 'L',
SVGPathCommand::LinetoLower => 'l',
SVGPathCommand::HorizontalUpper => 'H',
SVGPathCommand::HorizontalLower => 'h',
SVGPathCommand::VerticalUpper => 'V',
SVGPathCommand::VerticalLower => 'v',
SVGPathCommand::ClosepathUpper => 'Z',
SVGPathCommand::ClosepathLower => 'z',
SVGPathCommand::CurvetoUpper => 'C',
SVGPathCommand::CurvetoLower => 'c',
SVGPathCommand::SmoothCurvetoUpper => 'S',
SVGPathCommand::SmoothCurvetoLower => 's',
SVGPathCommand::ArcUpper => 'A',
SVGPathCommand::ArcLower => 'a',
SVGPathCommand::QuadraticUpper => 'Q',
SVGPathCommand::QuadraticLower => 'q',
SVGPathCommand::SmoothQuadraticUpper => 'T',
SVGPathCommand::SmoothQuadraticLower => 't',
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum WSP {
Space,
Tab,
LineFeed,
FormFeed,
CarriageReturn,
}
impl WSP {
pub fn to_char(&self) -> char {
match self {
WSP::Space => ' ',
WSP::Tab => '\t',
WSP::LineFeed => '\n',
WSP::FormFeed => '\x0C',
WSP::CarriageReturn => '\r',
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Sign {
Plus,
Minus,
}
impl Sign {
pub fn to_char(&self) -> char {
match self {
Sign::Plus => '+',
Sign::Minus => '-',
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum SVGPathCSTNode {
None,
Whitespace {
wsp: &'static WSP,
start: usize,
end: usize,
},
Segment(SVGPathSegment),
Sign {
sign: &'static Sign,
start: usize,
},
Number {
raw_number: String,
value: f64,
start: usize,
end: usize,
},
Comma {
start: usize,
},
Command(&'static SVGPathCommand),
}
#[derive(Debug, PartialEq, Clone)]
pub struct SVGPathSegment {
pub command: &'static SVGPathCommand,
pub args: Vec<f64>,
pub cst: Vec<SVGPathCSTNode>,
pub start: usize,
pub end: usize,
pub chained: bool,
pub chain_start: usize,
pub chain_end: usize,
}
fn new_segment(
command: &'static SVGPathCommand,
start: usize,
chained: bool,
) -> SVGPathSegment {
let capacity = command.capacity();
SVGPathSegment {
command,
args: Vec::with_capacity(capacity),
cst: Vec::with_capacity(capacity * 2),
start,
end: start,
chained,
chain_start: start,
chain_end: 0,
}
}
macro_rules! set_commands_chain_info {
($cst:expr, $nodes:expr, $chain_start:expr, $chain_end:expr) => {
for node in $nodes {
match node {
SVGPathCSTNode::Segment(mut segment) => {
segment.chain_start = $chain_start;
segment.chain_end = $chain_end;
$cst.push(SVGPathCSTNode::Segment(segment));
}
_ => $cst.push(node),
}
}
};
}
#[derive(Clone)]
struct Parser<'a> {
index: usize,
path: &'a str,
chars: Peekable<Chars<'a>>,
}
impl<'a> Parser<'a> {
pub fn new(path: &'a str) -> Self {
Self {
index: 0,
path,
chars: path.chars().peekable(),
}
}
fn next_char(&mut self) -> Option<char> {
self.index += 1;
self.chars.next()
}
fn check_unexpected_end(&mut self, expected: &str) -> Result<(), SyntaxError> {
if self.chars.peek().is_none() {
return Err(SyntaxError::UnexpectedEnding {
index: self.index - 1,
expected: expected.to_string(),
});
}
Ok(())
}
fn parse_whitespaces(&mut self) -> Vec<SVGPathCSTNode> {
let mut whitespaces = vec![];
while let Some(next) = self.chars.peek() {
match next {
' ' => {
whitespaces.push(SVGPathCSTNode::Whitespace {
wsp: &WSP::Space,
start: self.index,
end: self.index + 1,
});
self.next_char();
}
'\t' => {
whitespaces.push(SVGPathCSTNode::Whitespace {
wsp: &WSP::Tab,
start: self.index,
end: self.index + 1,
});
self.next_char();
}
'\n' => {
whitespaces.push(SVGPathCSTNode::Whitespace {
wsp: &WSP::LineFeed,
start: self.index,
end: self.index + 1,
});
self.next_char();
}
'\x0C' => {
whitespaces.push(SVGPathCSTNode::Whitespace {
wsp: &WSP::FormFeed,
start: self.index,
end: self.index + 1,
});
self.next_char();
}
'\r' => {
whitespaces.push(SVGPathCSTNode::Whitespace {
wsp: &WSP::CarriageReturn,
start: self.index,
end: self.index + 1,
});
self.next_char();
}
_ => break,
}
}
whitespaces
}
fn parse_comma_wsp(&mut self) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut comma_wsp = vec![];
if let Some(next) = self.chars.peek() {
if *next == ',' {
comma_wsp.push(SVGPathCSTNode::Comma { start: self.index });
self.next_char();
comma_wsp.extend(self.parse_whitespaces());
} else {
comma_wsp.extend(self.parse_whitespaces());
if let Some(next_after_wsp) = self.chars.peek() {
if *next_after_wsp == ',' {
comma_wsp.push(SVGPathCSTNode::Comma { start: self.index });
self.next_char();
comma_wsp.extend(self.parse_whitespaces());
}
}
}
} else {
return Err(SyntaxError::UnexpectedEnding {
index: self.index - 1,
expected: "comma or whitespace".to_string(),
});
}
Ok(comma_wsp)
}
fn parse_number(&mut self) -> Result<SVGPathCSTNode, SyntaxError> {
let mut number = String::new();
let mut has_dot = false;
let mut has_e = false;
let mut has_sign = false;
let mut has_digit = false;
let start = self.index;
while let Some(next) = self.chars.peek() {
match next {
'0' => {
if number == "0" {
break;
}
has_digit = true;
number.push(*next);
}
'1'..='9' => {
has_digit = true;
number.push(*next);
}
'.' => {
if has_dot {
break;
}
number.push(*next);
has_dot = true;
}
'e' | 'E' => {
if has_e {
return Err(SyntaxError::InvalidNumber {
number: number.clone(),
index: self.index,
});
}
number.push(*next);
has_e = true;
}
'+' | '-' => {
if has_sign || has_dot || has_digit && !has_e {
break;
}
number.push(*next);
has_sign = true;
}
_ => {
if !WSP_COMMA_CHARACTERS.contains(next)
&& !COMMAND_CHARACTERS.contains(next)
{
return Err(SyntaxError::InvalidCharacter {
character: *next,
index: self.index,
expected: "number or command".to_string(),
});
}
break;
}
}
self.next_char();
}
if !has_digit {
return Err(SyntaxError::InvalidNumber {
number: number.clone(),
index: self.index,
});
}
match number.parse::<f64>() {
Ok(value) => Ok(SVGPathCSTNode::Number {
raw_number: number,
value,
start,
end: self.index,
}),
Err(_) => Err(SyntaxError::InvalidNumber {
number: number.clone(),
index: self.index,
}),
}
}
fn parse_sign(&mut self) -> Option<SVGPathCSTNode> {
if let Some(next) = self.chars.peek() {
match next {
'+' => {
self.next_char();
return Some(SVGPathCSTNode::Sign {
sign: &Sign::Plus,
start: self.index - 1,
});
}
'-' => {
self.next_char();
return Some(SVGPathCSTNode::Sign {
sign: &Sign::Minus,
start: self.index - 1,
});
}
_ => return None,
}
}
None
}
fn parse_flag(&mut self) -> Result<f64, SyntaxError> {
let next = self.next_char();
if next.is_none() {
return Err(SyntaxError::UnexpectedEnding {
index: self.index,
expected: "flag (0 or 1)".to_string(),
});
}
match next.unwrap() {
'0' => Ok(0.0),
'1' => Ok(1.0),
_ => Err(SyntaxError::InvalidArcFlag {
index: self.index,
character: next.unwrap(),
}),
}
}
fn parse_coordinate(&mut self) -> Result<Coordinate, SyntaxError> {
let sign_node = self.parse_sign();
let SVGPathCSTNode::Number {
value,
start,
end,
raw_number,
} = self.parse_number()?
else {
unreachable!()
};
let coord_value = if let Some(SVGPathCSTNode::Sign { sign, .. }) = &sign_node {
match sign {
Sign::Plus => value,
Sign::Minus => -value,
}
} else {
value
};
Ok((
sign_node,
SVGPathCSTNode::Number {
value,
start,
end,
raw_number,
},
coord_value,
))
}
fn parse_coordinate_pair(
&mut self,
) -> Result<(Vec<SVGPathCSTNode>, (f64, f64)), SyntaxError> {
let mut nodes = vec![];
let (first_sign, first_number, first_value) = self.parse_coordinate()?;
if let Some(sign) = first_sign {
nodes.push(sign);
}
nodes.push(first_number);
nodes.extend(self.parse_comma_wsp()?);
let (second_sign, second_number, second_value) = self.parse_coordinate()?;
if let Some(sign) = second_sign {
nodes.push(sign);
}
nodes.push(second_number);
Ok((nodes, (first_value, second_value)))
}
fn parse_two_operands_command(
&mut self,
command: &'static SVGPathCommand,
) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut first_segment = new_segment(command, self.index - 1, false);
first_segment.cst.push(SVGPathCSTNode::Command(command));
first_segment.cst.extend(self.parse_whitespaces());
let (coord_nodes, coord_values) = self.parse_coordinate_pair()?;
first_segment.args.push(coord_values.0);
first_segment.args.push(coord_values.1);
first_segment.cst.extend(coord_nodes);
first_segment.end = self.index;
first_segment.chain_end = self.index;
let mut next_nodes = self.parse_whitespaces();
if let Some(mut next) = self.chars.peek() {
while !COMMAND_CHARACTERS.contains(next) {
let mut segment = new_segment(command, self.index, true);
let (coord_nodes, coord_values) = self.parse_coordinate_pair()?;
first_segment.chain_end = self.index;
segment.end = self.index;
segment.cst.extend(coord_nodes);
segment.args.push(coord_values.0);
segment.args.push(coord_values.1);
next_nodes.push(SVGPathCSTNode::Segment(segment));
match self.parse_comma_wsp() {
Ok(comma_wsp) => next_nodes.extend(comma_wsp),
Err(_) => break,
}
if let Some(next_) = self.chars.peek() {
next = next_;
} else {
break;
}
}
}
let (start, end) = (first_segment.chain_start, first_segment.chain_end);
let mut cst = vec![SVGPathCSTNode::Segment(first_segment)];
set_commands_chain_info!(cst, next_nodes, start, end);
Ok(cst)
}
fn parse_four_operands_command(
&mut self,
command: &'static SVGPathCommand,
) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut first_segment = new_segment(command, self.index - 1, false);
first_segment.cst.push(SVGPathCSTNode::Command(command));
for _ in 0..2 {
first_segment.cst.extend(self.parse_whitespaces());
self.check_unexpected_end("coordinate pair")?;
let (coord_nodes, coord_values) = self.parse_coordinate_pair()?;
first_segment.args.push(coord_values.0);
first_segment.args.push(coord_values.1);
first_segment.cst.extend(coord_nodes);
}
first_segment.chain_end = self.index;
first_segment.end = self.index;
let mut next_nodes = self.parse_whitespaces();
if let Some(mut next) = self.chars.peek() {
while !COMMAND_CHARACTERS.contains(next) {
let mut segment = new_segment(command, self.index, true);
let (coord_nodes_1, coord_values_1) = self.parse_coordinate_pair()?;
segment.cst.extend(coord_nodes_1);
segment.args.push(coord_values_1.0);
segment.args.push(coord_values_1.1);
segment.cst.extend(self.parse_whitespaces());
self.check_unexpected_end("coordinate pair")?;
let (coord_nodes_2, coord_values_2) = self.parse_coordinate_pair()?;
segment.cst.extend(coord_nodes_2);
segment.args.push(coord_values_2.0);
segment.args.push(coord_values_2.1);
first_segment.chain_end = self.index;
segment.end = self.index;
next_nodes.push(SVGPathCSTNode::Segment(segment));
if let Some(next_) = self.chars.peek() {
next = next_;
} else {
break;
}
}
}
let (start, end) = (first_segment.chain_start, first_segment.chain_end);
let mut cst = vec![SVGPathCSTNode::Segment(first_segment)];
set_commands_chain_info!(cst, next_nodes, start, end);
Ok(cst)
}
fn parse_curveto(
&mut self,
command: &'static SVGPathCommand,
) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut first_segment = new_segment(command, self.index - 1, false);
first_segment.cst.push(SVGPathCSTNode::Command(command));
for _ in 0..3 {
first_segment.cst.extend(self.parse_whitespaces());
self.check_unexpected_end("coordinate pair")?;
let (coord_nodes, coord_values) = self.parse_coordinate_pair()?;
first_segment.args.push(coord_values.0);
first_segment.args.push(coord_values.1);
first_segment.cst.extend(coord_nodes);
}
first_segment.chain_end = self.index;
first_segment.end = self.index;
let mut next_nodes = self.parse_whitespaces();
if let Some(mut next) = self.chars.peek() {
while !COMMAND_CHARACTERS.contains(next) {
let mut segment = new_segment(command, self.index, true);
let (coord_nodes_1, coord_values_1) = self.parse_coordinate_pair()?;
segment.cst.extend(coord_nodes_1);
segment.args.push(coord_values_1.0);
segment.args.push(coord_values_1.1);
segment.cst.extend(self.parse_whitespaces());
self.check_unexpected_end("coordinate pair")?;
let (coord_nodes_2, coord_values_2) = self.parse_coordinate_pair()?;
segment.cst.extend(coord_nodes_2);
segment.args.push(coord_values_2.0);
segment.args.push(coord_values_2.1);
segment.cst.extend(self.parse_whitespaces());
self.check_unexpected_end("coordinate pair")?;
let (coord_nodes_3, coord_values_3) = self.parse_coordinate_pair()?;
segment.cst.extend(coord_nodes_3);
segment.args.push(coord_values_3.0);
segment.args.push(coord_values_3.1);
first_segment.chain_end = self.index;
segment.end = self.index;
next_nodes.push(SVGPathCSTNode::Segment(segment));
next_nodes.extend(self.parse_whitespaces());
if let Some(next_) = self.chars.peek() {
next = next_;
} else {
break;
}
}
}
let (start, end) = (first_segment.chain_start, first_segment.chain_end);
let mut cst = vec![SVGPathCSTNode::Segment(first_segment)];
set_commands_chain_info!(cst, next_nodes, start, end);
Ok(cst)
}
fn parse_arc(
&mut self,
command: &'static SVGPathCommand,
) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut first_segment = new_segment(command, self.index - 1, false);
first_segment.cst.push(SVGPathCSTNode::Command(command));
first_segment.cst.extend(self.parse_whitespaces());
for _ in 0..3 {
self.check_unexpected_end("number")?;
let (sign_node, number_node, value) = self.parse_coordinate()?;
if sign_node.is_some() || value > 360.0 {
return Err(SyntaxError::InvalidArcRadius {
index: self.index,
value,
command: command.to_char(),
});
}
first_segment.args.push(value);
first_segment.cst.push(number_node);
first_segment.cst.extend(self.parse_comma_wsp()?);
}
for _ in 0..2 {
let value = self.parse_flag()?;
first_segment.args.push(value);
first_segment.cst.push(SVGPathCSTNode::Number {
raw_number: if value == 0.0 {
"0".to_string()
} else {
"1".to_string()
},
value,
start: self.index - 1,
end: self.index,
});
first_segment.cst.extend(self.parse_comma_wsp()?);
}
let (coord_nodes, coord_values) = self.parse_coordinate_pair()?;
first_segment.args.push(coord_values.0);
first_segment.args.push(coord_values.1);
first_segment.cst.extend(coord_nodes);
first_segment.chain_end = self.index;
first_segment.end = self.index;
let mut next_nodes = self.parse_whitespaces();
if let Some(mut next) = self.chars.peek() {
while !COMMAND_CHARACTERS.contains(next) {
let mut segment = new_segment(command, self.index, true);
for _ in 0..3 {
next_nodes.extend(self.parse_whitespaces());
self.check_unexpected_end("number")?;
let (sign_node, number_node, value) = self.parse_coordinate()?;
if sign_node.is_some() || value > 360.0 {
return Err(SyntaxError::InvalidArcRadius {
index: self.index,
value,
command: command.to_char(),
});
}
segment.args.push(value);
segment.cst.push(number_node);
segment.cst.extend(self.parse_comma_wsp()?);
}
for _ in 0..2 {
next_nodes.extend(self.parse_whitespaces());
let value = self.parse_flag()?;
segment.args.push(value);
segment.cst.push(SVGPathCSTNode::Number {
raw_number: if value == 0.0 {
"0".to_string()
} else {
"1".to_string()
},
value,
start: self.index - 1,
end: self.index,
});
segment.cst.extend(self.parse_comma_wsp()?);
}
self.check_unexpected_end("coordinate pair")?;
let (coord_nodes, coord_values) = self.parse_coordinate_pair()?;
segment.args.push(coord_values.0);
segment.args.push(coord_values.1);
segment.cst.extend(coord_nodes);
first_segment.chain_end = self.index;
segment.end = self.index;
next_nodes.push(SVGPathCSTNode::Segment(segment));
if let Some(next_) = self.chars.peek() {
next = next_;
} else {
break;
}
}
}
let (start, end) = (first_segment.chain_start, first_segment.chain_end);
let mut cst = vec![SVGPathCSTNode::Segment(first_segment)];
set_commands_chain_info!(cst, next_nodes, start, end);
Ok(cst)
}
fn parse_closepath(
&mut self,
command: &'static SVGPathCommand,
) -> Vec<SVGPathCSTNode> {
let mut segment = new_segment(command, self.index - 1, false);
segment.end = self.index;
segment.chain_end = self.index;
segment.cst.push(SVGPathCSTNode::Command(command));
vec![SVGPathCSTNode::Segment(segment)]
}
fn parse_horizontal_or_vertical(
&mut self,
command: &'static SVGPathCommand,
) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut first_segment = new_segment(command, self.index - 1, false);
first_segment.cst.push(SVGPathCSTNode::Command(command));
first_segment.cst.extend(self.parse_whitespaces());
let (sign, number, value) = self.parse_coordinate()?;
first_segment.args.push(value);
if let Some(sign) = sign {
first_segment.cst.push(sign);
}
first_segment.cst.push(number);
first_segment.end = self.index;
first_segment.chain_end = self.index;
let mut next_nodes = self.parse_whitespaces();
if let Some(mut next) = self.chars.peek() {
while !COMMAND_CHARACTERS.contains(next) {
let mut segment = new_segment(command, self.index, true);
let (sign, number, value) = self.parse_coordinate()?;
segment.end = self.index;
first_segment.chain_end = self.index;
segment.cst.push(number);
segment.args.push(value);
if let Some(sign) = sign {
segment.cst.push(sign);
}
next_nodes.push(SVGPathCSTNode::Segment(segment));
match self.parse_comma_wsp() {
Ok(comma_wsp) => next_nodes.extend(comma_wsp),
Err(_) => break,
}
if let Some(next_) = self.chars.peek() {
next = next_;
} else {
break;
}
}
}
let (start, end) = (first_segment.chain_start, first_segment.chain_end);
let mut cst = vec![SVGPathCSTNode::Segment(first_segment)];
set_commands_chain_info!(cst, next_nodes, start, end);
Ok(cst)
}
fn parse_drawto(
&mut self,
command: char,
) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
match command {
'm' => self.parse_two_operands_command(&SVGPathCommand::MovetoLower),
'M' => self.parse_two_operands_command(&SVGPathCommand::MovetoUpper),
'l' => self.parse_two_operands_command(&SVGPathCommand::LinetoLower),
'L' => self.parse_two_operands_command(&SVGPathCommand::LinetoUpper),
'h' => self.parse_horizontal_or_vertical(&SVGPathCommand::HorizontalLower),
'H' => self.parse_horizontal_or_vertical(&SVGPathCommand::HorizontalUpper),
'v' => self.parse_horizontal_or_vertical(&SVGPathCommand::VerticalLower),
'V' => self.parse_horizontal_or_vertical(&SVGPathCommand::VerticalUpper),
'z' => Ok(self.parse_closepath(&SVGPathCommand::ClosepathLower)),
'Z' => Ok(self.parse_closepath(&SVGPathCommand::ClosepathUpper)),
'c' => self.parse_curveto(&SVGPathCommand::CurvetoLower),
'C' => self.parse_curveto(&SVGPathCommand::CurvetoUpper),
'q' => self.parse_four_operands_command(&SVGPathCommand::QuadraticLower),
'Q' => self.parse_four_operands_command(&SVGPathCommand::QuadraticUpper),
's' => {
self.parse_four_operands_command(&SVGPathCommand::SmoothCurvetoLower)
}
'S' => {
self.parse_four_operands_command(&SVGPathCommand::SmoothCurvetoUpper)
}
'a' => self.parse_arc(&SVGPathCommand::ArcLower),
'A' => self.parse_arc(&SVGPathCommand::ArcUpper),
't' => {
self.parse_two_operands_command(&SVGPathCommand::SmoothQuadraticLower)
}
'T' => {
self.parse_two_operands_command(&SVGPathCommand::SmoothQuadraticUpper)
}
_ => Err(SyntaxError::InvalidCharacter {
character: command,
index: self.index - 1,
expected: "command".to_string(),
}),
}
}
pub fn parse(&mut self) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
if self.path.is_empty() {
return Ok(vec![]);
}
if self.path == "none" {
return Ok(vec![SVGPathCSTNode::None]);
}
let mut cst = self.parse_whitespaces();
if self.chars.peek().is_some() {
let next = self.next_char().unwrap();
if next == 'M' || next == 'm' {
cst.extend(self.parse_two_operands_command(match next {
'M' => &SVGPathCommand::MovetoUpper,
_ => &SVGPathCommand::MovetoLower,
})?);
cst.extend(self.parse_whitespaces());
while let Some(next) = self.next_char() {
cst.extend(self.parse_drawto(next)?);
cst.extend(self.parse_whitespaces());
}
} else {
return Err(SyntaxError::ExpectedMovetoCommand {
command: next,
index: self.index - 1,
});
}
}
Ok(cst)
}
}
pub fn svg_path_cst(path: &str) -> Result<Vec<SVGPathCSTNode>, SyntaxError> {
let mut parser = Parser::new(path);
parser.parse()
}