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