1use std::collections::HashSet;
4
5use super::{Property, PropertyId};
6use crate::compat::Feature;
7use crate::context::PropertyHandlerContext;
8use crate::declaration::{DeclarationBlock, DeclarationList};
9use crate::error::{ParserError, PrinterError};
10use crate::macros::*;
11use crate::printer::Printer;
12use crate::targets::should_compile;
13use crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
14use crate::values::length::LengthValue;
15use crate::values::number::CSSNumber;
16use crate::values::string::CowArcStr;
17use crate::values::{angle::Angle, length::LengthPercentage, percentage::Percentage};
18#[cfg(feature = "visitor")]
19use crate::visitor::Visit;
20use cssparser::*;
21
22#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
24#[cfg_attr(feature = "visitor", derive(Visit))]
25#[cfg_attr(
26 feature = "serde",
27 derive(serde::Serialize, serde::Deserialize),
28 serde(tag = "type", content = "value", rename_all = "kebab-case")
29)]
30#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
31#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
32pub enum FontWeight {
33 Absolute(AbsoluteFontWeight),
35 Bolder,
37 Lighter,
39}
40
41impl Default for FontWeight {
42 fn default() -> FontWeight {
43 FontWeight::Absolute(AbsoluteFontWeight::default())
44 }
45}
46
47impl IsCompatible for FontWeight {
48 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
49 match self {
50 FontWeight::Absolute(a) => a.is_compatible(browsers),
51 FontWeight::Bolder | FontWeight::Lighter => true,
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Parse)]
61#[cfg_attr(feature = "visitor", derive(Visit))]
62#[cfg_attr(
63 feature = "serde",
64 derive(serde::Serialize, serde::Deserialize),
65 serde(tag = "type", content = "value", rename_all = "kebab-case")
66)]
67#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
68pub enum AbsoluteFontWeight {
69 Weight(CSSNumber),
71 Normal,
73 Bold,
75}
76
77impl Default for AbsoluteFontWeight {
78 fn default() -> AbsoluteFontWeight {
79 AbsoluteFontWeight::Normal
80 }
81}
82
83impl ToCss for AbsoluteFontWeight {
84 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
85 where
86 W: std::fmt::Write,
87 {
88 use AbsoluteFontWeight::*;
89 match self {
90 Weight(val) => val.to_css(dest),
91 Normal => dest.write_str(if dest.minify { "400" } else { "normal" }),
92 Bold => dest.write_str(if dest.minify { "700" } else { "bold" }),
93 }
94 }
95}
96
97impl IsCompatible for AbsoluteFontWeight {
98 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
99 match self {
100 AbsoluteFontWeight::Weight(val) if !(*val >= 100.0 && *val <= 900.0 && *val % 100.0 == 0.0) => {
102 Feature::FontWeightNumber.is_compatible(browsers)
103 }
104 _ => true,
105 }
106 }
107}
108
109enum_property! {
110 #[allow(missing_docs)]
115 pub enum AbsoluteFontSize {
116 "xx-small": XXSmall,
117 "x-small": XSmall,
118 "small": Small,
119 "medium": Medium,
120 "large": Large,
121 "x-large": XLarge,
122 "xx-large": XXLarge,
123 "xxx-large": XXXLarge,
124 }
125}
126
127impl IsCompatible for AbsoluteFontSize {
128 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
129 use AbsoluteFontSize::*;
130 match self {
131 XXXLarge => Feature::FontSizeXXXLarge.is_compatible(browsers),
132 _ => true,
133 }
134 }
135}
136
137enum_property! {
138 #[allow(missing_docs)]
143 pub enum RelativeFontSize {
144 Smaller,
145 Larger,
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
151#[cfg_attr(feature = "visitor", derive(Visit))]
152#[cfg_attr(
153 feature = "serde",
154 derive(serde::Serialize, serde::Deserialize),
155 serde(tag = "type", content = "value", rename_all = "kebab-case")
156)]
157#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
158#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
159pub enum FontSize {
160 Length(LengthPercentage),
162 Absolute(AbsoluteFontSize),
164 Relative(RelativeFontSize),
166}
167
168impl IsCompatible for FontSize {
169 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
170 match self {
171 FontSize::Length(LengthPercentage::Dimension(LengthValue::Rem(..))) => {
172 Feature::FontSizeRem.is_compatible(browsers)
173 }
174 FontSize::Length(l) => l.is_compatible(browsers),
175 FontSize::Absolute(a) => a.is_compatible(browsers),
176 FontSize::Relative(..) => true,
177 }
178 }
179}
180
181enum_property! {
182 pub enum FontStretchKeyword {
187 "normal": Normal,
189 "ultra-condensed": UltraCondensed,
191 "extra-condensed": ExtraCondensed,
193 "condensed": Condensed,
195 "semi-condensed": SemiCondensed,
197 "semi-expanded": SemiExpanded,
199 "expanded": Expanded,
201 "extra-expanded": ExtraExpanded,
203 "ultra-expanded": UltraExpanded,
205 }
206}
207
208impl Default for FontStretchKeyword {
209 fn default() -> FontStretchKeyword {
210 FontStretchKeyword::Normal
211 }
212}
213
214impl Into<Percentage> for &FontStretchKeyword {
215 fn into(self) -> Percentage {
216 use FontStretchKeyword::*;
217 let val = match self {
218 UltraCondensed => 0.5,
219 ExtraCondensed => 0.625,
220 Condensed => 0.75,
221 SemiCondensed => 0.875,
222 Normal => 1.0,
223 SemiExpanded => 1.125,
224 Expanded => 1.25,
225 ExtraExpanded => 1.5,
226 UltraExpanded => 2.0,
227 };
228 Percentage(val)
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Parse)]
234#[cfg_attr(feature = "visitor", derive(Visit))]
235#[cfg_attr(
236 feature = "serde",
237 derive(serde::Serialize, serde::Deserialize),
238 serde(tag = "type", content = "value", rename_all = "kebab-case")
239)]
240#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
241#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
242pub enum FontStretch {
243 Keyword(FontStretchKeyword),
245 Percentage(Percentage),
247}
248
249impl Default for FontStretch {
250 fn default() -> FontStretch {
251 FontStretch::Keyword(FontStretchKeyword::default())
252 }
253}
254
255impl Into<Percentage> for &FontStretch {
256 fn into(self) -> Percentage {
257 match self {
258 FontStretch::Percentage(val) => val.clone(),
259 FontStretch::Keyword(keyword) => keyword.into(),
260 }
261 }
262}
263
264impl ToCss for FontStretch {
265 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
266 where
267 W: std::fmt::Write,
268 {
269 if dest.minify {
270 let percentage: Percentage = self.into();
271 return percentage.to_css(dest);
272 }
273
274 match self {
275 FontStretch::Percentage(val) => val.to_css(dest),
276 FontStretch::Keyword(val) => val.to_css(dest),
277 }
278 }
279}
280
281impl IsCompatible for FontStretch {
282 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
283 match self {
284 FontStretch::Percentage(..) => Feature::FontStretchPercentage.is_compatible(browsers),
285 FontStretch::Keyword(..) => true,
286 }
287 }
288}
289
290enum_property! {
291 #[allow(missing_docs)]
296 #[derive(Eq, Hash)]
297 pub enum GenericFontFamily {
298 "serif": Serif,
299 "sans-serif": SansSerif,
300 "cursive": Cursive,
301 "fantasy": Fantasy,
302 "monospace": Monospace,
303 "system-ui": SystemUI,
304 "emoji": Emoji,
305 "math": Math,
306 "fangsong": FangSong,
307 "ui-serif": UISerif,
308 "ui-sans-serif": UISansSerif,
309 "ui-monospace": UIMonospace,
310 "ui-rounded": UIRounded,
311
312 "initial": Initial,
316 "inherit": Inherit,
317 "unset": Unset,
318 "default": Default,
321
322 "revert": Revert,
325 "revert-layer": RevertLayer,
326 }
327}
328
329impl IsCompatible for GenericFontFamily {
330 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
331 use GenericFontFamily::*;
332 match self {
333 SystemUI => Feature::FontFamilySystemUi.is_compatible(browsers),
334 UISerif | UISansSerif | UIMonospace | UIRounded => Feature::ExtendedSystemFonts.is_compatible(browsers),
335 _ => true,
336 }
337 }
338}
339
340#[derive(Debug, Clone, PartialEq, Eq, Hash)]
342#[cfg_attr(feature = "visitor", derive(Visit))]
343#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
344#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))]
345#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
346pub enum FontFamily<'i> {
347 Generic(GenericFontFamily),
349 #[cfg_attr(feature = "serde", serde(borrow))]
351 FamilyName(FamilyName<'i>),
352}
353
354impl<'i> Parse<'i> for FontFamily<'i> {
355 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
356 if let Ok(value) = input.try_parse(GenericFontFamily::parse) {
357 return Ok(FontFamily::Generic(value));
358 }
359
360 let family = FamilyName::parse(input)?;
361 Ok(FontFamily::FamilyName(family))
362 }
363}
364
365impl<'i> ToCss for FontFamily<'i> {
366 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
367 where
368 W: std::fmt::Write,
369 {
370 match self {
371 FontFamily::Generic(val) => val.to_css(dest),
372 FontFamily::FamilyName(val) => val.to_css(dest),
373 }
374 }
375}
376
377#[derive(Debug, Clone, PartialEq, Eq, Hash)]
379#[cfg_attr(feature = "visitor", derive(Visit))]
380#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
381#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
382#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
383pub struct FamilyName<'i>(#[cfg_attr(feature = "serde", serde(borrow))] CowArcStr<'i>);
384
385impl<'i> Parse<'i> for FamilyName<'i> {
386 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
387 if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) {
388 return Ok(FamilyName(value.into()));
389 }
390
391 let value: CowArcStr<'i> = input.expect_ident()?.into();
392 let mut string = None;
393 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
394 if string.is_none() {
395 string = Some(value.to_string());
396 }
397
398 if let Some(string) = &mut string {
399 string.push(' ');
400 string.push_str(&ident);
401 }
402 }
403
404 let value = if let Some(string) = string {
405 string.into()
406 } else {
407 value
408 };
409
410 Ok(FamilyName(value))
411 }
412}
413
414impl<'i> ToCss for FamilyName<'i> {
415 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
416 where
417 W: std::fmt::Write,
418 {
419 let val = &self.0;
423 if !val.is_empty() && !GenericFontFamily::parse_string(val).is_ok() {
424 let mut id = String::new();
425 let mut first = true;
426 for slice in val.split(' ') {
427 if first {
428 first = false;
429 } else {
430 id.push(' ');
431 }
432 serialize_identifier(slice, &mut id)?;
433 }
434 if id.len() < val.len() + 2 {
435 return dest.write_str(&id);
436 }
437 }
438 serialize_string(&val, dest)?;
439 Ok(())
440 }
441}
442
443impl IsCompatible for FontFamily<'_> {
444 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
445 match self {
446 FontFamily::Generic(g) => g.is_compatible(browsers),
447 FontFamily::FamilyName(..) => true,
448 }
449 }
450}
451
452#[derive(Debug, Clone, PartialEq)]
454#[cfg_attr(feature = "visitor", derive(Visit))]
455#[cfg_attr(
456 feature = "serde",
457 derive(serde::Serialize, serde::Deserialize),
458 serde(tag = "type", content = "value", rename_all = "kebab-case")
459)]
460#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
461#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
462pub enum FontStyle {
463 Normal,
465 Italic,
467 Oblique(#[cfg_attr(feature = "serde", serde(default = "FontStyle::default_oblique_angle"))] Angle),
469}
470
471impl Default for FontStyle {
472 fn default() -> FontStyle {
473 FontStyle::Normal
474 }
475}
476
477impl FontStyle {
478 #[inline]
479 pub(crate) fn default_oblique_angle() -> Angle {
480 Angle::Deg(14.0)
481 }
482}
483
484impl<'i> Parse<'i> for FontStyle {
485 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
486 let location = input.current_source_location();
487 let ident = input.expect_ident()?;
488 match_ignore_ascii_case! { &*ident,
489 "normal" => Ok(FontStyle::Normal),
490 "italic" => Ok(FontStyle::Italic),
491 "oblique" => {
492 let angle = input.try_parse(Angle::parse).unwrap_or(FontStyle::default_oblique_angle());
493 Ok(FontStyle::Oblique(angle))
494 },
495 _ => Err(location.new_unexpected_token_error(
496 cssparser::Token::Ident(ident.clone())
497 ))
498 }
499 }
500}
501
502impl ToCss for FontStyle {
503 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
504 where
505 W: std::fmt::Write,
506 {
507 match self {
508 FontStyle::Normal => dest.write_str("normal"),
509 FontStyle::Italic => dest.write_str("italic"),
510 FontStyle::Oblique(angle) => {
511 dest.write_str("oblique")?;
512 if *angle != FontStyle::default_oblique_angle() {
513 dest.write_char(' ')?;
514 angle.to_css(dest)?;
515 }
516 Ok(())
517 }
518 }
519 }
520}
521
522impl IsCompatible for FontStyle {
523 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
524 match self {
525 FontStyle::Oblique(angle) if *angle != FontStyle::default_oblique_angle() => {
526 Feature::FontStyleObliqueAngle.is_compatible(browsers)
527 }
528 FontStyle::Normal | FontStyle::Italic | FontStyle::Oblique(..) => true,
529 }
530 }
531}
532
533enum_property! {
534 pub enum FontVariantCaps {
536 Normal,
538 SmallCaps,
540 AllSmallCaps,
542 PetiteCaps,
544 AllPetiteCaps,
546 Unicase,
548 TitlingCaps,
550 }
551}
552
553impl Default for FontVariantCaps {
554 fn default() -> FontVariantCaps {
555 FontVariantCaps::Normal
556 }
557}
558
559impl FontVariantCaps {
560 fn is_css2(&self) -> bool {
561 matches!(self, FontVariantCaps::Normal | FontVariantCaps::SmallCaps)
562 }
563
564 fn parse_css2<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
565 let value = Self::parse(input)?;
566 if !value.is_css2() {
567 return Err(input.new_custom_error(ParserError::InvalidValue));
568 }
569 Ok(value)
570 }
571}
572
573impl IsCompatible for FontVariantCaps {
574 fn is_compatible(&self, _browsers: crate::targets::Browsers) -> bool {
575 true
576 }
577}
578
579#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
581#[cfg_attr(feature = "visitor", derive(Visit))]
582#[cfg_attr(
583 feature = "serde",
584 derive(serde::Serialize, serde::Deserialize),
585 serde(tag = "type", content = "value", rename_all = "kebab-case")
586)]
587#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
588#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
589pub enum LineHeight {
590 Normal,
592 Number(CSSNumber),
594 Length(LengthPercentage),
596}
597
598impl Default for LineHeight {
599 fn default() -> LineHeight {
600 LineHeight::Normal
601 }
602}
603
604impl IsCompatible for LineHeight {
605 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
606 match self {
607 LineHeight::Length(l) => l.is_compatible(browsers),
608 LineHeight::Normal | LineHeight::Number(..) => true,
609 }
610 }
611}
612
613enum_property! {
614 pub enum VerticalAlignKeyword {
616 Baseline,
618 Sub,
620 Super,
622 Top,
624 TextTop,
626 Middle,
628 Bottom,
630 TextBottom,
632 }
633}
634
635#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
638#[cfg_attr(feature = "visitor", derive(Visit))]
639#[cfg_attr(
640 feature = "serde",
641 derive(serde::Serialize, serde::Deserialize),
642 serde(tag = "type", content = "value", rename_all = "kebab-case")
643)]
644#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
645#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
646pub enum VerticalAlign {
647 Keyword(VerticalAlignKeyword),
649 Length(LengthPercentage),
651}
652
653define_shorthand! {
654 pub struct Font<'i> {
656 #[cfg_attr(feature = "serde", serde(borrow))]
658 family: FontFamily(Vec<FontFamily<'i>>),
659 size: FontSize(FontSize),
661 style: FontStyle(FontStyle),
663 weight: FontWeight(FontWeight),
665 stretch: FontStretch(FontStretch),
667 line_height: LineHeight(LineHeight),
669 variant_caps: FontVariantCaps(FontVariantCaps),
671 }
672}
673
674impl<'i> Parse<'i> for Font<'i> {
675 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
676 let mut style = None;
677 let mut weight = None;
678 let mut stretch = None;
679 let size;
680 let mut variant_caps = None;
681 let mut count = 0;
682
683 loop {
684 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
686 count += 1;
687 continue;
688 }
689 if style.is_none() {
690 if let Ok(value) = input.try_parse(FontStyle::parse) {
691 style = Some(value);
692 count += 1;
693 continue;
694 }
695 }
696 if weight.is_none() {
697 if let Ok(value) = input.try_parse(FontWeight::parse) {
698 weight = Some(value);
699 count += 1;
700 continue;
701 }
702 }
703 if variant_caps.is_none() {
704 if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) {
705 variant_caps = Some(value);
706 count += 1;
707 continue;
708 }
709 }
710
711 if stretch.is_none() {
712 if let Ok(value) = input.try_parse(FontStretchKeyword::parse) {
713 stretch = Some(FontStretch::Keyword(value));
714 count += 1;
715 continue;
716 }
717 }
718 size = Some(FontSize::parse(input)?);
719 break;
720 }
721
722 if count > 4 {
723 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
724 }
725
726 let size = match size {
727 Some(s) => s,
728 None => return Err(input.new_custom_error(ParserError::InvalidDeclaration)),
729 };
730
731 let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
732 Some(LineHeight::parse(input)?)
733 } else {
734 None
735 };
736
737 let family = input.parse_comma_separated(FontFamily::parse)?;
738 Ok(Font {
739 family,
740 size,
741 style: style.unwrap_or_default(),
742 weight: weight.unwrap_or_default(),
743 stretch: stretch.unwrap_or_default(),
744 line_height: line_height.unwrap_or_default(),
745 variant_caps: variant_caps.unwrap_or_default(),
746 })
747 }
748}
749
750impl<'i> ToCss for Font<'i> {
751 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
752 where
753 W: std::fmt::Write,
754 {
755 if self.style != FontStyle::default() {
756 self.style.to_css(dest)?;
757 dest.write_char(' ')?;
758 }
759
760 if self.variant_caps != FontVariantCaps::default() {
761 self.variant_caps.to_css(dest)?;
762 dest.write_char(' ')?;
763 }
764
765 if self.weight != FontWeight::default() {
766 self.weight.to_css(dest)?;
767 dest.write_char(' ')?;
768 }
769
770 if self.stretch != FontStretch::default() {
771 self.stretch.to_css(dest)?;
772 dest.write_char(' ')?;
773 }
774
775 self.size.to_css(dest)?;
776
777 if self.line_height != LineHeight::default() {
778 dest.delim('/', true)?;
779 self.line_height.to_css(dest)?;
780 }
781
782 dest.write_char(' ')?;
783
784 let len = self.family.len();
785 for (idx, val) in self.family.iter().enumerate() {
786 val.to_css(dest)?;
787 if idx < len - 1 {
788 dest.delim(',', false)?;
789 }
790 }
791
792 Ok(())
793 }
794}
795
796property_bitflags! {
797 #[derive(Default, Debug)]
798 struct FontProperty: u8 {
799 const FontFamily = 1 << 0;
800 const FontSize = 1 << 1;
801 const FontStyle = 1 << 2;
802 const FontWeight = 1 << 3;
803 const FontStretch = 1 << 4;
804 const LineHeight = 1 << 5;
805 const FontVariantCaps = 1 << 6;
806 const Font = Self::FontFamily.bits() | Self::FontSize.bits() | Self::FontStyle.bits() | Self::FontWeight.bits() | Self::FontStretch.bits() | Self::LineHeight.bits() | Self::FontVariantCaps.bits();
807 }
808}
809
810#[derive(Default, Debug)]
811pub(crate) struct FontHandler<'i> {
812 family: Option<Vec<FontFamily<'i>>>,
813 size: Option<FontSize>,
814 style: Option<FontStyle>,
815 weight: Option<FontWeight>,
816 stretch: Option<FontStretch>,
817 line_height: Option<LineHeight>,
818 variant_caps: Option<FontVariantCaps>,
819 flushed_properties: FontProperty,
820 has_any: bool,
821}
822
823impl<'i> PropertyHandler<'i> for FontHandler<'i> {
824 fn handle_property(
825 &mut self,
826 property: &Property<'i>,
827 dest: &mut DeclarationList<'i>,
828 context: &mut PropertyHandlerContext<'i, '_>,
829 ) -> bool {
830 use Property::*;
831
832 macro_rules! flush {
833 ($prop: ident, $val: expr) => {{
834 if self.$prop.is_some() && self.$prop.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
835 self.flush(dest, context);
836 }
837 }};
838 }
839
840 macro_rules! property {
841 ($prop: ident, $val: ident) => {{
842 flush!($prop, $val);
843 self.$prop = Some($val.clone());
844 self.has_any = true;
845 }};
846 }
847
848 match property {
849 FontFamily(val) => property!(family, val),
850 FontSize(val) => property!(size, val),
851 FontStyle(val) => property!(style, val),
852 FontWeight(val) => property!(weight, val),
853 FontStretch(val) => property!(stretch, val),
854 FontVariantCaps(val) => property!(variant_caps, val),
855 LineHeight(val) => property!(line_height, val),
856 Font(val) => {
857 flush!(family, &val.family);
858 flush!(size, &val.size);
859 flush!(style, &val.style);
860 flush!(weight, &val.weight);
861 flush!(stretch, &val.stretch);
862 flush!(line_height, &val.line_height);
863 flush!(variant_caps, &val.variant_caps);
864 self.family = Some(val.family.clone());
865 self.size = Some(val.size.clone());
866 self.style = Some(val.style.clone());
867 self.weight = Some(val.weight.clone());
868 self.stretch = Some(val.stretch.clone());
869 self.line_height = Some(val.line_height.clone());
870 self.variant_caps = Some(val.variant_caps.clone());
871 self.has_any = true;
872 }
874 Unparsed(val) if is_font_property(&val.property_id) => {
875 self.flush(dest, context);
876 self
877 .flushed_properties
878 .insert(FontProperty::try_from(&val.property_id).unwrap());
879 dest.push(property.clone());
880 }
881 _ => return false,
882 }
883
884 true
885 }
886
887 fn finalize(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
888 self.flush(decls, context);
889 self.flushed_properties = FontProperty::empty();
890 }
891}
892
893impl<'i> FontHandler<'i> {
894 fn flush(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
895 if !self.has_any {
896 return;
897 }
898
899 self.has_any = false;
900
901 macro_rules! push {
902 ($prop: ident, $val: expr) => {
903 decls.push(Property::$prop($val));
904 self.flushed_properties.insert(FontProperty::$prop);
905 };
906 }
907
908 let mut family = std::mem::take(&mut self.family);
909 if !self.flushed_properties.contains(FontProperty::FontFamily) {
910 family = compatible_font_family(family, !should_compile!(context.targets, FontFamilySystemUi));
911 }
912 let size = std::mem::take(&mut self.size);
913 let style = std::mem::take(&mut self.style);
914 let weight = std::mem::take(&mut self.weight);
915 let stretch = std::mem::take(&mut self.stretch);
916 let line_height = std::mem::take(&mut self.line_height);
917 let variant_caps = std::mem::take(&mut self.variant_caps);
918
919 if let Some(family) = &mut family {
920 if family.len() > 1 {
921 let mut seen = HashSet::new();
923 family.retain(|f| seen.insert(f.clone()));
924 }
925 }
926
927 if family.is_some()
928 && size.is_some()
929 && style.is_some()
930 && weight.is_some()
931 && stretch.is_some()
932 && line_height.is_some()
933 && variant_caps.is_some()
934 {
935 let caps = variant_caps.unwrap();
936 push!(
937 Font,
938 Font {
939 family: family.unwrap(),
940 size: size.unwrap(),
941 style: style.unwrap(),
942 weight: weight.unwrap(),
943 stretch: stretch.unwrap(),
944 line_height: line_height.unwrap(),
945 variant_caps: if caps.is_css2() {
946 caps
947 } else {
948 FontVariantCaps::default()
949 },
950 }
951 );
952
953 if !caps.is_css2() {
956 push!(FontVariantCaps, variant_caps.unwrap());
957 }
958 } else {
959 if let Some(val) = family {
960 push!(FontFamily, val);
961 }
962
963 if let Some(val) = size {
964 push!(FontSize, val);
965 }
966
967 if let Some(val) = style {
968 push!(FontStyle, val);
969 }
970
971 if let Some(val) = variant_caps {
972 push!(FontVariantCaps, val);
973 }
974
975 if let Some(val) = weight {
976 push!(FontWeight, val);
977 }
978
979 if let Some(val) = stretch {
980 push!(FontStretch, val);
981 }
982
983 if let Some(val) = line_height {
984 push!(LineHeight, val);
985 }
986 }
987 }
988}
989
990const SYSTEM_UI: FontFamily = FontFamily::Generic(GenericFontFamily::SystemUI);
991
992const DEFAULT_SYSTEM_FONTS: &[&str] = &[
993 "-apple-system",
995 "BlinkMacSystemFont",
997 "Segoe UI", "Roboto", "Noto Sans", "Ubuntu", "Cantarell", "Helvetica Neue",
1003];
1004
1005#[inline]
1009fn compatible_font_family(mut family: Option<Vec<FontFamily>>, is_supported: bool) -> Option<Vec<FontFamily>> {
1010 if is_supported {
1011 return family;
1012 }
1013
1014 if let Some(families) = &mut family {
1015 if let Some(position) = families.iter().position(|v| *v == SYSTEM_UI) {
1016 families.splice(
1017 (position + 1)..(position + 1),
1018 DEFAULT_SYSTEM_FONTS
1019 .iter()
1020 .map(|name| FontFamily::FamilyName(FamilyName(CowArcStr::from(*name)))),
1021 );
1022 }
1023 }
1024
1025 return family;
1026}
1027
1028#[inline]
1029fn is_font_property(property_id: &PropertyId) -> bool {
1030 match property_id {
1031 PropertyId::FontFamily
1032 | PropertyId::FontSize
1033 | PropertyId::FontStyle
1034 | PropertyId::FontWeight
1035 | PropertyId::FontStretch
1036 | PropertyId::FontVariantCaps
1037 | PropertyId::LineHeight
1038 | PropertyId::Font => true,
1039 _ => false,
1040 }
1041}