187 lines
6.1 KiB
Rust
187 lines
6.1 KiB
Rust
use std::simd::{Simd, num::SimdUint};
|
|
|
|
use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2};
|
|
use cosmic_text::{
|
|
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
|
|
SwashContent,
|
|
};
|
|
use image::{GenericImageView, RgbaImage};
|
|
|
|
/// TODO: properly wrap this
|
|
pub mod text_lib {
|
|
pub use cosmic_text::*;
|
|
}
|
|
|
|
pub struct TextData {
|
|
pub font_system: FontSystem,
|
|
pub swash_cache: SwashCache,
|
|
glyph_cache: Vec<(Placement, CacheKey, Color)>,
|
|
}
|
|
|
|
impl Default for TextData {
|
|
fn default() -> Self {
|
|
Self {
|
|
font_system: FontSystem::new(),
|
|
swash_cache: SwashCache::new(),
|
|
glyph_cache: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct TextAttrs {
|
|
pub color: UiColor,
|
|
pub font_size: f32,
|
|
pub line_height: f32,
|
|
pub family: Family<'static>,
|
|
pub wrap: bool,
|
|
/// inner alignment of text region (within where it's drawn)
|
|
pub align: RegionAlign,
|
|
}
|
|
|
|
impl TextAttrs {
|
|
pub fn apply(&self, font_system: &mut FontSystem, buf: &mut Buffer, width: Option<f32>) {
|
|
buf.set_metrics_and_size(
|
|
font_system,
|
|
Metrics::new(self.font_size, self.line_height),
|
|
width,
|
|
None,
|
|
);
|
|
let attrs = Attrs::new().family(self.family);
|
|
let list = AttrsList::new(&attrs);
|
|
for line in &mut buf.lines {
|
|
line.set_attrs_list(list.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type TextBuffer = Buffer;
|
|
|
|
impl Default for TextAttrs {
|
|
fn default() -> Self {
|
|
let size = 14.0;
|
|
Self {
|
|
color: UiColor::WHITE,
|
|
font_size: size,
|
|
line_height: size * 1.2,
|
|
family: Family::SansSerif,
|
|
wrap: false,
|
|
align: Align::CENTER_LEFT,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TextData {
|
|
pub fn draw(
|
|
&mut self,
|
|
buffer: &mut TextBuffer,
|
|
attrs: &TextAttrs,
|
|
textures: &mut Textures,
|
|
) -> RenderedText {
|
|
// TODO: either this or the layout stuff (or both) is super slow,
|
|
// should probably do texture packing and things if possible.
|
|
// very visible if you add just a couple of wrapping texts and resize window
|
|
// should also be timed to figure out exactly what points need to be sped up
|
|
// let mut pixels = HashMap::<_, [u8; 4]>::default();
|
|
let mut min_x = 0;
|
|
let mut min_y = 0;
|
|
let mut max_x = 0;
|
|
let mut max_y = 0;
|
|
let text_color = {
|
|
let c = attrs.color;
|
|
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
|
};
|
|
let mut max_width = 0.0f32;
|
|
let mut height = 0.0;
|
|
|
|
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 => text_color,
|
|
};
|
|
|
|
if let Some(img) = self
|
|
.swash_cache
|
|
.get_image(&mut self.font_system, physical_glyph.cache_key)
|
|
{
|
|
let mut pos = img.placement;
|
|
pos.left += physical_glyph.x;
|
|
pos.top = physical_glyph.y + run.line_y as i32 - pos.top;
|
|
min_x = min_x.min(pos.left);
|
|
min_y = min_y.min(pos.top);
|
|
max_x = max_x.max(pos.left + pos.width as i32);
|
|
max_y = max_y.max(pos.top + pos.height as i32);
|
|
self.glyph_cache
|
|
.push((pos, physical_glyph.cache_key, glyph_color));
|
|
}
|
|
}
|
|
max_width = max_width.max(run.line_w);
|
|
height += run.line_height;
|
|
}
|
|
let img_width = (max_x - min_x + 1) as u32;
|
|
let img_height = (max_y - min_y + 1) as u32;
|
|
let mut image = RgbaImage::new(img_width, img_height);
|
|
|
|
for (pos, key, color) in self.glyph_cache.drain(..) {
|
|
let img = self
|
|
.swash_cache
|
|
.get_image(&mut self.font_system, key)
|
|
.as_ref()
|
|
.unwrap();
|
|
let mut merge = |i, color: [u8; 4]| {
|
|
let i = i as i32;
|
|
let x = (i % pos.width as i32 + pos.left - min_x) as u32;
|
|
let y = (i / pos.width as i32 + pos.top - min_y) as u32;
|
|
let pixel = &mut image[(x, y)].0;
|
|
// TODO: no clue if proper alpha blending should be done
|
|
*pixel = Simd::from(color).saturating_add(Simd::from(*pixel)).into();
|
|
};
|
|
|
|
match img.content {
|
|
SwashContent::Mask => {
|
|
for (i, a) in img.data.iter().enumerate() {
|
|
let mut color = color.as_rgba();
|
|
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
|
|
merge(i, color);
|
|
}
|
|
}
|
|
SwashContent::SubpixelMask => todo!("subpixel mask text rendering"),
|
|
SwashContent::Color => {
|
|
let (colors, _) = img.data.as_chunks::<4>();
|
|
for (i, color) in colors.iter().enumerate() {
|
|
merge(i, *color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let max_dim = 8192;
|
|
if image.width() > max_dim || image.height() > max_dim {
|
|
let width = image.width().min(max_dim);
|
|
let height = image.height().min(max_dim);
|
|
eprintln!(
|
|
"WARNING: image of size {:?} cropped to {:?} (texture too big)",
|
|
image.dimensions(),
|
|
(width, height)
|
|
);
|
|
image = image.view(0, 0, width, height).to_image();
|
|
}
|
|
|
|
RenderedText {
|
|
handle: textures.add(image),
|
|
top_left_offset: Vec2::new(min_x as f32, min_y as f32),
|
|
size: Vec2::new(max_width, height),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct RenderedText {
|
|
pub handle: TextureHandle,
|
|
pub top_left_offset: Vec2,
|
|
pub size: Vec2,
|
|
}
|