summaryrefslogtreecommitdiffstats
path: root/servo/components/style/font_face.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/font_face.rs')
-rw-r--r--servo/components/style/font_face.rs594
1 files changed, 594 insertions, 0 deletions
diff --git a/servo/components/style/font_face.rs b/servo/components/style/font_face.rs
new file mode 100644
index 0000000000..24204efc13
--- /dev/null
+++ b/servo/components/style/font_face.rs
@@ -0,0 +1,594 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! The [`@font-face`][ff] at-rule.
+//!
+//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
+
+use crate::error_reporting::ContextualParseError;
+use crate::parser::{Parse, ParserContext};
+#[cfg(feature = "gecko")]
+use crate::properties::longhands::font_language_override;
+use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::values::computed::font::FamilyName;
+use crate::values::generics::font::FontStyle as GenericFontStyle;
+#[cfg(feature = "gecko")]
+use crate::values::specified::font::SpecifiedFontFeatureSettings;
+use crate::values::specified::font::SpecifiedFontStyle;
+#[cfg(feature = "gecko")]
+use crate::values::specified::font::SpecifiedFontVariationSettings;
+use crate::values::specified::font::{AbsoluteFontWeight, FontStretch};
+use crate::values::specified::url::SpecifiedUrl;
+use crate::values::specified::Angle;
+#[cfg(feature = "gecko")]
+use cssparser::UnicodeRange;
+use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
+use cssparser::{CowRcStr, SourceLocation};
+use selectors::parser::SelectorParseErrorKind;
+use std::fmt::{self, Write};
+use style_traits::values::SequenceWriter;
+use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError};
+use style_traits::{StyleParseErrorKind, ToCss};
+
+/// A source for a font-face rule.
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
+pub enum Source {
+ /// A `url()` source.
+ Url(UrlSource),
+ /// A `local()` source.
+ #[css(function)]
+ Local(FamilyName),
+}
+
+impl OneOrMoreSeparated for Source {
+ type S = Comma;
+}
+
+/// A POD representation for Gecko. All pointers here are non-owned and as such
+/// can't outlive the rule they came from, but we can't enforce that via C++.
+///
+/// All the strings are of course utf8.
+#[cfg(feature = "gecko")]
+#[repr(u8)]
+#[allow(missing_docs)]
+pub enum FontFaceSourceListComponent {
+ Url(*const crate::gecko::url::CssUrl),
+ Local(*mut crate::gecko_bindings::structs::nsAtom),
+ FormatHint {
+ length: usize,
+ utf8_bytes: *const u8,
+ },
+}
+
+/// A `UrlSource` represents a font-face source that has been specified with a
+/// `url()` function.
+///
+/// <https://drafts.csswg.org/css-fonts/#src-desc>
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
+pub struct UrlSource {
+ /// The specified url.
+ pub url: SpecifiedUrl,
+ /// The format hints specified with the `format()` function.
+ pub format_hints: Vec<String>,
+}
+
+impl ToCss for UrlSource {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ self.url.to_css(dest)?;
+ if !self.format_hints.is_empty() {
+ dest.write_str(" format(")?;
+ {
+ let mut writer = SequenceWriter::new(dest, ", ");
+ for hint in self.format_hints.iter() {
+ writer.item(hint)?;
+ }
+ }
+ dest.write_char(')')?;
+ }
+ Ok(())
+ }
+}
+
+/// A font-display value for a @font-face rule.
+/// The font-display descriptor determines how a font face is displayed based
+/// on whether and when it is downloaded and ready to use.
+#[allow(missing_docs)]
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+#[derive(
+ Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
+)]
+#[repr(u8)]
+pub enum FontDisplay {
+ Auto,
+ Block,
+ Swap,
+ Fallback,
+ Optional,
+}
+
+macro_rules! impl_range {
+ ($range:ident, $component:ident) => {
+ impl Parse for $range {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ let first = $component::parse(context, input)?;
+ let second = input
+ .try_parse(|input| $component::parse(context, input))
+ .unwrap_or_else(|_| first.clone());
+ Ok($range(first, second))
+ }
+ }
+ impl ToCss for $range {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ self.0.to_css(dest)?;
+ if self.0 != self.1 {
+ dest.write_str(" ")?;
+ self.1.to_css(dest)?;
+ }
+ Ok(())
+ }
+ }
+ };
+}
+
+/// The font-weight descriptor:
+///
+/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
+impl_range!(FontWeightRange, AbsoluteFontWeight);
+
+/// The computed representation of the above so Gecko can read them easily.
+///
+/// This one is needed because cbindgen doesn't know how to generate
+/// specified::Number.
+#[repr(C)]
+#[allow(missing_docs)]
+pub struct ComputedFontWeightRange(f32, f32);
+
+#[inline]
+fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
+ if a > b {
+ (b, a)
+ } else {
+ (a, b)
+ }
+}
+
+impl FontWeightRange {
+ /// Returns a computed font-stretch range.
+ pub fn compute(&self) -> ComputedFontWeightRange {
+ let (min, max) = sort_range(self.0.compute().0, self.1.compute().0);
+ ComputedFontWeightRange(min, max)
+ }
+}
+
+/// The font-stretch descriptor:
+///
+/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+pub struct FontStretchRange(pub FontStretch, pub FontStretch);
+impl_range!(FontStretchRange, FontStretch);
+
+/// The computed representation of the above, so that
+/// Gecko can read them easily.
+#[repr(C)]
+#[allow(missing_docs)]
+pub struct ComputedFontStretchRange(f32, f32);
+
+impl FontStretchRange {
+ /// Returns a computed font-stretch range.
+ pub fn compute(&self) -> ComputedFontStretchRange {
+ fn compute_stretch(s: &FontStretch) -> f32 {
+ match *s {
+ FontStretch::Keyword(ref kw) => kw.compute().0,
+ FontStretch::Stretch(ref p) => p.get(),
+ FontStretch::System(..) => unreachable!(),
+ }
+ }
+
+ let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
+ ComputedFontStretchRange(min, max)
+ }
+}
+
+/// The font-style descriptor:
+///
+/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+#[allow(missing_docs)]
+pub enum FontStyle {
+ Normal,
+ Italic,
+ Oblique(Angle, Angle),
+}
+
+/// The computed representation of the above, with angles in degrees, so that
+/// Gecko can read them easily.
+#[repr(u8)]
+#[allow(missing_docs)]
+pub enum ComputedFontStyleDescriptor {
+ Normal,
+ Italic,
+ Oblique(f32, f32),
+}
+
+impl Parse for FontStyle {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ let style = SpecifiedFontStyle::parse(context, input)?;
+ Ok(match style {
+ GenericFontStyle::Normal => FontStyle::Normal,
+ GenericFontStyle::Italic => FontStyle::Italic,
+ GenericFontStyle::Oblique(angle) => {
+ let second_angle = input
+ .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
+ .unwrap_or_else(|_| angle.clone());
+
+ FontStyle::Oblique(angle, second_angle)
+ },
+ })
+ }
+}
+
+impl ToCss for FontStyle {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ match *self {
+ FontStyle::Normal => dest.write_str("normal"),
+ FontStyle::Italic => dest.write_str("italic"),
+ FontStyle::Oblique(ref first, ref second) => {
+ dest.write_str("oblique")?;
+ if *first != SpecifiedFontStyle::default_angle() || first != second {
+ dest.write_char(' ')?;
+ first.to_css(dest)?;
+ }
+ if first != second {
+ dest.write_char(' ')?;
+ second.to_css(dest)?;
+ }
+ Ok(())
+ },
+ }
+ }
+}
+
+impl FontStyle {
+ /// Returns a computed font-style descriptor.
+ pub fn compute(&self) -> ComputedFontStyleDescriptor {
+ match *self {
+ FontStyle::Normal => ComputedFontStyleDescriptor::Normal,
+ FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
+ FontStyle::Oblique(ref first, ref second) => {
+ let (min, max) = sort_range(
+ SpecifiedFontStyle::compute_angle_degrees(first),
+ SpecifiedFontStyle::compute_angle_degrees(second),
+ );
+ ComputedFontStyleDescriptor::Oblique(min, max)
+ },
+ }
+ }
+}
+
+/// Parse the block inside a `@font-face` rule.
+///
+/// Note that the prelude parsing code lives in the `stylesheets` module.
+pub fn parse_font_face_block(
+ context: &ParserContext,
+ input: &mut Parser,
+ location: SourceLocation,
+) -> FontFaceRuleData {
+ let mut rule = FontFaceRuleData::empty(location);
+ {
+ let parser = FontFaceRuleParser {
+ context: context,
+ rule: &mut rule,
+ };
+ let mut iter = DeclarationListParser::new(input, parser);
+ while let Some(declaration) = iter.next() {
+ if let Err((error, slice)) = declaration {
+ let location = error.location;
+ let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
+ context.log_css_error(location, error)
+ }
+ }
+ }
+ rule
+}
+
+/// A @font-face rule that is known to have font-family and src declarations.
+#[cfg(feature = "servo")]
+pub struct FontFace<'a>(&'a FontFaceRuleData);
+
+/// A list of effective sources that we send over through IPC to the font cache.
+#[cfg(feature = "servo")]
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
+pub struct EffectiveSources(Vec<Source>);
+
+#[cfg(feature = "servo")]
+impl<'a> FontFace<'a> {
+ /// Returns the list of effective sources for that font-face, that is the
+ /// sources which don't list any format hint, or the ones which list at
+ /// least "truetype" or "opentype".
+ pub fn effective_sources(&self) -> EffectiveSources {
+ EffectiveSources(
+ self.sources()
+ .iter()
+ .rev()
+ .filter(|source| {
+ if let Source::Url(ref url_source) = **source {
+ let hints = &url_source.format_hints;
+ // We support only opentype fonts and truetype is an alias for
+ // that format. Sources without format hints need to be
+ // downloaded in case we support them.
+ hints.is_empty() ||
+ hints.iter().any(|hint| {
+ hint == "truetype" || hint == "opentype" || hint == "woff"
+ })
+ } else {
+ true
+ }
+ })
+ .cloned()
+ .collect(),
+ )
+ }
+}
+
+#[cfg(feature = "servo")]
+impl Iterator for EffectiveSources {
+ type Item = Source;
+ fn next(&mut self) -> Option<Source> {
+ self.0.pop()
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.0.len(), Some(self.0.len()))
+ }
+}
+
+struct FontFaceRuleParser<'a, 'b: 'a> {
+ context: &'a ParserContext<'b>,
+ rule: &'a mut FontFaceRuleData,
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
+ type PreludeNoBlock = ();
+ type PreludeBlock = ();
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl Parse for Source {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Source, ParseError<'i>> {
+ if input
+ .try_parse(|input| input.expect_function_matching("local"))
+ .is_ok()
+ {
+ return input
+ .parse_nested_block(|input| FamilyName::parse(context, input))
+ .map(Source::Local);
+ }
+
+ let url = SpecifiedUrl::parse(context, input)?;
+
+ // Parsing optional format()
+ let format_hints = if input
+ .try_parse(|input| input.expect_function_matching("format"))
+ .is_ok()
+ {
+ input.parse_nested_block(|input| {
+ input.parse_comma_separated(|input| Ok(input.expect_string()?.as_ref().to_owned()))
+ })?
+ } else {
+ vec![]
+ };
+
+ Ok(Source::Url(UrlSource {
+ url: url,
+ format_hints: format_hints,
+ }))
+ }
+}
+
+macro_rules! is_descriptor_enabled {
+ ("font-display") => {
+ static_prefs::pref!("layout.css.font-display.enabled")
+ };
+ ("font-variation-settings") => {
+ static_prefs::pref!("layout.css.font-variations.enabled")
+ };
+ ($name:tt) => {
+ true
+ };
+}
+
+macro_rules! font_face_descriptors_common {
+ (
+ $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
+ ) => {
+ /// Data inside a `@font-face` rule.
+ ///
+ /// <https://drafts.csswg.org/css-fonts/#font-face-rule>
+ #[derive(Clone, Debug, PartialEq, ToShmem)]
+ pub struct FontFaceRuleData {
+ $(
+ #[$doc]
+ pub $ident: Option<$ty>,
+ )*
+ /// Line and column of the @font-face rule source code.
+ pub source_location: SourceLocation,
+ }
+
+ impl FontFaceRuleData {
+ /// Create an empty font-face rule
+ pub fn empty(location: SourceLocation) -> Self {
+ FontFaceRuleData {
+ $(
+ $ident: None,
+ )*
+ source_location: location,
+ }
+ }
+
+ /// Serialization of declarations in the FontFaceRule
+ pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
+ $(
+ if let Some(ref value) = self.$ident {
+ dest.write_str(concat!($name, ": "))?;
+ ToCss::to_css(value, &mut CssWriter::new(dest))?;
+ dest.write_str("; ")?;
+ }
+ )*
+ Ok(())
+ }
+ }
+
+ impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
+ type Declaration = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ match_ignore_ascii_case! { &*name,
+ $(
+ $name if is_descriptor_enabled!($name) => {
+ // DeclarationParser also calls parse_entirely
+ // so we’d normally not need to,
+ // but in this case we do because we set the value as a side effect
+ // rather than returning it.
+ let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
+ self.rule.$ident = Some(value)
+ },
+ )*
+ _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+impl ToCssWithGuard for FontFaceRuleData {
+ // Serialization of FontFaceRule is not specced.
+ fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@font-face { ")?;
+ self.decl_to_css(dest)?;
+ dest.write_str("}")
+ }
+}
+
+macro_rules! font_face_descriptors {
+ (
+ mandatory descriptors = [
+ $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
+ ]
+ optional descriptors = [
+ $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
+ ]
+ ) => {
+ font_face_descriptors_common! {
+ $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
+ $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
+ }
+
+ impl FontFaceRuleData {
+ /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
+ /// is valid as far as the CSS parser is concerned even if it doesn’t have
+ /// a font-family or src declaration.
+ ///
+ /// However both are required for the rule to represent an actual font face.
+ #[cfg(feature = "servo")]
+ pub fn font_face(&self) -> Option<FontFace> {
+ if $( self.$m_ident.is_some() )&&* {
+ Some(FontFace(self))
+ } else {
+ None
+ }
+ }
+ }
+
+ #[cfg(feature = "servo")]
+ impl<'a> FontFace<'a> {
+ $(
+ #[$m_doc]
+ pub fn $m_ident(&self) -> &$m_ty {
+ self.0 .$m_ident.as_ref().unwrap()
+ }
+ )*
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+font_face_descriptors! {
+ mandatory descriptors = [
+ /// The name of this font face
+ "font-family" family / mFamily: FamilyName,
+
+ /// The alternative sources for this font face.
+ "src" sources / mSrc: Vec<Source>,
+ ]
+ optional descriptors = [
+ /// The style of this font face.
+ "font-style" style / mStyle: FontStyle,
+
+ /// The weight of this font face.
+ "font-weight" weight / mWeight: FontWeightRange,
+
+ /// The stretch of this font face.
+ "font-stretch" stretch / mStretch: FontStretchRange,
+
+ /// The display of this font face.
+ "font-display" display / mDisplay: FontDisplay,
+
+ /// The ranges of code points outside of which this font face should not be used.
+ "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
+
+ /// The feature settings of this font face.
+ "font-feature-settings" feature_settings / mFontFeatureSettings: SpecifiedFontFeatureSettings,
+
+ /// The variation settings of this font face.
+ "font-variation-settings" variation_settings / mFontVariationSettings: SpecifiedFontVariationSettings,
+
+ /// The language override of this font face.
+ "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
+ ]
+}
+
+#[cfg(feature = "servo")]
+font_face_descriptors! {
+ mandatory descriptors = [
+ /// The name of this font face
+ "font-family" family / mFamily: FamilyName,
+
+ /// The alternative sources for this font face.
+ "src" sources / mSrc: Vec<Source>,
+ ]
+ optional descriptors = [
+ ]
+}