1use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::computed::FontWeight;
14use crate::values::generics::font::FontStyle as GenericFontStyle;
15use crate::values::specified::{url::SpecifiedUrl, Angle};
16use cssparser::{Parser, RuleBodyParser, SourceLocation};
17use std::fmt::{self, Write};
18use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
19
20pub use crate::properties::font_face::{DescriptorId, DescriptorParser, Descriptors};
21pub use crate::values::computed::font::{FamilyName, FontStretch, FontStyle};
22pub use crate::values::specified::font::{
23 AbsoluteFontWeight, FontFeatureSettings, FontLanguageOverride,
24 FontStretch as SpecifiedFontStretch, FontVariationSettings, MetricsOverride,
25 SpecifiedFontStyle,
26};
27
28#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
30#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
31pub enum Source {
32 Url(UrlSource),
34 #[css(function)]
36 Local(FamilyName),
37}
38
39#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
41#[css(comma)]
42pub struct SourceList(#[css(iterable)] pub Vec<Source>);
43
44impl Parse for SourceList {
48 fn parse<'i, 't>(
49 context: &ParserContext,
50 input: &mut Parser<'i, 't>,
51 ) -> Result<Self, ParseError<'i>> {
52 let list = input
54 .parse_comma_separated(|input| {
55 let s = input.parse_entirely(|input| Source::parse(context, input));
56 while input.next().is_ok() {}
57 Ok(s.ok())
58 })?
59 .into_iter()
60 .filter_map(|s| s)
61 .collect::<Vec<Source>>();
62 if list.is_empty() {
63 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
64 } else {
65 Ok(SourceList(list))
66 }
67 }
68}
69
70#[derive(
73 Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Parse, PartialEq, Serialize, ToCss, ToShmem,
74)]
75#[repr(u8)]
76#[allow(missing_docs)]
77pub enum FontFaceSourceFormatKeyword {
78 #[css(skip)]
79 None,
80 Collection,
81 EmbeddedOpentype,
82 Opentype,
83 Svg,
84 Truetype,
85 Woff,
86 Woff2,
87 #[css(skip)]
88 Unknown,
89}
90
91#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize, ToShmem)]
94#[repr(C)]
95pub struct FontFaceSourceTechFlags(u16);
96bitflags! {
97 impl FontFaceSourceTechFlags: u16 {
98 const FEATURES_OPENTYPE = 1 << 0;
100 const FEATURES_AAT = 1 << 1;
102 const FEATURES_GRAPHITE = 1 << 2;
104 const COLOR_COLRV0 = 1 << 3;
106 const COLOR_COLRV1 = 1 << 4;
108 const COLOR_SVG = 1 << 5;
110 const COLOR_SBIX = 1 << 6;
112 const COLOR_CBDT = 1 << 7;
114 const VARIATIONS = 1 << 8;
116 const PALETTES = 1 << 9;
118 const INCREMENTAL = 1 << 10;
120 }
121}
122
123impl FontFaceSourceTechFlags {
124 pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
126 Ok(try_match_ident_ignore_ascii_case! { input,
127 "features-opentype" => Self::FEATURES_OPENTYPE,
128 "features-aat" => Self::FEATURES_AAT,
129 "features-graphite" => Self::FEATURES_GRAPHITE,
130 "color-colrv0" => Self::COLOR_COLRV0,
131 "color-colrv1" => Self::COLOR_COLRV1,
132 "color-svg" => Self::COLOR_SVG,
133 "color-sbix" => Self::COLOR_SBIX,
134 "color-cbdt" => Self::COLOR_CBDT,
135 "variations" => Self::VARIATIONS,
136 "palettes" => Self::PALETTES,
137 "incremental" => Self::INCREMENTAL,
138 })
139 }
140}
141
142impl Parse for FontFaceSourceTechFlags {
143 fn parse<'i, 't>(
144 _context: &ParserContext,
145 input: &mut Parser<'i, 't>,
146 ) -> Result<Self, ParseError<'i>> {
147 let location = input.current_source_location();
148 let mut result = Self::empty();
151 input.parse_comma_separated(|input| {
152 let flag = Self::parse_one(input)?;
153 result.insert(flag);
154 Ok(())
155 })?;
156 if !result.is_empty() {
157 Ok(result)
158 } else {
159 Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
160 }
161 }
162}
163
164#[allow(unused_assignments)]
165impl ToCss for FontFaceSourceTechFlags {
166 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
167 where
168 W: fmt::Write,
169 {
170 let mut first = true;
171
172 macro_rules! write_if_flag {
173 ($s:expr => $f:ident) => {
174 if self.contains(Self::$f) {
175 if first {
176 first = false;
177 } else {
178 dest.write_str(", ")?;
179 }
180 dest.write_str($s)?;
181 }
182 };
183 }
184
185 write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
186 write_if_flag!("features-aat" => FEATURES_AAT);
187 write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
188 write_if_flag!("color-colrv0" => COLOR_COLRV0);
189 write_if_flag!("color-colrv1" => COLOR_COLRV1);
190 write_if_flag!("color-svg" => COLOR_SVG);
191 write_if_flag!("color-sbix" => COLOR_SBIX);
192 write_if_flag!("color-cbdt" => COLOR_CBDT);
193 write_if_flag!("variations" => VARIATIONS);
194 write_if_flag!("palettes" => PALETTES);
195 write_if_flag!("incremental" => INCREMENTAL);
196
197 Ok(())
198 }
199}
200
201#[derive(Clone, Debug, ToShmem, PartialEq)]
203pub struct FontFaceRule {
204 pub descriptors: Descriptors,
206 pub source_location: SourceLocation,
208}
209
210impl FontFaceRule {
211 pub fn empty(source_location: SourceLocation) -> Self {
213 Self {
214 descriptors: Default::default(),
215 source_location,
216 }
217 }
218}
219
220#[cfg(feature = "gecko")]
225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
226#[repr(u8)]
227#[allow(missing_docs)]
228pub enum FontFaceSourceListComponent {
229 Url(*const crate::url::CssUrl),
230 Local(*mut crate::gecko_bindings::structs::nsAtom),
231 FormatHintKeyword(FontFaceSourceFormatKeyword),
232 FormatHintString {
233 length: usize,
234 utf8_bytes: *const u8,
235 },
236 TechFlags(FontFaceSourceTechFlags),
237}
238
239#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize, ToCss, ToShmem)]
240#[repr(u8)]
241#[allow(missing_docs)]
242pub enum FontFaceSourceFormat {
243 Keyword(FontFaceSourceFormatKeyword),
244 String(String),
245}
246
247#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
252#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
253pub struct UrlSource {
254 pub url: SpecifiedUrl,
256 pub format_hint: Option<FontFaceSourceFormat>,
258 pub tech_flags: FontFaceSourceTechFlags,
260}
261
262impl ToCss for UrlSource {
263 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
264 where
265 W: fmt::Write,
266 {
267 self.url.to_css(dest)?;
268 if let Some(hint) = &self.format_hint {
269 dest.write_str(" format(")?;
270 hint.to_css(dest)?;
271 dest.write_char(')')?;
272 }
273 if !self.tech_flags.is_empty() {
274 dest.write_str(" tech(")?;
275 self.tech_flags.to_css(dest)?;
276 dest.write_char(')')?;
277 }
278 Ok(())
279 }
280}
281
282#[allow(missing_docs)]
286#[derive(
287 Clone,
288 Copy,
289 Debug,
290 Deserialize,
291 Eq,
292 MallocSizeOf,
293 Parse,
294 PartialEq,
295 Serialize,
296 ToComputedValue,
297 ToCss,
298 ToShmem,
299)]
300#[repr(u8)]
301pub enum FontDisplay {
302 Auto,
303 Block,
304 Swap,
305 Fallback,
306 Optional,
307}
308
309macro_rules! impl_range {
310 ($range:ident, $component:ident) => {
311 impl Parse for $range {
312 fn parse<'i, 't>(
313 context: &ParserContext,
314 input: &mut Parser<'i, 't>,
315 ) -> Result<Self, ParseError<'i>> {
316 let first = $component::parse(context, input)?;
317 let second = input
318 .try_parse(|input| $component::parse(context, input))
319 .unwrap_or_else(|_| first.clone());
320 Ok($range(first, second))
321 }
322 }
323 impl ToCss for $range {
324 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
325 where
326 W: fmt::Write,
327 {
328 self.0.to_css(dest)?;
329 if self.0 != self.1 {
330 dest.write_char(' ')?;
331 self.1.to_css(dest)?;
332 }
333 Ok(())
334 }
335 }
336 };
337}
338
339#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
343pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
344impl_range!(FontWeightRange, AbsoluteFontWeight);
345
346#[repr(C)]
351#[allow(missing_docs)]
352#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
353pub struct ComputedFontWeightRange(pub FontWeight, pub FontWeight);
354
355#[inline]
356fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
357 if a > b {
358 (b, a)
359 } else {
360 (a, b)
361 }
362}
363
364impl FontWeightRange {
365 pub fn compute(&self) -> Option<ComputedFontWeightRange> {
367 let (min, max) = sort_range(self.0.compute()?, self.1.compute()?);
368 Some(ComputedFontWeightRange(min, max))
369 }
370}
371
372#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
376pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
377impl_range!(FontStretchRange, SpecifiedFontStretch);
378
379#[repr(C)]
382#[allow(missing_docs)]
383#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
384pub struct ComputedFontStretchRange(pub FontStretch, pub FontStretch);
385
386impl FontStretchRange {
387 pub fn compute(&self) -> Option<ComputedFontStretchRange> {
390 fn compute_stretch(s: &SpecifiedFontStretch) -> Option<FontStretch> {
391 match *s {
392 SpecifiedFontStretch::Keyword(ref kw) => Some(kw.compute()),
393 SpecifiedFontStretch::Stretch(ref p) => {
394 Some(FontStretch::from_percentage(p.compute()?.0))
395 },
396 SpecifiedFontStretch::System(..) => unreachable!(),
397 }
398 }
399
400 let (min, max) = sort_range(compute_stretch(&self.0)?, compute_stretch(&self.1)?);
401 Some(ComputedFontStretchRange(min, max))
402 }
403}
404
405#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
409#[allow(missing_docs)]
410pub enum FontStyleRange {
411 Italic,
412 Oblique(Angle, Angle),
413}
414
415#[repr(C)]
418#[allow(missing_docs)]
419#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
420pub struct ComputedFontStyleRange(pub FontStyle, pub FontStyle);
421
422impl Parse for FontStyleRange {
423 fn parse<'i, 't>(
424 context: &ParserContext,
425 input: &mut Parser<'i, 't>,
426 ) -> Result<Self, ParseError<'i>> {
427 if input
430 .try_parse(|i| i.expect_ident_matching("normal"))
431 .is_ok()
432 {
433 return Ok(Self::Oblique(Angle::zero(), Angle::zero()));
434 }
435
436 let style = SpecifiedFontStyle::parse(context, input)?;
437 Ok(match style {
438 GenericFontStyle::Italic => Self::Italic,
439 GenericFontStyle::Oblique(angle) => {
440 let second_angle = input
441 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
442 .unwrap_or_else(|_| angle.clone());
443
444 Self::Oblique(angle, second_angle)
445 },
446 })
447 }
448}
449
450impl ToCss for FontStyleRange {
451 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
452 where
453 W: fmt::Write,
454 {
455 match *self {
456 Self::Italic => dest.write_str("italic"),
457 Self::Oblique(ref first, ref second) => {
458 if *first == Angle::zero() && first == second {
461 return dest.write_str("normal");
462 }
463 dest.write_str("oblique")?;
464 if *first != SpecifiedFontStyle::default_angle() || first != second {
465 dest.write_char(' ')?;
466 first.to_css(dest)?;
467 }
468 if first != second {
469 dest.write_char(' ')?;
470 second.to_css(dest)?;
471 }
472 Ok(())
473 },
474 }
475 }
476}
477
478impl FontStyleRange {
479 pub fn compute(&self) -> Option<ComputedFontStyleRange> {
481 Some(match *self {
482 Self::Italic => ComputedFontStyleRange(FontStyle::ITALIC, FontStyle::ITALIC),
483 Self::Oblique(ref first, ref second) => {
484 let (min, max) = sort_range(first.degrees()?, second.degrees()?);
485 ComputedFontStyleRange(FontStyle::oblique(min), FontStyle::oblique(max))
486 },
487 })
488 }
489}
490
491pub fn parse_font_face_block(
495 context: &ParserContext,
496 input: &mut Parser,
497 source_location: SourceLocation,
498) -> FontFaceRule {
499 let mut rule = FontFaceRule::empty(source_location);
500 {
501 let mut parser = DescriptorParser {
502 context,
503 descriptors: &mut rule.descriptors,
504 };
505 let mut iter = RuleBodyParser::new(input, &mut parser);
506 while let Some(declaration) = iter.next() {
507 if let Err((error, slice)) = declaration {
508 let location = error.location;
509 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
510 context.log_css_error(location, error)
511 }
512 }
513 }
514 rule
515}
516
517impl Parse for Source {
518 fn parse<'i, 't>(
519 context: &ParserContext,
520 input: &mut Parser<'i, 't>,
521 ) -> Result<Source, ParseError<'i>> {
522 if input
523 .try_parse(|input| input.expect_function_matching("local"))
524 .is_ok()
525 {
526 return input
527 .parse_nested_block(|input| FamilyName::parse(context, input))
528 .map(Source::Local);
529 }
530
531 let url = SpecifiedUrl::parse(context, input)?;
532
533 let format_hint = if input
535 .try_parse(|input| input.expect_function_matching("format"))
536 .is_ok()
537 {
538 input.parse_nested_block(|input| {
539 if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
540 Ok(Some(FontFaceSourceFormat::Keyword(kw)))
541 } else {
542 let s = input.expect_string()?.as_ref().to_owned();
543 Ok(Some(FontFaceSourceFormat::String(s)))
544 }
545 })?
546 } else {
547 None
548 };
549
550 let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled")
552 && input
553 .try_parse(|input| input.expect_function_matching("tech"))
554 .is_ok()
555 {
556 input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
557 } else {
558 FontFaceSourceTechFlags::empty()
559 };
560
561 Ok(Source::Url(UrlSource {
562 url,
563 format_hint,
564 tech_flags,
565 }))
566 }
567}
568
569impl ToCssWithGuard for FontFaceRule {
570 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
572 dest.write_str("@font-face { ")?;
573 self.descriptors.to_css(&mut CssWriter::new(dest))?;
574 dest.write_char('}')
575 }
576}