use crate::{ PatchRect, util::{HashMap, Vec2}, }; use image::RgbaImage; use swash::scale::image::{Content, Image}; /// Side of one page, and so of every layer of `render::page`'s array texture. pub(crate) const PAGE: u32 = 1024; /// Transparent margin kept around every glyph, so that sampling one cannot /// pick up its neighbour along a shared edge. const PAD: u32 = 1; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct GlyphKey { pub font: u64, pub glyph: u32, /// Font size in 1/16 px, so sizes that round to the same pixels share a /// raster instead of filling the atlas with near-duplicates. pub size: u32, /// Horizontal subpixel phase, in 1/4 px. pub subpixel: u8, /// Hash of the variation coordinates; a variable font at two weights is two /// different sets of pixels from one glyph id. pub coords: u64, } #[derive(Clone, Copy)] pub struct GlyphEntry { pub uv_min: Vec2, pub uv_max: Vec2, /// Offset from the glyph's pen position to the top-left of its pixels. pub left: i32, pub top: i32, pub width: u32, pub height: u32, pub is_colored: bool, /// Which atlas array layer this glyph is on. pub layer: u32, } impl GlyphEntry { const IS_COLORED: u32 = 1; pub(crate) fn flags(&self) -> u32 { if self.is_colored { Self::IS_COLORED } else { 0 } } } struct Page { image: RgbaImage, x: u32, y: u32, shelf_height: u32, } /// A rectangle of one page the renderer has not uploaded yet. #[derive(Clone, Copy)] pub struct PageUpload { pub layer: u32, pub rect: PatchRect, } #[derive(Default)] pub struct GlyphAtlas { pages: Vec, /// `None` for a glyph that rasterised to nothing -- a space, say. Cached /// too, so it is not re-rasterised on every layout. entries: HashMap>, uploads: Vec, } impl GlyphAtlas { pub fn get(&self, key: &GlyphKey) -> Option> { self.entries.get(key).copied() } pub fn insert(&mut self, key: GlyphKey, image: &Image) -> Option { let w = image.placement.width; let h = image.placement.height; if w == 0 || h == 0 { log::warn!( "glyph {} in font {} rasterized at {w}x{h}; skipping it", key.glyph, key.font, ); self.entries.insert(key, None); return None; } if w > PAGE - PAD * 2 || h > PAGE - PAD * 2 { log::warn!( "glyph {} in font {} rasterized at {w}x{h}, too large for the {PAGE}x{PAGE} atlas; skipping it", key.glyph, key.font, ); self.entries.insert(key, None); return None; } let (layer, x, y) = self.allocate(w, h); write_glyph(&mut self.pages[layer as usize].image, image, x, y); self.uploads.push(PageUpload { layer, rect: PatchRect { x, y, width: w, height: h, }, }); let scale = 1.0 / PAGE as f32; let entry = GlyphEntry { uv_min: Vec2::new(x as f32 * scale, y as f32 * scale), uv_max: Vec2::new((x + w) as f32 * scale, (y + h) as f32 * scale), left: image.placement.left, top: image.placement.top, width: w, height: h, is_colored: matches!(image.content, Content::Color), layer, }; self.entries.insert(key, Some(entry)); Some(entry) } fn allocate(&mut self, w: u32, h: u32) -> (u32, u32, u32) { if let Some((i, (x, y))) = self .pages .iter_mut() .enumerate() .find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position))) { return (i as u32, x, y); } self.pages.push(Page { image: RgbaImage::new(PAGE, PAGE), x: PAD + w + PAD, y: PAD, shelf_height: h + PAD, }); (self.pages.len() as u32 - 1, PAD, PAD) } /// Drains what has been written since the last call, for the renderer to /// upload. Nothing else is needed for a new page: wgpu leaves the rest of /// a fresh layer transparent, which is what an atlas wants. pub fn uploads(&mut self) -> impl Iterator { let pages = &self.pages; self.uploads .drain(..) .map(|upload| (upload, &pages[upload.layer as usize].image)) } pub fn insert_empty(&mut self, key: GlyphKey) { self.entries.insert(key, None); } pub fn page_count(&self) -> u32 { self.pages.len() as u32 } pub fn glyph_count(&self) -> usize { self.entries.len() } } impl Page { fn allocate(&mut self, w: u32, h: u32) -> Option<(u32, u32)> { let need_w = w + PAD; let need_h = h + PAD; if self.x + need_w > PAGE { if need_w + PAD > PAGE || self.y + self.shelf_height + need_h > PAGE { return None; } self.y += self.shelf_height; self.x = PAD; self.shelf_height = 0; } else if self.y + need_h > PAGE { return None; } let position = (self.x, self.y); self.x += need_w; self.shelf_height = self.shelf_height.max(need_h); Some(position) } } /// Mask glyphs keep coverage in alpha so their raster can be tinted at draw time. fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) { let width = image.placement.width as usize; let height = image.placement.height as usize; let page_stride = page.width() as usize * 4; let x = x as usize * 4; let y = y as usize; let page = page.as_mut(); for row in 0..height { let start = (y + row) * page_stride + x; let target = &mut page[start..start + width * 4]; match image.content { Content::Color => { let start = row * width * 4; target.copy_from_slice(&image.data[start..start + width * 4]); } Content::Mask => { let start = row * width; for (target, &alpha) in target .as_chunks_mut::<4>() .0 .iter_mut() .zip(&image.data[start..start + width]) { target.copy_from_slice(&[255, 255, 255, alpha]); } } Content::SubpixelMask => { let start = row * width * 4; for (target, source) in target .as_chunks_mut::<4>() .0 .iter_mut() .zip(image.data[start..start + width * 4].as_chunks::<4>().0) { target.copy_from_slice(&[255, 255, 255, source[1]]); } } } } } #[derive(Clone, Copy)] pub struct PlacedGlyph { pub entry: GlyphEntry, pub offset: Vec2, }