script/svgpath/stream.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
// 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),
}
}
}