use crate::{Rgba, Vec2};
use serde::{Deserialize, Serialize};
pub const MAX_TEXT_CONTENT_BYTES: usize = 4_096;
pub const MAX_FONT_FAMILY_BYTES: usize = 256;
pub const MAX_TEXT_GENERATOR_FONT_SIZE: f32 = 1.0;
pub const LINE_HEIGHT_RANGE: std::ops::RangeInclusive<f32> = 0.5..=3.0;
pub const MAX_TEXT_OUTLINE_WIDTH: f32 = 0.5;
pub const MAX_TEXT_PADDING: f32 = 2.0;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TextStyle {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub font_family: Option<String>,
#[serde(default = "default_font_size")]
pub font_size: f32,
#[serde(default = "white")]
pub color: Rgba,
#[serde(default = "default_line_height")]
pub line_height: f32,
#[serde(default)]
pub align: TextAlign,
#[serde(default = "default_max_width")]
pub max_width: f32,
#[serde(default = "default_position")]
pub position: Vec2,
#[serde(default = "default_anchor")]
pub anchor: Vec2,
#[serde(default)]
pub outline: Option<TextOutline>,
#[serde(default)]
pub background: Option<TextBackground>,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
font_family: None,
font_size: default_font_size(),
color: white(),
line_height: default_line_height(),
align: TextAlign::default(),
max_width: default_max_width(),
position: default_position(),
anchor: default_anchor(),
outline: None,
background: None,
}
}
}
const fn default_font_size() -> f32 {
0.05
}
const fn white() -> Rgba {
Rgba::new(1.0, 1.0, 1.0, 1.0)
}
const fn default_line_height() -> f32 {
1.2
}
const fn default_max_width() -> f32 {
0.9
}
const fn default_position() -> Vec2 {
Vec2::new(0.5, 0.92)
}
const fn default_anchor() -> Vec2 {
Vec2::new(0.5, 1.0)
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum TextAlign {
Start,
#[default]
Center,
End,
}
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TextOutline {
pub color: Rgba,
pub width: f32,
}
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TextBackground {
pub color: Rgba,
#[serde(default)]
pub padding: f32,
}
impl TextStyle {
pub(crate) fn is_valid(&self, max_font_size: f32) -> bool {
let finite = |value: Vec2| value.x.is_finite() && value.y.is_finite();
self.font_family
.as_ref()
.is_none_or(|family| !family.is_empty() && family.len() <= MAX_FONT_FAMILY_BYTES)
&& self.font_size > 0.0
&& self.font_size <= max_font_size
&& self.color.is_valid()
&& LINE_HEIGHT_RANGE.contains(&self.line_height)
&& self.max_width > 0.0
&& self.max_width <= 1.0
&& finite(self.position)
&& finite(self.anchor)
&& self.outline.is_none_or(|outline| {
outline.color.is_valid()
&& outline.width > 0.0
&& outline.width <= MAX_TEXT_OUTLINE_WIDTH
})
&& self.background.is_none_or(|background| {
background.color.is_valid()
&& (0.0..=MAX_TEXT_PADDING).contains(&background.padding)
})
}
}