use crate::font_family_query::FontFamilyQuery;
use crate::utils::ArcCowStr;
use cosmic_text::{Align, Color, Family};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::hash::Hash;
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextWrap {
#[default]
NoWrap,
Wrap,
BreakWord,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LineHeight(pub f32);
const DEFAULT_LINE_HEIGHT: f32 = 1.5;
impl LineHeight {
pub const DEFAULT: Self = Self(DEFAULT_LINE_HEIGHT);
}
impl Default for LineHeight {
fn default() -> Self {
Self::DEFAULT
}
}
impl Hash for LineHeight {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl From<f32> for LineHeight {
fn from(value: f32) -> Self {
Self(value)
}
}
impl Eq for LineHeight {}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FontSize(pub f32);
impl FontSize {
pub const fn new(size: f32) -> Self {
Self(size)
}
pub const fn value(&self) -> f32 {
self.0
}
}
impl Default for FontSize {
fn default() -> Self {
Self(1.5)
}
}
impl Hash for FontSize {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl From<f32> for FontSize {
fn from(value: f32) -> Self {
Self(value)
}
}
impl Eq for FontSize {}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LetterSpacing(pub f32);
impl Hash for LetterSpacing {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl From<f32> for LetterSpacing {
fn from(value: f32) -> Self {
Self(value)
}
}
impl Eq for LetterSpacing {}
impl From<LetterSpacing> for cosmic_text::LetterSpacing {
fn from(letter_spacing: LetterSpacing) -> Self {
cosmic_text::LetterSpacing(letter_spacing.0)
}
}
impl LetterSpacing {
pub const fn new(spacing: f32) -> Self {
Self(spacing)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FontColor(pub Color);
#[cfg(feature = "serialization")]
impl Serialize for FontColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u32(self.0 .0)
}
}
#[cfg(feature = "serialization")]
impl<'de> Deserialize<'de> for FontColor {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let color_value = u32::deserialize(deserializer)?;
Ok(FontColor(Color(color_value)))
}
}
impl FontColor {
pub const fn new(color: Color) -> Self {
Self(color)
}
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self(Color::rgb(r, g, b))
}
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self(Color::rgba(r, g, b, a))
}
}
impl From<Color> for FontColor {
fn from(color: Color) -> Self {
Self(color)
}
}
impl From<FontColor> for Color {
fn from(font_color: FontColor) -> Self {
font_color.0
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FontFamily {
Name(ArcCowStr),
SansSerif,
Serif,
Monospace,
Cursive,
Fantasy,
}
impl From<&'static str> for FontFamily {
fn from(value: &'static str) -> Self {
Self::Name(value.into())
}
}
impl From<String> for FontFamily {
fn from(value: String) -> Self {
Self::Name(value.into())
}
}
impl FontFamily {
pub fn new(family: impl Into<ArcCowStr>) -> Self {
Self::Name(family.into())
}
pub const fn sans_serif() -> Self {
Self::SansSerif
}
pub const fn serif() -> Self {
Self::Serif
}
pub const fn monospace() -> Self {
Self::Monospace
}
pub fn to_fontdb_family(&self) -> Family<'_> {
match self {
FontFamily::Name(a) => Family::Name(a),
FontFamily::SansSerif => Family::SansSerif,
FontFamily::Serif => Family::Serif,
FontFamily::Monospace => Family::Monospace,
FontFamily::Cursive => Family::Cursive,
FontFamily::Fantasy => Family::Fantasy,
}
}
pub fn parse(string: &str) -> Self {
match string {
"serif" => Self::Serif,
"monospace" => Self::Monospace,
"cursive" => Self::Cursive,
"fantasy" => Self::Fantasy,
"sans-serif" => Self::SansSerif,
name => Self::Name(name.to_string().into()),
}
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Weight(pub u16);
impl Weight {
pub const THIN: Weight = Weight(100);
pub const EXTRA_LIGHT: Weight = Weight(200);
pub const LIGHT: Weight = Weight(300);
pub const NORMAL: Weight = Weight(400);
pub const MEDIUM: Weight = Weight(500);
pub const SEMIBOLD: Weight = Weight(600);
pub const BOLD: Weight = Weight(700);
pub const EXTRA_BOLD: Weight = Weight(800);
pub const BLACK: Weight = Weight(900);
pub fn new(weight: u16) -> Self {
Self(weight)
}
}
impl From<Weight> for cosmic_text::Weight {
fn from(weight: Weight) -> Self {
cosmic_text::Weight(weight.0)
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum HorizontalTextAlignment {
#[default]
None,
Start,
End,
Center,
Left,
Right,
Justify,
}
impl HorizontalTextAlignment {
pub const fn is_centered(&self) -> bool {
matches!(self, HorizontalTextAlignment::Center)
}
}
impl From<HorizontalTextAlignment> for Option<Align> {
fn from(val: HorizontalTextAlignment) -> Self {
match val {
HorizontalTextAlignment::None => None,
HorizontalTextAlignment::Start => None,
HorizontalTextAlignment::End => Some(Align::End),
HorizontalTextAlignment::Center => Some(Align::Center),
HorizontalTextAlignment::Left => Some(Align::Left),
HorizontalTextAlignment::Right => Some(Align::Right),
HorizontalTextAlignment::Justify => Some(Align::Justified),
}
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub enum VerticalTextAlignment {
#[default]
None,
Start,
End,
Center,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TextStyle {
pub font_size: FontSize,
pub line_height: LineHeight,
pub font_color: FontColor,
pub horizontal_alignment: HorizontalTextAlignment,
pub vertical_alignment: VerticalTextAlignment,
pub wrap: Option<TextWrap>,
pub font_family: FontFamily,
pub weight: Weight,
pub letter_spacing: Option<LetterSpacing>,
}
impl Hash for TextStyle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.font_size.hash(state);
self.line_height.hash(state);
self.font_color.hash(state);
self.horizontal_alignment.hash(state);
self.vertical_alignment.hash(state);
self.wrap.hash(state);
}
}
impl Default for TextStyle {
fn default() -> Self {
Self::DEFAULT
}
}
impl TextStyle {
pub const DEFAULT: Self = Self {
font_size: FontSize(16.0),
line_height: LineHeight::DEFAULT,
font_color: FontColor(Color::rgb(255, 255, 255)),
horizontal_alignment: HorizontalTextAlignment::Start,
vertical_alignment: VerticalTextAlignment::Start,
wrap: None,
font_family: FontFamily::SansSerif,
weight: Weight::NORMAL,
letter_spacing: None,
};
pub const fn new(font_size: f32, font_color: Color) -> Self {
Self {
font_size: FontSize(font_size),
line_height: LineHeight::DEFAULT,
font_color: FontColor(font_color),
horizontal_alignment: HorizontalTextAlignment::Start,
vertical_alignment: VerticalTextAlignment::Start,
wrap: None,
font_family: FontFamily::SansSerif,
weight: Weight::NORMAL,
letter_spacing: None,
}
}
pub const fn with_font_size(mut self, font_size: f32) -> Self {
self.font_size = FontSize(font_size);
self
}
pub const fn with_line_height(mut self, line_height: f32) -> Self {
self.line_height = LineHeight(line_height);
self
}
pub const fn with_font_color(mut self, font_color: FontColor) -> Self {
self.font_color = font_color;
self
}
pub const fn with_horizontal_alignment(mut self, alignment: HorizontalTextAlignment) -> Self {
self.horizontal_alignment = alignment;
self
}
pub const fn with_vertical_alignment(mut self, alignment: VerticalTextAlignment) -> Self {
self.vertical_alignment = alignment;
self
}
pub const fn with_alignment(
mut self,
horizontal: HorizontalTextAlignment,
vertical: VerticalTextAlignment,
) -> Self {
self.horizontal_alignment = horizontal;
self.vertical_alignment = vertical;
self
}
pub const fn with_wrap(mut self, wrap: TextWrap) -> Self {
self.wrap = Some(wrap);
self
}
#[inline(always)]
pub const fn line_height_pt(&self) -> f32 {
self.line_height.0 * self.font_size.0
}
pub(crate) fn font_family_query(&self) -> FontFamilyQuery {
FontFamilyQuery {
family_query_string: match &self.font_family {
FontFamily::Name(name) => name.clone(),
FontFamily::SansSerif => "sans-serif".into(),
FontFamily::Serif => "serif".into(),
FontFamily::Monospace => "monospace".into(),
FontFamily::Cursive => "cursive".into(),
FontFamily::Fantasy => "fantasy".into(),
},
weight: self.weight,
}
}
}