Skip to content

Commit

Permalink
Add font-family parser.
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenzV authored Feb 4, 2024
1 parent fbcd381 commit 3764908
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 17 deletions.
4 changes: 2 additions & 2 deletions src/aspect_ratio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl std::str::FromStr for AspectRatio {
}

let start = s.pos();
let align = s.consume_ident();
let align = s.consume_ascii_ident();
let align = match align {
"none" => Align::None,
"xMinYMin" => Align::XMinYMin,
Expand All @@ -74,7 +74,7 @@ impl std::str::FromStr for AspectRatio {
let mut slice = false;
if !s.at_end() {
let start = s.pos();
let v = s.consume_ident();
let v = s.consume_ascii_ident();
match v {
"meet" => {}
"slice" => slice = true,
Expand Down
2 changes: 1 addition & 1 deletion src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ impl<'a> Stream<'a> {
}
} else {
// TODO: remove allocation
let name = self.consume_ident().to_ascii_lowercase();
let name = self.consume_ascii_ident().to_ascii_lowercase();
if name == "rgb" || name == "rgba" {
self.consume_byte(b'(')?;

Expand Down
12 changes: 11 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// List of all errors.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// An input data ended earlier than expected.
///
Expand All @@ -18,6 +18,13 @@ pub enum Error {
/// then we will get `InvalidNumber`, because at least some data is valid.
InvalidValue,

/// An invalid ident.
///
/// CSS idents have certain rules with regard to the characters they may contain.
/// For example, they may not start with a number. If an invalid ident is encountered,
/// this error will be returned.
InvalidIdent,

/// An invalid/unexpected character.
///
/// The first byte is an actual one, others - expected.
Expand Down Expand Up @@ -48,6 +55,9 @@ impl std::fmt::Display for Error {
Error::InvalidValue => {
write!(f, "invalid value")
}
Error::InvalidIdent => {
write!(f, "invalid ident")
}
Error::InvalidChar(ref chars, pos) => {
// Vec<u8> -> Vec<String>
let list: Vec<String> = chars
Expand Down
2 changes: 1 addition & 1 deletion src/filter_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl<'a> FilterValueListParser<'a> {
let s = &mut self.stream;

let start = s.pos();
let name = s.consume_ident();
let name = s.consume_ascii_ident();
s.skip_spaces();
s.consume_byte(b'(')?;
s.skip_spaces();
Expand Down
171 changes: 171 additions & 0 deletions src/font.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use crate::stream::Stream;
use crate::Error;
use std::fmt::Display;

/// Parses a list of font families and generic families from a string.
pub fn parse_font_families(text: &str) -> Result<Vec<FontFamily>, Error> {
let mut s = Stream::from(text);
let font_families = s.parse_font_families()?;

s.skip_spaces();
if !s.at_end() {
return Err(Error::UnexpectedData(s.calc_char_pos()));
}

Ok(font_families)
}

/// A type of font family.
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum FontFamily {
/// A serif font.
Serif,
/// A sans-serif font.
SansSerif,
/// A cursive font.
Cursive,
/// A fantasy font.
Fantasy,
/// A monospace font.
Monospace,
/// A custom named font.
Named(String),
}

impl Display for FontFamily {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
FontFamily::Monospace => "monospace".to_string(),
FontFamily::Serif => "serif".to_string(),
FontFamily::SansSerif => "sans-serif".to_string(),
FontFamily::Cursive => "cursive".to_string(),
FontFamily::Fantasy => "fantasy".to_string(),
FontFamily::Named(s) => format!("\"{}\"", s),
};
write!(f, "{}", str)
}
}

impl<'a> Stream<'a> {
pub fn parse_font_families(&mut self) -> Result<Vec<FontFamily>, Error> {
let mut families = vec![];

while !self.at_end() {
self.skip_spaces();

let family = {
let ch = self.curr_byte()?;
if ch == b'\'' || ch == b'\"' {
let res = self.parse_quoted_string()?;
FontFamily::Named(res.to_string())
} else {
let mut idents = vec![];

while let Some(c) = self.chars().next() {
if c != ',' {
idents.push(self.parse_ident()?.to_string());
self.skip_spaces();
} else {
break;
}
}

let joined = idents.join(" ");

// TODO: No CSS keyword must be matched as a family name...
match joined.as_str() {
"serif" => FontFamily::Serif,
"sans-serif" => FontFamily::SansSerif,
"cursive" => FontFamily::Cursive,
"fantasy" => FontFamily::Fantasy,
"monospace" => FontFamily::Monospace,
_ => FontFamily::Named(joined),
}
}
};

families.push(family);

if let Ok(b) = self.curr_byte() {
if b == b',' {
self.advance(1);
} else {
break;
}
}
}

let families = families
.into_iter()
.filter(|f| match f {
FontFamily::Named(s) => !s.is_empty(),
_ => true,
})
.collect();

Ok(families)
}
}

#[rustfmt::skip]
#[cfg(test)]
mod tests {
use super::*;

macro_rules! test {
($name:ident, $text:expr, $result:expr) => (
#[test]
fn $name() {
assert_eq!(parse_font_families($text).unwrap(), $result);
}
)
}

macro_rules! named {
($text:expr) => (
FontFamily::Named($text.to_string())
)
}

const SERIF: FontFamily = FontFamily::Serif;
const SANS_SERIF: FontFamily = FontFamily::SansSerif;
const FANTASY: FontFamily = FontFamily::Fantasy;
const MONOSPACE: FontFamily = FontFamily::Monospace;
const CURSIVE: FontFamily = FontFamily::Cursive;

test!(font_family_1, "Times New Roman", vec![named!("Times New Roman")]);
test!(font_family_2, "serif", vec![SERIF]);
test!(font_family_3, "sans-serif", vec![SANS_SERIF]);
test!(font_family_4, "cursive", vec![CURSIVE]);
test!(font_family_5, "fantasy", vec![FANTASY]);
test!(font_family_6, "monospace", vec![MONOSPACE]);
test!(font_family_7, "'Times New Roman'", vec![named!("Times New Roman")]);
test!(font_family_8, "'Times New Roman', sans-serif", vec![named!("Times New Roman"), SANS_SERIF]);
test!(font_family_9, "'Times New Roman', sans-serif", vec![named!("Times New Roman"), SANS_SERIF]);
test!(font_family_10, "Arial, sans-serif, 'fantasy'", vec![named!("Arial"), SANS_SERIF, named!("fantasy")]);
test!(font_family_11, " Arial , monospace , 'fantasy'", vec![named!("Arial"), MONOSPACE, named!("fantasy")]);
test!(font_family_12, "Times New Roman", vec![named!("Times New Roman")]);
test!(font_family_13, "\"Times New Roman\", sans-serif, sans-serif, \"Arial\"",
vec![named!("Times New Roman"), SANS_SERIF, SANS_SERIF, named!("Arial")]
);
test!(font_family_14, "Times New Roman,,,Arial", vec![named!("Times New Roman"), named!("Arial")]);
test!(font_family_15, "简体中文,sans-serif , ,\"日本語フォント\",Arial",
vec![named!("简体中文"), SANS_SERIF, named!("日本語フォント"), named!("Arial")]);

test!(font_family_16, "", vec![]);

macro_rules! font_family_err {
($name:ident, $text:expr, $result:expr) => (
#[test]
fn $name() {
assert_eq!(parse_font_families($text).unwrap_err().to_string(), $result);
}
)
}
font_family_err!(font_family_err_1, "Red/Black, sans-serif", "invalid ident");
font_family_err!(font_family_err_2, "\"Lucida\" Grande, sans-serif", "unexpected data at position 10");
font_family_err!(font_family_err_3, "Ahem!, sans-serif", "invalid ident");
font_family_err!(font_family_err_4, "test@foo, sans-serif", "invalid ident");
font_family_err!(font_family_err_5, "#POUND, sans-serif", "invalid ident");
font_family_err!(font_family_err_6, "Hawaii 5-0, sans-serif", "invalid ident");
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ mod directional_position;
mod enable_background;
mod error;
mod filter_functions;
mod font;
mod funciri;
mod length;
mod number;
Expand All @@ -91,6 +92,7 @@ pub use crate::directional_position::*;
pub use crate::enable_background::*;
pub use crate::error::*;
pub use crate::filter_functions::*;
pub use crate::font::*;
pub use crate::funciri::*;
pub use crate::length::*;
pub use crate::number::*;
Expand Down
2 changes: 1 addition & 1 deletion src/paint_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl std::str::FromStr for PaintOrder {
let mut s = Stream::from(text);
while !s.at_end() && order.len() < 3 {
s.skip_spaces();
let name = s.consume_ident();
let name = s.consume_ascii_ident();
s.skip_spaces();
let name = match name {
// `normal` is the special value that should short-circuit.
Expand Down
Loading

0 comments on commit 3764908

Please sign in to comment.