Skip to main content

style/
font_face.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The [`@font-face`][ff] at-rule.
6//!
7//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
8
9use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::computed::FontWeight;
14use crate::values::generics::font::FontStyle as GenericFontStyle;
15use crate::values::specified::{url::SpecifiedUrl, Angle};
16use cssparser::{Parser, RuleBodyParser, SourceLocation};
17use std::fmt::{self, Write};
18use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
19
20pub use crate::properties::font_face::{DescriptorId, DescriptorParser, Descriptors};
21pub use crate::values::computed::font::{FamilyName, FontStyle, FontWidth};
22pub use crate::values::specified::font::{
23    AbsoluteFontWeight, FontFeatureSettings, FontLanguageOverride, FontVariationSettings,
24    FontWidth as SpecifiedFontWidth, MetricsOverride, SpecifiedFontStyle,
25};
26
27/// A source for a font-face rule.
28#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
29#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
30pub enum Source {
31    /// A `url()` source.
32    Url(UrlSource),
33    /// A `local()` source.
34    #[css(function)]
35    Local(FamilyName),
36}
37
38/// A list of sources for the font-face src descriptor.
39#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
40#[css(comma)]
41pub struct SourceList(#[css(iterable)] pub Vec<Source>);
42
43// We can't just use OneOrMoreSeparated to derive Parse for the Source list,
44// because we want to filter out components that parsed as None, then fail if no
45// valid components remain. So we provide our own implementation here.
46impl Parse for SourceList {
47    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
48        // Parse the comma-separated list, then let filter_map discard any None items.
49        let list = input
50            .parse_comma_separated(|input| {
51                let s = input.parse_entirely(|input| Source::parse(context, input));
52                while input.next().is_ok() {}
53                Ok(s.ok())
54            })?
55            .into_iter()
56            .flatten()
57            .collect::<Vec<Source>>();
58        if list.is_empty() {
59            Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
60        } else {
61            Ok(SourceList(list))
62        }
63    }
64}
65
66/// Keywords for the font-face src descriptor's format() function.
67/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.)
68#[derive(
69    Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Parse, PartialEq, Serialize, ToCss, ToShmem,
70)]
71#[repr(u8)]
72#[allow(missing_docs)]
73pub enum FontFaceSourceFormatKeyword {
74    #[css(skip)]
75    None,
76    Collection,
77    EmbeddedOpentype,
78    Opentype,
79    Svg,
80    Truetype,
81    Woff,
82    Woff2,
83    #[css(skip)]
84    Unknown,
85}
86
87/// Flags for the @font-face tech() function, indicating font technologies
88/// required by the resource.
89#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize, ToShmem)]
90#[repr(C)]
91pub struct FontFaceSourceTechFlags(u16);
92bitflags! {
93    impl FontFaceSourceTechFlags: u16 {
94        /// Font requires OpenType feature support.
95        const FEATURES_OPENTYPE = 1 << 0;
96        /// Font requires Apple Advanced Typography support.
97        const FEATURES_AAT = 1 << 1;
98        /// Font requires Graphite shaping support.
99        const FEATURES_GRAPHITE = 1 << 2;
100        /// Font requires COLRv0 rendering support (simple list of colored layers).
101        const COLOR_COLRV0 = 1 << 3;
102        /// Font requires COLRv1 rendering support (graph of paint operations).
103        const COLOR_COLRV1 = 1 << 4;
104        /// Font requires SVG glyph rendering support.
105        const COLOR_SVG = 1 << 5;
106        /// Font has bitmap glyphs in 'sbix' format.
107        const COLOR_SBIX = 1 << 6;
108        /// Font has bitmap glyphs in 'CBDT' format.
109        const COLOR_CBDT = 1 << 7;
110        /// Font requires OpenType Variations support.
111        const VARIATIONS = 1 << 8;
112        /// Font requires CPAL palette selection support.
113        const PALETTES = 1 << 9;
114        /// Font requires support for incremental downloading.
115        const INCREMENTAL = 1 << 10;
116    }
117}
118
119impl FontFaceSourceTechFlags {
120    /// Parse a single font-technology keyword and return its flag.
121    pub fn parse_one(input: &mut Parser) -> Result<Self, ParseError> {
122        Ok(try_match_ident_ignore_ascii_case! { input,
123            "features-opentype" => Self::FEATURES_OPENTYPE,
124            "features-aat" => Self::FEATURES_AAT,
125            "features-graphite" => Self::FEATURES_GRAPHITE,
126            "color-colrv0" => Self::COLOR_COLRV0,
127            "color-colrv1" => Self::COLOR_COLRV1,
128            "color-svg" => Self::COLOR_SVG,
129            "color-sbix" => Self::COLOR_SBIX,
130            "color-cbdt" => Self::COLOR_CBDT,
131            "variations" => Self::VARIATIONS,
132            "palettes" => Self::PALETTES,
133            "incremental" => Self::INCREMENTAL,
134        })
135    }
136}
137
138impl Parse for FontFaceSourceTechFlags {
139    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
140        // We don't actually care about the return value of parse_comma_separated,
141        // because we insert the flags into result as we go.
142        let mut result = Self::empty();
143        input.parse_comma_separated(|input| {
144            let flag = Self::parse_one(input)?;
145            result.insert(flag);
146            Ok(())
147        })?;
148        if !result.is_empty() {
149            Ok(result)
150        } else {
151            Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
152        }
153    }
154}
155
156#[allow(unused_assignments)]
157impl ToCss for FontFaceSourceTechFlags {
158    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
159    where
160        W: fmt::Write,
161    {
162        let mut first = true;
163
164        macro_rules! write_if_flag {
165            ($s:expr => $f:ident) => {
166                if self.contains(Self::$f) {
167                    if first {
168                        first = false;
169                    } else {
170                        dest.write_str(", ")?;
171                    }
172                    dest.write_str($s)?;
173                }
174            };
175        }
176
177        write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
178        write_if_flag!("features-aat" => FEATURES_AAT);
179        write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
180        write_if_flag!("color-colrv0" => COLOR_COLRV0);
181        write_if_flag!("color-colrv1" => COLOR_COLRV1);
182        write_if_flag!("color-svg" => COLOR_SVG);
183        write_if_flag!("color-sbix" => COLOR_SBIX);
184        write_if_flag!("color-cbdt" => COLOR_CBDT);
185        write_if_flag!("variations" => VARIATIONS);
186        write_if_flag!("palettes" => PALETTES);
187        write_if_flag!("incremental" => INCREMENTAL);
188
189        Ok(())
190    }
191}
192
193/// <https://drafts.csswg.org/css-fonts/#font-face-rule>
194#[derive(Clone, Debug, ToShmem, PartialEq)]
195pub struct FontFaceRule {
196    /// The descriptors of the @font-face rule.
197    pub descriptors: Descriptors,
198    /// The parser location of the rule.
199    pub source_location: SourceLocation,
200}
201
202impl FontFaceRule {
203    /// Returns an empty rule.
204    pub fn empty(source_location: SourceLocation) -> Self {
205        Self {
206            descriptors: Default::default(),
207            source_location,
208        }
209    }
210}
211
212/// A POD representation for Gecko. All pointers here are non-owned and as such
213/// can't outlive the rule they came from, but we can't enforce that via C++.
214///
215/// All the strings are of course utf8.
216#[cfg(feature = "gecko")]
217#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218#[repr(u8)]
219#[allow(missing_docs)]
220pub enum FontFaceSourceListComponent {
221    Url(*const crate::url::CssUrl),
222    Local(*mut crate::gecko_bindings::structs::nsAtom),
223    FormatHintKeyword(FontFaceSourceFormatKeyword),
224    FormatHintString {
225        length: usize,
226        utf8_bytes: *const u8,
227    },
228    TechFlags(FontFaceSourceTechFlags),
229}
230
231#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize, ToCss, ToShmem)]
232#[repr(u8)]
233#[allow(missing_docs)]
234pub enum FontFaceSourceFormat {
235    Keyword(FontFaceSourceFormatKeyword),
236    String(String),
237}
238
239/// A `UrlSource` represents a font-face source that has been specified with a
240/// `url()` function.
241///
242/// <https://drafts.csswg.org/css-fonts/#src-desc>
243#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
244#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
245pub struct UrlSource {
246    /// The specified url.
247    pub url: SpecifiedUrl,
248    /// The format hint specified with the `format()` function, if present.
249    pub format_hint: Option<FontFaceSourceFormat>,
250    /// The font technology flags specified with the `tech()` function, if any.
251    pub tech_flags: FontFaceSourceTechFlags,
252}
253
254impl ToCss for UrlSource {
255    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
256    where
257        W: fmt::Write,
258    {
259        self.url.to_css(dest)?;
260        if let Some(hint) = &self.format_hint {
261            dest.write_str(" format(")?;
262            hint.to_css(dest)?;
263            dest.write_char(')')?;
264        }
265        if !self.tech_flags.is_empty() {
266            dest.write_str(" tech(")?;
267            self.tech_flags.to_css(dest)?;
268            dest.write_char(')')?;
269        }
270        Ok(())
271    }
272}
273
274/// A font-display value for a @font-face rule.
275/// The font-display descriptor determines how a font face is displayed based
276/// on whether and when it is downloaded and ready to use.
277#[allow(missing_docs)]
278#[derive(
279    Clone,
280    Copy,
281    Debug,
282    Deserialize,
283    Eq,
284    MallocSizeOf,
285    Parse,
286    PartialEq,
287    Serialize,
288    ToComputedValue,
289    ToCss,
290    ToShmem,
291)]
292#[repr(u8)]
293pub enum FontDisplay {
294    Auto,
295    Block,
296    Swap,
297    Fallback,
298    Optional,
299}
300
301macro_rules! impl_range {
302    ($range:ident, $component:ident) => {
303        impl Parse for $range {
304            fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
305                let first = $component::parse(context, input)?;
306                let second = input
307                    .try_parse(|input| $component::parse(context, input))
308                    .unwrap_or_else(|_| first.clone());
309                Ok($range(first, second))
310            }
311        }
312        impl ToCss for $range {
313            fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
314            where
315                W: fmt::Write,
316            {
317                self.0.to_css(dest)?;
318                if self.0 != self.1 {
319                    dest.write_char(' ')?;
320                    self.1.to_css(dest)?;
321                }
322                Ok(())
323            }
324        }
325    };
326}
327
328/// The font-weight descriptor:
329///
330/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
331#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
332pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
333impl_range!(FontWeightRange, AbsoluteFontWeight);
334
335/// The computed representation of the above so Gecko and Servo can read them easily.
336///
337/// This one is needed because cbindgen doesn't know how to generate
338/// specified::Number.
339#[repr(C)]
340#[allow(missing_docs)]
341#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
342pub struct ComputedFontWeightRange(pub FontWeight, pub FontWeight);
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    /// Returns a computed font-weight range, or None if either bound is an unresolvable calc.
355    pub fn compute(&self) -> Option<ComputedFontWeightRange> {
356        let (min, max) = sort_range(self.0.compute()?, self.1.compute()?);
357        Some(ComputedFontWeightRange(min, max))
358    }
359}
360
361/// The font-width descriptor:
362///
363/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-width
364#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
365pub struct FontWidthRange(pub SpecifiedFontWidth, pub SpecifiedFontWidth);
366impl_range!(FontWidthRange, SpecifiedFontWidth);
367
368/// The computed representation of the above, so that Gecko and Servo can read them
369/// easily.
370#[repr(C)]
371#[allow(missing_docs)]
372#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
373pub struct ComputedFontWidthRange(pub FontWidth, pub FontWidth);
374
375impl FontWidthRange {
376    /// Returns a computed font-width range, or None if any value contains a calc
377    /// expression that cannot be resolved at parse time.
378    pub fn compute(&self) -> Option<ComputedFontWidthRange> {
379        fn compute_width(s: &SpecifiedFontWidth) -> Option<FontWidth> {
380            match *s {
381                SpecifiedFontWidth::Keyword(ref kw) => Some(kw.compute()),
382                SpecifiedFontWidth::Width(ref p) => {
383                    Some(FontWidth::from_percentage(p.compute()?.0))
384                },
385                SpecifiedFontWidth::System(..) => unreachable!(),
386            }
387        }
388
389        let (min, max) = sort_range(compute_width(&self.0)?, compute_width(&self.1)?);
390        Some(ComputedFontWidthRange(min, max))
391    }
392}
393
394/// The font-style descriptor:
395///
396/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
397#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
398#[allow(missing_docs)]
399pub enum FontStyleRange {
400    Italic,
401    Oblique(Angle, Angle),
402}
403
404/// The computed representation of the above, with angles in degrees stored as
405/// signed 8.8 fixed-point values, so that Gecko and Servo can read them easily.
406#[repr(C)]
407#[allow(missing_docs)]
408#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
409pub struct ComputedFontStyleRange(pub FontStyle, pub FontStyle);
410
411impl Parse for FontStyleRange {
412    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
413        // We parse 'normal' explicitly here to distinguish it from 'oblique 0deg',
414        // because we must not accept a following angle.
415        if input
416            .try_parse(|i| i.expect_ident_matching("normal"))
417            .is_ok()
418        {
419            return Ok(Self::Oblique(Angle::zero(), Angle::zero()));
420        }
421
422        let style = SpecifiedFontStyle::parse(context, input)?;
423        Ok(match style {
424            GenericFontStyle::Italic => Self::Italic,
425            GenericFontStyle::Oblique(angle) => {
426                let second_angle = input
427                    .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
428                    .unwrap_or_else(|_| angle.clone());
429
430                Self::Oblique(angle, second_angle)
431            },
432        })
433    }
434}
435
436impl ToCss for FontStyleRange {
437    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
438    where
439        W: fmt::Write,
440    {
441        match *self {
442            Self::Italic => dest.write_str("italic"),
443            Self::Oblique(ref first, ref second) => {
444                // Not first.is_zero() because we don't want to serialize
445                // `oblique calc(0deg)` as `normal`.
446                if *first == Angle::zero() && first == second {
447                    return dest.write_str("normal");
448                }
449                dest.write_str("oblique")?;
450                if *first != SpecifiedFontStyle::default_angle() || first != second {
451                    dest.write_char(' ')?;
452                    first.to_css(dest)?;
453                }
454                if first != second {
455                    dest.write_char(' ')?;
456                    second.to_css(dest)?;
457                }
458                Ok(())
459            },
460        }
461    }
462}
463
464impl FontStyleRange {
465    /// Returns a computed font-style descriptor.
466    pub fn compute(&self) -> Option<ComputedFontStyleRange> {
467        Some(match *self {
468            Self::Italic => ComputedFontStyleRange(FontStyle::ITALIC, FontStyle::ITALIC),
469            Self::Oblique(ref first, ref second) => {
470                let (min, max) = sort_range(first.degrees()?, second.degrees()?);
471                ComputedFontStyleRange(FontStyle::oblique(min), FontStyle::oblique(max))
472            },
473        })
474    }
475}
476
477/// Parse the block inside a `@font-face` rule.
478///
479/// Note that the prelude parsing code lives in the `stylesheets` module.
480pub fn parse_font_face_block(
481    context: &ParserContext,
482    input: &mut Parser,
483    source_location: SourceLocation,
484) -> FontFaceRule {
485    let mut rule = FontFaceRule::empty(source_location);
486    {
487        let mut parser = DescriptorParser {
488            context,
489            descriptors: &mut rule.descriptors,
490        };
491        let iter = RuleBodyParser::new(input, &mut parser);
492        for declaration in iter {
493            if let Err((error, slice, location)) = declaration {
494                let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
495                context.log_css_error(location, error)
496            }
497        }
498    }
499    rule
500}
501
502impl Parse for Source {
503    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Source, ParseError> {
504        if input
505            .try_parse(|input| input.expect_function_matching("local"))
506            .is_ok()
507        {
508            return input
509                .parse_nested_block(|input| FamilyName::parse(context, input))
510                .map(Source::Local);
511        }
512
513        let url = SpecifiedUrl::parse(context, input)?;
514
515        // Parsing optional format()
516        let format_hint = if input
517            .try_parse(|input| input.expect_function_matching("format"))
518            .is_ok()
519        {
520            input.parse_nested_block(|input| {
521                if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
522                    Ok(Some(FontFaceSourceFormat::Keyword(kw)))
523                } else {
524                    let s = input.expect_string()?.as_ref().to_owned();
525                    Ok(Some(FontFaceSourceFormat::String(s)))
526                }
527            })?
528        } else {
529            None
530        };
531
532        // Parse optional tech()
533        let tech_flags = if crate::pref!("layout.css.font-tech.enabled", gecko = true)
534            && input
535                .try_parse(|input| input.expect_function_matching("tech"))
536                .is_ok()
537        {
538            input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
539        } else {
540            FontFaceSourceTechFlags::empty()
541        };
542
543        Ok(Source::Url(UrlSource {
544            url,
545            format_hint,
546            tech_flags,
547        }))
548    }
549}
550
551impl ToCssWithGuard for FontFaceRule {
552    // Serialization of FontFaceRule is not specced.
553    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
554        dest.write_str("@font-face { ")?;
555        self.descriptors.to_css(&mut CssWriter::new(dest))?;
556        dest.write_char('}')
557    }
558}