use compact_str::{CompactString, ToCompactString};
use crate::serialization::SerializationError;
#[derive(Debug, Eq, Clone, PartialEq)]
pub struct Glyph {
pub(crate) id: u16,
pub(crate) style: FontStyle,
pub(crate) symbol: CompactString,
pub(crate) pixel_coords: (i32, i32),
pub(crate) is_emoji: bool,
}
#[rustfmt::skip]
impl Glyph {
pub const UNASSIGNED_ID: u16 = 0xFFFF;
#[doc(hidden)]
pub const GLYPH_ID_MASK: u16 = 0b0000_0011_1111_1111; #[doc(hidden)]
pub const GLYPH_ID_EMOJI_MASK: u16 = 0b0001_1111_1111_1111; #[doc(hidden)]
pub const BOLD_FLAG: u16 = 0b0000_0100_0000_0000; #[doc(hidden)]
pub const ITALIC_FLAG: u16 = 0b0000_1000_0000_0000; #[doc(hidden)]
pub const EMOJI_FLAG: u16 = 0b0001_0000_0000_0000; #[doc(hidden)]
pub const UNDERLINE_FLAG: u16 = 0b0010_0000_0000_0000; #[doc(hidden)]
pub const STRIKETHROUGH_FLAG: u16 = 0b0100_0000_0000_0000; }
impl Glyph {
#[inline]
pub fn id(&self) -> u16 {
self.id
}
#[inline]
pub fn style(&self) -> FontStyle {
self.style
}
#[inline]
pub fn symbol(&self) -> &str {
&self.symbol
}
#[inline]
pub fn pixel_coords(&self) -> (i32, i32) {
self.pixel_coords
}
#[inline]
pub fn is_emoji(&self) -> bool {
self.is_emoji
}
#[inline]
pub fn set_pixel_coords(&mut self, pixel_coords: (i32, i32)) {
self.pixel_coords = pixel_coords;
}
pub fn new(symbol: &str, style: FontStyle, pixel_coords: (i32, i32)) -> Self {
let first_char = symbol.chars().next().unwrap();
let id = if symbol.len() == 1 && first_char.is_ascii() {
first_char as u16 | style.style_mask()
} else {
Self::UNASSIGNED_ID
};
Self {
id,
symbol: symbol.to_compact_string(),
style,
pixel_coords,
is_emoji: false,
}
}
pub fn new_with_id(
base_id: u16,
symbol: &str,
style: FontStyle,
pixel_coords: (i32, i32),
) -> Self {
Self {
id: base_id | style.style_mask(),
symbol: symbol.to_compact_string(),
style,
pixel_coords,
is_emoji: (base_id & Self::EMOJI_FLAG) != 0,
}
}
pub fn new_emoji(base_id: u16, symbol: &str, pixel_coords: (i32, i32)) -> Self {
Self {
id: base_id | Self::EMOJI_FLAG,
symbol: symbol.to_compact_string(),
style: FontStyle::Normal, pixel_coords,
is_emoji: true,
}
}
pub fn is_ascii(&self) -> bool {
self.symbol.len() == 1
}
pub fn base_id(&self) -> u16 {
if self.is_emoji {
self.id & Self::GLYPH_ID_EMOJI_MASK
} else {
self.id & Self::GLYPH_ID_MASK
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlyphEffect {
None = 0x0,
Underline = 0x2000,
Strikethrough = 0x4000,
}
impl GlyphEffect {
pub fn from_u16(v: u16) -> GlyphEffect {
match v {
0x0000 => GlyphEffect::None,
0x2000 => GlyphEffect::Underline,
0x4000 => GlyphEffect::Strikethrough,
0x6000 => GlyphEffect::Strikethrough,
_ => GlyphEffect::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FontStyle {
Normal = 0x0000,
Bold = 0x0400,
Italic = 0x0800,
BoldItalic = 0x0C00,
}
impl FontStyle {
pub const MASK: u16 = 0x0C00;
pub const ALL: [FontStyle; 4] =
[FontStyle::Normal, FontStyle::Bold, FontStyle::Italic, FontStyle::BoldItalic];
pub fn from_u16(v: u16) -> Result<FontStyle, SerializationError> {
match v {
0x0000 => Ok(FontStyle::Normal),
0x0400 => Ok(FontStyle::Bold),
0x0800 => Ok(FontStyle::Italic),
0x0C00 => Ok(FontStyle::BoldItalic),
_ => Err(SerializationError {
message: CompactString::new(format!("Invalid font style value: {v:#06x}")),
}),
}
}
pub(super) fn from_ordinal(ordinal: u8) -> Result<FontStyle, SerializationError> {
match ordinal {
0 => Ok(FontStyle::Normal),
1 => Ok(FontStyle::Bold),
2 => Ok(FontStyle::Italic),
3 => Ok(FontStyle::BoldItalic),
_ => Err(SerializationError {
message: CompactString::new(format!("Invalid font style ordinal: {ordinal}")),
}),
}
}
pub(super) const fn ordinal(&self) -> usize {
match self {
FontStyle::Normal => 0,
FontStyle::Bold => 1,
FontStyle::Italic => 2,
FontStyle::BoldItalic => 3,
}
}
pub const fn style_mask(&self) -> u16 {
*self as u16
}
}