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::computed::font::FontFamilyNameSyntax;
15use crate::values::generics::font::FontStyle as GenericFontStyle;
16use crate::values::specified::{Angle, url::SpecifiedUrl};
17use cssparser::{Parser, RuleBodyParser, SourceLocation};
18use std::fmt::{self, Write};
19use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
20
21pub use crate::properties::font_face::{DescriptorId, DescriptorParser, Descriptors};
22pub use crate::values::computed::font::{FamilyName, FontStyle, FontWidth};
23pub use crate::values::specified::font::{
24 AbsoluteFontWeight, FontFeatureSettings, FontLanguageOverride, FontVariationSettings,
25 FontWidth as SpecifiedFontWidth, MetricsOverride, 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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
49 let list = input
51 .parse_comma_separated(|input| {
52 let s = input.parse_entirely(|input| Source::parse(context, input));
53 while input.next().is_ok() {}
54 Ok(s.ok())
55 })?
56 .into_iter()
57 .flatten()
58 .collect::<Vec<Source>>();
59 if list.is_empty() {
60 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
61 } else {
62 Ok(SourceList(list))
63 }
64 }
65}
66
67#[derive(
70 Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Parse, PartialEq, Serialize, ToCss, ToShmem,
71)]
72#[repr(u8)]
73#[allow(missing_docs)]
74pub enum FontFaceSourceFormatKeyword {
75 #[css(skip)]
76 None,
77 Collection,
78 EmbeddedOpentype,
79 Opentype,
80 Svg,
81 Truetype,
82 Woff,
83 Woff2,
84 #[css(skip)]
85 Unknown,
86}
87
88#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize, ToShmem)]
91#[repr(C)]
92pub struct FontFaceSourceTechFlags(u16);
93bitflags! {
94 impl FontFaceSourceTechFlags: u16 {
95 const FEATURES_OPENTYPE = 1 << 0;
97 const FEATURES_AAT = 1 << 1;
99 const FEATURES_GRAPHITE = 1 << 2;
101 const COLOR_COLRV0 = 1 << 3;
103 const COLOR_COLRV1 = 1 << 4;
105 const COLOR_SVG = 1 << 5;
107 const COLOR_SBIX = 1 << 6;
109 const COLOR_CBDT = 1 << 7;
111 const VARIATIONS = 1 << 8;
113 const PALETTES = 1 << 9;
115 const INCREMENTAL = 1 << 10;
117 }
118}
119
120impl FontFaceSourceTechFlags {
121 pub fn parse_one(input: &mut Parser) -> Result<Self, ParseError> {
123 Ok(try_match_ident_ignore_ascii_case! { input,
124 "features-opentype" => Self::FEATURES_OPENTYPE,
125 "features-aat" => Self::FEATURES_AAT,
126 "features-graphite" => Self::FEATURES_GRAPHITE,
127 "color-colrv0" => Self::COLOR_COLRV0,
128 "color-colrv1" => Self::COLOR_COLRV1,
129 "color-svg" => Self::COLOR_SVG,
130 "color-sbix" => Self::COLOR_SBIX,
131 "color-cbdt" => Self::COLOR_CBDT,
132 "variations" => Self::VARIATIONS,
133 "palettes" => Self::PALETTES,
134 "incremental" => Self::INCREMENTAL,
135 })
136 }
137}
138
139impl Parse for FontFaceSourceTechFlags {
140 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
141 let mut result = Self::empty();
144 input.parse_comma_separated(|input| {
145 let flag = Self::parse_one(input)?;
146 result.insert(flag);
147 Ok(())
148 })?;
149 if !result.is_empty() {
150 Ok(result)
151 } else {
152 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
153 }
154 }
155}
156
157#[allow(unused_assignments)]
158impl ToCss for FontFaceSourceTechFlags {
159 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
160 where
161 W: fmt::Write,
162 {
163 let mut first = true;
164
165 macro_rules! write_if_flag {
166 ($s:expr => $f:ident) => {
167 if self.contains(Self::$f) {
168 if first {
169 first = false;
170 } else {
171 dest.write_str(", ")?;
172 }
173 dest.write_str($s)?;
174 }
175 };
176 }
177
178 write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
179 write_if_flag!("features-aat" => FEATURES_AAT);
180 write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
181 write_if_flag!("color-colrv0" => COLOR_COLRV0);
182 write_if_flag!("color-colrv1" => COLOR_COLRV1);
183 write_if_flag!("color-svg" => COLOR_SVG);
184 write_if_flag!("color-sbix" => COLOR_SBIX);
185 write_if_flag!("color-cbdt" => COLOR_CBDT);
186 write_if_flag!("variations" => VARIATIONS);
187 write_if_flag!("palettes" => PALETTES);
188 write_if_flag!("incremental" => INCREMENTAL);
189
190 Ok(())
191 }
192}
193
194#[derive(Clone, Debug, ToShmem, PartialEq)]
196pub struct FontFaceRule {
197 pub descriptors: Descriptors,
199 pub source_location: SourceLocation,
201}
202
203impl FontFaceRule {
204 pub fn empty(source_location: SourceLocation) -> Self {
206 Self {
207 descriptors: Default::default(),
208 source_location,
209 }
210 }
211}
212
213#[cfg(feature = "gecko")]
218#[derive(Clone, Copy, Debug, Eq, PartialEq)]
219#[repr(u8)]
220#[allow(missing_docs)]
221pub enum FontFaceSourceListComponent {
222 Url(*const crate::url::CssUrl),
223 Local(*mut crate::gecko_bindings::structs::nsAtom),
224 FormatHintKeyword(FontFaceSourceFormatKeyword),
225 FormatHintString {
226 length: usize,
227 utf8_bytes: *const u8,
228 },
229 TechFlags(FontFaceSourceTechFlags),
230}
231
232#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize, ToCss, ToShmem)]
233#[repr(u8)]
234#[allow(missing_docs)]
235pub enum FontFaceSourceFormat {
236 Keyword(FontFaceSourceFormatKeyword),
237 String(String),
238}
239
240#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
245#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
246pub struct UrlSource {
247 pub url: SpecifiedUrl,
249 pub format_hint: Option<FontFaceSourceFormat>,
251 pub tech_flags: FontFaceSourceTechFlags,
253}
254
255impl ToCss for UrlSource {
256 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
257 where
258 W: fmt::Write,
259 {
260 self.url.to_css(dest)?;
261 if let Some(hint) = &self.format_hint {
262 dest.write_str(" format(")?;
263 hint.to_css(dest)?;
264 dest.write_char(')')?;
265 }
266 if !self.tech_flags.is_empty() {
267 dest.write_str(" tech(")?;
268 self.tech_flags.to_css(dest)?;
269 dest.write_char(')')?;
270 }
271 Ok(())
272 }
273}
274
275#[allow(missing_docs)]
279#[derive(
280 Clone,
281 Copy,
282 Debug,
283 Deserialize,
284 Eq,
285 MallocSizeOf,
286 Parse,
287 PartialEq,
288 Serialize,
289 ToComputedValue,
290 ToCss,
291 ToShmem,
292)]
293#[repr(u8)]
294pub enum FontDisplay {
295 Auto,
296 Block,
297 Swap,
298 Fallback,
299 Optional,
300}
301
302macro_rules! impl_range {
303 ($range:ident, $component:ident) => {
304 impl Parse for $range {
305 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
306 let first = $component::parse(context, input)?;
307 let second = input
308 .try_parse(|input| $component::parse(context, input))
309 .unwrap_or_else(|_| first.clone());
310 Ok($range(first, second))
311 }
312 }
313 impl ToCss for $range {
314 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
315 where
316 W: fmt::Write,
317 {
318 self.0.to_css(dest)?;
319 if self.0 != self.1 {
320 dest.write_char(' ')?;
321 self.1.to_css(dest)?;
322 }
323 Ok(())
324 }
325 }
326 };
327}
328
329#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
333pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
334impl_range!(FontWeightRange, AbsoluteFontWeight);
335
336#[repr(C)]
341#[allow(missing_docs)]
342#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
343pub struct ComputedFontWeightRange(pub FontWeight, pub FontWeight);
344
345#[inline]
346fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
347 if a > b { (b, a) } else { (a, b) }
348}
349
350impl FontWeightRange {
351 pub fn compute(&self) -> Option<ComputedFontWeightRange> {
353 let (min, max) = sort_range(self.0.compute()?, self.1.compute()?);
354 Some(ComputedFontWeightRange(min, max))
355 }
356}
357
358#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
362pub struct FontWidthRange(pub SpecifiedFontWidth, pub SpecifiedFontWidth);
363impl_range!(FontWidthRange, SpecifiedFontWidth);
364
365#[repr(C)]
368#[allow(missing_docs)]
369#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
370pub struct ComputedFontWidthRange(pub FontWidth, pub FontWidth);
371
372impl FontWidthRange {
373 pub fn compute(&self) -> Option<ComputedFontWidthRange> {
376 fn compute_width(s: &SpecifiedFontWidth) -> Option<FontWidth> {
377 match *s {
378 SpecifiedFontWidth::Keyword(ref kw) => Some(kw.compute()),
379 SpecifiedFontWidth::Width(ref p) => {
380 Some(FontWidth::from_percentage(p.compute()?.0))
381 },
382 SpecifiedFontWidth::System(..) => unreachable!(),
383 }
384 }
385
386 let (min, max) = sort_range(compute_width(&self.0)?, compute_width(&self.1)?);
387 Some(ComputedFontWidthRange(min, max))
388 }
389}
390
391#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
395#[allow(missing_docs)]
396pub enum FontStyleRange {
397 Italic,
398 Oblique(Angle, Angle),
399}
400
401#[repr(C)]
404#[allow(missing_docs)]
405#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
406pub struct ComputedFontStyleRange(pub FontStyle, pub FontStyle);
407
408impl Parse for FontStyleRange {
409 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
410 if input
413 .try_parse(|i| i.expect_ident_matching("normal"))
414 .is_ok()
415 {
416 return Ok(Self::Oblique(Angle::zero(), Angle::zero()));
417 }
418
419 let style = SpecifiedFontStyle::parse(context, input)?;
420 Ok(match style {
421 GenericFontStyle::Italic => Self::Italic,
422 GenericFontStyle::Oblique(angle) => {
423 let second_angle = input
424 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
425 .unwrap_or_else(|_| angle.clone());
426
427 Self::Oblique(angle, second_angle)
428 },
429 })
430 }
431}
432
433impl ToCss for FontStyleRange {
434 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
435 where
436 W: fmt::Write,
437 {
438 match *self {
439 Self::Italic => dest.write_str("italic"),
440 Self::Oblique(ref first, ref second) => {
441 if *first == Angle::zero() && first == second {
444 return dest.write_str("normal");
445 }
446 dest.write_str("oblique")?;
447 if *first != SpecifiedFontStyle::default_angle() || first != second {
448 dest.write_char(' ')?;
449 first.to_css(dest)?;
450 }
451 if first != second {
452 dest.write_char(' ')?;
453 second.to_css(dest)?;
454 }
455 Ok(())
456 },
457 }
458 }
459}
460
461impl FontStyleRange {
462 pub fn compute(&self) -> Option<ComputedFontStyleRange> {
464 Some(match *self {
465 Self::Italic => ComputedFontStyleRange(FontStyle::ITALIC, FontStyle::ITALIC),
466 Self::Oblique(ref first, ref second) => {
467 let (min, max) = sort_range(first.degrees()?, second.degrees()?);
468 ComputedFontStyleRange(FontStyle::oblique(min), FontStyle::oblique(max))
469 },
470 })
471 }
472}
473
474pub fn parse_font_face_block(
478 context: &ParserContext,
479 input: &mut Parser,
480 source_location: SourceLocation,
481) -> FontFaceRule {
482 let mut rule = FontFaceRule::empty(source_location);
483 {
484 let mut parser = DescriptorParser {
485 context,
486 descriptors: &mut rule.descriptors,
487 };
488 let iter = RuleBodyParser::new(input, &mut parser);
489 for declaration in iter {
490 if let Err((error, slice, location)) = declaration {
491 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
492 context.log_css_error(location, error)
493 }
494 }
495 }
496 rule
497}
498
499impl Parse for Source {
500 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Source, ParseError> {
501 if input
502 .try_parse(|input| input.expect_function_matching("local"))
503 .is_ok()
504 {
505 let mut family_name =
506 input.parse_nested_block(|input| FamilyName::parse(context, input))?;
507 family_name.syntax = FontFamilyNameSyntax::Quoted;
509 return Ok(Source::Local(family_name));
510 }
511
512 let url = SpecifiedUrl::parse(context, input)?;
513
514 let format_hint = if input
516 .try_parse(|input| input.expect_function_matching("format"))
517 .is_ok()
518 {
519 input.parse_nested_block(|input| {
520 if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
521 Ok(Some(FontFaceSourceFormat::Keyword(kw)))
522 } else {
523 let s = input.expect_string()?.as_ref().to_owned();
524 Ok(Some(FontFaceSourceFormat::String(s)))
525 }
526 })?
527 } else {
528 None
529 };
530
531 let tech_flags = if crate::pref!("layout.css.font-tech.enabled", gecko = true)
533 && input
534 .try_parse(|input| input.expect_function_matching("tech"))
535 .is_ok()
536 {
537 input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
538 } else {
539 FontFaceSourceTechFlags::empty()
540 };
541
542 Ok(Source::Url(UrlSource {
543 url,
544 format_hint,
545 tech_flags,
546 }))
547 }
548}
549
550impl ToCssWithGuard for FontFaceRule {
551 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
553 dest.write_str("@font-face { ")?;
554 self.descriptors.to_css(&mut CssWriter::new(dest))?;
555 dest.write_char('}')
556 }
557}