Skip to content

Commit fbf1e15

Browse files
committed
Supports parsing @font-face
Part of linebender/resvg#541
1 parent 4647082 commit fbf1e15

File tree

3 files changed

+131
-11
lines changed

3 files changed

+131
-11
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ Since it's very simple we will start with limitations:
2121

2222
## Limitations
2323

24-
- [At-rules](https://www.w3.org/TR/CSS21/syndata.html#at-rules) are not supported.
25-
They will be skipped during parsing.
24+
- [Most at-rules](https://www.w3.org/TR/CSS21/syndata.html#at-rules) are not supported.
25+
They will be skipped during parsing. The only supported at-rule is `@font-face`.
2626
- Property values are not parsed.
2727
In CSS like `* { width: 5px }` you will get a `width` property with a `5px` value as a string.
2828
- CDO/CDC comments are not supported.
@@ -34,6 +34,7 @@ Since it's very simple we will start with limitations:
3434

3535
- Selector matching support.
3636
- The rules are sorted by specificity.
37+
- `@font-face` parsing support.
3738
- `!important` parsing support.
3839
- Has a high-level parsers and low-level, zero-allocation tokenizers.
3940
- No unsafe.

src/lib.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Since it's very simple we will start with limitations:
1212
1313
## Limitations
1414
15-
- [At-rules](https://www.w3.org/TR/CSS21/syndata.html#at-rules) are not supported.
16-
They will be skipped during parsing.
15+
- [Most at-rules](https://www.w3.org/TR/CSS21/syndata.html#at-rules) are not supported.
16+
They will be skipped during parsing. The only supported at-rule is `@font-face`.
1717
- Property values are not parsed.
1818
In CSS like `* { width: 5px }` you will get a `width` property with a `5px` value as a string.
1919
- CDO/CDC comments are not supported.
@@ -24,6 +24,7 @@ Since it's very simple we will start with limitations:
2424
2525
- Selector matching support.
2626
- The rules are sorted by specificity.
27+
- `@font-face` parsing support.
2728
- `!important` parsing support.
2829
- Has a high-level parsers and low-level, zero-allocation tokenizers.
2930
- No unsafe.
@@ -192,6 +193,13 @@ pub struct Declaration<'a> {
192193
pub important: bool,
193194
}
194195

196+
/// A `@font-face` rule.
197+
#[derive(Clone, Debug)]
198+
pub struct FontFaceRule<'a> {
199+
/// A list of declarations inside this `@font-face` rule.
200+
pub declarations: Vec<Declaration<'a>>,
201+
}
202+
195203
/// A rule.
196204
#[derive(Clone, Debug)]
197205
pub struct Rule<'a> {
@@ -206,17 +214,23 @@ pub struct Rule<'a> {
206214
pub struct StyleSheet<'a> {
207215
/// A list of rules.
208216
pub rules: Vec<Rule<'a>>,
217+
/// A list of `@font-face` rules.
218+
pub font_faces: Vec<FontFaceRule<'a>>,
209219
}
210220

211221
impl<'a> StyleSheet<'a> {
212222
/// Creates an empty style sheet.
213223
pub fn new() -> Self {
214-
StyleSheet { rules: Vec::new() }
224+
StyleSheet {
225+
rules: Vec::new(),
226+
font_faces: Vec::new(),
227+
}
215228
}
216229

217230
/// Parses a style sheet from text.
218231
///
219-
/// At-rules are not supported and will be skipped.
232+
/// Most at-rules are not supported and will be skipped, except `@font-face`
233+
/// rules which are parsed into [`FontFaceRule`]s.
220234
///
221235
/// # Errors
222236
///
@@ -242,7 +256,7 @@ impl<'a> StyleSheet<'a> {
242256
break;
243257
}
244258

245-
let _ = consume_statement(&mut s, &mut self.rules);
259+
let _ = consume_statement(&mut s, &mut self.rules, &mut self.font_faces);
246260
}
247261

248262
if !s.at_end() {
@@ -286,19 +300,50 @@ impl Default for StyleSheet<'_> {
286300
}
287301
}
288302

289-
fn consume_statement<'a>(s: &mut Stream<'a>, rules: &mut Vec<Rule<'a>>) -> Result<(), Error> {
303+
fn consume_statement<'a>(
304+
s: &mut Stream<'a>,
305+
rules: &mut Vec<Rule<'a>>,
306+
font_faces: &mut Vec<FontFaceRule<'a>>,
307+
) -> Result<(), Error> {
290308
if s.curr_byte() == Ok(b'@') {
291309
s.advance(1);
292-
consume_at_rule(s)
310+
consume_at_rule(s, font_faces)
293311
} else {
294312
consume_rule_set(s, rules)
295313
}
296314
}
297315

298-
fn consume_at_rule(s: &mut Stream<'_>) -> Result<(), Error> {
316+
fn consume_at_rule<'a>(
317+
s: &mut Stream<'a>,
318+
font_faces: &mut Vec<FontFaceRule<'a>>,
319+
) -> Result<(), Error> {
299320
let ident = s.consume_ident()?;
300-
warn!("The @{} rule is not supported. Skipped.", ident);
301321

322+
if ident == "font-face" {
323+
s.skip_spaces_and_comments()?;
324+
325+
if s.curr_byte() == Ok(b'{') {
326+
s.advance(1);
327+
328+
let declarations = consume_declarations(s)?;
329+
s.try_consume_byte(b'}');
330+
331+
if !declarations.is_empty() {
332+
font_faces.push(FontFaceRule { declarations });
333+
}
334+
} else {
335+
// Malformed `@font-face`; fall back to skipping it as an unknown at-rule.
336+
skip_at_rule_body(s)?;
337+
}
338+
} else {
339+
warn!("The @{} rule is not supported. Skipped.", ident);
340+
skip_at_rule_body(s)?;
341+
}
342+
343+
Ok(())
344+
}
345+
346+
fn skip_at_rule_body(s: &mut Stream<'_>) -> Result<(), Error> {
302347
s.skip_bytes(|c| c != b';' && c != b'{');
303348

304349
match s.curr_byte()? {

tests/stylesheet.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,77 @@ fn style_21() {
145145
let style = StyleSheet::parse(":le>*");
146146
assert_eq!(style.to_string(), "");
147147
}
148+
149+
#[test]
150+
fn font_face_01() {
151+
let style = StyleSheet::parse(
152+
"@font-face { font-family: 'Noto Serif'; src: url(NotoSerif.woff2) format('woff2'); }",
153+
);
154+
155+
assert_eq!(style.rules.len(), 0);
156+
assert_eq!(style.font_faces.len(), 1);
157+
158+
let ff = &style.font_faces[0];
159+
assert_eq!(
160+
ff.declarations,
161+
vec![
162+
Declaration {
163+
name: "font-family",
164+
value: "'Noto Serif'",
165+
important: false,
166+
},
167+
Declaration {
168+
name: "src",
169+
value: "url(NotoSerif.woff2) format('woff2')",
170+
important: false,
171+
},
172+
]
173+
);
174+
}
175+
176+
#[test]
177+
fn font_face_02_mixed_with_rules() {
178+
let style = StyleSheet::parse(
179+
"@font-face { font-family: 'MyFont'; src: url(https://foo.com/my.woff2); font-weight: normal; } div { color: red; }",
180+
);
181+
182+
assert_eq!(style.rules.len(), 1);
183+
assert_eq!(style.font_faces.len(), 1);
184+
185+
assert_eq!(style.rules[0].selector.to_string(), "div");
186+
assert_eq!(style.rules[0].declarations.len(), 1);
187+
assert_eq!(style.rules[0].declarations[0].name, "color");
188+
assert_eq!(style.rules[0].declarations[0].value, "red");
189+
190+
assert_eq!(style.font_faces[0].declarations[0].name, "font-family");
191+
assert_eq!(style.font_faces[0].declarations[0].value, "'MyFont'");
192+
assert_eq!(style.font_faces[0].declarations[1].name, "src");
193+
assert_eq!(
194+
style.font_faces[0].declarations[1].value,
195+
"url(https://foo.com/my.woff2)"
196+
);
197+
assert_eq!(style.font_faces[0].declarations[2].name, "font-weight");
198+
assert_eq!(style.font_faces[0].declarations[2].value, "normal");
199+
}
200+
201+
#[test]
202+
fn font_face_03_mixed_with_rules() {
203+
let style = StyleSheet::parse(
204+
"@font-palette-values --identifier { font-family: Bixa; override-colors: 0 green, 1 #999; } div { color: red; } @font-face { font-family: 'MyFont'; src: local('Airal'); font-weight: 200 800; }",
205+
);
206+
207+
assert_eq!(style.rules.len(), 1);
208+
assert_eq!(style.font_faces.len(), 1);
209+
210+
assert_eq!(style.rules[0].selector.to_string(), "div");
211+
assert_eq!(style.rules[0].declarations.len(), 1);
212+
assert_eq!(style.rules[0].declarations[0].name, "color");
213+
assert_eq!(style.rules[0].declarations[0].value, "red");
214+
215+
assert_eq!(style.font_faces[0].declarations[0].name, "font-family");
216+
assert_eq!(style.font_faces[0].declarations[0].value, "'MyFont'");
217+
assert_eq!(style.font_faces[0].declarations[1].name, "src");
218+
assert_eq!(style.font_faces[0].declarations[1].value, "local('Airal')");
219+
assert_eq!(style.font_faces[0].declarations[2].name, "font-weight");
220+
assert_eq!(style.font_faces[0].declarations[2].value, "200 800");
221+
}

0 commit comments

Comments
 (0)