Przeglądaj źródła

wallet: cleanup font rendering code, and make it more robust

darkfi 1 rok temu
rodzic
commit
56f4530a85

+ 4 - 2
bin/darkwallet/src/text/atlas.rs

@@ -1,10 +1,12 @@
-use super::{Glyph, Sprite, SpritePtr};
 use crate::{
     error::Result,
     gfx::{GfxTextureId, ManagedTexturePtr, Rectangle, RenderApi},
 };
 
-use super::glyph_str;
+use super::{
+    ft::{Sprite, SpritePtr},
+    glyph_str, Glyph,
+};
 
 /// Prevents render artifacts from aliasing.
 /// Even with aliasing turned off, some bleed still appears possibly

+ 143 - 0
bin/darkwallet/src/text/ft.rs

@@ -0,0 +1,143 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free sofreetypeware: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Sofreetypeware Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use freetype::face::LoadFlag as FtLoadFlag;
+use std::sync::Arc;
+
+pub type FreetypeFace = freetype::Face<&'static [u8]>;
+
+pub type SpritePtr = Arc<Sprite>;
+
+pub struct Sprite {
+    pub bmp: Vec<u8>,
+    pub bmp_width: usize,
+    pub bmp_height: usize,
+
+    pub bearing_x: f32,
+    pub bearing_y: f32,
+    pub has_fixed_sizes: bool,
+    pub has_color: bool,
+}
+
+fn load_ft_glyph<'a>(
+    face: &'a FreetypeFace,
+    glyph_id: u32,
+    flags: FtLoadFlag,
+) -> Option<&'a freetype::GlyphSlot> {
+    //debug!("load_glyph {} flags={flags:?}", glyph_id);
+    if let Err(err) = face.load_glyph(glyph_id, flags) {
+        error!(target: "text", "error loading glyph {glyph_id}: {err}");
+        return None
+    }
+    //debug!("load_glyph {} [done]", glyph_id);
+
+    // https://gist.github.com/jokertarot/7583938?permalink_comment_id=3327566#gistcomment-3327566
+
+    let glyph = face.glyph();
+    glyph.render_glyph(freetype::RenderMode::Normal).ok()?;
+    Some(glyph)
+}
+
+pub fn render_glyph(face: &FreetypeFace, glyph_id: u32) -> Option<Sprite> {
+    // If color is available then attempt to load it.
+    // Otherwise fallback to black and white.
+    let glyph = if face.has_color() {
+        match load_ft_glyph(face, glyph_id, FtLoadFlag::DEFAULT | FtLoadFlag::COLOR) {
+            Some(glyph) => glyph,
+            None => load_ft_glyph(face, glyph_id, FtLoadFlag::DEFAULT)?,
+        }
+    } else {
+        load_ft_glyph(face, glyph_id, FtLoadFlag::DEFAULT)?
+    };
+
+    let bmp = glyph.bitmap();
+    let buffer = bmp.buffer();
+    let bmp_width = bmp.width() as usize;
+    let bmp_height = bmp.rows() as usize;
+    let bearing_x = glyph.bitmap_left() as f32;
+    let bearing_y = glyph.bitmap_top() as f32;
+    let has_fixed_sizes = face.has_fixed_sizes();
+
+    let pixel_mode = bmp.pixel_mode().unwrap();
+    let bmp = match pixel_mode {
+        freetype::bitmap::PixelMode::Bgra => {
+            let mut tdata = vec![];
+            tdata.resize(4 * bmp_width * bmp_height, 0);
+            // Convert from BGRA to RGBA
+            for i in 0..bmp_width * bmp_height {
+                let idx = i * 4;
+                let b = buffer[idx];
+                let g = buffer[idx + 1];
+                let r = buffer[idx + 2];
+                let a = buffer[idx + 3];
+                tdata[idx] = r;
+                tdata[idx + 1] = g;
+                tdata[idx + 2] = b;
+                tdata[idx + 3] = a;
+            }
+            tdata
+        }
+        freetype::bitmap::PixelMode::Gray => {
+            // Convert from greyscale to RGBA8
+            let tdata: Vec<_> =
+                buffer.iter().flat_map(|coverage| vec![255, 255, 255, *coverage]).collect();
+            tdata
+        }
+        freetype::bitmap::PixelMode::Mono => {
+            // Convert from mono to RGBA8
+            let tdata: Vec<_> =
+                buffer.iter().flat_map(|coverage| vec![255, 255, 255, *coverage]).collect();
+            tdata
+        }
+        _ => panic!("unsupport pixel mode: {:?}", pixel_mode),
+    };
+
+    Some(Sprite {
+        bmp,
+        bmp_width,
+        bmp_height,
+        bearing_x,
+        bearing_y,
+        has_fixed_sizes,
+        has_color: face.has_color(),
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn render_simple() {
+        let ftlib = freetype::Library::init().unwrap();
+        let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
+        let face = ftlib.new_memory_face2(font_data, 0).unwrap();
+
+        // glyph 11 in IBM plex mono regular is 'h'
+        let glyph = render_glyph(&face, 11).unwrap();
+    }
+
+    #[test]
+    fn render_custom_glyph() {
+        let ftlib = freetype::Library::init().unwrap();
+        let font_data = include_bytes!("../../darkirc-emoji-svg.ttf") as &[u8];
+        let face = ftlib.new_memory_face2(font_data, 0).unwrap();
+
+        let glyph = render_glyph(&face, 4).unwrap();
+    }
+}

+ 13 - 88
bin/darkwallet/src/text/mod.rs

@@ -16,7 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use freetype as ft;
 use harfbuzz_sys::{
     freetype::hb_ft_font_create_referenced, hb_buffer_add_utf8, hb_buffer_create,
     hb_buffer_destroy, hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions,
@@ -34,7 +33,10 @@ use crate::gfx::Rectangle;
 
 mod atlas;
 pub use atlas::{make_texture_atlas, Atlas, RenderedAtlas};
-mod core;
+mod ft;
+use ft::{render_glyph, FreetypeFace, Sprite, SpritePtr};
+mod shape;
+use shape::{set_face_size, shape};
 mod wrap;
 pub use wrap::{glyph_str, wrap};
 
@@ -155,7 +157,7 @@ pub struct TextShaper {
 
 impl TextShaper {
     pub fn new() -> Arc<Self> {
-        let ftlib = ft::Library::init().unwrap();
+        let ftlib = freetype::Library::init().unwrap();
 
         let mut faces = vec![];
 
@@ -163,6 +165,10 @@ impl TextShaper {
         let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
         faces.push(ft_face);
 
+        let font_data = include_bytes!("../../darkirc-emoji-svg.ttf") as &[u8];
+        let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
+        faces.push(ft_face);
+
         let font_data = include_bytes!("../../NotoColorEmoji.ttf") as &[u8];
         let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
         faces.push(ft_face);
@@ -188,11 +194,11 @@ impl TextShaper {
 
         let size = font_size * window_scale;
         for face in intern.faces() {
-            core::set_face_size(face, size);
+            set_face_size(face, size);
         }
 
         let mut glyphs: Vec<Glyph> = vec![];
-        'next_glyph: for glyph_info in core::shape(intern.faces(), text) {
+        'next_glyph: for glyph_info in shape(intern.faces(), text) {
             let face_idx = glyph_info.face_idx;
             let face = intern.face(face_idx);
             let glyph_id = glyph_info.id;
@@ -243,75 +249,9 @@ impl TextShaper {
             }
 
             let face = intern.face(face_idx);
-            let mut flags = ft::face::LoadFlag::DEFAULT;
-            if face.has_color() {
-                flags |= ft::face::LoadFlag::COLOR;
-            }
-
-            //debug!("load_glyph {}", glyph_id);
-            if let Err(err) = face.load_glyph(glyph_id, flags) {
-                error!(target: "text", "error loading glyph {glyph_id}: {err}");
-                continue
-            }
-            //debug!("load_glyph {} [done]", glyph_id);
-
-            // https://gist.github.com/jokertarot/7583938?permalink_comment_id=3327566#gistcomment-3327566
-
-            let glyph = face.glyph();
-            glyph.render_glyph(ft::RenderMode::Normal).unwrap();
-
-            let bmp = glyph.bitmap();
-            let buffer = bmp.buffer();
-            let bmp_width = bmp.width() as usize;
-            let bmp_height = bmp.rows() as usize;
-            let bearing_x = glyph.bitmap_left() as f32;
-            let bearing_y = glyph.bitmap_top() as f32;
-            let has_fixed_sizes = face.has_fixed_sizes();
-
-            let pixel_mode = bmp.pixel_mode().unwrap();
-            let bmp = match pixel_mode {
-                ft::bitmap::PixelMode::Bgra => {
-                    let mut tdata = vec![];
-                    tdata.resize(4 * bmp_width * bmp_height, 0);
-                    // Convert from BGRA to RGBA
-                    for i in 0..bmp_width * bmp_height {
-                        let idx = i * 4;
-                        let b = buffer[idx];
-                        let g = buffer[idx + 1];
-                        let r = buffer[idx + 2];
-                        let a = buffer[idx + 3];
-                        tdata[idx] = r;
-                        tdata[idx + 1] = g;
-                        tdata[idx + 2] = b;
-                        tdata[idx + 3] = a;
-                    }
-                    tdata
-                }
-                ft::bitmap::PixelMode::Gray => {
-                    // Convert from greyscale to RGBA8
-                    let tdata: Vec<_> =
-                        buffer.iter().flat_map(|coverage| vec![255, 255, 255, *coverage]).collect();
-                    tdata
-                }
-                ft::bitmap::PixelMode::Mono => {
-                    // Convert from mono to RGBA8
-                    let tdata: Vec<_> =
-                        buffer.iter().flat_map(|coverage| vec![255, 255, 255, *coverage]).collect();
-                    tdata
-                }
-                _ => panic!("unsupport pixel mode: {:?}", pixel_mode),
-            };
-
-            let sprite = Arc::new(Sprite {
-                bmp,
-                bmp_width,
-                bmp_height,
-                bearing_x,
-                bearing_y,
-                has_fixed_sizes,
-                has_color: face.has_color(),
-            });
+            let Some(sprite) = render_glyph(&face, glyph_id) else { continue };
 
+            let sprite = Arc::new(sprite);
             intern.cache.insert(cache_key, Arc::downgrade(&sprite));
 
             let glyph =
@@ -347,19 +287,6 @@ struct CacheKey {
     face_idx: usize,
 }
 
-pub type SpritePtr = Arc<Sprite>;
-
-pub struct Sprite {
-    bmp: Vec<u8>,
-    pub bmp_width: usize,
-    pub bmp_height: usize,
-
-    pub bearing_x: f32,
-    pub bearing_y: f32,
-    pub has_fixed_sizes: bool,
-    pub has_color: bool,
-}
-
 #[derive(Clone)]
 pub struct Glyph {
     pub glyph_id: u32,
@@ -384,8 +311,6 @@ impl std::fmt::Debug for Glyph {
     }
 }
 
-type FreetypeFace = ft::Face<&'static [u8]>;
-
 struct FtFaces(Vec<FreetypeFace>);
 
 unsafe impl Send for FtFaces {}

+ 37 - 2
bin/darkwallet/src/text/core.rs → bin/darkwallet/src/text/shape.rs

@@ -60,10 +60,12 @@ impl<'a> Drop for HarfBuzzIter<'a> {
 
 pub(super) fn set_face_size(face: &mut FreetypeFace, size: f32) {
     if face.has_fixed_sizes() {
+        //debug!(target: "text", "fixed sizes");
         // emojis required a fixed size
         //face.set_char_size(109 * 64, 0, 72, 72).unwrap();
         face.select_size(0).unwrap();
     } else {
+        //debug!(target: "text", "set char size");
         face.set_char_size(size as isize * 64, 0, 96, 96).unwrap();
     }
 }
@@ -189,7 +191,10 @@ fn count_leading_null_glyphs(glyphs: &Vec<GlyphInfo>) -> usize {
 fn print_glyphs(ctx: &str, glyphs: &Vec<GlyphInfo>) {
     println!("{} ------------------", ctx);
     for (i, glyph) in glyphs.iter().enumerate() {
-        println!("{i}: {}/{} [{}, {}]", glyph.face_idx, glyph.id, glyph.cluster_start, glyph.cluster_end);
+        println!(
+            "{i}: {}/{} [{}, {}]",
+            glyph.face_idx, glyph.id, glyph.cluster_start, glyph.cluster_end
+        );
     }
     println!("---------------------");
 }
@@ -263,7 +268,7 @@ mod tests {
     use super::*;
 
     fn load_faces() -> Vec<FreetypeFace> {
-        let ftlib = ft::Library::init().unwrap();
+        let ftlib = freetype::Library::init().unwrap();
 
         let mut faces = vec![];
         let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
@@ -401,4 +406,34 @@ mod tests {
         assert_eq!(glyphs[15].cluster_end, 38);
         assert_eq!(glyphs[15].cluster_start, glyphs[14].cluster_end);
     }
+
+    #[test]
+    fn hb_shape_custom_emoji() {
+        let ftlib = ft::Library::init().unwrap();
+
+        let font_data = include_bytes!("../../darkirc-emoji-svg.ttf") as &[u8];
+        let mut face = ftlib.new_memory_face2(font_data, 0).unwrap();
+
+        let text = "\u{f0001}";
+
+        for (i, hbinf) in harfbuzz_shape(&mut face, text).enumerate() {
+            let glyph_id = hbinf.info.codepoint as u32;
+            // Index within this substr
+            let cluster = hbinf.info.cluster as usize;
+            println!("  {i}: glyph_id = {glyph_id}, cluster = {cluster}");
+        }
+    }
+
+    #[test]
+    fn custom_emoji() {
+        let ftlib = ft::Library::init().unwrap();
+
+        let font_data = include_bytes!("../../darkirc-emoji-svg.ttf") as &[u8];
+        let face = ftlib.new_memory_face2(font_data, 0).unwrap();
+
+        let mut faces = vec![face];
+        let text = "\u{f0001}";
+        let glyphs = shape(&mut faces, text);
+        //print_glyphs("", &glyphs);
+    }
 }

+ 0 - 3
bin/darkwallet/src/text/wrap.rs

@@ -259,9 +259,6 @@ mod tests {
         let glyphs = shaper.shape("hello world 123".to_string(), 32., 1.);
 
         let wrapped = wrap(200., 32., 1., &glyphs);
-        for x in &wrapped {
-            println!("'{x:?}'");
-        }
         assert_eq!(wrapped.len(), 3);
         assert_eq!(glyph_str(&wrapped[0]), "hello ");
         assert_eq!(glyph_str(&wrapped[1]), "world ");