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 needs_quotes = val.contains(" ");
426 if needs_quotes {
427 serialize_string(&val, dest)?;
428 return Ok(());
429 }
430
431 let mut id = String::new();
432 let mut first = true;
433 for slice in val.split(' ') {
434 if first {
435 first = false;
436 } else {
437 id.push(' ');
438 }
439 serialize_identifier(slice, &mut id)?;
440 }
441 if id.len() < val.len() + 2 {
442 return dest.write_str(&id);
443 }
444 }
445 serialize_string(&val, dest)?;
446 Ok(())
447 }
448}
449
450impl IsCompatible for FontFamily<'_> {
451 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
452 match self {
453 FontFamily::Generic(g) => g.is_compatible(browsers),
454 FontFamily::FamilyName(..) => true,
455 }
456 }
457}
458
459#[derive(Debug, Clone, PartialEq)]
461#[cfg_attr(feature = "visitor", derive(Visit))]
462#[cfg_attr(
463 feature = "serde",
464 derive(serde::Serialize, serde::Deserialize),
465 serde(tag = "type", content = "value", rename_all = "kebab-case")
466)]
467#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
468#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
469pub enum FontStyle {
470 Normal,
472 Italic,
474 Oblique(#[cfg_attr(feature = "serde", serde(default = "FontStyle::default_oblique_angle"))] Angle),
476}
477
478impl Default for FontStyle {
479 fn default() -> FontStyle {
480 FontStyle::Normal
481 }
482}
483
484impl FontStyle {
485 #[inline]
486 pub(crate) fn default_oblique_angle() -> Angle {
487 Angle::Deg(14.0)
488 }
489}
490
491impl<'i> Parse<'i> for FontStyle {
492 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
493 let location = input.current_source_location();
494 let ident = input.expect_ident()?;
495 match_ignore_ascii_case! { &*ident,
496 "normal" => Ok(FontStyle::Normal),
497 "italic" => Ok(FontStyle::Italic),
498 "oblique" => {
499 let angle = input.try_parse(Angle::parse).unwrap_or(FontStyle::default_oblique_angle());
500 Ok(FontStyle::Oblique(angle))
501 },
502 _ => Err(location.new_unexpected_token_error(
503 cssparser::Token::Ident(ident.clone())
504 ))
505 }
506 }
507}
508
509impl ToCss for FontStyle {
510 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
511 where
512 W: std::fmt::Write,
513 {
514 match self {
515 FontStyle::Normal => dest.write_str("normal"),
516 FontStyle::Italic => dest.write_str("italic"),
517 FontStyle::Oblique(angle) => {
518 dest.write_str("oblique")?;
519 if *angle != FontStyle::default_oblique_angle() {
520 dest.write_char(' ')?;
521 angle.to_css(dest)?;
522 }
523 Ok(())
524 }
525 }
526 }
527}
528
529impl IsCompatible for FontStyle {
530 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
531 match self {
532 FontStyle::Oblique(angle) if *angle != FontStyle::default_oblique_angle() => {
533 Feature::FontStyleObliqueAngle.is_compatible(browsers)
534 }
535 FontStyle::Normal | FontStyle::Italic | FontStyle::Oblique(..) => true,
536 }
537 }
538}
539
540enum_property! {
541 pub enum FontVariantCaps {
543 Normal,
545 SmallCaps,
547 AllSmallCaps,
549 PetiteCaps,
551 AllPetiteCaps,
553 Unicase,
555 TitlingCaps,
557 }
558}
559
560impl Default for FontVariantCaps {
561 fn default() -> FontVariantCaps {
562 FontVariantCaps::Normal
563 }
564}
565
566impl FontVariantCaps {
567 fn is_css2(&self) -> bool {
568 matches!(self, FontVariantCaps::Normal | FontVariantCaps::SmallCaps)
569 }
570
571 fn parse_css2<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
572 let value = Self::parse(input)?;
573 if !value.is_css2() {
574 return Err(input.new_custom_error(ParserError::InvalidValue));
575 }
576 Ok(value)
577 }
578}
579
580impl IsCompatible for FontVariantCaps {
581 fn is_compatible(&self, _browsers: crate::targets::Browsers) -> bool {
582 true
583 }
584}
585
586#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
588#[cfg_attr(feature = "visitor", derive(Visit))]
589#[cfg_attr(
590 feature = "serde",
591 derive(serde::Serialize, serde::Deserialize),
592 serde(tag = "type", content = "value", rename_all = "kebab-case")
593)]
594#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
595#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
596pub enum LineHeight {
597 Normal,
599 Number(CSSNumber),
601 Length(LengthPercentage),
603}
604
605impl Default for LineHeight {
606 fn default() -> LineHeight {
607 LineHeight::Normal
608 }
609}
610
611impl IsCompatible for LineHeight {
612 fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
613 match self {
614 LineHeight::Length(l) => l.is_compatible(browsers),
615 LineHeight::Normal | LineHeight::Number(..) => true,
616 }
617 }
618}
619
620enum_property! {
621 pub enum VerticalAlignKeyword {
623 Baseline,
625 Sub,
627 Super,
629 Top,
631 TextTop,
633 Middle,
635 Bottom,
637 TextBottom,
639 }
640}
641
642#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
645#[cfg_attr(feature = "visitor", derive(Visit))]
646#[cfg_attr(
647 feature = "serde",
648 derive(serde::Serialize, serde::Deserialize),
649 serde(tag = "type", content = "value", rename_all = "kebab-case")
650)]
651#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
652#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
653pub enum VerticalAlign {
654 Keyword(VerticalAlignKeyword),
656 Length(LengthPercentage),
658}
659
660define_shorthand! {
661 pub struct Font<'i> {
663 #[cfg_attr(feature = "serde", serde(borrow))]
665 family: FontFamily(Vec<FontFamily<'i>>),
666 size: FontSize(FontSize),
668 style: FontStyle(FontStyle),
670 weight: FontWeight(FontWeight),
672 stretch: FontStretch(FontStretch),
674 line_height: LineHeight(LineHeight),
676 variant_caps: FontVariantCaps(FontVariantCaps),
678 }
679}
680
681impl<'i> Parse<'i> for Font<'i> {
682 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
683 let mut style = None;
684 let mut weight = None;
685 let mut stretch = None;
686 let size;
687 let mut variant_caps = None;
688 let mut count = 0;
689
690 loop {
691 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
693 count += 1;
694 continue;
695 }
696 if style.is_none() {
697 if let Ok(value) = input.try_parse(FontStyle::parse) {
698 style = Some(value);
699 count += 1;
700 continue;
701 }
702 }
703 if weight.is_none() {
704 if let Ok(value) = input.try_parse(FontWeight::parse) {
705 weight = Some(value);
706 count += 1;
707 continue;
708 }
709 }
710 if variant_caps.is_none() {
711 if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) {
712 variant_caps = Some(value);
713 count += 1;
714 continue;
715 }
716 }
717
718 if stretch.is_none() {
719 if let Ok(value) = input.try_parse(FontStretchKeyword::parse) {
720 stretch = Some(FontStretch::Keyword(value));
721 count += 1;
722 continue;
723 }
724 }
725 size = Some(FontSize::parse(input)?);
726 break;
727 }
728
729 if count > 4 {
730 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
731 }
732
733 let size = match size {
734 Some(s) => s,
735 None => return Err(input.new_custom_error(ParserError::InvalidDeclaration)),
736 };
737
738 let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
739 Some(LineHeight::parse(input)?)
740 } else {
741 None
742 };
743
744 let family = input.parse_comma_separated(FontFamily::parse)?;
745 Ok(Font {
746 family,
747 size,
748 style: style.unwrap_or_default(),
749 weight: weight.unwrap_or_default(),
750 stretch: stretch.unwrap_or_default(),
751 line_height: line_height.unwrap_or_default(),
752 variant_caps: variant_caps.unwrap_or_default(),
753 })
754 }
755}
756
757impl<'i> ToCss for Font<'i> {
758 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
759 where
760 W: std::fmt::Write,
761 {
762 if self.style != FontStyle::default() {
763 self.style.to_css(dest)?;
764 dest.write_char(' ')?;
765 }
766
767 if self.variant_caps != FontVariantCaps::default() {
768 self.variant_caps.to_css(dest)?;
769 dest.write_char(' ')?;
770 }
771
772 if self.weight != FontWeight::default() {
773 self.weight.to_css(dest)?;
774 dest.write_char(' ')?;
775 }
776
777 if self.stretch != FontStretch::default() {
778 self.stretch.to_css(dest)?;
779 dest.write_char(' ')?;
780 }
781
782 self.size.to_css(dest)?;
783
784 if self.line_height != LineHeight::default() {
785 dest.delim('/', true)?;
786 self.line_height.to_css(dest)?;
787 }
788
789 dest.write_char(' ')?;
790
791 let len = self.family.len();
792 for (idx, val) in self.family.iter().enumerate() {
793 val.to_css(dest)?;
794 if idx < len - 1 {
795 dest.delim(',', false)?;
796 }
797 }
798
799 Ok(())
800 }
801}
802
803property_bitflags! {
804 #[derive(Default, Debug)]
805 struct FontProperty: u8 {
806 const FontFamily = 1 << 0;
807 const FontSize = 1 << 1;
808 const FontStyle = 1 << 2;
809 const FontWeight = 1 << 3;
810 const FontStretch = 1 << 4;
811 const LineHeight = 1 << 5;
812 const FontVariantCaps = 1 << 6;
813 const Font = Self::FontFamily.bits() | Self::FontSize.bits() | Self::FontStyle.bits() | Self::FontWeight.bits() | Self::FontStretch.bits() | Self::LineHeight.bits() | Self::FontVariantCaps.bits();
814 }
815}
816
817#[derive(Default, Debug)]
818pub(crate) struct FontHandler<'i> {
819 family: Option<Vec<FontFamily<'i>>>,
820 size: Option<FontSize>,
821 style: Option<FontStyle>,
822 weight: Option<FontWeight>,
823 stretch: Option<FontStretch>,
824 line_height: Option<LineHeight>,
825 variant_caps: Option<FontVariantCaps>,
826 flushed_properties: FontProperty,
827 has_any: bool,
828}
829
830impl<'i> PropertyHandler<'i> for FontHandler<'i> {
831 fn handle_property(
832 &mut self,
833 property: &Property<'i>,
834 dest: &mut DeclarationList<'i>,
835 context: &mut PropertyHandlerContext<'i, '_>,
836 ) -> bool {
837 use Property::*;
838
839 macro_rules! flush {
840 ($prop: ident, $val: expr) => {{
841 if self.$prop.is_some() && self.$prop.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
842 self.flush(dest, context);
843 }
844 }};
845 }
846
847 macro_rules! property {
848 ($prop: ident, $val: ident) => {{
849 flush!($prop, $val);
850 self.$prop = Some($val.clone());
851 self.has_any = true;
852 }};
853 }
854
855 match property {
856 FontFamily(val) => property!(family, val),
857 FontSize(val) => property!(size, val),
858 FontStyle(val) => property!(style, val),
859 FontWeight(val) => property!(weight, val),
860 FontStretch(val) => property!(stretch, val),
861 FontVariantCaps(val) => property!(variant_caps, val),
862 LineHeight(val) => property!(line_height, val),
863 Font(val) => {
864 flush!(family, &val.family);
865 flush!(size, &val.size);
866 flush!(style, &val.style);
867 flush!(weight, &val.weight);
868 flush!(stretch, &val.stretch);
869 flush!(line_height, &val.line_height);
870 flush!(variant_caps, &val.variant_caps);
871 self.family = Some(val.family.clone());
872 self.size = Some(val.size.clone());
873 self.style = Some(val.style.clone());
874 self.weight = Some(val.weight.clone());
875 self.stretch = Some(val.stretch.clone());
876 self.line_height = Some(val.line_height.clone());
877 self.variant_caps = Some(val.variant_caps.clone());
878 self.has_any = true;
879 }
881 Unparsed(val) if is_font_property(&val.property_id) => {
882 self.flush(dest, context);
883 self
884 .flushed_properties
885 .insert(FontProperty::try_from(&val.property_id).unwrap());
886 dest.push(property.clone());
887 }
888 _ => return false,
889 }
890
891 true
892 }
893
894 fn finalize(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
895 self.flush(decls, context);
896 self.flushed_properties = FontProperty::empty();
897 }
898}
899
900impl<'i> FontHandler<'i> {
901 fn flush(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
902 if !self.has_any {
903 return;
904 }
905
906 self.has_any = false;
907
908 macro_rules! push {
909 ($prop: ident, $val: expr) => {
910 decls.push(Property::$prop($val));
911 self.flushed_properties.insert(FontProperty::$prop);
912 };
913 }
914
915 let mut family = std::mem::take(&mut self.family);
916 if !self.flushed_properties.contains(FontProperty::FontFamily) {
917 family = compatible_font_family(family, !should_compile!(context.targets, FontFamilySystemUi));
918 }
919 let size = std::mem::take(&mut self.size);
920 let style = std::mem::take(&mut self.style);
921 let weight = std::mem::take(&mut self.weight);
922 let stretch = std::mem::take(&mut self.stretch);
923 let line_height = std::mem::take(&mut self.line_height);
924 let variant_caps = std::mem::take(&mut self.variant_caps);
925
926 if let Some(family) = &mut family {
927 if family.len() > 1 {
928 let mut seen = HashSet::new();
930 family.retain(|f| seen.insert(f.clone()));
931 }
932 }
933
934 if family.is_some()
935 && size.is_some()
936 && style.is_some()
937 && weight.is_some()
938 && stretch.is_some()
939 && line_height.is_some()
940 && variant_caps.is_some()
941 {
942 let caps = variant_caps.unwrap();
943 push!(
944 Font,
945 Font {
946 family: family.unwrap(),
947 size: size.unwrap(),
948 style: style.unwrap(),
949 weight: weight.unwrap(),
950 stretch: stretch.unwrap(),
951 line_height: line_height.unwrap(),
952 variant_caps: if caps.is_css2() {
953 caps
954 } else {
955 FontVariantCaps::default()
956 },
957 }
958 );
959
960 if !caps.is_css2() {
963 push!(FontVariantCaps, variant_caps.unwrap());
964 }
965 } else {
966 if let Some(val) = family {
967 push!(FontFamily, val);
968 }
969
970 if let Some(val) = size {
971 push!(FontSize, val);
972 }
973
974 if let Some(val) = style {
975 push!(FontStyle, val);
976 }
977
978 if let Some(val) = variant_caps {
979 push!(FontVariantCaps, val);
980 }
981
982 if let Some(val) = weight {
983 push!(FontWeight, val);
984 }
985
986 if let Some(val) = stretch {
987 push!(FontStretch, val);
988 }
989
990 if let Some(val) = line_height {
991 push!(LineHeight, val);
992 }
993 }
994 }
995}
996
997const SYSTEM_UI: FontFamily = FontFamily::Generic(GenericFontFamily::SystemUI);
998
999const DEFAULT_SYSTEM_FONTS: &[&str] = &[
1000 "-apple-system",
1002 "BlinkMacSystemFont",
1004 "Segoe UI", "Roboto", "Noto Sans", "Ubuntu", "Cantarell", "Helvetica Neue",
1010];
1011
1012#[inline]
1016fn compatible_font_family(mut family: Option<Vec<FontFamily>>, is_supported: bool) -> Option<Vec<FontFamily>> {
1017 if is_supported {
1018 return family;
1019 }
1020
1021 if let Some(families) = &mut family {
1022 if let Some(position) = families.iter().position(|v| *v == SYSTEM_UI) {
1023 families.splice(
1024 (position + 1)..(position + 1),
1025 DEFAULT_SYSTEM_FONTS
1026 .iter()
1027 .map(|name| FontFamily::FamilyName(FamilyName(CowArcStr::from(*name)))),
1028 );
1029 }
1030 }
1031
1032 return family;
1033}
1034
1035#[inline]
1036fn is_font_property(property_id: &PropertyId) -> bool {
1037 match property_id {
1038 PropertyId::FontFamily
1039 | PropertyId::FontSize
1040 | PropertyId::FontStyle
1041 | PropertyId::FontWeight
1042 | PropertyId::FontStretch
1043 | PropertyId::FontVariantCaps
1044 | PropertyId::LineHeight
1045 | PropertyId::Font => true,
1046 _ => false,
1047 }
1048}