use crate::error_reporting::ContextualParseError;
use crate::parser::{Parse, ParserContext};
use crate::properties::longhands::font_language_override;
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::computed::font::{FamilyName, FontStretch};
use crate::values::generics::font::FontStyle as GenericFontStyle;
use crate::values::specified::font::{
AbsoluteFontWeight, FontStretch as SpecifiedFontStretch, FontFeatureSettings,
FontVariationSettings, MetricsOverride, SpecifiedFontStyle,
};
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::{Angle, NonNegativePercentage};
use cssparser::UnicodeRange;
use cssparser::{
AtRuleParser, CowRcStr, DeclarationParser, Parser, QualifiedRuleParser, RuleBodyItemParser,
RuleBodyParser, SourceLocation,
};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError};
use style_traits::{StyleParseErrorKind, ToCss};
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
pub enum Source {
Url(UrlSource),
#[css(function)]
Local(FamilyName),
}
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[css(comma)]
pub struct SourceList(#[css(iterable)] pub Vec<Source>);
impl Parse for SourceList {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let list = input
.parse_comma_separated(|input| {
let s = input.parse_entirely(|input| Source::parse(context, input));
while input.next().is_ok() {}
Ok(s.ok())
})?
.into_iter()
.filter_map(|s| s)
.collect::<Vec<Source>>();
if list.is_empty() {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(SourceList(list))
}
}
}
#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceFormatKeyword {
#[css(skip)]
None,
Collection,
EmbeddedOpentype,
Opentype,
Svg,
Truetype,
Woff,
Woff2,
#[css(skip)]
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct FontFaceSourceTechFlags(u16);
bitflags! {
impl FontFaceSourceTechFlags: u16 {
const FEATURES_OPENTYPE = 1 << 0;
const FEATURES_AAT = 1 << 1;
const FEATURES_GRAPHITE = 1 << 2;
const COLOR_COLRV0 = 1 << 3;
const COLOR_COLRV1 = 1 << 4;
const COLOR_SVG = 1 << 5;
const COLOR_SBIX = 1 << 6;
const COLOR_CBDT = 1 << 7;
const VARIATIONS = 1 << 8;
const PALETTES = 1 << 9;
const INCREMENTAL = 1 << 10;
}
}
impl FontFaceSourceTechFlags {
pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Ok(try_match_ident_ignore_ascii_case! { input,
"features-opentype" => Self::FEATURES_OPENTYPE,
"features-aat" => Self::FEATURES_AAT,
"features-graphite" => Self::FEATURES_GRAPHITE,
"color-colrv0" => Self::COLOR_COLRV0,
"color-colrv1" => Self::COLOR_COLRV1,
"color-svg" => Self::COLOR_SVG,
"color-sbix" => Self::COLOR_SBIX,
"color-cbdt" => Self::COLOR_CBDT,
"variations" => Self::VARIATIONS,
"palettes" => Self::PALETTES,
"incremental" => Self::INCREMENTAL,
})
}
}
impl Parse for FontFaceSourceTechFlags {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let mut result = Self::empty();
input.parse_comma_separated(|input| {
let flag = Self::parse_one(input)?;
result.insert(flag);
Ok(())
})?;
if !result.is_empty() {
Ok(result)
} else {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
#[allow(unused_assignments)]
impl ToCss for FontFaceSourceTechFlags {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
let mut first = true;
macro_rules! write_if_flag {
($s:expr => $f:ident) => {
if self.contains(Self::$f) {
if first {
first = false;
} else {
dest.write_str(", ")?;
}
dest.write_str($s)?;
}
};
}
write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
write_if_flag!("features-aat" => FEATURES_AAT);
write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
write_if_flag!("color-colrv0" => COLOR_COLRV0);
write_if_flag!("color-colrv1" => COLOR_COLRV1);
write_if_flag!("color-svg" => COLOR_SVG);
write_if_flag!("color-sbix" => COLOR_SBIX);
write_if_flag!("color-cbdt" => COLOR_CBDT);
write_if_flag!("variations" => VARIATIONS);
write_if_flag!("palettes" => PALETTES);
write_if_flag!("incremental" => INCREMENTAL);
Ok(())
}
}
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceListComponent {
Url(*const crate::gecko::url::CssUrl),
Local(*mut crate::gecko_bindings::structs::nsAtom),
FormatHintKeyword(FontFaceSourceFormatKeyword),
FormatHintString {
length: usize,
utf8_bytes: *const u8,
},
TechFlags(FontFaceSourceTechFlags),
}
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceFormat {
Keyword(FontFaceSourceFormatKeyword),
String(String),
}
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
pub struct UrlSource {
pub url: SpecifiedUrl,
pub format_hint: Option<FontFaceSourceFormat>,
pub tech_flags: FontFaceSourceTechFlags,
}
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 let Some(hint) = &self.format_hint {
dest.write_str(" format(")?;
hint.to_css(dest)?;
dest.write_char(')')?;
}
if !self.tech_flags.is_empty() {
dest.write_str(" tech(")?;
self.tech_flags.to_css(dest)?;
dest.write_char(')')?;
}
Ok(())
}
}
#[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_char(' ')?;
self.1.to_css(dest)?;
}
Ok(())
}
}
};
}
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
impl_range!(FontWeightRange, AbsoluteFontWeight);
#[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 {
pub fn compute(&self) -> ComputedFontWeightRange {
let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value());
ComputedFontWeightRange(min, max)
}
}
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
impl_range!(FontStretchRange, SpecifiedFontStretch);
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontStretchRange(FontStretch, FontStretch);
impl FontStretchRange {
pub fn compute(&self) -> ComputedFontStretchRange {
fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch {
match *s {
SpecifiedFontStretch::Keyword(ref kw) => kw.compute(),
SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()),
SpecifiedFontStretch::System(..) => unreachable!(),
}
}
let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
ComputedFontStretchRange(min, max)
}
}
#[derive(Clone, Debug, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub enum FontStyle {
Normal,
Italic,
Oblique(Angle, Angle),
}
#[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 {
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)
},
}
}
}
pub fn parse_font_face_block(
context: &ParserContext,
input: &mut Parser,
location: SourceLocation,
) -> FontFaceRuleData {
let mut rule = FontFaceRuleData::empty(location);
{
let mut parser = FontFaceRuleParser {
context,
rule: &mut rule,
};
let mut iter = RuleBodyParser::new(input, &mut 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
}
#[cfg(feature = "servo")]
pub struct FontFace<'a>(&'a FontFaceRuleData);
struct FontFaceRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
rule: &'a mut FontFaceRuleData,
}
impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
type Prelude = ();
type QualifiedRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
for FontFaceRuleParser<'a, 'b>
{
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}
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)?;
let format_hint = if input
.try_parse(|input| input.expect_function_matching("format"))
.is_ok()
{
input.parse_nested_block(|input| {
if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
Ok(Some(FontFaceSourceFormat::Keyword(kw)))
} else {
let s = input.expect_string()?.as_ref().to_owned();
Ok(Some(FontFaceSourceFormat::String(s)))
}
})?
} else {
None
};
let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled") &&
input
.try_parse(|input| input.expect_function_matching("tech"))
.is_ok()
{
input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
} else {
FontFaceSourceTechFlags::empty()
};
Ok(Source::Url(UrlSource {
url,
format_hint,
tech_flags,
}))
}
}
macro_rules! is_descriptor_enabled {
("font-variation-settings") => {
static_prefs::pref!("layout.css.font-variations.enabled")
};
("size-adjust") => {
static_prefs::pref!("layout.css.size-adjust.enabled")
};
($name:tt) => {
true
};
}
macro_rules! font_face_descriptors_common {
(
$( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
) => {
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontFaceRuleData {
$(
#[$doc]
pub $ident: Option<$ty>,
)*
pub source_location: SourceLocation,
}
impl FontFaceRuleData {
pub fn empty(location: SourceLocation) -> Self {
FontFaceRuleData {
$(
$ident: None,
)*
source_location: location,
}
}
pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
$(
if let Some(ref value) = self.$ident {
dest.write_str(concat!($name, ": "))?;
value.to_css(&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) => {
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 {
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
dest.write_str("@font-face { ")?;
self.decl_to_css(dest)?;
dest.write_char('}')
}
}
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 {
#[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()
}
)*
}
}
}
font_face_descriptors! {
mandatory descriptors = [
"font-family" family / mFamily: FamilyName,
"src" sources / mSrc: SourceList,
]
optional descriptors = [
"font-style" style / mStyle: FontStyle,
"font-weight" weight / mWeight: FontWeightRange,
"font-stretch" stretch / mStretch: FontStretchRange,
"font-display" display / mDisplay: FontDisplay,
"unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
"font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings,
"font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings,
"font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
"ascent-override" ascent_override / mAscentOverride: MetricsOverride,
"descent-override" descent_override / mDescentOverride: MetricsOverride,
"line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,
"size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
]
}