servo/components/script/svgpath/stream.rs
Lukas Lihotzki 251eeb2c2d
Add Path2D (#35783)
Signed-off-by: Lukas Lihotzki <lukas@lihotzki.de>
2025-03-26 12:12:44 +00:00

162 lines
4 KiB
Rust

// Copyright 2018 the SVG Types Authors
// Copyright 2025 the Servo Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use crate::svgpath::Error;
/// A streaming text parsing interface.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Stream<'a> {
text: &'a str,
pos: usize,
}
impl<'a> From<&'a str> for Stream<'a> {
#[inline]
fn from(text: &'a str) -> Self {
Stream { text, pos: 0 }
}
}
impl<'a> Stream<'a> {
/// Returns the current position in bytes.
#[inline]
pub fn pos(&self) -> usize {
self.pos
}
/// Sets current position equal to the end.
///
/// Used to indicate end of parsing on error.
#[inline]
pub fn jump_to_end(&mut self) {
self.pos = self.text.len();
}
/// Checks if the stream is reached the end.
///
/// Any [`pos()`] value larger than original text length indicates stream end.
///
/// Accessing stream after reaching end via safe methods will produce
/// an error.
///
/// Accessing stream after reaching end via *_unchecked methods will produce
/// a Rust's bound checking error.
///
/// [`pos()`]: #method.pos
#[inline]
pub fn at_end(&self) -> bool {
self.pos >= self.text.len()
}
/// Returns a byte from a current stream position.
#[inline]
pub fn curr_byte(&self) -> Result<u8, Error> {
if self.at_end() {
return Err(Error);
}
Ok(self.curr_byte_unchecked())
}
/// Returns a byte from a current stream position.
///
/// # Panics
///
/// - if the current position is after the end of the data
#[inline]
pub fn curr_byte_unchecked(&self) -> u8 {
self.text.as_bytes()[self.pos]
}
/// Checks that current byte is equal to provided.
///
/// Returns `false` if no bytes left.
#[inline]
pub fn is_curr_byte_eq(&self, c: u8) -> bool {
if !self.at_end() {
self.curr_byte_unchecked() == c
} else {
false
}
}
/// Returns a next byte from a current stream position.
#[inline]
pub fn next_byte(&self) -> Result<u8, Error> {
if self.pos + 1 >= self.text.len() {
return Err(Error);
}
Ok(self.text.as_bytes()[self.pos + 1])
}
/// Advances by `n` bytes.
#[inline]
pub fn advance(&mut self, n: usize) {
debug_assert!(self.pos + n <= self.text.len());
self.pos += n;
}
/// Skips whitespaces.
///
/// Accepted values: `' ' \n \r \t`.
pub fn skip_spaces(&mut self) {
while !self.at_end() && matches!(self.curr_byte_unchecked(), b' ' | b'\t' | b'\n' | b'\r') {
self.advance(1);
}
}
/// Consumes bytes by the predicate.
pub fn skip_bytes<F>(&mut self, f: F)
where
F: Fn(&Stream<'_>, u8) -> bool,
{
while !self.at_end() {
let c = self.curr_byte_unchecked();
if f(self, c) {
self.advance(1);
} else {
break;
}
}
}
/// Slices data from `pos` to the current position.
#[inline]
pub fn slice_back(&self, pos: usize) -> &'a str {
&self.text[pos..self.pos]
}
/// Skips digits.
pub fn skip_digits(&mut self) {
self.skip_bytes(|_, c| c.is_ascii_digit());
}
#[inline]
pub(crate) fn parse_list_separator(&mut self) {
if self.is_curr_byte_eq(b',') {
self.advance(1);
}
}
// By the SVG spec 'large-arc' and 'sweep' must contain only one char
// and can be written without any separators, e.g.: 10 20 30 01 10 20.
pub(crate) fn parse_flag(&mut self) -> Result<bool, Error> {
self.skip_spaces();
let c = self.curr_byte()?;
match c {
b'0' | b'1' => {
self.advance(1);
if self.is_curr_byte_eq(b',') {
self.advance(1);
}
self.skip_spaces();
Ok(c == b'1')
},
_ => Err(Error),
}
}
}