1use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::generics::font::FontStyle as GenericFontStyle;
14use crate::values::specified::{url::SpecifiedUrl, Angle};
15use cssparser::{Parser, RuleBodyParser, SourceLocation};
16use std::fmt::{self, Write};
17use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
18
19pub use crate::properties::font_face::{DescriptorId, DescriptorParser, Descriptors};
20pub use crate::values::computed::font::{FamilyName, FontStretch};
21pub use crate::values::specified::font::{
22 AbsoluteFontWeight, FontFeatureSettings, FontLanguageOverride,
23 FontStretch as SpecifiedFontStretch, FontVariationSettings, MetricsOverride,
24 SpecifiedFontStyle,
25};
26
27#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
29#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
30pub enum Source {
31 Url(UrlSource),
33 #[css(function)]
35 Local(FamilyName),
36}
37
38#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
40#[css(comma)]
41pub struct SourceList(#[css(iterable)] pub Vec<Source>);
42
43impl Parse for SourceList {
47 fn parse<'i, 't>(
48 context: &ParserContext,
49 input: &mut Parser<'i, 't>,
50 ) -> Result<Self, ParseError<'i>> {
51 let list = input
53 .parse_comma_separated(|input| {
54 let s = input.parse_entirely(|input| Source::parse(context, input));
55 while input.next().is_ok() {}
56 Ok(s.ok())
57 })?
58 .into_iter()
59 .filter_map(|s| s)
60 .collect::<Vec<Source>>();
61 if list.is_empty() {
62 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
63 } else {
64 Ok(SourceList(list))
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
72#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
73#[repr(u8)]
74#[allow(missing_docs)]
75pub enum FontFaceSourceFormatKeyword {
76 #[css(skip)]
77 None,
78 Collection,
79 EmbeddedOpentype,
80 Opentype,
81 Svg,
82 Truetype,
83 Woff,
84 Woff2,
85 #[css(skip)]
86 Unknown,
87}
88
89#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
92#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
93#[repr(C)]
94pub struct FontFaceSourceTechFlags(u16);
95bitflags! {
96 impl FontFaceSourceTechFlags: u16 {
97 const FEATURES_OPENTYPE = 1 << 0;
99 const FEATURES_AAT = 1 << 1;
101 const FEATURES_GRAPHITE = 1 << 2;
103 const COLOR_COLRV0 = 1 << 3;
105 const COLOR_COLRV1 = 1 << 4;
107 const COLOR_SVG = 1 << 5;
109 const COLOR_SBIX = 1 << 6;
111 const COLOR_CBDT = 1 << 7;
113 const VARIATIONS = 1 << 8;
115 const PALETTES = 1 << 9;
117 const INCREMENTAL = 1 << 10;
119 }
120}
121
122impl FontFaceSourceTechFlags {
123 pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
125 Ok(try_match_ident_ignore_ascii_case! { input,
126 "features-opentype" => Self::FEATURES_OPENTYPE,
127 "features-aat" => Self::FEATURES_AAT,
128 "features-graphite" => Self::FEATURES_GRAPHITE,
129 "color-colrv0" => Self::COLOR_COLRV0,
130 "color-colrv1" => Self::COLOR_COLRV1,
131 "color-svg" => Self::COLOR_SVG,
132 "color-sbix" => Self::COLOR_SBIX,
133 "color-cbdt" => Self::COLOR_CBDT,
134 "variations" => Self::VARIATIONS,
135 "palettes" => Self::PALETTES,
136 "incremental" => Self::INCREMENTAL,
137 })
138 }
139}
140
141impl Parse for FontFaceSourceTechFlags {
142 fn parse<'i, 't>(
143 _context: &ParserContext,
144 input: &mut Parser<'i, 't>,
145 ) -> Result<Self, ParseError<'i>> {
146 let location = input.current_source_location();
147 let mut result = Self::empty();
150 input.parse_comma_separated(|input| {
151 let flag = Self::parse_one(input)?;
152 result.insert(flag);
153 Ok(())
154 })?;
155 if !result.is_empty() {
156 Ok(result)
157 } else {
158 Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
159 }
160 }
161}
162
163#[allow(unused_assignments)]
164impl ToCss for FontFaceSourceTechFlags {
165 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
166 where
167 W: fmt::Write,
168 {
169 let mut first = true;
170
171 macro_rules! write_if_flag {
172 ($s:expr => $f:ident) => {
173 if self.contains(Self::$f) {
174 if first {
175 first = false;
176 } else {
177 dest.write_str(", ")?;
178 }
179 dest.write_str($s)?;
180 }
181 };
182 }
183
184 write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
185 write_if_flag!("features-aat" => FEATURES_AAT);
186 write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
187 write_if_flag!("color-colrv0" => COLOR_COLRV0);
188 write_if_flag!("color-colrv1" => COLOR_COLRV1);
189 write_if_flag!("color-svg" => COLOR_SVG);
190 write_if_flag!("color-sbix" => COLOR_SBIX);
191 write_if_flag!("color-cbdt" => COLOR_CBDT);
192 write_if_flag!("variations" => VARIATIONS);
193 write_if_flag!("palettes" => PALETTES);
194 write_if_flag!("incremental" => INCREMENTAL);
195
196 Ok(())
197 }
198}
199
200#[derive(Clone, Debug, ToShmem, PartialEq)]
202pub struct FontFaceRule {
203 pub descriptors: Descriptors,
205 pub source_location: SourceLocation,
207}
208
209impl FontFaceRule {
210 pub fn empty(source_location: SourceLocation) -> Self {
212 Self {
213 descriptors: Default::default(),
214 source_location,
215 }
216 }
217}
218
219#[cfg(feature = "gecko")]
224#[derive(Clone, Copy, Debug, Eq, PartialEq)]
225#[repr(u8)]
226#[allow(missing_docs)]
227pub enum FontFaceSourceListComponent {
228 Url(*const crate::url::CssUrl),
229 Local(*mut crate::gecko_bindings::structs::nsAtom),
230 FormatHintKeyword(FontFaceSourceFormatKeyword),
231 FormatHintString {
232 length: usize,
233 utf8_bytes: *const u8,
234 },
235 TechFlags(FontFaceSourceTechFlags),
236}
237
238#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
239#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
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#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
287#[derive(
288 Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
289)]
290#[repr(u8)]
291pub enum FontDisplay {
292 Auto,
293 Block,
294 Swap,
295 Fallback,
296 Optional,
297}
298
299macro_rules! impl_range {
300 ($range:ident, $component:ident) => {
301 impl Parse for $range {
302 fn parse<'i, 't>(
303 context: &ParserContext,
304 input: &mut Parser<'i, 't>,
305 ) -> Result<Self, ParseError<'i>> {
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)]
342pub struct ComputedFontWeightRange(f32, f32);
343
344#[inline]
345fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
346 if a > b {
347 (b, a)
348 } else {
349 (a, b)
350 }
351}
352
353impl FontWeightRange {
354 pub fn compute(&self) -> Option<ComputedFontWeightRange> {
356 let (min, max) = sort_range(self.0.compute()?.value(), self.1.compute()?.value());
357 Some(ComputedFontWeightRange(min, max))
358 }
359}
360
361#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
365pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
366impl_range!(FontStretchRange, SpecifiedFontStretch);
367
368#[repr(C)]
371#[allow(missing_docs)]
372pub struct ComputedFontStretchRange(FontStretch, FontStretch);
373
374impl FontStretchRange {
375 pub fn compute(&self) -> Option<ComputedFontStretchRange> {
378 fn compute_stretch(s: &SpecifiedFontStretch) -> Option<FontStretch> {
379 match *s {
380 SpecifiedFontStretch::Keyword(ref kw) => Some(kw.compute()),
381 SpecifiedFontStretch::Stretch(ref p) => {
382 Some(FontStretch::from_percentage(p.compute()?.0))
383 },
384 SpecifiedFontStretch::System(..) => unreachable!(),
385 }
386 }
387
388 let (min, max) = sort_range(compute_stretch(&self.0)?, compute_stretch(&self.1)?);
389 Some(ComputedFontStretchRange(min, max))
390 }
391}
392
393#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
397#[allow(missing_docs)]
398pub enum FontStyle {
399 Italic,
400 Oblique(Angle, Angle),
401}
402
403#[repr(u8)]
406#[allow(missing_docs)]
407pub enum ComputedFontStyleDescriptor {
408 Italic,
409 Oblique(f32, f32),
410}
411
412impl Parse for FontStyle {
413 fn parse<'i, 't>(
414 context: &ParserContext,
415 input: &mut Parser<'i, 't>,
416 ) -> Result<Self, ParseError<'i>> {
417 if input
420 .try_parse(|i| i.expect_ident_matching("normal"))
421 .is_ok()
422 {
423 return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
424 }
425
426 let style = SpecifiedFontStyle::parse(context, input)?;
427 Ok(match style {
428 GenericFontStyle::Italic => FontStyle::Italic,
429 GenericFontStyle::Oblique(angle) => {
430 let second_angle = input
431 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
432 .unwrap_or_else(|_| angle.clone());
433
434 FontStyle::Oblique(angle, second_angle)
435 },
436 })
437 }
438}
439
440impl ToCss for FontStyle {
441 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
442 where
443 W: fmt::Write,
444 {
445 match *self {
446 FontStyle::Italic => dest.write_str("italic"),
447 FontStyle::Oblique(ref first, ref second) => {
448 if *first == Angle::zero() && first == second {
451 return dest.write_str("normal");
452 }
453 dest.write_str("oblique")?;
454 if *first != SpecifiedFontStyle::default_angle() || first != second {
455 dest.write_char(' ')?;
456 first.to_css(dest)?;
457 }
458 if first != second {
459 dest.write_char(' ')?;
460 second.to_css(dest)?;
461 }
462 Ok(())
463 },
464 }
465 }
466}
467
468impl FontStyle {
469 pub fn compute(&self) -> Option<ComputedFontStyleDescriptor> {
471 match *self {
472 FontStyle::Italic => Some(ComputedFontStyleDescriptor::Italic),
473 FontStyle::Oblique(ref first, ref second) => {
474 let first = SpecifiedFontStyle::compute_angle_degrees(first)?;
475 let second = SpecifiedFontStyle::compute_angle_degrees(second)?;
476 let (min, max) = sort_range(first, second);
477 Some(ComputedFontStyleDescriptor::Oblique(min, max))
478 },
479 }
480 }
481}
482
483pub fn parse_font_face_block(
487 context: &ParserContext,
488 input: &mut Parser,
489 source_location: SourceLocation,
490) -> FontFaceRule {
491 let mut rule = FontFaceRule::empty(source_location);
492 {
493 let mut parser = DescriptorParser {
494 context,
495 descriptors: &mut rule.descriptors,
496 };
497 let mut iter = RuleBodyParser::new(input, &mut parser);
498 while let Some(declaration) = iter.next() {
499 if let Err((error, slice)) = declaration {
500 let location = error.location;
501 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
502 context.log_css_error(location, error)
503 }
504 }
505 }
506 rule
507}
508
509impl Parse for Source {
510 fn parse<'i, 't>(
511 context: &ParserContext,
512 input: &mut Parser<'i, 't>,
513 ) -> Result<Source, ParseError<'i>> {
514 if input
515 .try_parse(|input| input.expect_function_matching("local"))
516 .is_ok()
517 {
518 return input
519 .parse_nested_block(|input| FamilyName::parse(context, input))
520 .map(Source::Local);
521 }
522
523 let url = SpecifiedUrl::parse(context, input)?;
524
525 let format_hint = if input
527 .try_parse(|input| input.expect_function_matching("format"))
528 .is_ok()
529 {
530 input.parse_nested_block(|input| {
531 if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
532 Ok(Some(FontFaceSourceFormat::Keyword(kw)))
533 } else {
534 let s = input.expect_string()?.as_ref().to_owned();
535 Ok(Some(FontFaceSourceFormat::String(s)))
536 }
537 })?
538 } else {
539 None
540 };
541
542 let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled")
544 && input
545 .try_parse(|input| input.expect_function_matching("tech"))
546 .is_ok()
547 {
548 input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
549 } else {
550 FontFaceSourceTechFlags::empty()
551 };
552
553 Ok(Source::Url(UrlSource {
554 url,
555 format_hint,
556 tech_flags,
557 }))
558 }
559}
560
561impl ToCssWithGuard for FontFaceRule {
562 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
564 dest.write_str("@font-face { ")?;
565 self.descriptors.to_css(&mut CssWriter::new(dest))?;
566 dest.write_char('}')
567 }
568}