#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HorizontalAlignment {
#[default]
Left,
Center,
Right,
Free,
}
impl HorizontalAlignment {
pub(crate) fn to_u8(self) -> u8 {
match self { Self::Left => 0, Self::Center => 1, Self::Right => 2, Self::Free => 3 }
}
pub(crate) fn from_u8(v: u8) -> Self {
match v { 1 => Self::Center, 2 => Self::Right, 3 => Self::Free, _ => Self::Left }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VerticalAlignment {
#[default]
Top,
Middle,
Bottom,
Free,
}
impl VerticalAlignment {
pub(crate) fn to_u8(self) -> u8 {
match self { Self::Top => 0, Self::Middle => 1, Self::Bottom => 2, Self::Free => 3 }
}
pub(crate) fn from_u8(v: u8) -> Self {
match v { 1 => Self::Middle, 2 => Self::Bottom, 3 => Self::Free, _ => Self::Top }
}
}
#[cfg(feature = "default-fonts")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DefaultFont {
Sans,
Serif,
Mono,
}
#[cfg(feature = "default-fonts")]
impl DefaultFont {
pub fn id(self) -> &'static str {
match self {
Self::Sans => "sans",
Self::Serif => "serif",
Self::Mono => "mono",
}
}
}
#[derive(Debug, Clone)]
pub struct TextLabel {
pub id: usize,
pub text: String,
pub x: f32,
pub y: f32,
pub font_size: f32,
pub color: [f32; 4],
pub visible: bool,
pub font_id: String,
pub zindex: i32,
pub horizontal_alignment: HorizontalAlignment,
pub vertical_alignment: VerticalAlignment,
pub dirty: bool,
pub position_dirty: bool,
pub rasterized_w: u32,
pub rasterized_h: u32,
pub rasterized_font_size: f32,
pub rasterized_vp_w: f32,
pub rasterized_vp_h: f32,
pub margin_x: f32,
pub margin_y: f32,
}
pub struct TextLabelBuilder<'a> {
pub(crate) overlay: &'a mut crate::text_overlay::TextOverlay,
pub(crate) text: String,
pub(crate) x: f32,
pub(crate) y: f32,
pub(crate) font_size: f32,
pub(crate) color: [f32; 4],
pub(crate) font_id: String,
pub(crate) visible: bool,
pub(crate) zindex: Option<i32>,
pub(crate) horizontal_alignment: HorizontalAlignment,
pub(crate) vertical_alignment: VerticalAlignment,
}
impl<'a> TextLabelBuilder<'a> {
pub fn at(mut self, x: f32, y: f32) -> Self {
self.x = x; self.y = y; self
}
pub fn with_color(mut self, color: [f32; 4]) -> Self {
self.color = color; self
}
pub fn with_font_size(mut self, size: f32) -> Self {
self.font_size = size; self
}
pub fn with_font(mut self, font_id: impl Into<String>) -> Self {
self.font_id = font_id.into(); self
}
pub fn with_zindex(mut self, z: i32) -> Self {
self.zindex = Some(z); self
}
pub fn with_horizontal_alignment(mut self, alignment: HorizontalAlignment) -> Self {
self.horizontal_alignment = alignment;
self
}
pub fn with_vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = alignment;
self
}
pub fn hidden(mut self) -> Self {
self.visible = false; self
}
pub fn build(self) -> TextLabelHandle {
let id = self.overlay.next_id;
self.overlay.next_id += 1;
let zindex = self.zindex.unwrap_or(id as i32);
self.overlay.labels.insert(id, TextLabel {
id,
text: self.text,
x: self.x,
y: self.y,
font_size: self.font_size,
color: self.color,
visible: self.visible,
font_id: self.font_id,
zindex,
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
dirty: true,
position_dirty: false,
rasterized_w: 0,
rasterized_h: 0,
rasterized_font_size: 0.0,
rasterized_vp_w: 0.0,
rasterized_vp_h: 0.0,
margin_x: self.x,
margin_y: self.y,
});
TextLabelHandle { id }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TextLabelHandle {
pub id: usize,
}
impl TextLabelHandle {
pub fn label<'a>(&self, overlay: &'a crate::text_overlay::TextOverlay) -> Option<&'a TextLabel> {
overlay.labels.get(&self.id)
}
pub fn exists(&self, overlay: &crate::text_overlay::TextOverlay) -> bool {
overlay.labels.contains_key(&self.id)
}
pub fn set_text(&self, overlay: &mut crate::text_overlay::TextOverlay, text: impl Into<String>) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.text = text.into();
l.dirty = true;
true
} else { false }
}
pub fn move_to(&self, overlay: &mut crate::text_overlay::TextOverlay, x: f32, y: f32) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.x = x;
l.y = y;
l.margin_x = x;
l.margin_y = y;
l.horizontal_alignment = HorizontalAlignment::Free;
l.vertical_alignment = VerticalAlignment::Free;
l.position_dirty = true;
true
} else { false }
}
pub fn set_color(&self, overlay: &mut crate::text_overlay::TextOverlay, color: [f32; 4]) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.color = color; l.dirty = true; true
} else { false }
}
pub fn set_font_size(&self, overlay: &mut crate::text_overlay::TextOverlay, size: f32) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.font_size = size; l.dirty = true; true
} else { false }
}
pub fn set_font(&self, overlay: &mut crate::text_overlay::TextOverlay, font_id: impl Into<String>) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.font_id = font_id.into(); l.dirty = true; true
} else { false }
}
pub fn set_zindex(&self, overlay: &mut crate::text_overlay::TextOverlay, z: i32) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.zindex = z; true
} else { false }
}
pub fn set_horizontal_alignment(&self, overlay: &mut crate::text_overlay::TextOverlay, alignment: HorizontalAlignment) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.horizontal_alignment = alignment; true
} else { false }
}
pub fn set_vertical_alignment(&self, overlay: &mut crate::text_overlay::TextOverlay, alignment: VerticalAlignment) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.vertical_alignment = alignment; true
} else { false }
}
pub fn show(&self, overlay: &mut crate::text_overlay::TextOverlay) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.visible = true; true
} else { false }
}
pub fn hide(&self, overlay: &mut crate::text_overlay::TextOverlay) -> bool {
if let Some(l) = overlay.labels.get_mut(&self.id) {
l.visible = false; true
} else { false }
}
pub fn remove(&self, overlay: &mut crate::text_overlay::TextOverlay) -> bool {
overlay.labels.remove(&self.id).is_some()
}
}
pub(crate) fn rasterize_text(
font: &fontdue::Font,
text: &str,
font_size: f32,
color: [f32; 4],
) -> (Vec<u8>, u32, u32) {
if text.is_empty() {
return (vec![0u8; 4], 1, 1);
}
let mut glyphs: Vec<(fontdue::Metrics, Vec<u8>)> = Vec::with_capacity(text.len());
let mut total_advance = 0.0f32;
let mut max_above_baseline: i32 = 0;
let mut min_below_baseline: i32 = 0;
for ch in text.chars() {
let (metrics, bitmap) = font.rasterize(ch, font_size);
total_advance += metrics.advance_width;
let above = metrics.ymin + metrics.height as i32;
if above > max_above_baseline { max_above_baseline = above; }
if metrics.ymin < min_below_baseline { min_below_baseline = metrics.ymin; }
glyphs.push((metrics, bitmap));
}
let text_height = ((max_above_baseline - min_below_baseline) as u32).max(1) + 2;
let text_width = (total_advance.ceil() as u32).max(1) + 2;
let mut pixels = vec![0u8; (text_width * text_height * 4) as usize];
let r = (color[0] * 255.0) as u8;
let g = (color[1] * 255.0) as u8;
let b = (color[2] * 255.0) as u8;
let base_alpha = color[3];
let baseline_y = max_above_baseline as i32 + 1;
let mut cursor_x = 1.0f32;
for (metrics, bitmap) in &glyphs {
for gy in 0..metrics.height {
for gx in 0..metrics.width {
let alpha_byte = bitmap[gy * metrics.width + gx];
if alpha_byte == 0 { continue; }
let px = cursor_x as i32 + gx as i32 + metrics.xmin;
let py = baseline_y - metrics.ymin - metrics.height as i32 + 1 + gy as i32;
if px >= 0 && px < text_width as i32 && py >= 0 && py < text_height as i32 {
let idx = ((py as u32 * text_width + px as u32) * 4) as usize;
pixels[idx] = r;
pixels[idx + 1] = g;
pixels[idx + 2] = b;
pixels[idx + 3] = ((alpha_byte as f32 / 255.0) * base_alpha * 255.0) as u8;
}
}
}
cursor_x += metrics.advance_width;
}
(pixels, text_width, text_height)
}