1use crate::derives::*;
10use crate::error_reporting::ContextualParseError;
11use crate::parser::{Parse, ParserContext};
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::values::computed::font::FontStyleFixedPoint;
14use crate::values::computed::FontWeight;
15use crate::values::generics::font::FontStyle as GenericFontStyle;
16use crate::values::specified::{url::SpecifiedUrl, Angle};
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, FontStretch};
23pub use crate::values::specified::font::{
24 AbsoluteFontWeight, FontFeatureSettings, FontLanguageOverride,
25 FontStretch as SpecifiedFontStretch, FontVariationSettings, MetricsOverride,
26 SpecifiedFontStyle,
27};
28
29#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
31#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
32pub enum Source {
33 Url(UrlSource),
35 #[css(function)]
37 Local(FamilyName),
38}
39
40#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
42#[css(comma)]
43pub struct SourceList(#[css(iterable)] pub Vec<Source>);
44
45impl Parse for SourceList {
49 fn parse<'i, 't>(
50 context: &ParserContext,
51 input: &mut Parser<'i, 't>,
52 ) -> Result<Self, ParseError<'i>> {
53 let list = input
55 .parse_comma_separated(|input| {
56 let s = input.parse_entirely(|input| Source::parse(context, input));
57 while input.next().is_ok() {}
58 Ok(s.ok())
59 })?
60 .into_iter()
61 .filter_map(|s| s)
62 .collect::<Vec<Source>>();
63 if list.is_empty() {
64 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
65 } else {
66 Ok(SourceList(list))
67 }
68 }
69}
70
71#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
74#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
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, Eq, MallocSizeOf, PartialEq, ToShmem)]
94#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
95#[repr(C)]
96pub struct FontFaceSourceTechFlags(u16);
97bitflags! {
98 impl FontFaceSourceTechFlags: u16 {
99 const FEATURES_OPENTYPE = 1 << 0;
101 const FEATURES_AAT = 1 << 1;
103 const FEATURES_GRAPHITE = 1 << 2;
105 const COLOR_COLRV0 = 1 << 3;
107 const COLOR_COLRV1 = 1 << 4;
109 const COLOR_SVG = 1 << 5;
111 const COLOR_SBIX = 1 << 6;
113 const COLOR_CBDT = 1 << 7;
115 const VARIATIONS = 1 << 8;
117 const PALETTES = 1 << 9;
119 const INCREMENTAL = 1 << 10;
121 }
122}
123
124impl FontFaceSourceTechFlags {
125 pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
127 Ok(try_match_ident_ignore_ascii_case! { input,
128 "features-opentype" => Self::FEATURES_OPENTYPE,
129 "features-aat" => Self::FEATURES_AAT,
130 "features-graphite" => Self::FEATURES_GRAPHITE,
131 "color-colrv0" => Self::COLOR_COLRV0,
132 "color-colrv1" => Self::COLOR_COLRV1,
133 "color-svg" => Self::COLOR_SVG,
134 "color-sbix" => Self::COLOR_SBIX,
135 "color-cbdt" => Self::COLOR_CBDT,
136 "variations" => Self::VARIATIONS,
137 "palettes" => Self::PALETTES,
138 "incremental" => Self::INCREMENTAL,
139 })
140 }
141}
142
143impl Parse for FontFaceSourceTechFlags {
144 fn parse<'i, 't>(
145 _context: &ParserContext,
146 input: &mut Parser<'i, 't>,
147 ) -> Result<Self, ParseError<'i>> {
148 let location = input.current_source_location();
149 let mut result = Self::empty();
152 input.parse_comma_separated(|input| {
153 let flag = Self::parse_one(input)?;
154 result.insert(flag);
155 Ok(())
156 })?;
157 if !result.is_empty() {
158 Ok(result)
159 } else {
160 Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
161 }
162 }
163}
164
165#[allow(unused_assignments)]
166impl ToCss for FontFaceSourceTechFlags {
167 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
168 where
169 W: fmt::Write,
170 {
171 let mut first = true;
172
173 macro_rules! write_if_flag {
174 ($s:expr => $f:ident) => {
175 if self.contains(Self::$f) {
176 if first {
177 first = false;
178 } else {
179 dest.write_str(", ")?;
180 }
181 dest.write_str($s)?;
182 }
183 };
184 }
185
186 write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
187 write_if_flag!("features-aat" => FEATURES_AAT);
188 write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
189 write_if_flag!("color-colrv0" => COLOR_COLRV0);
190 write_if_flag!("color-colrv1" => COLOR_COLRV1);
191 write_if_flag!("color-svg" => COLOR_SVG);
192 write_if_flag!("color-sbix" => COLOR_SBIX);
193 write_if_flag!("color-cbdt" => COLOR_CBDT);
194 write_if_flag!("variations" => VARIATIONS);
195 write_if_flag!("palettes" => PALETTES);
196 write_if_flag!("incremental" => INCREMENTAL);
197
198 Ok(())
199 }
200}
201
202#[derive(Clone, Debug, ToShmem, PartialEq)]
204pub struct FontFaceRule {
205 pub descriptors: Descriptors,
207 pub source_location: SourceLocation,
209}
210
211impl FontFaceRule {
212 pub fn empty(source_location: SourceLocation) -> Self {
214 Self {
215 descriptors: Default::default(),
216 source_location,
217 }
218 }
219}
220
221#[cfg(feature = "gecko")]
226#[derive(Clone, Copy, Debug, Eq, PartialEq)]
227#[repr(u8)]
228#[allow(missing_docs)]
229pub enum FontFaceSourceListComponent {
230 Url(*const crate::url::CssUrl),
231 Local(*mut crate::gecko_bindings::structs::nsAtom),
232 FormatHintKeyword(FontFaceSourceFormatKeyword),
233 FormatHintString {
234 length: usize,
235 utf8_bytes: *const u8,
236 },
237 TechFlags(FontFaceSourceTechFlags),
238}
239
240#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)]
241#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
242#[repr(u8)]
243#[allow(missing_docs)]
244pub enum FontFaceSourceFormat {
245 Keyword(FontFaceSourceFormatKeyword),
246 String(String),
247}
248
249#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
254#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
255pub struct UrlSource {
256 pub url: SpecifiedUrl,
258 pub format_hint: Option<FontFaceSourceFormat>,
260 pub tech_flags: FontFaceSourceTechFlags,
262}
263
264impl ToCss for UrlSource {
265 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
266 where
267 W: fmt::Write,
268 {
269 self.url.to_css(dest)?;
270 if let Some(hint) = &self.format_hint {
271 dest.write_str(" format(")?;
272 hint.to_css(dest)?;
273 dest.write_char(')')?;
274 }
275 if !self.tech_flags.is_empty() {
276 dest.write_str(" tech(")?;
277 self.tech_flags.to_css(dest)?;
278 dest.write_char(')')?;
279 }
280 Ok(())
281 }
282}
283
284#[allow(missing_docs)]
288#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
289#[derive(
290 Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
291)]
292#[repr(u8)]
293pub enum FontDisplay {
294 Auto,
295 Block,
296 Swap,
297 Fallback,
298 Optional,
299}
300
301macro_rules! impl_range {
302 ($range:ident, $component:ident) => {
303 impl Parse for $range {
304 fn parse<'i, 't>(
305 context: &ParserContext,
306 input: &mut Parser<'i, 't>,
307 ) -> Result<Self, ParseError<'i>> {
308 let first = $component::parse(context, input)?;
309 let second = input
310 .try_parse(|input| $component::parse(context, input))
311 .unwrap_or_else(|_| first.clone());
312 Ok($range(first, second))
313 }
314 }
315 impl ToCss for $range {
316 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
317 where
318 W: fmt::Write,
319 {
320 self.0.to_css(dest)?;
321 if self.0 != self.1 {
322 dest.write_char(' ')?;
323 self.1.to_css(dest)?;
324 }
325 Ok(())
326 }
327 }
328 };
329}
330
331#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
335pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
336impl_range!(FontWeightRange, AbsoluteFontWeight);
337
338#[repr(C)]
343#[allow(missing_docs)]
344#[cfg_attr(
345 feature = "servo",
346 derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)
347)]
348pub struct ComputedFontWeightRange(pub FontWeight, pub FontWeight);
349
350#[inline]
351fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
352 if a > b {
353 (b, a)
354 } else {
355 (a, b)
356 }
357}
358
359impl FontWeightRange {
360 pub fn compute(&self) -> Option<ComputedFontWeightRange> {
362 let (min, max) = sort_range(self.0.compute()?, self.1.compute()?);
363 Some(ComputedFontWeightRange(min, max))
364 }
365}
366
367#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
371pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
372impl_range!(FontStretchRange, SpecifiedFontStretch);
373
374#[repr(C)]
377#[allow(missing_docs)]
378#[cfg_attr(
379 feature = "servo",
380 derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)
381)]
382pub struct ComputedFontStretchRange(pub FontStretch, pub FontStretch);
383
384impl FontStretchRange {
385 pub fn compute(&self) -> Option<ComputedFontStretchRange> {
388 fn compute_stretch(s: &SpecifiedFontStretch) -> Option<FontStretch> {
389 match *s {
390 SpecifiedFontStretch::Keyword(ref kw) => Some(kw.compute()),
391 SpecifiedFontStretch::Stretch(ref p) => {
392 Some(FontStretch::from_percentage(p.compute()?.0))
393 },
394 SpecifiedFontStretch::System(..) => unreachable!(),
395 }
396 }
397
398 let (min, max) = sort_range(compute_stretch(&self.0)?, compute_stretch(&self.1)?);
399 Some(ComputedFontStretchRange(min, max))
400 }
401}
402
403#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
407#[allow(missing_docs)]
408pub enum FontStyle {
409 Italic,
410 Oblique(Angle, Angle),
411}
412
413#[repr(u8)]
416#[allow(missing_docs)]
417#[cfg_attr(feature = "servo", derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize))]
418pub enum ComputedFontStyleDescriptor {
419 Italic,
420 Oblique(FontStyleFixedPoint, FontStyleFixedPoint),
421}
422
423impl Parse for FontStyle {
424 fn parse<'i, 't>(
425 context: &ParserContext,
426 input: &mut Parser<'i, 't>,
427 ) -> Result<Self, ParseError<'i>> {
428 if input
431 .try_parse(|i| i.expect_ident_matching("normal"))
432 .is_ok()
433 {
434 return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
435 }
436
437 let style = SpecifiedFontStyle::parse(context, input)?;
438 Ok(match style {
439 GenericFontStyle::Italic => FontStyle::Italic,
440 GenericFontStyle::Oblique(angle) => {
441 let second_angle = input
442 .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
443 .unwrap_or_else(|_| angle.clone());
444
445 FontStyle::Oblique(angle, second_angle)
446 },
447 })
448 }
449}
450
451impl ToCss for FontStyle {
452 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
453 where
454 W: fmt::Write,
455 {
456 match *self {
457 FontStyle::Italic => dest.write_str("italic"),
458 FontStyle::Oblique(ref first, ref second) => {
459 if *first == Angle::zero() && first == second {
462 return dest.write_str("normal");
463 }
464 dest.write_str("oblique")?;
465 if *first != SpecifiedFontStyle::default_angle() || first != second {
466 dest.write_char(' ')?;
467 first.to_css(dest)?;
468 }
469 if first != second {
470 dest.write_char(' ')?;
471 second.to_css(dest)?;
472 }
473 Ok(())
474 },
475 }
476 }
477}
478
479impl FontStyle {
480 pub fn compute(&self) -> Option<ComputedFontStyleDescriptor> {
482 match *self {
483 FontStyle::Italic => Some(ComputedFontStyleDescriptor::Italic),
484 FontStyle::Oblique(ref first, ref second) => {
485 let first = SpecifiedFontStyle::compute_angle_degrees(first)?;
486 let second = SpecifiedFontStyle::compute_angle_degrees(second)?;
487 let (min, max) = sort_range(first, second);
488 Some(ComputedFontStyleDescriptor::Oblique(
489 FontStyleFixedPoint::from_float(min),
490 FontStyleFixedPoint::from_float(max),
491 ))
492 },
493 }
494 }
495}
496
497pub fn parse_font_face_block(
501 context: &ParserContext,
502 input: &mut Parser,
503 source_location: SourceLocation,
504) -> FontFaceRule {
505 let mut rule = FontFaceRule::empty(source_location);
506 {
507 let mut parser = DescriptorParser {
508 context,
509 descriptors: &mut rule.descriptors,
510 };
511 let mut iter = RuleBodyParser::new(input, &mut parser);
512 while let Some(declaration) = iter.next() {
513 if let Err((error, slice)) = declaration {
514 let location = error.location;
515 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
516 context.log_css_error(location, error)
517 }
518 }
519 }
520 rule
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
575impl ToCssWithGuard for FontFaceRule {
576 fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
578 dest.write_str("@font-face { ")?;
579 self.descriptors.to_css(&mut CssWriter::new(dest))?;
580 dest.write_char('}')
581 }
582}