1use super::Location;
4use crate::error::{ParserError, PrinterError};
5use crate::macros::enum_property;
6use crate::printer::Printer;
7use crate::properties::custom::CustomProperty;
8use crate::properties::font::{FontFamily, FontStretch, FontStyle as FontStyleProperty, FontWeight};
9use crate::stylesheet::ParserOptions;
10use crate::traits::{Parse, ToCss};
11use crate::values::angle::Angle;
12use crate::values::size::Size2D;
13use crate::values::string::CowArcStr;
14use crate::values::url::Url;
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18use std::fmt::Write;
19
20#[derive(Debug, PartialEq, Clone)]
22#[cfg_attr(feature = "visitor", derive(Visit))]
23#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
26pub struct FontFaceRule<'i> {
27 #[cfg_attr(feature = "serde", serde(borrow))]
29 pub properties: Vec<FontFaceProperty<'i>>,
30 #[cfg_attr(feature = "visitor", skip_visit)]
32 pub loc: Location,
33}
34
35#[derive(Debug, Clone, PartialEq)]
39#[cfg_attr(feature = "visitor", derive(Visit))]
40#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
41#[cfg_attr(
42 feature = "serde",
43 derive(serde::Serialize, serde::Deserialize),
44 serde(tag = "type", content = "value", rename_all = "kebab-case")
45)]
46#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
47pub enum FontFaceProperty<'i> {
48 #[cfg_attr(feature = "serde", serde(borrow))]
50 Source(Vec<Source<'i>>),
51 FontFamily(FontFamily<'i>),
53 FontStyle(FontStyle),
55 FontWeight(Size2D<FontWeight>),
57 FontStretch(Size2D<FontStretch>),
59 UnicodeRange(Vec<UnicodeRange>),
61 Custom(CustomProperty<'i>),
63}
64
65#[derive(Debug, Clone, PartialEq)]
68#[cfg_attr(feature = "visitor", derive(Visit))]
69#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
70#[cfg_attr(
71 feature = "serde",
72 derive(serde::Serialize, serde::Deserialize),
73 serde(tag = "type", content = "value", rename_all = "kebab-case")
74)]
75#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
76pub enum Source<'i> {
77 Url(UrlSource<'i>),
79 #[cfg_attr(feature = "serde", serde(borrow))]
81 Local(FontFamily<'i>),
82}
83
84impl<'i> Parse<'i> for Source<'i> {
85 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
86 match input.try_parse(UrlSource::parse) {
87 Ok(url) => return Ok(Source::Url(url)),
88 e @ Err(ParseError {
89 kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid),
90 ..
91 }) => {
92 return Err(e.err().unwrap());
93 }
94 _ => {}
95 }
96
97 input.expect_function_matching("local")?;
98 let local = input.parse_nested_block(FontFamily::parse)?;
99 Ok(Source::Local(local))
100 }
101}
102
103impl<'i> ToCss for Source<'i> {
104 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
105 where
106 W: std::fmt::Write,
107 {
108 match self {
109 Source::Url(url) => url.to_css(dest),
110 Source::Local(local) => {
111 dest.write_str("local(")?;
112 local.to_css(dest)?;
113 dest.write_char(')')
114 }
115 }
116 }
117}
118
119#[derive(Debug, Clone, PartialEq)]
122#[cfg_attr(feature = "visitor", derive(Visit))]
123#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
126pub struct UrlSource<'i> {
127 pub url: Url<'i>,
129 #[cfg_attr(feature = "serde", serde(borrow))]
131 pub format: Option<FontFormat<'i>>,
132 pub tech: Vec<FontTechnology>,
134}
135
136impl<'i> Parse<'i> for UrlSource<'i> {
137 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
138 let url = Url::parse(input)?;
139
140 let format = if input.try_parse(|input| input.expect_function_matching("format")).is_ok() {
141 Some(input.parse_nested_block(FontFormat::parse)?)
142 } else {
143 None
144 };
145
146 let tech = if input.try_parse(|input| input.expect_function_matching("tech")).is_ok() {
147 input.parse_nested_block(Vec::<FontTechnology>::parse)?
148 } else {
149 vec![]
150 };
151
152 Ok(UrlSource { url, format, tech })
153 }
154}
155
156impl<'i> ToCss for UrlSource<'i> {
157 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
158 where
159 W: std::fmt::Write,
160 {
161 self.url.to_css(dest)?;
162 if let Some(format) = &self.format {
163 dest.whitespace()?;
164 dest.write_str("format(")?;
165 format.to_css(dest)?;
166 dest.write_char(')')?;
167 }
168
169 if !self.tech.is_empty() {
170 dest.whitespace()?;
171 dest.write_str("tech(")?;
172 self.tech.to_css(dest)?;
173 dest.write_char(')')?;
174 }
175 Ok(())
176 }
177}
178
179#[derive(Debug, Clone, PartialEq)]
183#[cfg_attr(feature = "visitor", derive(Visit))]
184#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
185#[cfg_attr(
186 feature = "serde",
187 derive(serde::Serialize, serde::Deserialize),
188 serde(tag = "type", content = "value", rename_all = "lowercase")
189)]
190#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
191pub enum FontFormat<'i> {
192 WOFF,
195 WOFF2,
197 TrueType,
199 OpenType,
201 #[cfg_attr(feature = "serde", serde(rename = "embedded-opentype"))]
203 EmbeddedOpenType,
204 Collection,
206 SVG,
208 #[cfg_attr(feature = "serde", serde(borrow))]
210 String(CowArcStr<'i>),
211}
212
213impl<'i> Parse<'i> for FontFormat<'i> {
214 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
215 let s = input.expect_ident_or_string()?;
216 match_ignore_ascii_case! { &s,
217 "woff" => Ok(FontFormat::WOFF),
218 "woff2" => Ok(FontFormat::WOFF2),
219 "truetype" => Ok(FontFormat::TrueType),
220 "opentype" => Ok(FontFormat::OpenType),
221 "embedded-opentype" => Ok(FontFormat::EmbeddedOpenType),
222 "collection" => Ok(FontFormat::Collection),
223 "svg" => Ok(FontFormat::SVG),
224 _ => Ok(FontFormat::String(s.into()))
225 }
226 }
227}
228
229impl<'i> ToCss for FontFormat<'i> {
230 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
231 where
232 W: std::fmt::Write,
233 {
234 use FontFormat::*;
235 let s = match self {
236 WOFF => "woff",
237 WOFF2 => "woff2",
238 TrueType => "truetype",
239 OpenType => "opentype",
240 EmbeddedOpenType => "embedded-opentype",
241 Collection => "collection",
242 SVG => "svg",
243 String(s) => &s,
244 };
245 serialize_string(&s, dest)?;
248 Ok(())
249 }
250}
251
252enum_property! {
253 pub enum FontTechnology {
257 "features-opentype": FeaturesOpentype,
263 "features-aat": FeaturesAat,
266 "features-graphite": FeaturesGraphite,
269
270 "color-colrv0": ColorCOLRv0,
275 "color-colrv1": ColorCOLRv1,
277 "color-svg": ColorSVG,
279 "color-sbix": ColorSbix,
281 "color-cbdt": ColorCBDT,
283
284 "variations": Variations,
287 "palettes": Palettes,
290 "incremental": Incremental,
293 }
294}
295
296#[derive(Debug, Clone, PartialEq)]
300#[cfg_attr(feature = "visitor", derive(Visit))]
301#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
302#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
303#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
304pub struct UnicodeRange {
305 pub start: u32,
307 pub end: u32,
309}
310
311impl<'i> Parse<'i> for UnicodeRange {
312 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
313 let range = cssparser::UnicodeRange::parse(input)?;
314 Ok(UnicodeRange {
315 start: range.start,
316 end: range.end,
317 })
318 }
319}
320
321impl ToCss for UnicodeRange {
322 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
323 where
324 W: std::fmt::Write,
325 {
326 if self.start != self.end {
328 let mut shift = 24;
330 let mut mask = 0xf << shift;
331 while shift > 0 {
332 let c1 = self.start & mask;
333 let c2 = self.end & mask;
334 if c1 != c2 {
335 break;
336 }
337
338 mask = mask >> 4;
339 shift -= 4;
340 }
341
342 shift += 4;
345 let remainder_mask = (1 << shift) - 1;
346 let start_remainder = self.start & remainder_mask;
347 let end_remainder = self.end & remainder_mask;
348
349 if start_remainder == 0 && end_remainder == remainder_mask {
350 let start = (self.start & !remainder_mask) >> shift;
351 if start != 0 {
352 write!(dest, "U+{:X}", start)?;
353 } else {
354 dest.write_str("U+")?;
355 }
356
357 while shift > 0 {
358 dest.write_char('?')?;
359 shift -= 4;
360 }
361
362 return Ok(());
363 }
364 }
365
366 write!(dest, "U+{:X}", self.start)?;
367 if self.end != self.start {
368 write!(dest, "-{:X}", self.end)?;
369 }
370 Ok(())
371 }
372}
373
374#[derive(Debug, Clone, PartialEq)]
376#[cfg_attr(feature = "visitor", derive(Visit))]
377#[cfg_attr(
378 feature = "serde",
379 derive(serde::Serialize, serde::Deserialize),
380 serde(tag = "type", content = "value", rename_all = "kebab-case")
381)]
382#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
383#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
384pub enum FontStyle {
385 Normal,
387 Italic,
389 Oblique(#[cfg_attr(feature = "serde", serde(default = "FontStyle::default_oblique_angle"))] Size2D<Angle>),
391}
392
393impl Default for FontStyle {
394 fn default() -> FontStyle {
395 FontStyle::Normal
396 }
397}
398
399impl FontStyle {
400 #[inline]
401 fn default_oblique_angle() -> Size2D<Angle> {
402 Size2D(
403 FontStyleProperty::default_oblique_angle(),
404 FontStyleProperty::default_oblique_angle(),
405 )
406 }
407}
408
409impl<'i> Parse<'i> for FontStyle {
410 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
411 Ok(match FontStyleProperty::parse(input)? {
412 FontStyleProperty::Normal => FontStyle::Normal,
413 FontStyleProperty::Italic => FontStyle::Italic,
414 FontStyleProperty::Oblique(angle) => {
415 let second_angle = input.try_parse(Angle::parse).unwrap_or_else(|_| angle.clone());
416 FontStyle::Oblique(Size2D(angle, second_angle))
417 }
418 })
419 }
420}
421
422impl ToCss for FontStyle {
423 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
424 where
425 W: std::fmt::Write,
426 {
427 match self {
428 FontStyle::Normal => dest.write_str("normal"),
429 FontStyle::Italic => dest.write_str("italic"),
430 FontStyle::Oblique(angle) => {
431 dest.write_str("oblique")?;
432 if *angle != FontStyle::default_oblique_angle() {
433 dest.write_char(' ')?;
434 angle.to_css(dest)?;
435 }
436 Ok(())
437 }
438 }
439 }
440}
441
442pub(crate) struct FontFaceDeclarationParser;
443
444impl<'i> cssparser::DeclarationParser<'i> for FontFaceDeclarationParser {
446 type Declaration = FontFaceProperty<'i>;
447 type Error = ParserError<'i>;
448
449 fn parse_value<'t>(
450 &mut self,
451 name: CowRcStr<'i>,
452 input: &mut cssparser::Parser<'i, 't>,
453 ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
454 macro_rules! property {
455 ($property: ident, $type: ty) => {
456 if let Ok(c) = <$type>::parse(input) {
457 if input.expect_exhausted().is_ok() {
458 return Ok(FontFaceProperty::$property(c));
459 }
460 }
461 };
462 }
463
464 let state = input.state();
465 match_ignore_ascii_case! { &name,
466 "src" => {
467 if let Ok(sources) = input.parse_comma_separated(Source::parse) {
468 return Ok(FontFaceProperty::Source(sources))
469 }
470 },
471 "font-family" => property!(FontFamily, FontFamily),
472 "font-weight" => property!(FontWeight, Size2D<FontWeight>),
473 "font-style" => property!(FontStyle, FontStyle),
474 "font-stretch" => property!(FontStretch, Size2D<FontStretch>),
475 "unicode-range" => property!(UnicodeRange, Vec<UnicodeRange>),
476 _ => {}
477 }
478
479 input.reset(&state);
480 return Ok(FontFaceProperty::Custom(CustomProperty::parse(
481 name.into(),
482 input,
483 &ParserOptions::default(),
484 )?));
485 }
486}
487
488impl<'i> AtRuleParser<'i> for FontFaceDeclarationParser {
490 type Prelude = ();
491 type AtRule = FontFaceProperty<'i>;
492 type Error = ParserError<'i>;
493}
494
495impl<'i> QualifiedRuleParser<'i> for FontFaceDeclarationParser {
496 type Prelude = ();
497 type QualifiedRule = FontFaceProperty<'i>;
498 type Error = ParserError<'i>;
499}
500
501impl<'i> RuleBodyItemParser<'i, FontFaceProperty<'i>, ParserError<'i>> for FontFaceDeclarationParser {
502 fn parse_qualified(&self) -> bool {
503 false
504 }
505
506 fn parse_declarations(&self) -> bool {
507 true
508 }
509}
510
511impl<'i> ToCss for FontFaceRule<'i> {
512 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
513 where
514 W: std::fmt::Write,
515 {
516 #[cfg(feature = "sourcemap")]
517 dest.add_mapping(self.loc);
518 dest.write_str("@font-face")?;
519 dest.whitespace()?;
520 dest.write_char('{')?;
521 dest.indent();
522 let len = self.properties.len();
523 for (i, prop) in self.properties.iter().enumerate() {
524 dest.newline()?;
525 prop.to_css(dest)?;
526 if i != len - 1 || !dest.minify {
527 dest.write_char(';')?;
528 }
529 }
530 dest.dedent();
531 dest.newline()?;
532 dest.write_char('}')
533 }
534}
535
536impl<'i> ToCss for FontFaceProperty<'i> {
537 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
538 where
539 W: std::fmt::Write,
540 {
541 use FontFaceProperty::*;
542 macro_rules! property {
543 ($prop: literal, $value: expr) => {{
544 dest.write_str($prop)?;
545 dest.delim(':', false)?;
546 $value.to_css(dest)
547 }};
548 ($prop: literal, $value: expr, $multi: expr) => {{
549 dest.write_str($prop)?;
550 dest.delim(':', false)?;
551 let len = $value.len();
552 for (idx, val) in $value.iter().enumerate() {
553 val.to_css(dest)?;
554 if idx < len - 1 {
555 dest.delim(',', false)?;
556 }
557 }
558 Ok(())
559 }};
560 }
561
562 match self {
563 Source(value) => property!("src", value, true),
564 FontFamily(value) => property!("font-family", value),
565 FontStyle(value) => property!("font-style", value),
566 FontWeight(value) => property!("font-weight", value),
567 FontStretch(value) => property!("font-stretch", value),
568 UnicodeRange(value) => property!("unicode-range", value),
569 Custom(custom) => {
570 dest.write_str(custom.name.as_ref())?;
571 dest.delim(':', false)?;
572 custom.value.to_css(dest, true)
573 }
574 }
575 }
576}