1use crate::error_reporting::ContextualParseError;
10use crate::parser::{Parse, ParserContext};
11use crate::properties::longhands::font_language_override;
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::str::CssStringWriter;
14use crate::values::computed::font::{FamilyName, FontStretch};
15use crate::values::generics::font::FontStyle as GenericFontStyle;
16use crate::values::specified::font::{
17 AbsoluteFontWeight, FontFeatureSettings, FontStretch as SpecifiedFontStretch,
18 FontVariationSettings, MetricsOverride, SpecifiedFontStyle,
19};
20use crate::values::specified::url::SpecifiedUrl;
21use crate::values::specified::{Angle, NonNegativePercentage};
22use cssparser::UnicodeRange;
23use cssparser::{
24 AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserState, QualifiedRuleParser,
25 RuleBodyItemParser, RuleBodyParser, SourceLocation,
26};
27use selectors::parser::SelectorParseErrorKind;
28use std::fmt::{self, Write};
29use style_traits::{CssWriter, ParseError};
30use style_traits::{StyleParseErrorKind, ToCss};
31
32#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
34#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
35pub enum Source {
36 Url(UrlSource),
38 #[css(function)]
40 Local(FamilyName),
41}
42
43#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
45#[css(comma)]
46pub struct SourceList(#[css(iterable)] pub Vec<Source>);
47
48impl Parse for SourceList {
52 fn parse<'i, 't>(
53 context: &ParserContext,
54 input: &mut Parser<'i, 't>,
55 ) -> Result<Self, ParseError<'i>> {
56 let list = input
58 .parse_comma_separated(|input| {
59 let s = input.parse_entirely(|input| Source::parse(context, input));
60 while input.next().is_ok() {}
61 Ok(s.ok())
62 })?
63 .into_iter()
64 .filter_map(|s| s)
65 .collect::<Vec<Source>>();
66 if list.is_empty() {
67 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
68 } else {
69 Ok(SourceList(list))
70 }
71 }
72}
73
74#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)]
77#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
78#[repr(u8)]
79#[allow(missing_docs)]
80pub enum FontFaceSourceFormatKeyword {
81 #[css(skip)]
82 None,
83 Collection,
84 EmbeddedOpentype,
85 Opentype,
86 Svg,
87 Truetype,
88 Woff,
89 Woff2,
90 #[css(skip)]
91 Unknown,
92}
93
94#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
97#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
98#[repr(C)]
99pub struct FontFaceSourceTechFlags(u16);
100bitflags! {
101 impl FontFaceSourceTechFlags: u16 {
102 const FEATURES_OPENTYPE = 1 << 0;
104 const FEATURES_AAT = 1 << 1;
106 const FEATURES_GRAPHITE = 1 << 2;
108 const COLOR_COLRV0 = 1 << 3;
110 const COLOR_COLRV1 = 1 << 4;
112 const COLOR_SVG = 1 << 5;
114 const COLOR_SBIX = 1 << 6;
116 const COLOR_CBDT = 1 << 7;
118 const VARIATIONS = 1 << 8;
120 const PALETTES = 1 << 9;
122 const INCREMENTAL = 1 << 10;
124 }
125}
126
127impl FontFaceSourceTechFlags {
128 pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
130 Ok(try_match_ident_ignore_ascii_case! { input,
131 "features-opentype" => Self::FEATURES_OPENTYPE,
132 "features-aat" => Self::FEATURES_AAT,
133 "features-graphite" => Self::FEATURES_GRAPHITE,
134 "color-colrv0" => Self::COLOR_COLRV0,
135 "color-colrv1" => Self::COLOR_COLRV1,
136 "color-svg" => Self::COLOR_SVG,
137 "color-sbix" => Self::COLOR_SBIX,
138 "color-cbdt" => Self::COLOR_CBDT,
139 "variations" => Self::VARIATIONS,
140 "palettes" => Self::PALETTES,
141 "incremental" => Self::INCREMENTAL,
142 })
143 }
144}
145
146impl Parse for FontFaceSourceTechFlags {
147 fn parse<'i, 't>(
148 _context: &ParserContext,
149 input: &mut Parser<'i, 't>,
150 ) -> Result<Self, ParseError<'i>> {
151 let location = input.current_source_location();
152 let mut result = Self::empty();
155 input.parse_comma_separated(|input| {
156 let flag = Self::parse_one(input)?;
157 result.insert(flag);
158 Ok(())
159 })?;
160 if !result.is_empty() {
161 Ok(result)
162 } else {
163 Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
164 }
165 }
166}
167
168#[allow(unused_assignments)]
169impl ToCss for FontFaceSourceTechFlags {
170 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
171 where
172 W: fmt::Write,
173 {
174 let mut first = true;
175
176 macro_rules! write_if_flag {
177 ($s:expr => $f:ident) => {
178 if self.contains(Self::$f) {
179 if first {
180 first = false;
181 } else {
182 dest.write_str(", ")?;
183 }
184 dest.write_str($s)?;
185 }
186 };
187 }
188
189 write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
190 write_if_flag!("features-aat" => FEATURES_AAT);
191 write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
192 write_if_flag!("color-colrv0" => COLOR_COLRV0);
193 write_if_flag!("color-colrv1" => COLOR_COLRV1);
194 write_if_flag!("color-svg" => COLOR_SVG);
195 write_if_flag!("color-sbix" => COLOR_SBIX);
196 write_if_flag!("color-cbdt" => COLOR_CBDT);
197 write_if_flag!("variations" => VARIATIONS);
198 write_if_flag!("palettes" => PALETTES);
199 write_if_flag!("incremental" => INCREMENTAL);
200
201 Ok(())
202 }
203}
204
205#[cfg(feature = "gecko")]
210#[derive(Clone, Copy, Debug, Eq, PartialEq)]
211#[repr(u8)]
212#[allow(missing_docs)]
213pub enum FontFaceSourceListComponent {
214 Url(*const crate::gecko::url::CssUrl),
215 Local(*mut crate::gecko_bindings::structs::nsAtom),
216 FormatHintKeyword(FontFaceSourceFormatKeyword),
217 FormatHintString {
218 length: usize,
219 utf8_bytes: *const u8,
220 },
221 TechFlags(FontFaceSourceTechFlags),
222}
223
224#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
225#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
226#[repr(u8)]
227#[allow(missing_docs)]
228pub enum FontFaceSourceFormat {
229 Keyword(FontFaceSourceFormatKeyword),
230 String(String),
231}
232
233#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
238#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
239pub struct UrlSource {
240 pub url: SpecifiedUrl,
242 pub format_hint: Option<FontFaceSourceFormat>,
244 pub tech_flags: FontFaceSourceTechFlags,
246}
247
248impl ToCss for UrlSource {
249 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
250 where
251 W: fmt::Write,
252 {
253 self.url.to_css(dest)?;
254 if let Some(hint) = &self.format_hint {
255 dest.write_str(" format(")?;
256 hint.to_css(dest)?;
257 dest.write_char(')')?;
258 }
259 if !self.tech_flags.is_empty() {
260 dest.write_str(" tech(")?;
261 self.tech_flags.to_css(dest)?;
262 dest.write_char(')')?;
263 }
264 Ok(())
265 }
266}
267
268#[allow(missing_docs)]
272#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
273#[derive(
274 Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
275)]
276#[repr(u8)]
277pub enum FontDisplay {
278 Auto,
279 Block,
280 Swap,
281 Fallback,
282 Optional,
283}
284
285macro_rules! impl_range {
286 ($range:ident, $component:ident) => {
287 impl Parse for $range {
288 fn parse<'i, 't>(
289 context: &ParserContext,
290 input: &mut Parser<'i, 't>,
291 ) -> Result<Self, ParseError<'i>> {
292 let first = $component::parse(context, input)?;
293 let second = input
294 .try_parse(|input| $component::parse(context, input))
295 .unwrap_or_else(|_| first.clone());
296 Ok($range(first, second))
297 }
298 }
299 impl ToCss for $range {
300 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
301 where
302 W: fmt::Write,
303 {
304 self.0.to_css(dest)?;
305 if self.0 != self.1 {
306 dest.write_char(' ')?;
307 self.1.to_css(dest)?;
308 }
309 Ok(())
310 }
311 }
312 };
313}
314
315#[derive(Clone, Debug, PartialEq, ToShmem)]
319pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
320impl_range!(FontWeightRange, AbsoluteFontWeight);
321
322#[repr(C)]
327#[allow(missing_docs)]
328pub struct ComputedFontWeightRange(f32, f32);
329
330#[inline]
331fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
332 if a > b {
333 (b, a)
334 } else {
335 (a, b)
336 }
337}
338
339impl FontWeightRange {
340 pub fn compute(&self) -> ComputedFontWeightRange {
342 let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value());
343 ComputedFontWeightRange(min, max)
344 }
345}
346
347#[derive(Clone, Debug, PartialEq, ToShmem)]
351pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
352impl_range!(FontStretchRange, SpecifiedFontStretch);
353
354#[repr(C)]
357#[allow(missing_docs)]
358pub struct ComputedFontStretchRange(FontStretch, FontStretch);
359
360impl FontStretchRange {
361 pub fn compute(&self) -> ComputedFontStretchRange {
363 fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch {
364 match *s {
365 SpecifiedFontStretch::Keyword(ref kw) => kw.compute(),
366 SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()),
367 SpecifiedFontStretch::System(..) => unreachable!(),
368 }
369 }
370
371 let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
372 ComputedFontStretchRange(min, max)
373 }
374}
375
376#[derive(Clone, Debug, PartialEq, ToShmem)]
380#[allow(missing_docs)]
381pub enum FontStyle {
382 Italic,
383 Oblique(Angle, Angle),
384}
385
386#[repr(u8)]
389#[allow(missing_docs)]
390pub enum ComputedFontStyleDescriptor {
391 Italic,
392 Oblique(f32, f32),
393}
394
395impl Parse for FontStyle {
396 fn parse<'i, 't>(
397 context: &ParserContext,
398 input: &mut Parser<'i, 't>,
399 ) -> Result<Self, ParseError<'i>> {
400 if input
403 .try_parse(|i| i.expect_ident_matching("normal"))
404 .is_ok()
405 {
406 return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
407 }
408
409 let style = SpecifiedFontStyle::parse(context, input)?;
410 Ok(match style {
411 GenericFontStyle::Italic => FontStyle::Italic,
412 GenericFontStyle::Oblique(angle) => {
413 let second_angle = input
414 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
415 .unwrap_or_else(|_| angle.clone());
416
417 FontStyle::Oblique(angle, second_angle)
418 },
419 })
420 }
421}
422
423impl ToCss for FontStyle {
424 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
425 where
426 W: fmt::Write,
427 {
428 match *self {
429 FontStyle::Italic => dest.write_str("italic"),
430 FontStyle::Oblique(ref first, ref second) => {
431 if *first == Angle::zero() && first == second {
434 return dest.write_str("normal");
435 }
436 dest.write_str("oblique")?;
437 if *first != SpecifiedFontStyle::default_angle() || first != second {
438 dest.write_char(' ')?;
439 first.to_css(dest)?;
440 }
441 if first != second {
442 dest.write_char(' ')?;
443 second.to_css(dest)?;
444 }
445 Ok(())
446 },
447 }
448 }
449}
450
451impl FontStyle {
452 pub fn compute(&self) -> ComputedFontStyleDescriptor {
454 match *self {
455 FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
456 FontStyle::Oblique(ref first, ref second) => {
457 let (min, max) = sort_range(
458 SpecifiedFontStyle::compute_angle_degrees(first),
459 SpecifiedFontStyle::compute_angle_degrees(second),
460 );
461 ComputedFontStyleDescriptor::Oblique(min, max)
462 },
463 }
464 }
465}
466
467pub fn parse_font_face_block(
471 context: &ParserContext,
472 input: &mut Parser,
473 location: SourceLocation,
474) -> FontFaceRuleData {
475 let mut rule = FontFaceRuleData::empty(location);
476 {
477 let mut parser = FontFaceRuleParser {
478 context,
479 rule: &mut rule,
480 };
481 let mut iter = RuleBodyParser::new(input, &mut parser);
482 while let Some(declaration) = iter.next() {
483 if let Err((error, slice)) = declaration {
484 let location = error.location;
485 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
486 context.log_css_error(location, error)
487 }
488 }
489 }
490 rule
491}
492
493#[cfg(feature = "servo")]
495pub struct FontFace<'a>(&'a FontFaceRuleData);
496
497struct FontFaceRuleParser<'a, 'b: 'a> {
498 context: &'a ParserContext<'b>,
499 rule: &'a mut FontFaceRuleData,
500}
501
502impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
504 type Prelude = ();
505 type AtRule = ();
506 type Error = StyleParseErrorKind<'i>;
507}
508
509impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
510 type Prelude = ();
511 type QualifiedRule = ();
512 type Error = StyleParseErrorKind<'i>;
513}
514
515impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
516 for FontFaceRuleParser<'a, 'b>
517{
518 fn parse_qualified(&self) -> bool {
519 false
520 }
521 fn parse_declarations(&self) -> bool {
522 true
523 }
524}
525
526impl Parse for Source {
527 fn parse<'i, 't>(
528 context: &ParserContext,
529 input: &mut Parser<'i, 't>,
530 ) -> Result<Source, ParseError<'i>> {
531 if input
532 .try_parse(|input| input.expect_function_matching("local"))
533 .is_ok()
534 {
535 return input
536 .parse_nested_block(|input| FamilyName::parse(context, input))
537 .map(Source::Local);
538 }
539
540 let url = SpecifiedUrl::parse(context, input)?;
541
542 let format_hint = if input
544 .try_parse(|input| input.expect_function_matching("format"))
545 .is_ok()
546 {
547 input.parse_nested_block(|input| {
548 if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
549 Ok(Some(FontFaceSourceFormat::Keyword(kw)))
550 } else {
551 let s = input.expect_string()?.as_ref().to_owned();
552 Ok(Some(FontFaceSourceFormat::String(s)))
553 }
554 })?
555 } else {
556 None
557 };
558
559 let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled")
561 && input
562 .try_parse(|input| input.expect_function_matching("tech"))
563 .is_ok()
564 {
565 input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
566 } else {
567 FontFaceSourceTechFlags::empty()
568 };
569
570 Ok(Source::Url(UrlSource {
571 url,
572 format_hint,
573 tech_flags,
574 }))
575 }
576}
577
578macro_rules! is_descriptor_enabled {
579 ("font-variation-settings") => {
580 static_prefs::pref!("layout.css.font-variations.enabled")
581 };
582 ("size-adjust") => {
583 cfg!(feature = "gecko")
584 };
585 ($name:tt) => {
586 true
587 };
588}
589
590macro_rules! font_face_descriptors_common {
591 (
592 $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
593 ) => {
594 #[derive(Clone, Debug, PartialEq, ToShmem)]
598 pub struct FontFaceRuleData {
599 $(
600 #[$doc]
601 pub $ident: Option<$ty>,
602 )*
603 pub source_location: SourceLocation,
605 }
606
607 impl FontFaceRuleData {
608 pub fn empty(location: SourceLocation) -> Self {
610 FontFaceRuleData {
611 $(
612 $ident: None,
613 )*
614 source_location: location,
615 }
616 }
617
618 pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
620 $(
621 if let Some(ref value) = self.$ident {
622 dest.write_str(concat!($name, ": "))?;
623 value.to_css(&mut CssWriter::new(dest))?;
624 dest.write_str("; ")?;
625 }
626 )*
627 Ok(())
628 }
629 }
630
631 impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
632 type Declaration = ();
633 type Error = StyleParseErrorKind<'i>;
634
635 fn parse_value<'t>(
636 &mut self,
637 name: CowRcStr<'i>,
638 input: &mut Parser<'i, 't>,
639 _declaration_start: &ParserState,
640 ) -> Result<(), ParseError<'i>> {
641 match_ignore_ascii_case! { &*name,
642 $(
643 $name if is_descriptor_enabled!($name) => {
644 let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
649 self.rule.$ident = Some(value)
650 },
651 )*
652 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
653 }
654 Ok(())
655 }
656 }
657 }
658}
659
660impl ToCssWithGuard for FontFaceRuleData {
661 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
663 dest.write_str("@font-face { ")?;
664 self.decl_to_css(dest)?;
665 dest.write_char('}')
666 }
667}
668
669macro_rules! font_face_descriptors {
670 (
671 mandatory descriptors = [
672 $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
673 ]
674 optional descriptors = [
675 $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
676 ]
677 ) => {
678 font_face_descriptors_common! {
679 $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
680 $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
681 }
682
683 impl FontFaceRuleData {
684 #[cfg(feature = "servo")]
690 pub fn font_face(&self) -> Option<FontFace> {
691 if $( self.$m_ident.is_some() )&&* {
692 Some(FontFace(self))
693 } else {
694 None
695 }
696 }
697 }
698
699 #[cfg(feature = "servo")]
700 impl<'a> FontFace<'a> {
701 $(
702 #[$m_doc]
703 pub fn $m_ident(&self) -> &$m_ty {
704 self.0 .$m_ident.as_ref().unwrap()
705 }
706 )*
707 }
708 }
709}
710
711font_face_descriptors! {
712 mandatory descriptors = [
713 "font-family" family / mFamily: FamilyName,
715
716 "src" sources / mSrc: SourceList,
718 ]
719 optional descriptors = [
720 "font-style" style / mStyle: FontStyle,
722
723 "font-weight" weight / mWeight: FontWeightRange,
725
726 "font-stretch" stretch / mStretch: FontStretchRange,
728
729 "font-display" display / mDisplay: FontDisplay,
731
732 "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
734
735 "font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings,
737
738 "font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings,
740
741 "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
743
744 "ascent-override" ascent_override / mAscentOverride: MetricsOverride,
746
747 "descent-override" descent_override / mDescentOverride: MetricsOverride,
749
750 "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,
752
753 "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
755 ]
756}