#![allow(dead_code)] use std::{ collections::HashMap, sync::{Arc, Weak}, }; use ash::vk::Extent2D; use cosmic_text::{CacheKey, FontSystem, PhysicalGlyph, SwashCache}; use glam::IVec2; use guillotiere::size2; #[cfg(test)] use image::{GenericImage, GenericImageView}; use crate::{ def_monotonic_id, util::{self, F32, Rect2D}, }; const ROBOTO_BYTES: &[u8] = include_bytes!("../../../assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf"); const NOTO_SANS_HAN_BYTES: &[u8] = include_bytes!("../../../assets/fonts/Noto_Sans_SC/NotoSansSC-VariableFont_wght.ttf"); def_monotonic_id!(pub FontId); type FontData = Arc + Send + Sync>; struct FontStore { fonts: HashMap, } impl FontStore { fn new() -> Self { Self { fonts: HashMap::new(), } } fn add_font_bytes(&mut self, bytes: FontData) -> FontId { let id = FontId::new(); self.fonts.insert(id, bytes); id } fn as_source(&self, id: FontId) -> Option { self.fonts .get(&id) .map(|bytes| cosmic_text::fontdb::Source::Binary(Arc::clone(bytes))) } } #[derive(Debug, Default)] struct FontAtlasSets { sets: HashMap, } impl FontAtlasSets { fn new() -> Self { Self { sets: HashMap::new(), } } fn get(&self, key: &FontId) -> Option<&FontAtlasSet> { self.sets.get(key) } fn get_mut(&mut self, key: &FontId) -> Option<&mut FontAtlasSet> { self.sets.get_mut(key) } } /// Per-Font Font Atlas Set #[derive(Debug, Default)] struct FontAtlasSet { // TODO: add proper plural of atlas to english language. atlantes: HashMap>, } impl FontAtlasSet { fn new() -> Self { Self { atlantes: HashMap::new(), } } fn get_glyph_info(&self, key: CacheKey) -> Option { self.atlantes .get(&F32::from_bits(key.font_size_bits))? .iter() .find_map(|atlas| { atlas .glyphs .get(&key) .map(|&(rect, offset)| (Arc::downgrade(&atlas.image), rect, offset)) }) .map(|(image, rect, offset)| AtlasGlyphInfo { image, rect, offset, }) } fn add_glyph( &mut self, font_system: &mut FontSystem, swash_cache: &mut SwashCache, physical: PhysicalGlyph, ) -> Result { let key = physical.cache_key; let atlantes = self .atlantes .entry(F32::from_bits(physical.cache_key.font_size_bits)) .or_insert_with(|| vec![FontAtlas::new(512)]); let (data, size, offset) = get_outlined_glyph_texture(font_system, swash_cache, physical)?; if !atlantes .iter_mut() .any(|atlas| atlas.add_glyph(key, &data, size, offset).is_some()) { let max_size = size.height.max(size.height); let x2_or_512 = (1u32 << (32 - max_size.leading_zeros())).max(512); atlantes.push(FontAtlas::new(x2_or_512)); atlantes .last_mut() .unwrap() .add_glyph(key, &data, size, offset) .ok_or(Error::FailedToRasterizeGlyph(key))?; } Ok(self.get_glyph_info(key).unwrap()) } } #[derive(Debug)] struct Image(parking_lot::RwLock); #[derive(Debug, Clone)] struct ImageInner { image: Vec, image_size: Extent2D, } impl ImageInner { fn bytes(&self) -> &[u8] { self.image.as_slice() } fn bytes_mut(&mut self) -> &mut [u8] { self.image.as_mut_slice() } fn width(&self) -> u32 { self.image_size.width } fn height(&self) -> u32 { self.image_size.height } } #[derive(Debug)] struct AtlasGlyphInfo { image: Weak, rect: Rect2D, offset: IVec2, } struct FontAtlas { // TODO: this image will be host-coherent and host-visible, so that it can // be updated with new glyphs. // that begs the question: // - should it be staged to a device-local image when it's used? // - does it make sense to use VK_EXT_external_memory_host here? It's // supported on virtually all Windows and Linux drivers. // - how to sync this? I need to make sure that when this image is written // to, it isn't also being read from. Usually, these images will be // write-only, except when rendering. Since currently my rendering may // happen on any thread at any time, that's problematic. // // In fact, this is an awful type to use here because of the unique access // requirement for mapping. image: Arc, // stores sub-rect of image and placement offset of glyph glyphs: HashMap, allocator: guillotiere::AtlasAllocator, } impl std::fmt::Debug for FontAtlas { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FontAtlas") .field("image", &self.image) .field("glyphs", &self.glyphs) .finish_non_exhaustive() } } impl FontAtlas { fn new(size: u32) -> Self { let num_bytes = size * size * 4; Self { image: Arc::new(Image(parking_lot::RwLock::new(ImageInner { image: vec![0; num_bytes as usize], image_size: Extent2D { width: size, height: size, }, }))), glyphs: HashMap::new(), allocator: guillotiere::AtlasAllocator::new(size2( size.try_into().unwrap(), size.try_into().unwrap(), )), } } fn has_glyph(&self, key: CacheKey) -> bool { self.glyphs.contains_key(&key) } fn add_glyph( &mut self, key: CacheKey, data: &[u8], size: Extent2D, offset: IVec2, ) -> Option { let allocation = self.allocator.allocate(guillotiere::size2( (size.width + 1).try_into().unwrap(), (size.height + 1).try_into().unwrap(), ))?; let rect = allocation.rectangle; let x = rect.min.x; let y = rect.min.y; let width = rect.width() - 1; let height = rect.height() - 1; let rect = Rect2D::new_from_size(IVec2::new(x, y), IVec2::new(width, height)); self.glyphs.insert(key, (rect, offset)); // put data into image array let mut image = self.image.0.write(); for line_y in 0..height { let y = y + line_y; let image_offset = 4 * y as u32 * image.width() + 4 * x as u32; let glyph_offset = 4 * line_y * width; let len = 4 * width as usize; let dst = &mut image.bytes_mut()[image_offset as usize..][..len]; let src = &data[glyph_offset as usize..][..len]; dst.copy_from_slice(src); } Some(AtlasGlyphInfo { image: Arc::downgrade(&self.image), rect, offset, }) } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Failed to rasterize glyph {0:?}.")] FailedToRasterizeGlyph(CacheKey), } fn get_outlined_glyph_texture( font_system: &mut FontSystem, swash_cache: &mut SwashCache, glyph: PhysicalGlyph, ) -> Result<(Vec, Extent2D, IVec2), Error> { let image = swash_cache .get_image_uncached(font_system, glyph.cache_key) .ok_or(Error::FailedToRasterizeGlyph(glyph.cache_key))?; let cosmic_text::Placement { left, top, width, height, } = image.placement; let data = match image.content { cosmic_text::SwashContent::Mask => image .data .iter() .flat_map(|a| [255, 255, 255, *a]) .collect(), cosmic_text::SwashContent::Color => image.data, cosmic_text::SwashContent::SubpixelMask => { // TODO: implement todo!() } }; let extent = Extent2D { width, height }; Ok((data, extent, IVec2::new(left, top))) } #[cfg(test)] mod tests { use super::*; use cosmic_text::{Attrs, Buffer, Family, Metrics}; use glam::Vec2; #[test] fn test() { let mut font_store = FontStore::new(); let mut db = cosmic_text::fontdb::Database::new(); let mut font_id_map = HashMap::::new(); let mut reverse_font_id_map = HashMap::::new(); let roboto = font_store.add_font_bytes(Arc::new(ROBOTO_BYTES)); let noto_han = font_store.add_font_bytes(Arc::new(NOTO_SANS_HAN_BYTES)); let id = *db .load_font_source(font_store.as_source(roboto).unwrap()) .last() .unwrap(); font_id_map.insert(roboto, id); reverse_font_id_map.insert(id, roboto); let id = *db .load_font_source(font_store.as_source(noto_han).unwrap()) .last() .unwrap(); font_id_map.insert(noto_han, id); reverse_font_id_map.insert(id, noto_han); db.set_sans_serif_family("Roboto"); // db.load_system_fonts(); let locale = sys_locale::get_locale().unwrap_or("en-DK".to_string()); let mut font_system = FontSystem::new_with_locale_and_db(locale, db); let mut swash = SwashCache::new(); let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0)); let mut buf = buffer.borrow_with(&mut font_system); let path = format!("../../assets/testing/hello.txt"); let attrs = Attrs::new() .family(Family::SansSerif) .metrics(Metrics::new(48.0, 56.0)); let _text = std::fs::read_to_string(path).expect("hello.txt"); buf.set_text( "Hello, World! 你好! 안녕하세요", attrs, cosmic_text::Shaping::Advanced, ); //buf.set_size(Some(400.0), Some(400.0)); buf.set_wrap(cosmic_text::Wrap::Word); for line in buf.lines.iter_mut() { line.set_align(Some(cosmic_text::Align::Left)); } buf.shape_until_scroll(false); let mut font_atlantes = FontAtlasSets::new(); let mut glyphs = Vec::new(); let mut size = Vec2::new(0.0, 0.0); _ = buffer .layout_runs() .flat_map(|run| { size.x = size.x.max(run.line_w); size.y = size.y + run.line_height; run.glyphs.iter().map(move |glyph| (glyph, run.line_y)) }) .try_for_each(|(glyph, line_y)| -> Result<(), Error> { let font_id = *reverse_font_id_map.get(&glyph.font_id).unwrap(); let set = font_atlantes.sets.entry(font_id).or_default(); let physical = glyph.physical((0.0, 0.0), 1.0); let glyph_info = set .get_glyph_info(physical.cache_key) .map(Ok) .unwrap_or_else(|| { set.add_glyph(&mut font_system, &mut swash, physical.clone()) })?; let pos = { let x = glyph_info.offset.x as f32 + physical.x as f32; let y = line_y.round() + physical.y as f32 - glyph_info.offset.y as f32; Vec2::new(x, y) }; let size = glyph_info.rect.size(); glyphs.push((glyph_info, pos, size)); Ok(()) }); let (width, height) = { let tmp = size.ceil().as_uvec2(); (tmp.x, tmp.y) }; let mut image = image::RgbaImage::from_pixel(width, height, image::Rgba([0, 0, 0, 255])); eprintln!("glyphs: {glyphs:#?}"); eprintln!("image: {width}x{height}"); for (info, pos, _size) in glyphs { let atlas_image = info.image.upgrade().unwrap(); let glyph = atlas_image.0.read(); let atlas_image = image::ImageBuffer::, _>::from_raw( glyph.width(), glyph.height(), glyph.bytes(), ) .unwrap(); let glyph = atlas_image.view( info.rect.top_left().x as u32, info.rect.top_left().y as u32, info.rect.width() as u32, info.rect.height() as u32, ); eprintln!("rect: {:?}", info.rect); eprintln!("image_size: {:?}", image.dimensions()); image .copy_from(&*glyph, pos.x as u32, pos.y as u32) .unwrap(); } image.save("rendered.png").unwrap(); } }