]> git.nega.tv - josh/narcissus/commitdiff
Small improvements for font handling
authorJoshua Simmons <josh@nega.tv>
Sun, 26 Feb 2023 22:43:00 +0000 (23:43 +0100)
committerJoshua Simmons <josh@nega.tv>
Sun, 26 Feb 2023 22:43:00 +0000 (23:43 +0100)
Automatically determine whether to use oversampling.
Expand glyph capacity slightly.
Draw a range of font sizes.

bins/narcissus/src/main.rs
libs/narcissus-font/src/cache.rs
libs/narcissus-font/src/font.rs

index 897254deecb0048b106f5d25324be3ea74af70f3..b5269b8360e3ae3d21a166a4ad6ecc126f552cae 100644 (file)
@@ -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;
             }
         }
 
index 1d0cd7ae11789bb3654d393e3fc1efa37c88437c..65af7e913a11912f43b47bd7fcf3229e18da8451 100644 (file)
@@ -31,14 +31,14 @@ pub struct CachedGlyph {
 struct GlyphKey<Family> {
     family: Family,
     glyph_index: GlyphIndex,
-    scale: FiniteF32,
+    size_px: FiniteF32,
 }
 
 #[derive(PartialEq, Eq, PartialOrd, Ord)]
 struct Glyph<F> {
     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<GlyphKey<F::Family>, 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)
index a7638d3e36009e2ed22e606bd417621dfe052970..bf598fd52aa5fcbc43657c7a2b50c6ab1a61c4c5 100644 (file)
@@ -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<GlyphIndex> {
-        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<GlyphIndex> {
+        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(