#![warn(unused_must_use)]
use crate::PrintResult;
use crate::compat::Feature;
use crate::css_parser as css;
use crate::error::ParserError;
use crate::printer::Printer;
use bun_alloc::ArenaVecExt as _;
use crate::values as css_values;
use css_values::angle::Angle;
use css_values::length::{LengthPercentage, LengthValue};
use css_values::number::{CSSNumber, CSSNumberFns};
use css_values::percentage::{DimensionPercentage, Percentage};
use bun_collections::VecExt;
use bun_alloc::core_alloc::AllocVec;
use bun_alloc::core_alloc::Global;
pub type FontFamilyList = AllocVec<FontFamily, Global>;
use crate::generics::{CssEql, DeepClone};
use css::CssResult;
#[derive(Clone, PartialEq)]
pub enum FontWeight {
Absolute(AbsoluteFontWeight),
Bolder,
Lighter,
}
impl FontWeight {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Self> {
if let Ok(v) = input.try_parse(AbsoluteFontWeight::parse) {
return Ok(FontWeight::Absolute(v));
}
let location = input.current_source_location();
let ident = input.expect_ident_cloned()?;
crate::match_ignore_ascii_case! { ident, {
b"bolder" => Ok(FontWeight::Bolder),
b"lighter" => Ok(FontWeight::Lighter),
_ => Err(location.new_unexpected_token_error(crate::Token::Ident(ident))),
}}
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> PrintResult<()> {
match self {
FontWeight::Absolute(a) => a.to_css(dest),
FontWeight::Bolder => dest.write_str("bolder"),
FontWeight::Lighter => dest.write_str("lighter"),
}
}
#[inline]
pub(crate) fn default() -> FontWeight {
FontWeight::Absolute(AbsoluteFontWeight::default())
}
pub(crate) fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
match self {
FontWeight::Absolute(a) => a.is_compatible(browsers),
FontWeight::Bolder | FontWeight::Lighter => true,
}
}
}
#[derive(Clone, PartialEq)]
pub enum AbsoluteFontWeight {
Weight(CSSNumber),
Normal,
Bold,
}
impl AbsoluteFontWeight {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Self> {
if let Ok(n) = input.try_parse(CSSNumberFns::parse) {
return Ok(AbsoluteFontWeight::Weight(n));
}
let location = input.current_source_location();
let ident = input.expect_ident_cloned()?;
crate::match_ignore_ascii_case! { ident, {
b"normal" => Ok(AbsoluteFontWeight::Normal),
b"bold" => Ok(AbsoluteFontWeight::Bold),
_ => Err(location.new_unexpected_token_error(crate::Token::Ident(ident))),
}}
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> PrintResult<()> {
match self {
AbsoluteFontWeight::Weight(weight) => CSSNumberFns::to_css(*weight, dest),
AbsoluteFontWeight::Normal => {
dest.write_str(if dest.minify { "400" } else { "normal" })
}
AbsoluteFontWeight::Bold => dest.write_str(if dest.minify { "700" } else { "bold" }),
}
}
pub(crate) fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
match self {
AbsoluteFontWeight::Weight(val) => {
if !((*val >= 100.0 && *val <= 900.0) && (*val % 100.0) == 0.0) {
Feature::FontWeightNumber.is_compatible(browsers)
} else {
true
}
}
_ => true,
}
}
#[inline]
pub(crate) fn default() -> AbsoluteFontWeight {
AbsoluteFontWeight::Normal
}
}
#[derive(Clone, PartialEq, css::Parse, css::ToCss)]
pub enum FontSize {
Length(LengthPercentage),
Absolute(AbsoluteFontSize),
Relative(RelativeFontSize),
}
impl FontSize {
pub(crate) fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
match self {
FontSize::Length(l) => match l {
DimensionPercentage::Dimension(LengthValue::Rem(_)) => {
Feature::FontSizeRem.is_compatible(browsers)
}
_ => l.is_compatible(browsers),
},
FontSize::Absolute(a) => a.is_compatible(browsers),
FontSize::Relative(_) => true,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum AbsoluteFontSize {
XxSmall,
XSmall,
Small,
Medium,
Large,
XLarge,
XxLarge,
XxxLarge,
}
impl AbsoluteFontSize {
pub(crate) fn is_compatible(self, browsers: &crate::targets::Browsers) -> bool {
match self {
AbsoluteFontSize::XxxLarge => Feature::FontSizeXXXLarge.is_compatible(browsers),
_ => true,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum RelativeFontSize {
Smaller,
Larger,
}
#[derive(Copy, Clone, PartialEq)]
pub enum FontStretch {
Keyword(FontStretchKeyword),
Percentage(Percentage),
}
impl FontStretch {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Self> {
if let Ok(kw) = input.try_parse(FontStretchKeyword::parse) {
return Ok(FontStretch::Keyword(kw));
}
Percentage::parse(input).map(FontStretch::Percentage)
}
pub(crate) fn to_css(self, dest: &mut Printer) -> PrintResult<()> {
if dest.minify {
let percentage: Percentage = self.into_percentage();
return percentage.to_css(dest);
}
match self {
FontStretch::Percentage(val) => val.to_css(dest),
FontStretch::Keyword(kw) => kw.to_css(dest),
}
}
pub(crate) fn into_percentage(self) -> Percentage {
match self {
FontStretch::Percentage(val) => val,
FontStretch::Keyword(kw) => kw.into_percentage(),
}
}
pub(crate) fn is_compatible(self, browsers: &crate::targets::Browsers) -> bool {
match self {
FontStretch::Percentage(_) => Feature::FontStretchPercentage.is_compatible(browsers),
FontStretch::Keyword(_) => true,
}
}
#[inline]
pub(crate) fn default() -> FontStretch {
FontStretch::Keyword(FontStretchKeyword::default())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum FontStretchKeyword {
Normal,
UltraCondensed,
ExtraCondensed,
Condensed,
SemiCondensed,
SemiExpanded,
Expanded,
ExtraExpanded,
UltraExpanded,
}
impl FontStretchKeyword {
#[inline]
pub(crate) fn default() -> FontStretchKeyword {
FontStretchKeyword::Normal
}
pub(crate) fn into_percentage(self) -> Percentage {
let val: f32 = match self {
FontStretchKeyword::UltraCondensed => 0.5,
FontStretchKeyword::ExtraCondensed => 0.625,
FontStretchKeyword::Condensed => 0.75,
FontStretchKeyword::SemiCondensed => 0.875,
FontStretchKeyword::Normal => 1.0,
FontStretchKeyword::SemiExpanded => 1.125,
FontStretchKeyword::Expanded => 1.25,
FontStretchKeyword::ExtraExpanded => 1.5,
FontStretchKeyword::UltraExpanded => 2.0,
};
Percentage { v: val }
}
}
pub enum FontFamily {
Generic(GenericFontFamily),
FamilyName(*const [u8]),
}
pub(crate) type FontFamilyHashMap<V> = bun_collections::ArrayHashMap<FontFamily, V>;
impl FontFamily {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Self> {
if let Ok(value) = input.try_parse(|p| p.expect_string().map(std::ptr::from_ref::<[u8]>)) {
return Ok(FontFamily::FamilyName(value));
}
if let Ok(value) = input.try_parse(GenericFontFamily::parse) {
return Ok(FontFamily::Generic(value));
}
let bump: &'static bun_alloc::Arena =
unsafe { &*std::ptr::from_ref::<bun_alloc::Arena>(input.arena()) };
let value: *const [u8] = std::ptr::from_ref::<[u8]>(input.expect_ident()?);
let mut string: Option<bun_alloc::ArenaVec<'_, u8>> = None;
while let Ok(ident) = input.try_parse(|p| p.expect_ident().map(std::ptr::from_ref::<[u8]>))
{
if string.is_none() {
let mut s = bun_alloc::ArenaVec::<u8>::new_in(bump);
s.extend_from_slice(unsafe { crate::arena_str(value) });
string = Some(s);
}
if let Some(s) = string.as_mut() {
s.push(b' ');
s.extend_from_slice(unsafe { crate::arena_str(ident) });
}
}
let final_value: *const [u8] = match string {
Some(s) => std::ptr::from_ref::<[u8]>(s.into_bump_slice()),
None => value,
};
Ok(FontFamily::FamilyName(final_value))
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> PrintResult<()> {
match self {
FontFamily::Generic(val) => val.to_css(dest),
FontFamily::FamilyName(val_ptr) => {
let val: &[u8] = unsafe { crate::arena_str(*val_ptr) };
if !val.is_empty()
&& !css::parse_utility::parse_string::<GenericFontFamily>(
dest.arena,
val,
GenericFontFamily::parse,
)
.is_ok()
{
let mut id = bun_alloc::ArenaVec::<u8>::new_in(dest.arena);
let mut first = true;
for slice in val.split(|b| *b == b' ') {
if first {
first = false;
} else {
id.push(b' ');
}
let _ = css::serializer::serialize_identifier(slice, &mut id);
}
if id.len() < val.len() + 2 {
return dest.write_str(&id[..]);
}
}
dest.serialize_string(val)
}
}
}
pub(crate) fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
match self {
FontFamily::Generic(g) => g.is_compatible(browsers),
FontFamily::FamilyName(_) => true,
}
}
}
impl PartialEq for FontFamily {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(FontFamily::Generic(a), FontFamily::Generic(b)) => a == b,
(FontFamily::FamilyName(a), FontFamily::FamilyName(b)) => {
unsafe { (&**a).eq(&**b) }
}
_ => false,
}
}
}
impl Eq for FontFamily {}
impl core::hash::Hash for FontFamily {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
FontFamily::Generic(g) => g.hash(state),
FontFamily::FamilyName(p) => {
unsafe { (&**p).hash(state) }
}
}
}
}
impl Clone for FontFamily {
fn clone(&self) -> Self {
match self {
FontFamily::Generic(g) => FontFamily::Generic(*g),
FontFamily::FamilyName(n) => FontFamily::FamilyName(*n),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum GenericFontFamily {
Serif,
SansSerif,
Cursive,
Fantasy,
Monospace,
SystemUi,
Emoji,
Math,
Fangsong,
UiSerif,
UiSansSerif,
UiMonospace,
UiRounded,
Initial,
Inherit,
Unset,
Default,
Revert,
RevertLayer,
}
impl GenericFontFamily {
pub(crate) fn is_compatible(self, browsers: &crate::targets::Browsers) -> bool {
match self {
GenericFontFamily::SystemUi => Feature::FontFamilySystemUi.is_compatible(browsers),
GenericFontFamily::UiSerif
| GenericFontFamily::UiSansSerif
| GenericFontFamily::UiMonospace
| GenericFontFamily::UiRounded => Feature::ExtendedSystemFonts.is_compatible(browsers),
_ => true,
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum FontStyle {
Normal,
Italic,
Oblique(Angle),
}
impl FontStyle {
pub(crate) fn default() -> FontStyle {
FontStyle::Normal
}
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<FontStyle> {
let location = input.current_source_location();
let ident = input.expect_ident_cloned()?;
crate::match_ignore_ascii_case! { ident, {
b"normal" => Ok(FontStyle::Normal),
b"italic" => Ok(FontStyle::Italic),
b"oblique" => {
let angle = input
.try_parse(Angle::parse)
.unwrap_or_else(|_| FontStyle::default_oblique_angle());
Ok(FontStyle::Oblique(angle))
},
_ => Err(location.new_unexpected_token_error(crate::Token::Ident(ident))),
}}
}
pub(crate) fn to_css(self, dest: &mut Printer) -> PrintResult<()> {
match self {
FontStyle::Normal => dest.write_str("normal"),
FontStyle::Italic => dest.write_str("italic"),
FontStyle::Oblique(angle) => {
dest.write_str("oblique")?;
if angle != FontStyle::default_oblique_angle() {
dest.write_char(b' ')?;
angle.to_css(dest)?;
}
Ok(())
}
}
}
pub(crate) fn is_compatible(self, browsers: &crate::targets::Browsers) -> bool {
match self {
FontStyle::Oblique(angle) => {
if angle != FontStyle::default_oblique_angle() {
Feature::FontStyleObliqueAngle.is_compatible(browsers)
} else {
true
}
}
FontStyle::Normal | FontStyle::Italic => true,
}
}
pub(crate) fn default_oblique_angle() -> Angle {
Angle::Deg(14.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub enum FontVariantCaps {
Normal,
SmallCaps,
AllSmallCaps,
PetiteCaps,
AllPetiteCaps,
Unicase,
TitlingCaps,
}
impl FontVariantCaps {
pub(crate) fn default() -> FontVariantCaps {
FontVariantCaps::Normal
}
fn is_css2(self) -> bool {
matches!(self, FontVariantCaps::Normal | FontVariantCaps::SmallCaps)
}
pub(crate) fn parse_css2(input: &mut css::Parser) -> CssResult<FontVariantCaps> {
let value = FontVariantCaps::parse(input)?;
if !value.is_css2() {
return Err(input.new_custom_error(ParserError::invalid_value));
}
Ok(value)
}
pub(crate) fn is_compatible(self, _: &crate::targets::Browsers) -> bool {
true
}
}
#[derive(Clone, PartialEq)]
pub enum LineHeight {
Normal,
Number(CSSNumber),
Length(LengthPercentage),
}
impl LineHeight {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Self> {
if input
.try_parse(|p| p.expect_ident_matching(b"normal"))
.is_ok()
{
return Ok(LineHeight::Normal);
}
if let Ok(n) = input.try_parse(CSSNumberFns::parse) {
return Ok(LineHeight::Number(n));
}
LengthPercentage::parse(input).map(LineHeight::Length)
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> PrintResult<()> {
match self {
LineHeight::Normal => dest.write_str("normal"),
LineHeight::Number(n) => CSSNumberFns::to_css(*n, dest),
LineHeight::Length(l) => l.to_css(dest),
}
}
pub(crate) fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool {
match self {
LineHeight::Length(l) => l.is_compatible(browsers),
LineHeight::Normal | LineHeight::Number(_) => true,
}
}
pub(crate) fn default() -> LineHeight {
LineHeight::Normal
}
}
#[derive(DeepClone, CssEql)]
pub struct Font {
pub family: FontFamilyList,
pub size: FontSize,
pub style: FontStyle,
pub weight: FontWeight,
pub stretch: FontStretch,
pub line_height: LineHeight,
pub variant_caps: FontVariantCaps,
}
impl Font {
pub(crate) fn parse(input: &mut css::Parser) -> CssResult<Font> {
let mut style: Option<FontStyle> = None;
let mut weight: Option<FontWeight> = None;
let mut stretch: Option<FontStretch> = None;
let final_size: FontSize;
let mut variant_caps: Option<FontVariantCaps> = None;
let mut count: i32 = 0;
loop {
if input
.try_parse(|i| i.expect_ident_matching(b"normal"))
.is_ok()
{
count += 1;
continue;
}
if style.is_none() {
if let Ok(value) = input.try_parse(FontStyle::parse) {
style = Some(value);
count += 1;
continue;
}
}
if weight.is_none() {
if let Ok(value) = input.try_parse(FontWeight::parse) {
weight = Some(value);
count += 1;
continue;
}
}
if variant_caps.is_some() {
if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) {
variant_caps = Some(value);
count += 1;
continue;
}
}
if stretch.is_none() {
if let Ok(value) = input.try_parse(FontStretchKeyword::parse) {
stretch = Some(FontStretch::Keyword(value));
count += 1;
continue;
}
}
final_size = FontSize::parse(input)?;
break;
}
if count > 4 {
return Err(input.new_custom_error(ParserError::invalid_declaration));
}
let line_height = if input.try_parse(|i| i.expect_delim(b'/')).is_ok() {
Some(LineHeight::parse(input)?)
} else {
None
};
let family: FontFamilyList = input.parse_comma_separated(FontFamily::parse)?;
Ok(Font {
family,
size: final_size,
style: style.unwrap_or_else(FontStyle::default),
weight: weight.unwrap_or_else(FontWeight::default),
stretch: stretch.unwrap_or_else(FontStretch::default),
line_height: line_height.unwrap_or_else(LineHeight::default),
variant_caps: variant_caps.unwrap_or_else(FontVariantCaps::default),
})
}
pub(crate) fn to_css(&self, dest: &mut Printer) -> PrintResult<()> {
if self.style != FontStyle::default() {
self.style.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.variant_caps != FontVariantCaps::default() {
self.variant_caps.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.weight != FontWeight::default() {
self.weight.to_css(dest)?;
dest.write_char(b' ')?;
}
if self.stretch != FontStretch::default() {
self.stretch.to_css(dest)?;
dest.write_char(b' ')?;
}
self.size.to_css(dest)?;
if self.line_height != LineHeight::default() {
dest.delim(b'/', true)?;
self.line_height.to_css(dest)?;
}
dest.write_char(b' ')?;
let len = self.family.len();
for (idx, val) in self.family.slice_const().iter().enumerate() {
val.to_css(dest)?;
if idx < len - 1 {
dest.delim(b',', false)?;
}
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)]
pub(crate) enum VerticalAlignKeyword {
Baseline,
Sub,
Super,
Top,
TextTop,
Middle,
Bottom,
TextBottom,
}
bitflags::bitflags! {
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FontProperty: u8 {
const FONT_FAMILY = 1 << 0;
const FONT_SIZE = 1 << 1;
const FONT_STYLE = 1 << 2;
const FONT_WEIGHT = 1 << 3;
const FONT_STRETCH = 1 << 4;
const LINE_HEIGHT = 1 << 5;
const FONT_VARIANT_CAPS = 1 << 6;
}
}
impl FontProperty {
const FONT: FontProperty = FontProperty::all();
pub(crate) fn try_from_property_id(
property_id: crate::properties::PropertyIdTag,
) -> Option<FontProperty> {
use crate::properties::PropertyIdTag;
match property_id {
PropertyIdTag::FontFamily => Some(FontProperty::FONT_FAMILY),
PropertyIdTag::FontSize => Some(FontProperty::FONT_SIZE),
PropertyIdTag::FontStyle => Some(FontProperty::FONT_STYLE),
PropertyIdTag::FontWeight => Some(FontProperty::FONT_WEIGHT),
PropertyIdTag::FontStretch => Some(FontProperty::FONT_STRETCH),
PropertyIdTag::LineHeight => Some(FontProperty::LINE_HEIGHT),
PropertyIdTag::FontVariantCaps => Some(FontProperty::FONT_VARIANT_CAPS),
PropertyIdTag::Font => Some(FontProperty::FONT),
_ => None,
}
}
}
#[derive(Default)]
pub struct FontHandler {
family: Option<FontFamilyList>,
size: Option<FontSize>,
style: Option<FontStyle>,
weight: Option<FontWeight>,
stretch: Option<FontStretch>,
line_height: Option<LineHeight>,
variant_caps: Option<FontVariantCaps>,
flushed_properties: FontProperty,
has_any: bool,
}
impl FontHandler {
pub(crate) fn handle_property(
&mut self,
property: &crate::properties::Property,
dest: &mut crate::DeclarationList<'_>,
context: &mut crate::PropertyHandlerContext<'_>,
) -> bool {
use crate::properties::Property;
let arena = dest.bump();
macro_rules! flush_helper {
($this:expr, $field:ident, $val:expr) => {{
if $this.$field.is_some()
&& !crate::generic::eql($this.$field.as_ref().unwrap(), $val)
&& context.targets.browsers.is_some()
&& !crate::generic::is_compatible(
$val,
context.targets.browsers.as_ref().unwrap(),
)
{
$this.flush(dest, context);
}
}};
}
macro_rules! property_helper {
($this:expr, $field:ident, $val:expr) => {{
flush_helper!($this, $field, $val);
$this.$field = Some(crate::generic::deep_clone($val, arena));
$this.has_any = true;
}};
}
match property {
Property::FontFamily(val) => property_helper!(self, family, val),
Property::FontSize(val) => property_helper!(self, size, val),
Property::FontStyle(val) => property_helper!(self, style, val),
Property::FontWeight(val) => property_helper!(self, weight, val),
Property::FontStretch(val) => property_helper!(self, stretch, val),
Property::FontVariantCaps(val) => property_helper!(self, variant_caps, val),
Property::LineHeight(val) => property_helper!(self, line_height, val),
Property::Font(val) => {
flush_helper!(self, family, &val.family);
flush_helper!(self, size, &val.size);
flush_helper!(self, style, &val.style);
flush_helper!(self, weight, &val.weight);
flush_helper!(self, stretch, &val.stretch);
flush_helper!(self, line_height, &val.line_height);
flush_helper!(self, variant_caps, &val.variant_caps);
self.family = Some(crate::generic::deep_clone(&val.family, arena));
self.size = Some(val.size.clone());
self.style = Some(val.style);
self.weight = Some(val.weight.clone());
self.stretch = Some(val.stretch);
self.line_height = Some(val.line_height.clone());
self.variant_caps = Some(val.variant_caps);
self.has_any = true;
}
Property::Unparsed(val) => {
if is_font_property(&val.property_id) {
self.flush(dest, context);
self.flushed_properties
.insert(FontProperty::try_from_property_id(val.property_id.tag()).unwrap());
dest.push(property.deep_clone(arena));
} else {
return false;
}
}
_ => return false,
}
true
}
pub(crate) fn finalize(
&mut self,
decls: &mut crate::DeclarationList<'_>,
context: &mut crate::PropertyHandlerContext<'_>,
) {
self.flush(decls, context);
self.flushed_properties = FontProperty::empty();
}
fn flush(
&mut self,
decls: &mut crate::DeclarationList<'_>,
context: &mut crate::PropertyHandlerContext<'_>,
) {
use crate::properties::Property;
macro_rules! push_prop {
(Font, $val:expr) => {{
decls.push(Property::Font($val));
self.flushed_properties.insert(FontProperty::FONT);
}};
($variant:ident, $flag:ident, $val:expr) => {{
decls.push(Property::$variant($val));
self.flushed_properties.insert(FontProperty::$flag);
}};
}
if !self.has_any {
return;
}
self.has_any = false;
let mut family: Option<FontFamilyList> = self.family.take();
if !self.flushed_properties.contains(FontProperty::FONT_FAMILY) {
family = compatible_font_family(
family,
!context
.targets
.should_compile_same(Feature::FontFamilySystemUi),
);
}
let size: Option<FontSize> = self.size.take();
let style: Option<FontStyle> = self.style.take();
let weight: Option<FontWeight> = self.weight.take();
let stretch: Option<FontStretch> = self.stretch.take();
let line_height: Option<LineHeight> = self.line_height.take();
let variant_caps: Option<FontVariantCaps> = self.variant_caps.take();
if let Some(f) = family.as_mut() {
if f.len() > 1 {
let mut seen: FontFamilyHashMap<()> = Default::default();
let mut i: usize = 0;
while i < f.len() {
let key = f.at(i).clone();
if seen.contains_key(&key) {
let _ = f.ordered_remove(i);
} else {
seen.insert(key, ());
i += 1;
}
}
}
}
if let (Some(_), Some(_), Some(_), Some(_), Some(_), Some(_), Some(variant_caps_v)) = (
family.as_ref(),
size.as_ref(),
style.as_ref(),
weight.as_ref(),
stretch.as_ref(),
line_height.as_ref(),
variant_caps.as_ref(),
) {
let caps = *variant_caps_v;
push_prop!(
Font,
Font {
family: family.unwrap(),
size: size.unwrap(),
style: style.unwrap(),
weight: weight.unwrap(),
stretch: stretch.unwrap(),
line_height: line_height.unwrap(),
variant_caps: if caps.is_css2() {
caps
} else {
FontVariantCaps::default()
},
}
);
if !caps.is_css2() {
push_prop!(FontVariantCaps, FONT_VARIANT_CAPS, caps);
}
} else {
if let Some(val) = family {
push_prop!(FontFamily, FONT_FAMILY, val);
}
if let Some(val) = size {
push_prop!(FontSize, FONT_SIZE, val);
}
if let Some(val) = style {
push_prop!(FontStyle, FONT_STYLE, val);
}
if let Some(val) = variant_caps {
push_prop!(FontVariantCaps, FONT_VARIANT_CAPS, val);
}
if let Some(val) = weight {
push_prop!(FontWeight, FONT_WEIGHT, val);
}
if let Some(val) = stretch {
push_prop!(FontStretch, FONT_STRETCH, val);
}
if let Some(val) = line_height {
push_prop!(LineHeight, LINE_HEIGHT, val);
}
}
}
}
fn is_system_ui(f: &FontFamily) -> bool {
matches!(f, FontFamily::Generic(GenericFontFamily::SystemUi))
}
const DEFAULT_SYSTEM_FONTS: &[&[u8]] = &[
b"-apple-system",
b"BlinkMacSystemFont",
b"Segoe UI", b"Roboto", b"Noto Sans", b"Ubuntu", b"Cantarell", b"Helvetica Neue",
];
#[inline]
fn compatible_font_family(
_family: Option<FontFamilyList>,
is_supported: bool,
) -> Option<FontFamilyList> {
let mut family = _family;
if is_supported {
return family;
}
if let Some(families) = family.as_mut() {
if let Some(i) = families.slice_const().iter().position(is_system_ui) {
for (j, name) in DEFAULT_SYSTEM_FONTS.iter().enumerate() {
families.insert(
i + j + 1,
FontFamily::FamilyName(std::ptr::from_ref::<[u8]>(*name)),
);
}
}
}
family
}
#[inline]
fn is_font_property(property_id: &crate::properties::PropertyId) -> bool {
use crate::properties::PropertyId;
matches!(
property_id,
PropertyId::FontFamily
| PropertyId::FontSize
| PropertyId::FontStyle
| PropertyId::FontWeight
| PropertyId::FontStretch
| PropertyId::FontVariantCaps
| PropertyId::LineHeight
| PropertyId::Font
)
}