1use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::generics::font::FontStyle as GenericFontStyle;
14use crate::values::specified::{url::SpecifiedUrl, Angle};
15use cssparser::{Parser, RuleBodyParser, SourceLocation};
16use std::fmt::{self, Write};
17use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
18
19pub use crate::properties::font_face::{DescriptorId, DescriptorParser, Descriptors};
20pub use crate::values::computed::font::{FamilyName, FontStretch};
21pub use crate::values::specified::font::{
22 AbsoluteFontWeight, FontFeatureSettings, FontLanguageOverride,
23 FontStretch as SpecifiedFontStretch, FontVariationSettings, MetricsOverride,
24 SpecifiedFontStyle,
25};
26
27#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
29#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
30pub enum Source {
31 Url(UrlSource),
33 #[css(function)]
35 Local(FamilyName),
36}
37
38#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
40#[css(comma)]
41pub struct SourceList(#[css(iterable)] pub Vec<Source>);
42
43impl Parse for SourceList {
47 fn parse<'i, 't>(
48 context: &ParserContext,
49 input: &mut Parser<'i, 't>,
50 ) -> Result<Self, ParseError<'i>> {
51 let list = input
53 .parse_comma_separated(|input| {
54 let s = input.parse_entirely(|input| Source::parse(context, input));
55 while input.next().is_ok() {}
56 Ok(s.ok())
57 })?
58 .into_iter()
59 .filter_map(|s| s)
60 .collect::<Vec<Source>>();
61 if list.is_empty() {
62 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
63 } else {
64 Ok(SourceList(list))
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
72#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
73#[repr(u8)]
74#[allow(missing_docs)]
75pub enum FontFaceSourceFormatKeyword {
76 #[css(skip)]
77 None,
78 Collection,
79 EmbeddedOpentype,
80 Opentype,
81 Svg,
82 Truetype,
83 Woff,
84 Woff2,
85 #[css(skip)]
86 Unknown,
87}
88
89#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
92#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
93#[repr(C)]
94pub struct FontFaceSourceTechFlags(u16);
95bitflags! {
96 impl FontFaceSourceTechFlags: u16 {
97 const FEATURES_OPENTYPE = 1 << 0;
99 const FEATURES_AAT = 1 << 1;
101 const FEATURES_GRAPHITE = 1 << 2;
103 const COLOR_COLRV0 = 1 << 3;
105 const COLOR_COLRV1 = 1 << 4;
107 const COLOR_SVG = 1 << 5;
109 const COLOR_SBIX = 1 << 6;
111 const COLOR_CBDT = 1 << 7;
113 const VARIATIONS = 1 << 8;
115 const PALETTES = 1 << 9;
117 const INCREMENTAL = 1 << 10;
119 }
120}
121
122impl FontFaceSourceTechFlags {
123 pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
125 Ok(try_match_ident_ignore_ascii_case! { input,
126 "features-opentype" => Self::FEATURES_OPENTYPE,
127 "features-aat" => Self::FEATURES_AAT,
128 "features-graphite" => Self::FEATURES_GRAPHITE,
129 "color-colrv0" => Self::COLOR_COLRV0,
130 "color-colrv1" => Self::COLOR_COLRV1,
131 "color-svg" => Self::COLOR_SVG,
132 "color-sbix" => Self::COLOR_SBIX,
133 "color-cbdt" => Self::COLOR_CBDT,
134 "variations" => Self::VARIATIONS,
135 "palettes" => Self::PALETTES,
136 "incremental" => Self::INCREMENTAL,
137 })
138 }
139}
140
141impl Parse for FontFaceSourceTechFlags {
142 fn parse<'i, 't>(
143 _context: &ParserContext,
144 input: &mut Parser<'i, 't>,
145 ) -> Result<Self, ParseError<'i>> {
146 let location = input.current_source_location();
147 let mut result = Self::empty();
150 input.parse_comma_separated(|input| {
151 let flag = Self::parse_one(input)?;
152 result.insert(flag);
153 Ok(())
154 })?;
155 if !result.is_empty() {
156 Ok(result)
157 } else {
158 Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
159 }
160 }
161}
162
163#[allow(unused_assignments)]
164impl ToCss for FontFaceSourceTechFlags {
165 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
166 where
167 W: fmt::Write,
168 {
169 let mut first = true;
170
171 macro_rules! write_if_flag {
172 ($s:expr => $f:ident) => {
173 if self.contains(Self::$f) {
174 if first {
175 first = false;
176 } else {
177 dest.write_str(", ")?;
178 }
179 dest.write_str($s)?;
180 }
181 };
182 }
183
184 write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
185 write_if_flag!("features-aat" => FEATURES_AAT);
186 write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
187 write_if_flag!("color-colrv0" => COLOR_COLRV0);
188 write_if_flag!("color-colrv1" => COLOR_COLRV1);
189 write_if_flag!("color-svg" => COLOR_SVG);
190 write_if_flag!("color-sbix" => COLOR_SBIX);
191 write_if_flag!("color-cbdt" => COLOR_CBDT);
192 write_if_flag!("variations" => VARIATIONS);
193 write_if_flag!("palettes" => PALETTES);
194 write_if_flag!("incremental" => INCREMENTAL);
195
196 Ok(())
197 }
198}
199
200#[derive(Clone, Debug, ToShmem, PartialEq)]
202pub struct FontFaceRule {
203 pub descriptors: Descriptors,
205 pub source_location: SourceLocation,
207}
208
209impl FontFaceRule {
210 pub fn empty(source_location: SourceLocation) -> Self {
212 Self {
213 descriptors: Default::default(),
214 source_location,
215 }
216 }
217}
218
219#[cfg(feature = "gecko")]
224#[derive(Clone, Copy, Debug, Eq, PartialEq)]
225#[repr(u8)]
226#[allow(missing_docs)]
227pub enum FontFaceSourceListComponent {
228 Url(*const crate::gecko::url::CssUrl),
229 Local(*mut crate::gecko_bindings::structs::nsAtom),
230 FormatHintKeyword(FontFaceSourceFormatKeyword),
231 FormatHintString {
232 length: usize,
233 utf8_bytes: *const u8,
234 },
235 TechFlags(FontFaceSourceTechFlags),
236}
237
238#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
239#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
240#[repr(u8)]
241#[allow(missing_docs)]
242pub enum FontFaceSourceFormat {
243 Keyword(FontFaceSourceFormatKeyword),
244 String(String),
245}
246
247#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
252#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
253pub struct UrlSource {
254 pub url: SpecifiedUrl,
256 pub format_hint: Option<FontFaceSourceFormat>,
258 pub tech_flags: FontFaceSourceTechFlags,
260}
261
262impl ToCss for UrlSource {
263 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
264 where
265 W: fmt::Write,
266 {
267 self.url.to_css(dest)?;
268 if let Some(hint) = &self.format_hint {
269 dest.write_str(" format(")?;
270 hint.to_css(dest)?;
271 dest.write_char(')')?;
272 }
273 if !self.tech_flags.is_empty() {
274 dest.write_str(" tech(")?;
275 self.tech_flags.to_css(dest)?;
276 dest.write_char(')')?;
277 }
278 Ok(())
279 }
280}
281
282#[allow(missing_docs)]
286#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
287#[derive(
288 Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
289)]
290#[repr(u8)]
291pub enum FontDisplay {
292 Auto,
293 Block,
294 Swap,
295 Fallback,
296 Optional,
297}
298
299macro_rules! impl_range {
300 ($range:ident, $component:ident) => {
301 impl Parse for $range {
302 fn parse<'i, 't>(
303 context: &ParserContext,
304 input: &mut Parser<'i, 't>,
305 ) -> Result<Self, ParseError<'i>> {
306 let first = $component::parse(context, input)?;
307 let second = input
308 .try_parse(|input| $component::parse(context, input))
309 .unwrap_or_else(|_| first.clone());
310 Ok($range(first, second))
311 }
312 }
313 impl ToCss for $range {
314 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
315 where
316 W: fmt::Write,
317 {
318 self.0.to_css(dest)?;
319 if self.0 != self.1 {
320 dest.write_char(' ')?;
321 self.1.to_css(dest)?;
322 }
323 Ok(())
324 }
325 }
326 };
327}
328
329#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
333pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
334impl_range!(FontWeightRange, AbsoluteFontWeight);
335
336#[repr(C)]
341#[allow(missing_docs)]
342pub struct ComputedFontWeightRange(f32, f32);
343
344#[inline]
345fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
346 if a > b {
347 (b, a)
348 } else {
349 (a, b)
350 }
351}
352
353impl FontWeightRange {
354 pub fn compute(&self) -> ComputedFontWeightRange {
356 let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value());
357 ComputedFontWeightRange(min, max)
358 }
359}
360
361#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
365pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
366impl_range!(FontStretchRange, SpecifiedFontStretch);
367
368#[repr(C)]
371#[allow(missing_docs)]
372pub struct ComputedFontStretchRange(FontStretch, FontStretch);
373
374impl FontStretchRange {
375 pub fn compute(&self) -> ComputedFontStretchRange {
377 fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch {
378 match *s {
379 SpecifiedFontStretch::Keyword(ref kw) => kw.compute(),
380 SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()),
381 SpecifiedFontStretch::System(..) => unreachable!(),
382 }
383 }
384
385 let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
386 ComputedFontStretchRange(min, max)
387 }
388}
389
390#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
394#[allow(missing_docs)]
395pub enum FontStyle {
396 Italic,
397 Oblique(Angle, Angle),
398}
399
400#[repr(u8)]
403#[allow(missing_docs)]
404pub enum ComputedFontStyleDescriptor {
405 Italic,
406 Oblique(f32, f32),
407}
408
409impl Parse for FontStyle {
410 fn parse<'i, 't>(
411 context: &ParserContext,
412 input: &mut Parser<'i, 't>,
413 ) -> Result<Self, ParseError<'i>> {
414 if input
417 .try_parse(|i| i.expect_ident_matching("normal"))
418 .is_ok()
419 {
420 return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
421 }
422
423 let style = SpecifiedFontStyle::parse(context, input)?;
424 Ok(match style {
425 GenericFontStyle::Italic => FontStyle::Italic,
426 GenericFontStyle::Oblique(angle) => {
427 let second_angle = input
428 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
429 .unwrap_or_else(|_| angle.clone());
430
431 FontStyle::Oblique(angle, second_angle)
432 },
433 })
434 }
435}
436
437impl ToCss for FontStyle {
438 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
439 where
440 W: fmt::Write,
441 {
442 match *self {
443 FontStyle::Italic => dest.write_str("italic"),
444 FontStyle::Oblique(ref first, ref second) => {
445 if *first == Angle::zero() && first == second {
448 return dest.write_str("normal");
449 }
450 dest.write_str("oblique")?;
451 if *first != SpecifiedFontStyle::default_angle() || first != second {
452 dest.write_char(' ')?;
453 first.to_css(dest)?;
454 }
455 if first != second {
456 dest.write_char(' ')?;
457 second.to_css(dest)?;
458 }
459 Ok(())
460 },
461 }
462 }
463}
464
465impl FontStyle {
466 pub fn compute(&self) -> ComputedFontStyleDescriptor {
468 match *self {
469 FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
470 FontStyle::Oblique(ref first, ref second) => {
471 let (min, max) = sort_range(
472 SpecifiedFontStyle::compute_angle_degrees(first),
473 SpecifiedFontStyle::compute_angle_degrees(second),
474 );
475 ComputedFontStyleDescriptor::Oblique(min, max)
476 },
477 }
478 }
479}
480
481pub fn parse_font_face_block(
485 context: &ParserContext,
486 input: &mut Parser,
487 source_location: SourceLocation,
488) -> FontFaceRule {
489 let mut rule = FontFaceRule::empty(source_location);
490 {
491 let mut parser = DescriptorParser {
492 context,
493 descriptors: &mut rule.descriptors,
494 };
495 let mut iter = RuleBodyParser::new(input, &mut parser);
496 while let Some(declaration) = iter.next() {
497 if let Err((error, slice)) = declaration {
498 let location = error.location;
499 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
500 context.log_css_error(location, error)
501 }
502 }
503 }
504 rule
505}
506
507
508impl Parse for Source {
509 fn parse<'i, 't>(
510 context: &ParserContext,
511 input: &mut Parser<'i, 't>,
512 ) -> Result<Source, ParseError<'i>> {
513 if input
514 .try_parse(|input| input.expect_function_matching("local"))
515 .is_ok()
516 {
517 return input
518 .parse_nested_block(|input| FamilyName::parse(context, input))
519 .map(Source::Local);
520 }
521
522 let url = SpecifiedUrl::parse(context, input)?;
523
524 let format_hint = if input
526 .try_parse(|input| input.expect_function_matching("format"))
527 .is_ok()
528 {
529 input.parse_nested_block(|input| {
530 if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
531 Ok(Some(FontFaceSourceFormat::Keyword(kw)))
532 } else {
533 let s = input.expect_string()?.as_ref().to_owned();
534 Ok(Some(FontFaceSourceFormat::String(s)))
535 }
536 })?
537 } else {
538 None
539 };
540
541 let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled")
543 && input
544 .try_parse(|input| input.expect_function_matching("tech"))
545 .is_ok()
546 {
547 input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
548 } else {
549 FontFaceSourceTechFlags::empty()
550 };
551
552 Ok(Source::Url(UrlSource {
553 url,
554 format_hint,
555 tech_flags,
556 }))
557 }
558}
559
560impl ToCssWithGuard for FontFaceRule {
561 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
563 dest.write_str("@font-face { ")?;
564 self.descriptors.to_css(&mut CssWriter::new(dest))?;
565 dest.write_char('}')
566 }
567}