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::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/// 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<'i, 't>(
48        context: &ParserContext,
49        input: &mut Parser<'i, 't>,
50    ) -> Result<Self, ParseError<'i>> {
51        // Parse the comma-separated list, then let filter_map discard any None items.
52        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/// Keywords for the font-face src descriptor's format() function.
70/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.)
71#[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/// Flags for the @font-face tech() function, indicating font technologies
90/// required by the resource.
91#[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        /// Font requires OpenType feature support.
98        const FEATURES_OPENTYPE = 1 << 0;
99        /// Font requires Apple Advanced Typography support.
100        const FEATURES_AAT = 1 << 1;
101        /// Font requires Graphite shaping support.
102        const FEATURES_GRAPHITE = 1 << 2;
103        /// Font requires COLRv0 rendering support (simple list of colored layers).
104        const COLOR_COLRV0 = 1 << 3;
105        /// Font requires COLRv1 rendering support (graph of paint operations).
106        const COLOR_COLRV1 = 1 << 4;
107        /// Font requires SVG glyph rendering support.
108        const COLOR_SVG = 1 << 5;
109        /// Font has bitmap glyphs in 'sbix' format.
110        const COLOR_SBIX = 1 << 6;
111        /// Font has bitmap glyphs in 'CBDT' format.
112        const COLOR_CBDT = 1 << 7;
113        /// Font requires OpenType Variations support.
114        const VARIATIONS = 1 << 8;
115        /// Font requires CPAL palette selection support.
116        const PALETTES = 1 << 9;
117        /// Font requires support for incremental downloading.
118        const INCREMENTAL = 1 << 10;
119    }
120}
121
122impl FontFaceSourceTechFlags {
123    /// Parse a single font-technology keyword and return its flag.
124    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        // We don't actually care about the return value of parse_comma_separated,
148        // because we insert the flags into result as we go.
149        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/// <https://drafts.csswg.org/css-fonts/#font-face-rule>
201#[derive(Clone, Debug, ToShmem, PartialEq)]
202pub struct FontFaceRule {
203    /// The descriptors of the @font-face rule.
204    pub descriptors: Descriptors,
205    /// The parser location of the rule.
206    pub source_location: SourceLocation,
207}
208
209impl FontFaceRule {
210    /// Returns an empty rule.
211    pub fn empty(source_location: SourceLocation) -> Self {
212        Self {
213            descriptors: Default::default(),
214            source_location,
215        }
216    }
217}
218
219/// A POD representation for Gecko. All pointers here are non-owned and as such
220/// can't outlive the rule they came from, but we can't enforce that via C++.
221///
222/// All the strings are of course utf8.
223#[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/// A `UrlSource` represents a font-face source that has been specified with a
248/// `url()` function.
249///
250/// <https://drafts.csswg.org/css-fonts/#src-desc>
251#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
252#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
253pub struct UrlSource {
254    /// The specified url.
255    pub url: SpecifiedUrl,
256    /// The format hint specified with the `format()` function, if present.
257    pub format_hint: Option<FontFaceSourceFormat>,
258    /// The font technology flags specified with the `tech()` function, if any.
259    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/// A font-display value for a @font-face rule.
283/// The font-display descriptor determines how a font face is displayed based
284/// on whether and when it is downloaded and ready to use.
285#[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/// The font-weight descriptor:
330///
331/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
332#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
333pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
334impl_range!(FontWeightRange, AbsoluteFontWeight);
335
336/// The computed representation of the above so Gecko can read them easily.
337///
338/// This one is needed because cbindgen doesn't know how to generate
339/// specified::Number.
340#[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    /// Returns a computed font-stretch range.
355    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/// The font-stretch descriptor:
362///
363/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
364#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
365pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
366impl_range!(FontStretchRange, SpecifiedFontStretch);
367
368/// The computed representation of the above, so that Gecko can read them
369/// easily.
370#[repr(C)]
371#[allow(missing_docs)]
372pub struct ComputedFontStretchRange(FontStretch, FontStretch);
373
374impl FontStretchRange {
375    /// Returns a computed font-stretch range.
376    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/// The font-style descriptor:
391///
392/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
393#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
394#[allow(missing_docs)]
395pub enum FontStyle {
396    Italic,
397    Oblique(Angle, Angle),
398}
399
400/// The computed representation of the above, with angles in degrees, so that
401/// Gecko can read them easily.
402#[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        // We parse 'normal' explicitly here to distinguish it from 'oblique 0deg',
415        // because we must not accept a following angle.
416        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                // Not first.is_zero() because we don't want to serialize
446                // `oblique calc(0deg)` as `normal`.
447                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    /// Returns a computed font-style descriptor.
467    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
481/// Parse the block inside a `@font-face` rule.
482///
483/// Note that the prelude parsing code lives in the `stylesheets` module.
484pub 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        // Parsing optional format()
525        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        // Parse optional tech()
542        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    // Serialization of FontFaceRule is not specced.
562    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}