From: Joshua Simmons Date: Sun, 26 Feb 2023 22:43:00 +0000 (+0100) Subject: Small improvements for font handling X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=5a10ef5601df344a1f3974d25e3d8609d53a5d9f;p=josh%2Fnarcissus Small improvements for font handling Automatically determine whether to use oversampling. Expand glyph capacity slightly. Draw a range of font sizes. --- diff --git a/bins/narcissus/src/main.rs b/bins/narcissus/src/main.rs index 897254d..b5269b8 100644 --- a/bins/narcissus/src/main.rs +++ b/bins/narcissus/src/main.rs @@ -2,7 +2,7 @@ use std::{path::Path, time::Instant}; use narcissus_app::{create_app, Event, Key, WindowDesc}; use narcissus_core::{default, obj, rand::Pcg64}; -use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache, Oversample}; +use narcissus_font::{CachedGlyph, CachedGlyphIndex, FontCollection, GlyphCache}; use narcissus_gpu::{ create_device, Access, Bind, Buffer, BufferDesc, BufferImageCopy, BufferUsageFlags, ClearValue, Device, Extent2d, Extent3d, Image, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, @@ -27,7 +27,7 @@ const MAX_SHARKS: usize = 262_144; const NUM_SHARKS: usize = 50; const MAX_GLYPH_INSTANCES: usize = 262_144; -const MAX_GLYPHS: usize = 1024; +const MAX_GLYPHS: usize = 8192; /// Marker trait indicates it's safe to convert a given type directly to an array of bytes. /// @@ -335,7 +335,7 @@ pub fn main() { let text_pipeline = TextPipeline::new(device.as_ref()); let fonts = Fonts::new(); - let mut glyph_cache = GlyphCache::new(&fonts, 512, 512, 1, Oversample::X2, Oversample::X2); + let mut glyph_cache = GlyphCache::new(&fonts, 1024, 512, 1); let blåhaj_image = load_image("bins/narcissus/data/blåhaj.png"); let (blåhaj_vertices, blåhaj_indices) = load_obj("bins/narcissus/data/blåhaj.obj"); @@ -526,63 +526,60 @@ pub fn main() { basic_uniforms.write(BasicUniforms { clip_from_model }); // Do some Font Shit.' - let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½"; - let line1 = "加盟国は、国際連合と協力して"; + let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½ Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge. ½½½½"; + let line1 = "加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して"; let mut glyph_instances = Vec::new(); - let mut glyphs = Vec::new(); + let mut glyph_indices = Vec::new(); + let mut x; let mut y = 0.0; let mut rng = Pcg64::new(); - for line in 0..100 { - let font_family = if line & 1 == 0 { - FontFamily::RobotoRegular + for line in 0..34 { + let (font_family, font_size_px, text) = if line & 1 == 0 { + (FontFamily::RobotoRegular, 10.0, line0) } else { - FontFamily::NotoSansJapanese + (FontFamily::NotoSansJapanese, 20.0, line1) }; - let font = fonts.font(font_family); - - let v_metrics = font.vertical_metrics(); - let font_scale = font.scale_for_pixel_height(if line & 1 == 0 { 25.0 } else { 40.0 }); - y += v_metrics.ascent * font_scale - v_metrics.descent * font_scale - + v_metrics.line_gap * font_scale; - y = y.trunc(); + let font_size_px = font_size_px + (line / 2) as f32 * 2.0; - let mut x = 0.0; - - glyphs.clear(); + let font = fonts.font(font_family); + let scale = font.scale_for_size_px(font_size_px); - let text = if line & 1 == 0 { line0 } else { line1 }; + x = 0.0; + y += (font.ascent() - font.descent() + font.line_gap()) * scale; + y = y.trunc(); - glyphs.extend(text.chars().map(|c| { - font.glyph_id(c) - .unwrap_or_else(|| font.glyph_id('□').unwrap()) + glyph_indices.clear(); + glyph_indices.extend(text.chars().map(|c| { + font.glyph_index(c) + .unwrap_or_else(|| font.glyph_index('□').unwrap()) })); let mut prev_glyph_index = None; - for glyph_index in glyphs.iter().copied() { - let cached_glyph_index = - glyph_cache.cache_glyph(font_family, glyph_index, font_scale); - - if let Some(prev_glyph_index) = prev_glyph_index { - x += font.kerning_advance(prev_glyph_index, glyph_index) * font_scale; + for glyph_index in glyph_indices.iter().copied() { + if let Some(prev_glyph_index) = prev_glyph_index.replace(glyph_index) { + x += font.kerning_advance(prev_glyph_index, glyph_index) * scale; } + let cached_glyph_index = + glyph_cache.cache_glyph(font_family, glyph_index, font_size_px); + const COLOR_SERIES: [u32; 4] = [0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63]; + let color = COLOR_SERIES[rng.next_bound_u64(4) as usize]; glyph_instances.push(GlyphInstance { cached_glyph_index, x, y, - color: COLOR_SERIES[rng.next_bound_u64(4) as usize], + color, }); let h_metrics = font.horizontal_metrics(glyph_index); - x += h_metrics.advance_width * font_scale; - prev_glyph_index = Some(glyph_index); + x += h_metrics.advance_width * scale; } } diff --git a/libs/narcissus-font/src/cache.rs b/libs/narcissus-font/src/cache.rs index 1d0cd7a..65af7e9 100644 --- a/libs/narcissus-font/src/cache.rs +++ b/libs/narcissus-font/src/cache.rs @@ -31,14 +31,14 @@ pub struct CachedGlyph { struct GlyphKey { family: Family, glyph_index: GlyphIndex, - scale: FiniteF32, + size_px: FiniteF32, } #[derive(PartialEq, Eq, PartialOrd, Ord)] struct Glyph { family: F, glyph_index: GlyphIndex, - scale: FiniteF32, + size_px: FiniteF32, cached_glyph_index: CachedGlyphIndex, } @@ -49,8 +49,6 @@ where fonts: &'a F, padding: usize, - oversample_h: Oversample, - oversample_v: Oversample, next_cached_glyph_index: u32, cached_glyph_lookup: FxHashMap, CachedGlyphIndex>, @@ -71,20 +69,11 @@ impl<'a, F> GlyphCache<'a, F> where F: FontCollection<'a>, { - pub fn new( - fonts: &'a F, - width: usize, - height: usize, - padding: usize, - oversample_h: Oversample, - oversample_v: Oversample, - ) -> Self { + pub fn new(fonts: &'a F, width: usize, height: usize, padding: usize) -> Self { Self { fonts, padding, - oversample_h, - oversample_v, glyphs: Vec::new(), @@ -110,16 +99,24 @@ where self.height } + fn oversample_for_size(size_px: f32) -> Oversample { + if size_px <= 25.0 { + Oversample::X2 + } else { + Oversample::None + } + } + pub fn cache_glyph( &mut self, family: F::Family, glyph_index: GlyphIndex, - scale: f32, + size_px: f32, ) -> CachedGlyphIndex { let key = GlyphKey { family, glyph_index, - scale: FiniteF32::new(scale).unwrap(), + size_px: FiniteF32::new(size_px).unwrap(), }; *self.cached_glyph_lookup.entry(key).or_insert_with(|| { @@ -138,7 +135,7 @@ where |(glyph_key, &cached_glyph_index)| Glyph { family: glyph_key.family, glyph_index: glyph_key.glyph_index, - scale: glyph_key.scale, + size_px: glyph_key.size_px, cached_glyph_index, }, )); @@ -147,23 +144,24 @@ where self.glyphs.sort_unstable(); let padding = self.padding as i32; - let oversample_h = self.oversample_h.as_i32(); - let oversample_v = self.oversample_v.as_i32(); self.rects.clear(); self.rects.extend(self.glyphs.iter().map(|glyph| { - let scale = glyph.scale.get(); + let font = self.fonts.font(glyph.family); + let size_px = glyph.size_px.get(); + let scale = font.scale_for_size_px(size_px); + let oversample = Self::oversample_for_size(size_px); - let bitmap_box = self.fonts.font(glyph.family).glyph_bitmap_box( + let bitmap_box = font.glyph_bitmap_box( glyph.glyph_index, - scale * oversample_h as f32, - scale * oversample_v as f32, + scale * oversample.as_f32(), + scale * oversample.as_f32(), 0.0, 0.0, ); - let w = bitmap_box.x1 - bitmap_box.x0 + padding + oversample_h - 1; - let h = bitmap_box.y1 - bitmap_box.y0 + padding + oversample_v - 1; + let w = bitmap_box.x1 - bitmap_box.x0 + padding + oversample.as_i32() - 1; + let h = bitmap_box.y1 - bitmap_box.y0 + padding + oversample.as_i32() - 1; Rect { id: glyph.cached_glyph_index.0 as i32, @@ -176,15 +174,13 @@ where })); self.packer.clear(); - self.packer.pack(&mut self.rects); + assert!(self.packer.pack(&mut self.rects)); self.texture.fill(0); self.cached_glyphs .resize(self.glyphs.len(), CachedGlyph::default()); let padding = self.padding as i32; - let oversample_h = oversample_h as f32; - let oversample_v = oversample_v as f32; for (glyph, rect) in self.glyphs.iter().zip(self.rects.iter_mut()) { let font = self.fonts.font(glyph.family); @@ -195,9 +191,12 @@ where rect.w -= padding; rect.h -= padding; - let scale = glyph.scale.get(); - let scale_x = scale * oversample_h; - let scale_y = scale * oversample_v; + let size_px = glyph.size_px.get(); + let scale = font.scale_for_size_px(size_px); + let oversample = Self::oversample_for_size(size_px); + + let scale_x = scale * oversample.as_f32(); + let scale_y = scale * oversample.as_f32(); let (sub_x, sub_y) = font.render_glyph_bitmap( &mut self.texture, @@ -210,8 +209,8 @@ where scale_y, 0.0, 0.0, - self.oversample_h, - self.oversample_v, + oversample, + oversample, glyph.glyph_index, ); @@ -229,16 +228,16 @@ where y1: _, } = font.glyph_bitmap_box( glyph.glyph_index, - scale * oversample_h, - scale * oversample_v, + scale * oversample.as_f32(), + scale * oversample.as_f32(), 0.0, 0.0, ); - cached_glyph.offset_x0 = x0 as f32 / oversample_h + sub_x; - cached_glyph.offset_y0 = y0 as f32 / oversample_v + sub_y; - cached_glyph.offset_x1 = (x0 + rect.w) as f32 / oversample_h + sub_x; - cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample_v + sub_y; + cached_glyph.offset_x0 = x0 as f32 / oversample.as_f32() + sub_x; + cached_glyph.offset_y0 = y0 as f32 / oversample.as_f32() + sub_y; + cached_glyph.offset_x1 = (x0 + rect.w) as f32 / oversample.as_f32() + sub_x; + cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y; } (&self.cached_glyphs, &self.texture) diff --git a/libs/narcissus-font/src/font.rs b/libs/narcissus-font/src/font.rs index a7638d3..bf598fd 100644 --- a/libs/narcissus-font/src/font.rs +++ b/libs/narcissus-font/src/font.rs @@ -3,12 +3,12 @@ use std::{marker::PhantomData, mem::MaybeUninit, num::NonZeroI32}; use stb_truetype_sys::{ stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBoxSubpixel, stbtt_GetGlyphHMetrics, stbtt_GetGlyphKernAdvance, - stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, truetype, + stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, truetype, }; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Oversample { - X1, + None, X2, X3, X4, @@ -22,6 +22,10 @@ impl Oversample { pub fn as_i32(self) -> i32 { self as i32 + 1 } + + pub fn as_f32(self) -> f32 { + (self as i32) as f32 + 1.0 + } } /// Font vertical metrics in unscaled coordinates. @@ -75,6 +79,7 @@ pub struct GlyphBitmapBox { pub struct Font<'a> { info: truetype::FontInfo, phantom: PhantomData<&'a [u8]>, + vertical_metrics: VerticalMetrics, } impl<'a> Font<'a> { @@ -90,33 +95,52 @@ impl<'a> Font<'a> { info.assume_init() }; + let vertical_metrics = { + let mut ascent = 0; + let mut descent = 0; + let mut line_gap = 0; + // Safety: We've just initialized the font info above. + unsafe { stbtt_GetFontVMetrics(&info, &mut ascent, &mut descent, &mut line_gap) }; + VerticalMetrics { + ascent: ascent as f32, + descent: descent as f32, + line_gap: line_gap as f32, + } + }; + Self { info, phantom: PhantomData, + vertical_metrics, } } - pub fn scale_for_pixel_height(&self, height: f32) -> f32 { - unsafe { stbtt_ScaleForPixelHeight(&self.info, height) } + /// Returns a scale factor to produce a font whose "height" is `size_px` pixels tall. + /// + /// Height is measured as the distance from the highest ascender to the lowest descender. + pub fn scale_for_size_px(&self, size_px: f32) -> f32 { + size_px / (self.vertical_metrics.ascent - self.vertical_metrics.descent) } - pub fn vertical_metrics(&self) -> VerticalMetrics { - let mut ascent = 0; - let mut descent = 0; - let mut line_gap = 0; - unsafe { - stbtt_GetFontVMetrics(&self.info, &mut ascent, &mut descent, &mut line_gap); - } - VerticalMetrics { - ascent: ascent as f32, - descent: descent as f32, - line_gap: line_gap as f32, - } + /// Return the font's vertical ascent in unscaled coordinates. + pub fn ascent(&self) -> f32 { + self.vertical_metrics.ascent + } + + /// Return the font's vertical descent in unscaled coordinates. + pub fn descent(&self) -> f32 { + self.vertical_metrics.descent + } + + /// Return the font's line gap in unscaled coordinates. + pub fn line_gap(&self) -> f32 { + self.vertical_metrics.line_gap } - pub fn glyph_id(&self, c: char) -> Option { - let glyph_id = unsafe { stbtt_FindGlyphIndex(&self.info, c as i32) }; - NonZeroI32::new(glyph_id).map(|glyph_id| GlyphIndex(glyph_id)) + /// Return the `GlyphIndex` for the character, or `None` if the font has no matching glyph. + pub fn glyph_index(&self, c: char) -> Option { + let glyph_index = unsafe { stbtt_FindGlyphIndex(&self.info, c as i32) }; + NonZeroI32::new(glyph_index).map(|glyph_index| GlyphIndex(glyph_index)) } pub fn glyph_bitmap_box(