Files
iris/src/layout/text.rs
2025-08-28 01:52:00 -04:00

126 lines
3.5 KiB
Rust

use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping, SwashCache};
use image::{Rgba, RgbaImage};
use crate::{
layout::{TextureHandle, Textures, UiColor, Vec2},
util::HashMap,
};
pub struct TextData {
font_system: FontSystem,
swash_cache: SwashCache,
}
impl Default for TextData {
fn default() -> Self {
Self {
font_system: FontSystem::new(),
swash_cache: SwashCache::new(),
}
}
}
#[derive(Clone, Copy)]
pub struct TextAttrs {
pub color: UiColor,
pub size: f32,
pub line_height: f32,
pub family: Family<'static>,
}
pub type TextBuffer = Buffer;
impl Default for TextAttrs {
fn default() -> Self {
let size = 14.0;
Self {
color: UiColor::WHITE,
size,
line_height: size * 1.2,
family: Family::SansSerif,
}
}
}
impl TextData {
pub fn draw(
&mut self,
buffer: &mut TextBuffer,
content: &str,
attrs: &TextAttrs,
textures: &mut Textures,
) -> (TextureHandle, TextOffset) {
buffer.set_metrics(
&mut self.font_system,
Metrics::new(attrs.size, attrs.line_height),
);
buffer.set_text(
&mut self.font_system,
content,
&Attrs::new().family(attrs.family),
Shaping::Advanced,
);
let mut pixels = HashMap::new();
let mut min_x = 0;
let mut min_y = 0;
let mut max_x = 0;
let mut max_y = 0;
let c = attrs.color;
let mut max_width = 0.0f32;
for run in buffer.layout_runs() {
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => cosmic_text::Color::rgba(c.r, c.g, c.b, c.a),
};
self.swash_cache.with_pixels(
&mut self.font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
let x = physical_glyph.x + x;
let y = run.line_y as i32 + physical_glyph.y + y;
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
pixels.insert((x, y), Rgba(color.as_rgba()));
},
);
}
max_width = max_width.max(run.line_w);
}
let width = (max_x - min_x + 1) as u32;
let height = (max_y - min_y + 1) as u32;
let mut image = RgbaImage::new(width, height);
for ((x, y), color) in pixels {
let x = (x - min_x) as u32;
let y = (y - min_y) as u32;
image.put_pixel(x, y, color);
}
let offset = TextOffset {
top_left: Vec2::new(min_x as f32, min_y as f32),
bot_right: Vec2::new(
max_width - max_x as f32,
attrs.line_height * buffer.lines.len() as f32 - max_y as f32,
),
};
(textures.add(image), offset)
}
}
#[derive(Clone, Copy)]
pub struct TextOffset {
pub top_left: Vec2,
pub bot_right: Vec2,
}
impl TextOffset {
pub fn size(&self, handle: &TextureHandle) -> Vec2 {
handle.size() - self.top_left + self.bot_right
}
}