use super::size_spacing::{parse_numeric_value, unwrap_arbitrary_value};
use crate::theme_config::{UiThemeConfig, resolve_theme_numeric_value_in};
use crate::utility::{
ParseUtilityError, UtilityFontFamilyRole, UtilityFontStyle, UtilityStylePatch,
UtilityTextTransform, UtilityVal,
};
pub(super) fn apply_text_utility_token(
config: &UiThemeConfig,
token: &str,
patch: &mut UtilityStylePatch,
) -> Result<bool, ParseUtilityError> {
if let Some(role) = parse_font_family_token(token) {
patch.font_family_role = Some(role);
return Ok(true);
}
if let Some(weight) = parse_font_weight_token(config, token) {
patch.font_weight = Some(weight);
return Ok(true);
}
if let Some(style) = parse_font_style_token(token) {
patch.font_style = Some(style);
return Ok(true);
}
if let Some(size) = parse_text_size_token(config, token)? {
patch.font_size = Some(size);
return Ok(true);
}
if let Some(line_height) = parse_line_height_token(config, token)? {
patch.line_height = Some(line_height);
return Ok(true);
}
if let Some(letter_spacing) = parse_tracking_token(config, token)? {
patch.letter_spacing_em = Some(letter_spacing);
return Ok(true);
}
if let Some(transform) = parse_text_transform_token(token) {
patch.text_transform = Some(transform);
return Ok(true);
}
Ok(false)
}
pub(super) fn parse_text_size_token_value(
config: &UiThemeConfig,
token: &str,
raw: &str,
) -> Result<Option<f32>, ParseUtilityError> {
match raw {
"hint" => Ok(Some(theme_text_size(config, "hint"))),
"meta" => Ok(Some(theme_text_size(config, "meta"))),
"body" => Ok(Some(theme_text_size(config, "body"))),
"control" => Ok(Some(theme_text_size(config, "control"))),
"control-compact" => Ok(Some(theme_text_size(config, "control-compact"))),
"title" => Ok(Some(theme_text_size(config, "title"))),
"display" => Ok(Some(theme_text_size(config, "display"))),
_ if raw.starts_with('[') => Ok(Some(parse_text_size_value(config, token, raw)?)),
_ => Ok(None),
}
}
fn parse_text_size_token(
config: &UiThemeConfig,
token: &str,
) -> Result<Option<f32>, ParseUtilityError> {
let Some(raw) = token.strip_prefix("text-") else {
return Ok(None);
};
parse_text_size_token_value(config, token, raw)
}
fn parse_text_size_value(
config: &UiThemeConfig,
token: &str,
raw: &str,
) -> Result<f32, ParseUtilityError> {
match parse_numeric_value(config, token, unwrap_arbitrary_value(raw))? {
UtilityVal::Px(value) => Ok(value),
UtilityVal::Percent(_) | UtilityVal::Vw(_) | UtilityVal::Vh(_) | UtilityVal::Auto => {
Err(ParseUtilityError::new(token, "invalid text size value"))
}
}
}
fn parse_font_family_token(token: &str) -> Option<UtilityFontFamilyRole> {
match token {
"font-sans" => Some(UtilityFontFamilyRole::Sans),
"font-serif" => Some(UtilityFontFamilyRole::Serif),
"font-mono" => Some(UtilityFontFamilyRole::Mono),
_ => None,
}
}
fn parse_font_weight_token(config: &UiThemeConfig, token: &str) -> Option<u16> {
let key = match token {
"font-thin" => "thin",
"font-extralight" => "extralight",
"font-light" => "light",
"font-normal" => "normal",
"font-medium" => "medium",
"font-semibold" => "semibold",
"font-bold" => "bold",
"font-extrabold" => "extrabold",
"font-black" => "black",
_ => return None,
};
Some(theme_font_weight(config, key))
}
fn parse_font_style_token(token: &str) -> Option<UtilityFontStyle> {
match token {
"italic" => Some(UtilityFontStyle::Italic),
"not-italic" => Some(UtilityFontStyle::Normal),
_ => None,
}
}
fn parse_line_height_token(
config: &UiThemeConfig,
token: &str,
) -> Result<Option<f32>, ParseUtilityError> {
let Some(raw) = token.strip_prefix("leading-") else {
return Ok(None);
};
let value = match raw {
"none" => theme_line_height(config, "none"),
"tight" => theme_line_height(config, "tight"),
"snug" => theme_line_height(config, "snug"),
"normal" => theme_line_height(config, "normal"),
"relaxed" => theme_line_height(config, "relaxed"),
_ if raw.starts_with('[') => parse_line_height_value(config, token, raw)?,
_ => return Ok(None),
};
Ok(Some(value))
}
fn parse_line_height_value(
config: &UiThemeConfig,
token: &str,
raw: &str,
) -> Result<f32, ParseUtilityError> {
match parse_numeric_value(config, token, unwrap_arbitrary_value(raw))? {
UtilityVal::Px(value) => Ok(value),
_ => Err(ParseUtilityError::new(token, "invalid line height value")),
}
}
fn parse_tracking_token(
config: &UiThemeConfig,
token: &str,
) -> Result<Option<f32>, ParseUtilityError> {
let Some(raw) = token.strip_prefix("tracking-") else {
return Ok(None);
};
let value = match raw {
"tighter" => theme_tracking(config, "tighter"),
"tight" => theme_tracking(config, "tight"),
"normal" => theme_tracking(config, "normal"),
"wide" => theme_tracking(config, "wide"),
"wider" => theme_tracking(config, "wider"),
_ if raw.starts_with('[') => parse_tracking_value(token, raw)?,
_ => return Ok(None),
};
Ok(Some(value))
}
fn parse_tracking_value(token: &str, raw: &str) -> Result<f32, ParseUtilityError> {
let raw = unwrap_arbitrary_value(raw).trim();
let Some(value) = raw.strip_suffix("em") else {
return Err(ParseUtilityError::new(
token,
"tracking arbitrary value must use em units",
));
};
value
.trim()
.parse::<f32>()
.map_err(|_| ParseUtilityError::new(token, "invalid tracking value"))
}
fn parse_text_transform_token(token: &str) -> Option<UtilityTextTransform> {
match token {
"uppercase" => Some(UtilityTextTransform::Uppercase),
"lowercase" => Some(UtilityTextTransform::Lowercase),
"capitalize" => Some(UtilityTextTransform::Capitalize),
"normal-case" => Some(UtilityTextTransform::None),
_ => None,
}
}
fn theme_text_size(config: &UiThemeConfig, name: &str) -> f32 {
resolve_theme_numeric_value_in(config, &format!("var(--text-{name})")).unwrap_or(0.0)
}
fn theme_line_height(config: &UiThemeConfig, name: &str) -> f32 {
match name {
"none" => config.typography.leading_none,
"tight" => config.typography.leading_tight,
"snug" => config.typography.leading_snug,
"normal" => config.typography.leading_normal,
"relaxed" => config.typography.leading_relaxed,
_ => config.typography.leading_normal,
}
}
fn theme_tracking(config: &UiThemeConfig, name: &str) -> f32 {
match name {
"tighter" => config.typography.tracking_tighter,
"tight" => config.typography.tracking_tight,
"normal" => config.typography.tracking_normal,
"wide" => config.typography.tracking_wide,
"wider" => config.typography.tracking_wider,
_ => config.typography.tracking_normal,
}
}
fn theme_font_weight(config: &UiThemeConfig, name: &str) -> u16 {
match name {
"thin" => config.typography.weight_thin,
"extralight" => config.typography.weight_extralight,
"light" => config.typography.weight_light,
"normal" => config.typography.weight_normal,
"medium" => config.typography.weight_medium,
"semibold" => config.typography.weight_semibold,
"bold" => config.typography.weight_bold,
"extrabold" => config.typography.weight_extrabold,
"black" => config.typography.weight_black,
_ => config.typography.weight_normal,
}
}