Skip to main content

fleuron/style/sheet/
face.rs

1//! `@font-face`: the descriptors of one face, and where its file
2//! comes from.
3
4use cssparser::{
5    AtRuleParser, CowRcStr, DeclarationParser, ParseError, Parser, ParserState,
6    QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser,
7};
8
9use crate::Warning;
10use crate::style::properties::{Family, FontStyle};
11
12use super::declaration::{Spec, at, longhand};
13use super::value::{families, font_style, weight};
14use super::{StyleError, warning};
15
16/// One `@font-face`: an identity, and the sources to try for it.
17///
18/// Slope and weight are what the sheet declared, not what the file
19/// says about itself; a sheet that declares neither leaves the file
20/// to describe its own cuts.
21#[derive(Debug, Clone, PartialEq)]
22pub struct FontFace {
23    /// The family a selector names the face by.
24    pub family: String,
25    /// The declared slope, if the sheet declared one.
26    pub style: Option<FontStyle>,
27    /// The declared weight, if the sheet declared one.
28    pub weight: Option<u16>,
29    /// The sources, in the order to try them.
30    pub src: Vec<Src>,
31}
32
33/// One entry of a `@font-face` `src` list.
34#[derive(Debug, Clone, PartialEq)]
35pub enum Src {
36    /// A url for the host loader to resolve. The engine opens
37    /// nothing itself.
38    Url(String),
39    /// A face by name, for a host that has one installed.
40    Local(String),
41}
42
43/// One `@font-face` body.
44pub(super) fn font_face(input: &mut Parser<'_, '_>, sheet: &str) -> (FontFace, Vec<Warning>) {
45    let mut body = FontFaceBody;
46    let collected: Vec<_> = RuleBodyParser::new(input, &mut body)
47        .map(|result| result.map_err(|(error, _)| error))
48        .collect();
49    let mut face = FontFace {
50        family: String::new(),
51        style: None,
52        weight: None,
53        src: Vec::new(),
54    };
55    let mut warnings = Vec::new();
56    for result in collected {
57        let declarations = match result {
58            Ok(declarations) => declarations,
59            Err(error) => {
60                warnings.push(warning(sheet, &error));
61                continue;
62            }
63        };
64        for declaration in declarations {
65            match declaration {
66                FaceDeclaration::Family(family) => face.family = family,
67                FaceDeclaration::Style(style) => face.style = Some(style),
68                FaceDeclaration::Weight(weight) => face.weight = Some(weight),
69                FaceDeclaration::Src(src) => face.src = src,
70            }
71        }
72    }
73    (face, warnings)
74}
75
76/// The descriptors of an `@font-face` rule.
77pub(crate) const FONT_FACE_DESCRIPTORS: &[Spec<FaceDeclaration>] = &[
78    Spec {
79        name: "font-family",
80        inherited: false,
81        syntax: "<family-name>",
82        examples: &["\"Author Serif\"", "Author Serif"],
83        read: |name, input| {
84            let family = match families(input)?.first() {
85                Some(Family::Named(family)) => family.clone(),
86                Some(Family::Generic(generic)) => generic.keyword().to_string(),
87                None => {
88                    return Err(input.new_custom_error(StyleError::UnsupportedValue(name.clone())));
89                }
90            };
91            Ok(vec![FaceDeclaration::Family(family)])
92        },
93    },
94    Spec {
95        name: "font-style",
96        inherited: false,
97        syntax: "normal | italic | oblique",
98        examples: &["italic"],
99        read: |name, input| longhand(name, input, font_style, FaceDeclaration::Style),
100    },
101    Spec {
102        name: "font-weight",
103        inherited: false,
104        syntax: "normal | bold | <number [1,1000]>",
105        examples: &["bold", "600"],
106        read: |name, input| longhand(name, input, weight, FaceDeclaration::Weight),
107    },
108    Spec {
109        name: "src",
110        inherited: false,
111        syntax: "[ <url> format(<string>)? | local(<string>) ]#",
112        examples: &[
113            "url(fonts/serif.otf)",
114            "url(\"fonts/serif.woff2\") format(\"woff2\")",
115            "local(\"Author Serif\"), url(fonts/serif.otf)",
116        ],
117        read: |_, input| Ok(vec![FaceDeclaration::Src(sources(input)?)]),
118    },
119];
120
121/// The body of one `@font-face`.
122struct FontFaceBody;
123
124/// One `@font-face` descriptor.
125pub(crate) enum FaceDeclaration {
126    Family(String),
127    Style(FontStyle),
128    Weight(u16),
129    Src(Vec<Src>),
130}
131
132impl<'i> DeclarationParser<'i> for FontFaceBody {
133    type Declaration = Vec<FaceDeclaration>;
134    type Error = StyleError<'i>;
135
136    fn parse_value<'t>(
137        &mut self,
138        name: CowRcStr<'i>,
139        input: &mut Parser<'i, 't>,
140        start: &ParserState,
141    ) -> Result<Self::Declaration, ParseError<'i, Self::Error>> {
142        at(start, |input| {
143            let Some(spec) = Spec::find(FONT_FACE_DESCRIPTORS, &name) else {
144                return Err(input.new_custom_error(StyleError::UnsupportedProperty(name.clone())));
145            };
146            let declarations = spec.read(&name, input)?;
147            input.expect_exhausted()?;
148            Ok(declarations)
149        })(input)
150    }
151}
152
153impl<'i> AtRuleParser<'i> for FontFaceBody {
154    type Prelude = ();
155    type AtRule = Vec<FaceDeclaration>;
156    type Error = StyleError<'i>;
157}
158
159impl<'i> QualifiedRuleParser<'i> for FontFaceBody {
160    type Prelude = ();
161    type QualifiedRule = Vec<FaceDeclaration>;
162    type Error = StyleError<'i>;
163}
164
165impl<'i> RuleBodyItemParser<'i, Vec<FaceDeclaration>, StyleError<'i>> for FontFaceBody {
166    fn parse_declarations(&self) -> bool {
167        true
168    }
169
170    fn parse_qualified(&self) -> bool {
171        false
172    }
173}
174
175/// A `src` list: urls for the host to resolve, or faces it may
176/// already have. `format()` hints are read and dropped — the loader
177/// hands back bytes and the registry decides what they are.
178fn sources<'i>(input: &mut Parser<'i, '_>) -> Result<Vec<Src>, ParseError<'i, StyleError<'i>>> {
179    input.parse_comma_separated(|input| {
180        if let Ok(url) = input.try_parse(|input| input.expect_url()) {
181            let source = Src::Url(url.as_ref().to_string());
182            let _ = input.try_parse(|input| {
183                input.expect_function_matching("format")?;
184                input.parse_nested_block(|input| {
185                    input
186                        .expect_string()
187                        .map(|_| ())
188                        .map_err(ParseError::<StyleError<'_>>::from)
189                })
190            });
191            return Ok(source);
192        }
193        input.expect_function_matching("local")?;
194        let name = input.parse_nested_block(|input| {
195            input
196                .expect_string()
197                .map(|name| name.as_ref().to_string())
198                .map_err(ParseError::from)
199        })?;
200        Ok(Src::Local(name))
201    })
202}