use crate::{Result, TextError};
use rustc_hash::FxHashMap;
#[derive(Debug, Clone, Copy)]
pub struct AtlasRegion {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl AtlasRegion {
pub fn uv_bounds(&self, atlas_width: u32, atlas_height: u32) -> [f32; 4] {
let u_min = self.x as f32 / atlas_width as f32;
let v_min = self.y as f32 / atlas_height as f32;
let u_max = (self.x + self.width) as f32 / atlas_width as f32;
let v_max = (self.y + self.height) as f32 / atlas_height as f32;
[u_min, v_min, u_max, v_max]
}
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphInfo {
pub region: AtlasRegion,
pub bearing_x: i16,
pub bearing_y: i16,
pub advance: u16,
pub font_size: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct GlyphKey {
font_id: u32,
glyph_id: u16,
size_key: u16,
}
impl GlyphKey {
fn new(font_id: u32, glyph_id: u16, font_size: f32) -> Self {
let size_key = (font_size * 2.0).round() as u16;
Self {
font_id,
glyph_id,
size_key,
}
}
}
#[derive(Debug)]
struct Shelf {
y: u32,
height: u32,
x: u32,
}
pub struct GlyphAtlas {
width: u32,
height: u32,
pixels: Vec<u8>,
glyphs: FxHashMap<GlyphKey, GlyphInfo>,
shelves: Vec<Shelf>,
padding: u32,
dirty: bool,
}
impl GlyphAtlas {
const MAX_SIZE: u32 = 4096;
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
pixels: vec![0; (width * height) as usize],
glyphs: FxHashMap::default(),
shelves: Vec::new(),
padding: 2, dirty: true,
}
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn pixels(&self) -> &[u8] {
&self.pixels
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn mark_clean(&mut self) {
self.dirty = false;
}
pub fn get_glyph(&self, font_id: u32, glyph_id: u16, font_size: f32) -> Option<&GlyphInfo> {
let key = GlyphKey::new(font_id, glyph_id, font_size);
self.glyphs.get(&key)
}
fn allocate(&mut self, width: u32, height: u32) -> Result<AtlasRegion> {
let padded_width = width + self.padding;
let padded_height = height + self.padding;
let mut best_shelf = None;
let mut best_y = u32::MAX;
for (i, shelf) in self.shelves.iter().enumerate() {
if shelf.height >= padded_height
&& shelf.x + padded_width <= self.width
&& shelf.y < best_y
{
best_y = shelf.y;
best_shelf = Some(i);
}
}
if let Some(shelf_idx) = best_shelf {
let shelf = &mut self.shelves[shelf_idx];
let region = AtlasRegion {
x: shelf.x,
y: shelf.y,
width,
height,
};
shelf.x += padded_width;
return Ok(region);
}
let new_y = self.shelves.last().map(|s| s.y + s.height).unwrap_or(0);
if new_y + padded_height > self.height {
return Err(TextError::AtlasFull);
}
let region = AtlasRegion {
x: 0,
y: new_y,
width,
height,
};
self.shelves.push(Shelf {
y: new_y,
height: padded_height,
x: padded_width,
});
Ok(region)
}
#[allow(clippy::too_many_arguments)]
pub fn insert_glyph(
&mut self,
font_id: u32,
glyph_id: u16,
font_size: f32,
width: u32,
height: u32,
bearing_x: i16,
bearing_y: i16,
advance: u16,
bitmap: &[u8],
) -> Result<GlyphInfo> {
let key = GlyphKey::new(font_id, glyph_id, font_size);
if let Some(info) = self.glyphs.get(&key) {
return Ok(*info);
}
let region = self.allocate(width, height)?;
for y in 0..height {
let src_offset = (y * width) as usize;
let dst_offset = ((region.y + y) * self.width + region.x) as usize;
let row_end = src_offset + width as usize;
if row_end <= bitmap.len() && dst_offset + width as usize <= self.pixels.len() {
self.pixels[dst_offset..dst_offset + width as usize]
.copy_from_slice(&bitmap[src_offset..row_end]);
}
}
let info = GlyphInfo {
region,
bearing_x,
bearing_y,
advance,
font_size,
};
self.glyphs.insert(key, info);
self.dirty = true;
Ok(info)
}
pub fn grow(&mut self) -> bool {
let new_width = (self.width * 2).min(Self::MAX_SIZE);
let new_height = (self.height * 2).min(Self::MAX_SIZE);
if new_width == self.width && new_height == self.height {
return false; }
let mut new_pixels = vec![0u8; (new_width * new_height) as usize];
for y in 0..self.height {
let src_start = (y * self.width) as usize;
let src_end = src_start + self.width as usize;
let dst_start = (y * new_width) as usize;
let dst_end = dst_start + self.width as usize;
new_pixels[dst_start..dst_end].copy_from_slice(&self.pixels[src_start..src_end]);
}
self.pixels = new_pixels;
self.width = new_width;
self.height = new_height;
self.dirty = true;
true
}
pub fn clear(&mut self) {
self.glyphs.clear();
self.shelves.clear();
self.pixels.fill(0);
self.dirty = true;
}
pub fn glyph_count(&self) -> usize {
self.glyphs.len()
}
pub fn utilization(&self) -> f32 {
let used_height = self.shelves.last().map(|s| s.y + s.height).unwrap_or(0);
used_height as f32 / self.height as f32
}
}
impl Default for GlyphAtlas {
fn default() -> Self {
Self::new(1024, 1024)
}
}
impl std::fmt::Debug for GlyphAtlas {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GlyphAtlas")
.field("dimensions", &(self.width, self.height))
.field("glyph_count", &self.glyphs.len())
.field(
"utilization",
&format!("{:.1}%", self.utilization() * 100.0),
)
.field("dirty", &self.dirty)
.finish()
}
}
pub struct ColorGlyphAtlas {
width: u32,
height: u32,
pixels: Vec<u8>,
glyphs: FxHashMap<GlyphKey, GlyphInfo>,
shelves: Vec<Shelf>,
padding: u32,
dirty: bool,
}
impl ColorGlyphAtlas {
const MAX_SIZE: u32 = 4096;
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
pixels: vec![0; (width * height * 4) as usize], glyphs: FxHashMap::default(),
shelves: Vec::new(),
padding: 2,
dirty: true,
}
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn pixels(&self) -> &[u8] {
&self.pixels
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn mark_clean(&mut self) {
self.dirty = false;
}
pub fn get_glyph(&self, font_id: u32, glyph_id: u16, font_size: f32) -> Option<&GlyphInfo> {
let key = GlyphKey::new(font_id, glyph_id, font_size);
self.glyphs.get(&key)
}
fn allocate(&mut self, width: u32, height: u32) -> Result<AtlasRegion> {
let padded_width = width + self.padding;
let padded_height = height + self.padding;
let mut best_shelf = None;
let mut best_y = u32::MAX;
for (i, shelf) in self.shelves.iter().enumerate() {
if shelf.height >= padded_height
&& shelf.x + padded_width <= self.width
&& shelf.y < best_y
{
best_y = shelf.y;
best_shelf = Some(i);
}
}
if let Some(shelf_idx) = best_shelf {
let shelf = &mut self.shelves[shelf_idx];
let region = AtlasRegion {
x: shelf.x,
y: shelf.y,
width,
height,
};
shelf.x += padded_width;
return Ok(region);
}
let new_y = self.shelves.last().map(|s| s.y + s.height).unwrap_or(0);
if new_y + padded_height > self.height {
return Err(TextError::AtlasFull);
}
let region = AtlasRegion {
x: 0,
y: new_y,
width,
height,
};
self.shelves.push(Shelf {
y: new_y,
height: padded_height,
x: padded_width,
});
Ok(region)
}
#[allow(clippy::too_many_arguments)]
pub fn insert_glyph(
&mut self,
font_id: u32,
glyph_id: u16,
font_size: f32,
width: u32,
height: u32,
bearing_x: i16,
bearing_y: i16,
advance: u16,
bitmap: &[u8],
) -> Result<GlyphInfo> {
let key = GlyphKey::new(font_id, glyph_id, font_size);
if let Some(info) = self.glyphs.get(&key) {
return Ok(*info);
}
let region = self.allocate(width, height)?;
for y in 0..height {
let src_offset = (y * width * 4) as usize;
let dst_offset = ((region.y + y) * self.width * 4 + region.x * 4) as usize;
let row_bytes = (width * 4) as usize;
if src_offset + row_bytes <= bitmap.len() && dst_offset + row_bytes <= self.pixels.len()
{
self.pixels[dst_offset..dst_offset + row_bytes]
.copy_from_slice(&bitmap[src_offset..src_offset + row_bytes]);
}
}
let info = GlyphInfo {
region,
bearing_x,
bearing_y,
advance,
font_size,
};
self.glyphs.insert(key, info);
self.dirty = true;
Ok(info)
}
pub fn grow(&mut self) -> bool {
let new_width = (self.width * 2).min(Self::MAX_SIZE);
let new_height = (self.height * 2).min(Self::MAX_SIZE);
if new_width == self.width && new_height == self.height {
return false;
}
let mut new_pixels = vec![0u8; (new_width * new_height * 4) as usize];
for y in 0..self.height {
let src_start = (y * self.width * 4) as usize;
let row_bytes = (self.width * 4) as usize;
let dst_start = (y * new_width * 4) as usize;
new_pixels[dst_start..dst_start + row_bytes]
.copy_from_slice(&self.pixels[src_start..src_start + row_bytes]);
}
self.pixels = new_pixels;
self.width = new_width;
self.height = new_height;
self.dirty = true;
true
}
pub fn clear(&mut self) {
self.glyphs.clear();
self.shelves.clear();
self.pixels.fill(0);
self.dirty = true;
}
pub fn glyph_count(&self) -> usize {
self.glyphs.len()
}
pub fn utilization(&self) -> f32 {
let used_height = self.shelves.last().map(|s| s.y + s.height).unwrap_or(0);
used_height as f32 / self.height as f32
}
}
impl Default for ColorGlyphAtlas {
fn default() -> Self {
Self::new(512, 512)
}
}
impl std::fmt::Debug for ColorGlyphAtlas {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ColorGlyphAtlas")
.field("dimensions", &(self.width, self.height))
.field("glyph_count", &self.glyphs.len())
.field(
"utilization",
&format!("{:.1}%", self.utilization() * 100.0),
)
.field("dirty", &self.dirty)
.finish()
}
}