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, FontStretch as SpecifiedFontStretch, FontFeatureSettings,
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.try_parse(|i| i.expect_ident_matching("normal")).is_ok() {
403 return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
404 }
405
406 let style = SpecifiedFontStyle::parse(context, input)?;
407 Ok(match style {
408 GenericFontStyle::Italic => FontStyle::Italic,
409 GenericFontStyle::Oblique(angle) => {
410 let second_angle = input
411 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
412 .unwrap_or_else(|_| angle.clone());
413
414 FontStyle::Oblique(angle, second_angle)
415 },
416 })
417 }
418}
419
420impl ToCss for FontStyle {
421 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
422 where
423 W: fmt::Write,
424 {
425 match *self {
426 FontStyle::Italic => dest.write_str("italic"),
427 FontStyle::Oblique(ref first, ref second) => {
428 if *first == Angle::zero() && first == second {
431 return dest.write_str("normal");
432 }
433 dest.write_str("oblique")?;
434 if *first != SpecifiedFontStyle::default_angle() || first != second {
435 dest.write_char(' ')?;
436 first.to_css(dest)?;
437 }
438 if first != second {
439 dest.write_char(' ')?;
440 second.to_css(dest)?;
441 }
442 Ok(())
443 },
444 }
445 }
446}
447
448impl FontStyle {
449 pub fn compute(&self) -> ComputedFontStyleDescriptor {
451 match *self {
452 FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
453 FontStyle::Oblique(ref first, ref second) => {
454 let (min, max) = sort_range(
455 SpecifiedFontStyle::compute_angle_degrees(first),
456 SpecifiedFontStyle::compute_angle_degrees(second),
457 );
458 ComputedFontStyleDescriptor::Oblique(min, max)
459 },
460 }
461 }
462}
463
464pub fn parse_font_face_block(
468 context: &ParserContext,
469 input: &mut Parser,
470 location: SourceLocation,
471) -> FontFaceRuleData {
472 let mut rule = FontFaceRuleData::empty(location);
473 {
474 let mut parser = FontFaceRuleParser {
475 context,
476 rule: &mut rule,
477 };
478 let mut iter = RuleBodyParser::new(input, &mut parser);
479 while let Some(declaration) = iter.next() {
480 if let Err((error, slice)) = declaration {
481 let location = error.location;
482 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
483 context.log_css_error(location, error)
484 }
485 }
486 }
487 rule
488}
489
490#[cfg(feature = "servo")]
492pub struct FontFace<'a>(&'a FontFaceRuleData);
493
494struct FontFaceRuleParser<'a, 'b: 'a> {
495 context: &'a ParserContext<'b>,
496 rule: &'a mut FontFaceRuleData,
497}
498
499impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
501 type Prelude = ();
502 type AtRule = ();
503 type Error = StyleParseErrorKind<'i>;
504}
505
506impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
507 type Prelude = ();
508 type QualifiedRule = ();
509 type Error = StyleParseErrorKind<'i>;
510}
511
512impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
513 for FontFaceRuleParser<'a, 'b>
514{
515 fn parse_qualified(&self) -> bool {
516 false
517 }
518 fn parse_declarations(&self) -> bool {
519 true
520 }
521}
522
523impl Parse for Source {
524 fn parse<'i, 't>(
525 context: &ParserContext,
526 input: &mut Parser<'i, 't>,
527 ) -> Result<Source, ParseError<'i>> {
528 if input
529 .try_parse(|input| input.expect_function_matching("local"))
530 .is_ok()
531 {
532 return input
533 .parse_nested_block(|input| FamilyName::parse(context, input))
534 .map(Source::Local);
535 }
536
537 let url = SpecifiedUrl::parse(context, input)?;
538
539 let format_hint = if input
541 .try_parse(|input| input.expect_function_matching("format"))
542 .is_ok()
543 {
544 input.parse_nested_block(|input| {
545 if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
546 Ok(Some(FontFaceSourceFormat::Keyword(kw)))
547 } else {
548 let s = input.expect_string()?.as_ref().to_owned();
549 Ok(Some(FontFaceSourceFormat::String(s)))
550 }
551 })?
552 } else {
553 None
554 };
555
556 let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled") &&
558 input
559 .try_parse(|input| input.expect_function_matching("tech"))
560 .is_ok()
561 {
562 input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
563 } else {
564 FontFaceSourceTechFlags::empty()
565 };
566
567 Ok(Source::Url(UrlSource {
568 url,
569 format_hint,
570 tech_flags,
571 }))
572 }
573}
574
575macro_rules! is_descriptor_enabled {
576 ("font-variation-settings") => {
577 static_prefs::pref!("layout.css.font-variations.enabled")
578 };
579 ("size-adjust") => {
580 cfg!(feature = "gecko")
581 };
582 ($name:tt) => {
583 true
584 };
585}
586
587macro_rules! font_face_descriptors_common {
588 (
589 $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
590 ) => {
591 #[derive(Clone, Debug, PartialEq, ToShmem)]
595 pub struct FontFaceRuleData {
596 $(
597 #[$doc]
598 pub $ident: Option<$ty>,
599 )*
600 pub source_location: SourceLocation,
602 }
603
604 impl FontFaceRuleData {
605 pub fn empty(location: SourceLocation) -> Self {
607 FontFaceRuleData {
608 $(
609 $ident: None,
610 )*
611 source_location: location,
612 }
613 }
614
615 pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
617 $(
618 if let Some(ref value) = self.$ident {
619 dest.write_str(concat!($name, ": "))?;
620 value.to_css(&mut CssWriter::new(dest))?;
621 dest.write_str("; ")?;
622 }
623 )*
624 Ok(())
625 }
626 }
627
628 impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
629 type Declaration = ();
630 type Error = StyleParseErrorKind<'i>;
631
632 fn parse_value<'t>(
633 &mut self,
634 name: CowRcStr<'i>,
635 input: &mut Parser<'i, 't>,
636 _declaration_start: &ParserState,
637 ) -> Result<(), ParseError<'i>> {
638 match_ignore_ascii_case! { &*name,
639 $(
640 $name if is_descriptor_enabled!($name) => {
641 let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
646 self.rule.$ident = Some(value)
647 },
648 )*
649 _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
650 }
651 Ok(())
652 }
653 }
654 }
655}
656
657impl ToCssWithGuard for FontFaceRuleData {
658 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
660 dest.write_str("@font-face { ")?;
661 self.decl_to_css(dest)?;
662 dest.write_char('}')
663 }
664}
665
666macro_rules! font_face_descriptors {
667 (
668 mandatory descriptors = [
669 $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
670 ]
671 optional descriptors = [
672 $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
673 ]
674 ) => {
675 font_face_descriptors_common! {
676 $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
677 $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
678 }
679
680 impl FontFaceRuleData {
681 #[cfg(feature = "servo")]
687 pub fn font_face(&self) -> Option<FontFace> {
688 if $( self.$m_ident.is_some() )&&* {
689 Some(FontFace(self))
690 } else {
691 None
692 }
693 }
694 }
695
696 #[cfg(feature = "servo")]
697 impl<'a> FontFace<'a> {
698 $(
699 #[$m_doc]
700 pub fn $m_ident(&self) -> &$m_ty {
701 self.0 .$m_ident.as_ref().unwrap()
702 }
703 )*
704 }
705 }
706}
707
708font_face_descriptors! {
709 mandatory descriptors = [
710 "font-family" family / mFamily: FamilyName,
712
713 "src" sources / mSrc: SourceList,
715 ]
716 optional descriptors = [
717 "font-style" style / mStyle: FontStyle,
719
720 "font-weight" weight / mWeight: FontWeightRange,
722
723 "font-stretch" stretch / mStretch: FontStretchRange,
725
726 "font-display" display / mDisplay: FontDisplay,
728
729 "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
731
732 "font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings,
734
735 "font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings,
737
738 "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
740
741 "ascent-override" ascent_override / mAscentOverride: MetricsOverride,
743
744 "descent-override" descent_override / mDescentOverride: MetricsOverride,
746
747 "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,
749
750 "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
752 ]
753}