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