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