Browse Source

app: delete freetype 🎉🎉🎉

jkds 7 months ago
parent
commit
94a0db125e

+ 0 - 121
bin/app/Cargo.lock

@@ -1251,58 +1251,12 @@ dependencies = [
  "futures",
 ]
 
-[[package]]
-name = "core-foundation"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
-[[package]]
-name = "core-graphics"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation",
- "core-graphics-types",
- "foreign-types",
- "libc",
-]
-
-[[package]]
-name = "core-graphics-types"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation",
- "libc",
-]
-
-[[package]]
-name = "core-text"
-version = "20.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
-dependencies = [
- "core-foundation",
- "core-graphics",
- "foreign-types",
- "libc",
-]
-
 [[package]]
 name = "core2"
 version = "0.4.0"
@@ -1562,11 +1516,9 @@ dependencies = [
  "easy-parallel",
  "file-rotate",
  "fluent",
- "freetype-rs",
  "fud",
  "futures",
  "glam",
- "harfbuzz-sys",
  "image",
  "indoc",
  "miniquad",
@@ -2450,33 +2402,6 @@ dependencies = [
  "yeslogic-fontconfig-sys",
 ]
 
-[[package]]
-name = "foreign-types"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
-dependencies = [
- "foreign-types-macros",
- "foreign-types-shared",
-]
-
-[[package]]
-name = "foreign-types-macros"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.112",
-]
-
-[[package]]
-name = "foreign-types-shared"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
-
 [[package]]
 name = "form_urlencoded"
 version = "1.2.2"
@@ -2486,26 +2411,6 @@ dependencies = [
  "percent-encoding",
 ]
 
-[[package]]
-name = "freetype-rs"
-version = "0.37.0"
-source = "git+https://github.com/narodnik/freetype-rs#bad2e69dc20d35f33eaee8cd26a2dbf588026ea8"
-dependencies = [
- "bitflags 2.10.0",
- "freetype-sys",
- "libc",
-]
-
-[[package]]
-name = "freetype-sys"
-version = "0.22.1"
-source = "git+https://github.com/narodnik/freetype-sys2#ed8af21a697670e06fc5f072c5b2276ebe7f76ec"
-dependencies = [
- "cc",
- "libz-sys",
- "pkg-config",
-]
-
 [[package]]
 name = "fs-mistrust"
 version = "0.13.1"
@@ -2890,20 +2795,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "harfbuzz-sys"
-version = "0.6.1"
-source = "git+https://github.com/narodnik/rust-harfbuzz2#450d37dad132906335ec760cd51463a1ede359ee"
-dependencies = [
- "cc",
- "core-graphics",
- "core-text",
- "foreign-types",
- "freetype-sys",
- "pkg-config",
- "windows 0.58.0",
-]
-
 [[package]]
 name = "harfrust"
 version = "0.3.2"
@@ -3580,18 +3471,6 @@ dependencies = [
  "vcpkg",
 ]
 
-[[package]]
-name = "libz-sys"
-version = "1.1.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
-
 [[package]]
 name = "linebender_resource_handle"
 version = "0.1.1"

+ 0 - 12
bin/app/Cargo.toml

@@ -11,12 +11,6 @@ repository = "https://codeberg.org/darkrenaissance/darkfi"
 [dependencies]
 miniquad = { git = "https://github.com/not-fl3/miniquad" }
 
-# Currently latest version links to freetype-sys 0.19 but we use 0.21
-#harfbuzz-sys = "0.6.1"
-#harfbuzz-sys = { git = "https://github.com/servo/rust-harfbuzz", features = ["bundled"] }
-harfbuzz-sys = { git = "https://github.com/narodnik/rust-harfbuzz2", features = ["bundled"] }
-freetype-rs = { version = "0.37.0", features = ["bundled"] }
-
 image = "0.25.9"
 qoi = "0.4.1"
 
@@ -84,12 +78,6 @@ schema-app = []
 schema-test = []
 
 [patch.crates-io]
-# We can remove these patches. But unfortunately harfbuzz-sys is still linking
-# the old freetype libs so we need to fix that first.
-# Fucking servo rust-harfbuzz
-freetype-rs = { git = "https://github.com/narodnik/freetype-rs" }
-freetype-sys = { git = "https://github.com/narodnik/freetype-sys2" }
-
 halo2_proofs = { git="https://github.com/parazyd/halo2", branch="v032" }
 halo2_gadgets = { git="https://github.com/parazyd/halo2", branch="v032" }
 # This patch didn't work for me

+ 1 - 4
bin/app/src/app/mod.rs

@@ -31,7 +31,6 @@ use crate::{
     plugin::PluginSettings,
     prop::{Property, PropertyAtomicGuard, PropertySubType, PropertyType, PropertyValue, Role},
     scene::{Pimpl, SceneNode, SceneNodePtr, SceneNodeType},
-    text::TextShaperPtr,
     ui::{chatview, Window},
     util::i18n::I18nBabelFish,
     ExecutorPtr,
@@ -59,7 +58,6 @@ pub type AppPtr = Arc<App>;
 pub struct App {
     pub sg_root: SceneNodePtr,
     pub render_api: RenderApi,
-    pub text_shaper: TextShaperPtr,
     pub tasks: SyncMutex<Vec<Task<()>>>,
     pub ex: ExecutorPtr,
 }
@@ -68,10 +66,9 @@ impl App {
     pub fn new(
         sg_root: SceneNodePtr,
         render_api: RenderApi,
-        text_shaper: TextShaperPtr,
         ex: ExecutorPtr,
     ) -> Arc<Self> {
-        Arc::new(Self { sg_root, ex, render_api, text_shaper, tasks: SyncMutex::new(vec![]) })
+        Arc::new(Self { sg_root, ex, render_api, tasks: SyncMutex::new(vec![]) })
     }
 
     /// Does not require miniquad to be init. Created the scene graph tree / schema and all

+ 5 - 7
bin/app/src/app/schema/settings.rs

@@ -26,6 +26,7 @@ use crate::{
     scene::{SceneNodePtr, Slot},
     shape,
     ui::{Button, EditBox, Layer, ShapeVertex, Text, VectorArt, VectorShape},
+    util::i18n::I18nBabelFish,
     ExecutorPtr,
 };
 
@@ -134,7 +135,7 @@ impl Setting {
     }
 }
 
-pub async fn make(app: &App, window: SceneNodePtr, _ex: ExecutorPtr) {
+pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     let mut cc = Compiler::new();
     cc.add_const_f32("BORDER_RIGHT_SCALE", BORDER_RIGHT_SCALE);
     cc.add_const_f32("SEARCH_PADDING_X", SEARCH_PADDING_X);
@@ -281,8 +282,7 @@ pub async fn make(app: &App, window: SceneNodePtr, _ex: ExecutorPtr) {
                 me,
                 window_scale.clone(),
                 app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
+                i18n_fish.clone(),
             )
         })
         .await;
@@ -383,8 +383,7 @@ pub async fn make(app: &App, window: SceneNodePtr, _ex: ExecutorPtr) {
                 me,
                 window_scale.clone(),
                 app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
+                i18n_fish.clone(),
             )
         })
         .await;
@@ -410,8 +409,7 @@ pub async fn make(app: &App, window: SceneNodePtr, _ex: ExecutorPtr) {
                 me,
                 window_scale.clone(),
                 app.render_api.clone(),
-                app.text_shaper.clone(),
-                app.ex.clone(),
+                i18n_fish.clone(),
             )
         })
         .await;

+ 2 - 6
bin/app/src/main.rs

@@ -45,7 +45,6 @@ mod pubsub;
 //mod ringbuf;
 mod scene;
 mod shape;
-mod text;
 mod text2;
 mod ui;
 mod util;
@@ -55,7 +54,6 @@ use crate::{
     gfx::EpochIndex,
     prop::{Property, PropertySubType, PropertyType},
     scene::{CallArgType, SceneNode, SceneNodeType},
-    text::TextShaper,
     util::AsyncRuntime,
 };
 #[cfg(feature = "enable-netdebug")]
@@ -152,9 +150,7 @@ impl God {
         let render_api = gfx::RenderApi::new(method_send);
         let event_pub = gfx::GraphicsEventPublisher::new();
 
-        let text_shaper = TextShaper::new();
-
-        let app = App::new(sg_root.clone(), render_api.clone(), text_shaper, fg_ex.clone());
+        let app = App::new(sg_root.clone(), render_api.clone(), fg_ex.clone());
 
         let app2 = app.clone();
         let cv_app_is_setup = Arc::new(CondVar::new());
@@ -482,7 +478,7 @@ fn main() {
 
     GOD.get_or_init(God::new);
 
-    // Reuse render_api, event_pub and text_shaper
+    // Reuse render_api and event_pub
     // No need for setup(), just wait for gfx start then call .start()
     // ZMQ, darkirc stay running
 

+ 0 - 224
bin/app/src/text/atlas.rs

@@ -1,224 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software 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 crate::gfx::{DebugTag, ManagedTexturePtr, Rectangle, RenderApi};
-use miniquad::TextureFormat;
-
-use super::{
-    ft::{Sprite, SpritePtr},
-    Glyph,
-};
-
-/// Prevents render artifacts from aliasing.
-/// Even with aliasing turned off, some bleed still appears possibly
-/// due to UV coord calcs. Adding a gap perfectly fixes this.
-const ATLAS_GAP: usize = 2;
-
-/// Convenience wrapper fn. Use if rendering a single line of glyphs.
-pub fn make_texture_atlas(
-    render_api: &RenderApi,
-    tag: DebugTag,
-    glyphs: &Vec<Glyph>,
-) -> RenderedAtlas {
-    let mut atlas = Atlas::new(render_api, tag);
-    atlas.push(&glyphs);
-    atlas.make()
-}
-
-/// Responsible for aggregating glyphs, and then producing a single software
-/// blitted texture usable in a single draw call.
-/// This makes OpenGL batch precomputation of meshes efficient.
-///
-/// ```rust
-///     let mut atlas = Atlas::new(&render_api);
-///     atlas.push(&glyphs);    // repeat as needed for shaped lines
-///     let atlas = atlas.make().unwrap();
-///     let uv = atlas.fetch_uv(glyph_id).unwrap();
-///     let atlas_texture_id = atlas.texture_id;
-/// ```
-#[derive(Clone)]
-pub struct Atlas<'a> {
-    glyph_ids: Vec<u32>,
-    sprites: Vec<SpritePtr>,
-    // LHS x pos of glyph
-    x_pos: Vec<usize>,
-
-    width: usize,
-    height: usize,
-
-    render_api: &'a RenderApi,
-    tag: DebugTag,
-}
-
-impl<'a> Atlas<'a> {
-    pub fn new(render_api: &'a RenderApi, tag: DebugTag) -> Self {
-        Self {
-            glyph_ids: vec![],
-            sprites: vec![],
-            x_pos: vec![],
-            width: ATLAS_GAP,
-            // Not really important to set a value here since it will
-            // get overwritten.
-            // FYI glyphs have a gap on all sides (top and bottom here).
-            height: 2 * ATLAS_GAP,
-            render_api,
-            tag,
-        }
-    }
-
-    fn push_glyph(&mut self, glyph: &Glyph) {
-        if self.glyph_ids.contains(&glyph.glyph_id) {
-            return
-        }
-
-        self.glyph_ids.push(glyph.glyph_id);
-        self.sprites.push(glyph.sprite.clone());
-
-        let sprite = &glyph.sprite;
-        self.x_pos.push(self.width);
-
-        // Gap on the top and bottom
-        let height = ATLAS_GAP + sprite.bmp_height + ATLAS_GAP;
-        self.height = std::cmp::max(height, self.height);
-
-        // Gap between glyphs and on both sides
-        self.width += sprite.bmp_width + ATLAS_GAP;
-    }
-
-    /// Push a line of shaped text represented as `Vec<Glyph>`
-    /// to this atlas.
-    pub fn push(&mut self, glyphs: &Vec<Glyph>) {
-        for glyph in glyphs {
-            self.push_glyph(glyph);
-        }
-    }
-
-    fn render(&self) -> Vec<u8> {
-        let mut atlas = vec![0; 4 * self.width * self.height];
-        // For drawing debug lines we want a single white pixel.
-        // This is very useful to have in our texture for debugging.
-        atlas[0] = 255;
-        atlas[1] = 255;
-        atlas[2] = 255;
-        atlas[3] = 255;
-
-        let y = ATLAS_GAP;
-        // Copy all the sprites to our atlas.
-        // They should have ATLAS_GAP spacing on all sides to avoid bleeding.
-        for (sprite, x) in self.sprites.iter().zip(self.x_pos.iter()) {
-            copy_image(sprite, *x, y, &mut atlas, self.width);
-        }
-
-        atlas
-    }
-
-    fn compute_uvs(&self) -> Vec<Rectangle> {
-        // UV coords are in the range [0, 1]
-        let mut uvs = vec![];
-
-        let (self_w, self_h) = (self.width as f32, self.height as f32);
-        let y = ATLAS_GAP as f32;
-
-        for (sprite, x) in self.sprites.iter().zip(self.x_pos.iter()) {
-            let x = *x as f32;
-            let sprite_w = sprite.bmp_width as f32;
-            let sprite_h = sprite.bmp_height as f32;
-
-            let uv = Rectangle {
-                x: x / self_w,
-                y: y / self_h,
-                w: sprite_w / self_w,
-                h: sprite_h / self_h,
-            };
-            uvs.push(uv);
-        }
-
-        uvs
-    }
-
-    /// Invalidate this atlas and produce the finalized result.
-    /// Each glyph is given a sub-rect within the texture, accessible by calling
-    /// `rendered_atlas.fetch_uv(my_glyph_id)`.
-    /// The texture ID is a struct member: `rendered_atlas.texture_id`.
-    pub fn make(self) -> RenderedAtlas {
-        //if self.glyph_ids.is_empty() {
-        //    return Err(Error::AtlasIsEmpty)
-        //}
-
-        assert_eq!(self.glyph_ids.len(), self.sprites.len());
-        assert_eq!(self.glyph_ids.len(), self.x_pos.len());
-
-        let atlas = self.render();
-        let texture = self.render_api.new_texture(
-            self.width as u16,
-            self.height as u16,
-            atlas,
-            TextureFormat::RGBA8,
-            self.tag,
-        );
-
-        let uv_rects = self.compute_uvs();
-        let glyph_ids = self.glyph_ids;
-
-        RenderedAtlas { glyph_ids, uv_rects, texture }
-    }
-}
-
-/// Copy a sprite to (x, y) position within the atlas texture.
-/// Both image formats are RGBA flat vecs.
-fn copy_image(sprite: &Sprite, x: usize, y: usize, atlas: &mut Vec<u8>, atlas_width: usize) {
-    for i in 0..sprite.bmp_height {
-        for j in 0..sprite.bmp_width {
-            let src_y = i * sprite.bmp_width;
-            let off_src = 4 * (src_y + j);
-
-            let dest_y = (y + i) * atlas_width;
-            let off_dest = 4 * (dest_y + j + x);
-
-            atlas[off_dest] = sprite.bmp[off_src];
-            atlas[off_dest + 1] = sprite.bmp[off_src + 1];
-            atlas[off_dest + 2] = sprite.bmp[off_src + 2];
-            atlas[off_dest + 3] = sprite.bmp[off_src + 3];
-        }
-    }
-}
-
-/// Final result computed from `Atlas::make()`.
-#[derive(Clone)]
-pub struct RenderedAtlas {
-    glyph_ids: Vec<u32>,
-    /// UV rectangle within the texture.
-    uv_rects: Vec<Rectangle>,
-    /// Allocated atlas texture.
-    pub texture: ManagedTexturePtr,
-}
-
-impl RenderedAtlas {
-    /// Get UV coords for a glyph within the rendered atlas.
-    pub fn fetch_uv(&self, glyph_id: u32) -> Option<&Rectangle> {
-        let glyphs_len = self.glyph_ids.len();
-        assert_eq!(glyphs_len, self.uv_rects.len());
-
-        for i in 0..glyphs_len {
-            if self.glyph_ids[i] == glyph_id {
-                return Some(&self.uv_rects[i])
-            }
-        }
-        None
-    }
-}

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

@@ -1,143 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 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();
-    }
-}

+ 0 - 369
bin/app/src/text/mod.rs

@@ -1,369 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software 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 std::{
-    collections::HashMap,
-    ffi::OsStr,
-    path::PathBuf,
-    sync::{Arc, Mutex as SyncMutex, Weak},
-};
-
-use crate::gfx::Rectangle;
-
-mod atlas;
-pub use atlas::{make_texture_atlas, Atlas, RenderedAtlas};
-mod ft;
-use ft::{render_glyph, FreetypeFace, Sprite, SpritePtr};
-mod shape;
-use shape::{set_face_size, shape};
-mod wrap;
-pub use wrap::wrap;
-
-// Upscale emoji relative to font size
-pub const EMOJI_SCALE_FACT: f32 = 1.6;
-// How much of the emoji is above baseline?
-pub const EMOJI_PROP_ABOVE_BASELINE: f32 = 0.8;
-
-#[cfg(target_os = "android")]
-fn custom_font_path() -> PathBuf {
-    crate::android::get_external_storage_path().join("font")
-}
-#[cfg(not(target_os = "android"))]
-fn custom_font_path() -> PathBuf {
-    dirs::data_local_dir().unwrap().join("darkfi/app/font")
-}
-
-// From https://sourceforge.net/projects/freetype/files/freetype2/2.6/
-//
-// * An `FT_Face' object can only be safely used from one thread at
-//   a time.
-//
-// * An `FT_Library'  object can  now be used  without modification
-//   from multiple threads at the same time.
-//
-// * `FT_Face' creation and destruction  with the same `FT_Library'
-//   object can only be done from one thread at a time.
-//
-// One can use a single  `FT_Library' object across threads as long
-// as a mutex lock is used around `FT_New_Face' and `FT_Done_Face'.
-// Any calls to `FT_Load_Glyph' and similar API are safe and do not
-// need the lock  to be held as  long as the same  `FT_Face' is not
-// used from multiple threads at the same time.
-
-// Harfbuzz is threadsafe.
-
-// Notes:
-// * All ft init and face creation should happen at startup.
-// * FT faces protected behind a Mutex
-// * Glyph cache. Key is (glyph_id, font_size)
-// * Glyph texture cache: (glyph_id, font_size, color)
-
-#[derive(Clone)]
-pub struct GlyphPositionIter<'a> {
-    font_size: f32,
-    window_scale: f32,
-    glyphs: &'a Vec<Glyph>,
-    current_x: f32,
-    current_y: f32,
-    i: usize,
-}
-
-impl<'a> GlyphPositionIter<'a> {
-    pub fn new(font_size: f32, window_scale: f32, glyphs: &'a Vec<Glyph>, baseline_y: f32) -> Self {
-        let start_y = baseline_y * window_scale;
-        Self { font_size, window_scale, glyphs, current_x: 0., current_y: start_y, i: 0 }
-    }
-}
-
-impl<'a> Iterator for GlyphPositionIter<'a> {
-    type Item = Rectangle;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        assert!(self.i <= self.glyphs.len());
-        if self.i == self.glyphs.len() {
-            return None
-        }
-
-        let glyph = &self.glyphs[self.i];
-        let sprite = &glyph.sprite;
-
-        // current_x/y is scaled real coords
-        // but the returned rect is unscaled
-
-        let rect = if sprite.has_fixed_sizes {
-            // Downscale by height
-            let w = (sprite.bmp_width as f32 * EMOJI_SCALE_FACT * self.font_size) /
-                sprite.bmp_height as f32;
-            let h = EMOJI_SCALE_FACT * self.font_size;
-
-            let x = self.current_x / self.window_scale;
-            let y = self.current_y / self.window_scale - (EMOJI_PROP_ABOVE_BASELINE * h);
-
-            self.current_x += w * self.window_scale;
-
-            Rectangle { x, y, w, h }
-        } else {
-            let (w, h) = (sprite.bmp_width as f32, sprite.bmp_height as f32);
-
-            let off_x = glyph.x_offset as f32 / 64.;
-            let off_y = glyph.y_offset as f32 / 64.;
-
-            let x = self.current_x + off_x + sprite.bearing_x;
-            let y = self.current_y - off_y - sprite.bearing_y;
-
-            let x_advance = glyph.x_advance;
-            let y_advance = glyph.y_advance;
-            self.current_x += x_advance;
-            self.current_y += y_advance;
-
-            // Downscale back again
-            Rectangle { x, y, w, h } / self.window_scale
-        };
-
-        self.i += 1;
-        Some(rect)
-    }
-}
-
-struct TextShaperInternal {
-    font_faces: FtFaces,
-    cache: TextShaperCache,
-}
-
-impl TextShaperInternal {
-    #[inline]
-    fn faces<'a>(&'a mut self) -> &'a mut Vec<FreetypeFace> {
-        &mut self.font_faces.0
-    }
-    #[inline]
-    fn face<'a>(&'a mut self, idx: usize) -> &'a mut FreetypeFace {
-        &mut self.font_faces.0[idx]
-    }
-}
-
-pub struct TextShaper {
-    intern: SyncMutex<TextShaperInternal>,
-    _fonts_data: Vec<Vec<u8>>,
-}
-
-impl TextShaper {
-    pub fn new() -> Arc<Self> {
-        let ftlib = freetype::Library::init().unwrap();
-
-        let mut fonts_data = vec![];
-        if let Ok(read_dir) = std::fs::read_dir(custom_font_path()) {
-            for entry in read_dir {
-                let Ok(entry) = entry else {
-                    warn!(target: "text", "Skipping unknown in custom font path");
-                    continue
-                };
-                let font_path = entry.path();
-                if font_path.is_dir() {
-                    warn!(target: "text", "Skipping {font_path:?} in custom font path: is directory");
-                    continue
-                }
-                let Some(font_ext) = font_path.extension().and_then(OsStr::to_str) else {
-                    warn!(target: "text", "Skipping {font_path:?} in custom font path: missing file extension");
-                    continue
-                };
-                if !["ttf", "otf"].contains(&font_ext) {
-                    warn!(target: "text", "Skipping {font_path:?} in custom font path: unsupported file extension (supported: ttf, otf)");
-                    continue
-                }
-                let font_data: Vec<u8> = match std::fs::read(&font_path) {
-                    Ok(font_data) => font_data,
-                    Err(err) => {
-                        warn!(target: "text", "Unexpected error loading {font_path:?} in custom font path: {err}");
-                        continue
-                    }
-                };
-                info!(target: "text", "Loaded custom font: {font_path:?}");
-                fonts_data.push(font_data);
-            }
-        }
-        fonts_data.reserve_exact(fonts_data.len() + 2);
-
-        let mut faces = vec![];
-
-        let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
-        let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
-        faces.push(ft_face);
-
-        for font_data in &fonts_data {
-            let face = unsafe { Self::load_font_face(&ftlib, font_data) };
-            faces.push(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);
-
-        Arc::new(Self {
-            intern: SyncMutex::new(TextShaperInternal {
-                font_faces: FtFaces(faces),
-                cache: HashMap::new(),
-            }),
-            _fonts_data: fonts_data,
-        })
-    }
-
-    /// Beware: recasts font_data as static. Make sure data outlives the face.
-    unsafe fn load_font_face(ftlib: &freetype::Library, font_data: &[u8]) -> FreetypeFace {
-        let font_data = &*(font_data as *const _);
-        let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
-        ft_face
-    }
-
-    pub fn shape(&self, text: String, font_size: f32, window_scale: f32) -> Vec<Glyph> {
-        //debug!(target: "text", "shape('{}', {})", text, font_size);
-        if text.is_empty() {
-            return vec![]
-        }
-
-        let text = &text;
-
-        // Freetype faces are not threadsafe
-        let mut intern = self.intern.lock().unwrap();
-
-        let size = font_size * window_scale;
-        for face in intern.faces() {
-            set_face_size(face, size);
-        }
-
-        let mut glyphs: Vec<Glyph> = vec![];
-        '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;
-            let substr = glyph_info.substr(text).to_string();
-
-            let x_offset = glyph_info.x_offset as f32 / 64.;
-            let y_offset = glyph_info.y_offset as f32 / 64.;
-            let x_advance = glyph_info.x_advance as f32 / 64.;
-            let y_advance = glyph_info.y_advance as f32 / 64.;
-
-            // Check cache
-            // If it exists in the cache then skip
-            // Relevant info:
-            // * glyph_id
-            // * font_size (for non-fixed size faces)
-            // * face_idx
-            let cache_key = CacheKey {
-                glyph_id,
-                font_size: if face.has_fixed_sizes() {
-                    FontSize::Fixed
-                } else {
-                    FontSize::from((font_size, window_scale))
-                },
-                face_idx,
-            };
-
-            //debug!(target: "text", "cache_key: {:?}", cache_key);
-            'load_sprite: {
-                if let Some(sprite) = intern.cache.get(&cache_key) {
-                    let Some(sprite) = sprite.upgrade() else {
-                        break 'load_sprite;
-                    };
-
-                    //debug!(target: "text", "found glyph!");
-                    let glyph = Glyph {
-                        glyph_id,
-                        substr,
-                        sprite,
-                        x_offset,
-                        y_offset,
-                        x_advance,
-                        y_advance,
-                    };
-
-                    glyphs.push(glyph);
-                    continue 'next_glyph;
-                }
-            }
-
-            let face = intern.face(face_idx);
-            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 =
-                Glyph { glyph_id, substr, sprite, x_offset, y_offset, x_advance, y_advance };
-
-            //debug!(target: "text", "pushing glyph...");
-            glyphs.push(glyph);
-        }
-
-        glyphs
-    }
-}
-
-#[derive(Eq, Hash, PartialEq, Debug)]
-enum FontSize {
-    Fixed,
-    Size((u32, u32)),
-}
-
-impl FontSize {
-    /// You can't use f32 in Hash and Eq impls
-    fn from(size: (f32, f32)) -> Self {
-        let font_size = (size.0 * 1000.).round() as u32;
-        let scale = (size.1 * 1000.).round() as u32;
-        Self::Size((font_size, scale))
-    }
-}
-
-#[derive(Eq, Hash, PartialEq, Debug)]
-struct CacheKey {
-    glyph_id: u32,
-    font_size: FontSize,
-    face_idx: usize,
-}
-
-#[derive(Clone)]
-pub struct Glyph {
-    pub glyph_id: u32,
-    // Substring this glyph corresponds to
-    pub substr: String,
-
-    pub sprite: SpritePtr,
-
-    // Normally these are i32, we provide the conversions
-    pub x_offset: f32,
-    pub y_offset: f32,
-    pub x_advance: f32,
-    pub y_advance: f32,
-}
-
-impl std::fmt::Debug for Glyph {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Glyph")
-            .field("glyph_id", &self.glyph_id)
-            .field("substr", &self.substr)
-            .finish()
-    }
-}
-
-struct FtFaces(Vec<FreetypeFace>);
-
-unsafe impl Send for FtFaces {}
-unsafe impl Sync for FtFaces {}
-
-pub type TextShaperPtr = Arc<TextShaper>;
-
-type TextShaperCache = HashMap<CacheKey, Weak<Sprite>>;

+ 0 - 132
bin/app/src/text/old_atlas.rs

@@ -1,132 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software 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 miniquad::{TextureFormat, TextureId};
-
-use crate::{
-    error::Result,
-    gfx::{Rectangle, RenderApi},
-    util::ansi_texture,
-};
-
-use super::{Glyph, Sprite};
-
-pub struct RenderedAtlas {
-    pub uv_rects: Vec<Rectangle>,
-    pub texture_id: GfxTextureId,
-}
-
-const ATLAS_GAP: usize = 2;
-
-pub async fn make_texture_atlas(
-    render_api: &RenderApi,
-    font_size: f32,
-    glyphs: &Vec<Glyph>,
-) -> Result<RenderedAtlas> {
-    // First compute total size of the atlas
-    let mut total_width = ATLAS_GAP;
-    let mut total_height = ATLAS_GAP;
-
-    // Glyph IDs already rendered so we don't do it twice
-    let mut rendered = vec![];
-
-    for (idx, glyph) in glyphs.iter().enumerate() {
-        let sprite = &glyph.sprite;
-        assert_eq!(sprite.bmp.len(), 4 * sprite.bmp_width * sprite.bmp_height);
-
-        // Already done this one so skip
-        if rendered.contains(&glyph.glyph_id) {
-            continue
-        }
-        rendered.push(glyph.glyph_id);
-
-        total_width += sprite.bmp_width + ATLAS_GAP;
-        total_height = std::cmp::max(total_height, sprite.bmp_height);
-    }
-    total_width += ATLAS_GAP;
-    total_height += 2 * ATLAS_GAP;
-
-    // Allocate the big texture now
-    let mut atlas_bmp = vec![0; 4 * total_width * total_height];
-    // For debug lines we want a single white pixel.
-    atlas_bmp[0] = 255;
-    atlas_bmp[1] = 255;
-    atlas_bmp[2] = 255;
-    atlas_bmp[3] = 255;
-
-    // Calculate dimensions of final product first
-    let mut current_x = ATLAS_GAP;
-    let mut rendered_glyphs: Vec<u32> = vec![];
-    let mut uv_rects: Vec<Rectangle> = vec![];
-
-    for (idx, glyph) in glyphs.iter().enumerate() {
-        let sprite = &glyph.sprite;
-
-        // Did we already rendered this glyph?
-        // If so just copy the UV rect from before.
-        let mut uv_rect = None;
-        for (rendered_glyph_id, rendered_uv_rect) in rendered_glyphs.iter().zip(uv_rects.iter()) {
-            if *rendered_glyph_id == glyph.glyph_id {
-                uv_rect = Some(rendered_uv_rect.clone());
-            }
-        }
-
-        let uv_rect = match uv_rect {
-            Some(uv_rect) => uv_rect,
-            // Allocating a new glyph sprite in the atlas
-            None => {
-                copy_image(sprite, &mut atlas_bmp, total_width, current_x);
-
-                // Compute UV coords
-                let uv_rect = Rectangle {
-                    x: (ATLAS_GAP + current_x) as f32 / total_width as f32,
-                    y: ATLAS_GAP as f32 / total_height as f32,
-                    w: sprite.bmp_width as f32 / total_width as f32,
-                    h: sprite.bmp_height as f32 / total_height as f32,
-                };
-
-                current_x += sprite.bmp_width + ATLAS_GAP;
-
-                uv_rect
-            }
-        };
-
-        rendered_glyphs.push(glyph.glyph_id);
-        uv_rects.push(uv_rect);
-    }
-
-    // Finally allocate the texture
-    let texture_id = render_api
-        .new_texture(total_width as u16, total_height as u16, atlas_bmp, TextureFormat::RGBA8)
-        .await?;
-
-    Ok(RenderedAtlas { uv_rects, texture_id })
-}
-
-fn copy_image(sprite: &Sprite, atlas_bmp: &mut Vec<u8>, total_width: usize, current_x: usize) {
-    for i in 0..sprite.bmp_height {
-        for j in 0..sprite.bmp_width {
-            let off_dest = 4 * ((i + ATLAS_GAP) * total_width + j + current_x + ATLAS_GAP);
-            let off_src = 4 * (i * sprite.bmp_width + j);
-            atlas_bmp[off_dest] = sprite.bmp[off_src];
-            atlas_bmp[off_dest + 1] = sprite.bmp[off_src + 1];
-            atlas_bmp[off_dest + 2] = sprite.bmp[off_src + 2];
-            atlas_bmp[off_dest + 3] = sprite.bmp[off_src + 3];
-        }
-    }
-}

+ 0 - 434
bin/app/src/text/shape.rs

@@ -1,434 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software 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 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,
-    hb_buffer_guess_segment_properties, hb_buffer_set_cluster_level, hb_buffer_set_content_type,
-    hb_buffer_t, hb_font_destroy, hb_font_t, hb_glyph_info_t, hb_glyph_position_t, hb_shape,
-    HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES, HB_BUFFER_CONTENT_TYPE_UNICODE,
-};
-use std::{iter::Peekable, os, vec::IntoIter};
-
-type FreetypeFace = ft::Face<&'static [u8]>;
-
-struct HarfBuzzInfo<'a> {
-    info: &'a hb_glyph_info_t,
-    pos: &'a hb_glyph_position_t,
-}
-
-struct HarfBuzzIter<'a> {
-    hb_font: *mut hb_font_t,
-    buf: *mut hb_buffer_t,
-    infos_iter: std::slice::Iter<'a, hb_glyph_info_t>,
-    pos_iter: std::slice::Iter<'a, hb_glyph_position_t>,
-}
-
-impl<'a> Iterator for HarfBuzzIter<'a> {
-    type Item = HarfBuzzInfo<'a>;
-    fn next(&mut self) -> Option<Self::Item> {
-        let info = self.infos_iter.next()?;
-        let pos = self.pos_iter.next()?;
-        Some(HarfBuzzInfo { info, pos })
-    }
-}
-
-impl<'a> Drop for HarfBuzzIter<'a> {
-    fn drop(&mut self) {
-        unsafe {
-            hb_buffer_destroy(self.buf);
-            hb_font_destroy(self.hb_font);
-        }
-    }
-}
-
-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();
-    }
-}
-
-fn harfbuzz_shape<'a>(face: &mut FreetypeFace, text: &str) -> HarfBuzzIter<'a> {
-    let utf8_ptr = text.as_ptr() as *const _;
-    // https://harfbuzz.github.io/a-simple-shaping-example.html
-    let (hb_font, buf, glyph_infos, glyph_pos) = unsafe {
-        let ft_face_ptr: freetype::freetype_sys::FT_Face = face.raw_mut();
-        let hb_font = hb_ft_font_create_referenced(ft_face_ptr);
-        let buf = hb_buffer_create();
-        hb_buffer_set_content_type(buf, HB_BUFFER_CONTENT_TYPE_UNICODE);
-        hb_buffer_set_cluster_level(buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES);
-        hb_buffer_add_utf8(
-            buf,
-            utf8_ptr,
-            text.len() as os::raw::c_int,
-            0 as os::raw::c_uint,
-            text.len() as os::raw::c_int,
-        );
-        hb_buffer_guess_segment_properties(buf);
-        hb_shape(hb_font, buf, std::ptr::null(), 0 as os::raw::c_uint);
-
-        let mut length: u32 = 0;
-        let glyph_infos = hb_buffer_get_glyph_infos(buf, &mut length as *mut u32);
-        let glyph_infos: &[hb_glyph_info_t] =
-            std::slice::from_raw_parts(glyph_infos as *const _, length as usize);
-
-        let glyph_pos = hb_buffer_get_glyph_positions(buf, &mut length as *mut u32);
-        let glyph_pos: &[hb_glyph_position_t] =
-            std::slice::from_raw_parts(glyph_pos as *const _, length as usize);
-
-        (hb_font, buf, glyph_infos, glyph_pos)
-    };
-
-    let infos_iter = glyph_infos.iter();
-    let pos_iter = glyph_pos.iter();
-
-    HarfBuzzIter { hb_font, buf, infos_iter, pos_iter }
-}
-
-pub(super) struct GlyphInfo {
-    pub face_idx: usize,
-    pub id: u32,
-    pub cluster_start: usize,
-    pub cluster_end: usize,
-    pub x_offset: i32,
-    pub y_offset: i32,
-    pub x_advance: i32,
-    pub y_advance: i32,
-}
-
-impl GlyphInfo {
-    pub fn substr<'a>(&self, text: &'a str) -> &'a str {
-        // RTL
-        let start = std::cmp::min(self.cluster_start, self.cluster_end);
-        let end = std::cmp::max(self.cluster_start, self.cluster_end);
-        &text[start..end]
-    }
-}
-
-struct ShapedGlyphs {
-    glyphs: Vec<GlyphInfo>,
-}
-
-impl ShapedGlyphs {
-    fn new(glyphs: Vec<GlyphInfo>) -> Self {
-        Self { glyphs }
-    }
-
-    fn has_zero(&self) -> bool {
-        self.glyphs.iter().any(|g| g.id == 0)
-    }
-
-    fn fill_zeros(&mut self, fallback: Vec<GlyphInfo>) {
-        let mut primary_iter = std::mem::take(&mut self.glyphs).into_iter().peekable();
-        let mut fallback_iter = fallback.into_iter().peekable();
-        assert!(self.glyphs.is_empty());
-
-        while let Some(primary_glyph) = primary_iter.next() {
-            if primary_glyph.id != 0 {
-                Self::consume(&mut fallback_iter, primary_glyph.cluster_start);
-                self.glyphs.push(primary_glyph);
-                continue
-            }
-
-            let mut fallbacks = Self::consume(&mut fallback_iter, primary_glyph.cluster_start);
-
-            let Some(last_fallback) = fallbacks.last() else { continue };
-            let cluster_end = last_fallback.cluster_end;
-
-            self.glyphs.append(&mut fallbacks);
-
-            Self::drop_replaced(&mut primary_iter, cluster_end);
-        }
-    }
-
-    fn consume(iter: &mut Peekable<IntoIter<GlyphInfo>>, cluster_bound: usize) -> Vec<GlyphInfo> {
-        let mut consumed = vec![];
-        while let Some(glyph) = iter.peek() {
-            if glyph.cluster_start > cluster_bound {
-                break
-            }
-
-            let glyph = iter.next().unwrap();
-            consumed.push(glyph);
-        }
-        consumed
-    }
-    fn drop_replaced(iter: &mut Peekable<IntoIter<GlyphInfo>>, cluster_end: usize) {
-        while let Some(glyph) = iter.peek() {
-            if glyph.cluster_start >= cluster_end {
-                break
-            }
-            let _ = iter.next();
-        }
-    }
-}
-
-/*
-fn print_glyphs(ctx: &str, glyphs: &Vec<GlyphInfo>, indent: usize) {
-    let ws = " ".repeat(2 * indent);
-    println!("{ws}{} ------------------", ctx);
-    for (i, glyph) in glyphs.iter().enumerate() {
-        println!(
-            "{ws}{i}: {}/{} [{}, {}]",
-            glyph.face_idx, glyph.id, glyph.cluster_start, glyph.cluster_end
-        );
-    }
-    println!("{ws}---------------------");
-}
-*/
-
-fn face_shape(face: &mut FreetypeFace, text: &str, face_idx: usize) -> Vec<GlyphInfo> {
-    let mut glyphs: Vec<GlyphInfo> = vec![];
-    for (i, hbinf) in harfbuzz_shape(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}");
-
-        if i != 0 {
-            glyphs.last_mut().unwrap().cluster_end = cluster;
-        }
-
-        glyphs.push(GlyphInfo {
-            face_idx,
-            id: glyph_id,
-            cluster_start: cluster,
-            cluster_end: 0,
-            x_offset: hbinf.pos.x_offset,
-            y_offset: hbinf.pos.y_offset,
-            x_advance: hbinf.pos.x_advance,
-            y_advance: hbinf.pos.y_advance,
-        });
-    }
-    if let Some(last) = glyphs.last_mut() {
-        last.cluster_end = text.len();
-    }
-    glyphs
-}
-
-/// Shape text using fallback fonts. We shape it using the primary font, then go down through
-/// the list of fallbacks. For every zero we encounter, take the remaining text on that line
-/// and try to shape it. Then replace that glyph + any others in the cluster with the new one.
-/// [More info](https://zachbayl.in/blog/font_fallback_revery/)
-pub(super) fn shape(faces: &mut Vec<FreetypeFace>, text: &str) -> Vec<GlyphInfo> {
-    let glyphs = face_shape(&mut faces[0], text, 0);
-    let mut shaped = ShapedGlyphs::new(glyphs);
-
-    // Go down successively in our fallbacks
-    for face_idx in 1..faces.len() {
-        if !shaped.has_zero() {
-            break
-        }
-
-        let glyphs = face_shape(&mut faces[face_idx], text, face_idx);
-        shaped.fill_zeros(glyphs);
-    }
-    shaped.glyphs
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn load_faces() -> Vec<FreetypeFace> {
-        let ftlib = freetype::Library::init().unwrap();
-
-        let mut faces = vec![];
-        let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
-        let face = ftlib.new_memory_face2(font_data, 0).unwrap();
-        faces.push(face);
-
-        let font_data = include_bytes!("../../NotoColorEmoji.ttf") as &[u8];
-        let face = ftlib.new_memory_face2(font_data, 0).unwrap();
-        faces.push(face);
-
-        //let font_data = include_bytes!("../noto-serif-cjk-jp-regular.otf") as &[u8];
-        //let face = ftlib.new_memory_face2(font_data, 0).unwrap();
-        //faces.push(face);
-
-        faces
-    }
-
-    #[test]
-    fn simple_shape_test() {
-        let mut faces = load_faces();
-        let text = "\u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}";
-        let glyphs = shape(&mut faces, text);
-
-        assert_eq!(glyphs.len(), 1);
-        assert_eq!(glyphs[0].face_idx, 1);
-        assert_eq!(glyphs[0].id, 1895);
-        assert_eq!(glyphs[0].cluster_start, 0);
-        assert_eq!(glyphs[0].cluster_end, 16);
-    }
-
-    #[test]
-    fn simple_double_shape_test() {
-        let mut faces = load_faces();
-        let text =
-            "\u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}\u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f}";
-        let glyphs = shape(&mut faces, text);
-
-        assert_eq!(glyphs.len(), 2);
-        assert_eq!(glyphs[0].face_idx, 1);
-        assert_eq!(glyphs[0].id, 1895);
-        assert_eq!(glyphs[0].cluster_start, 0);
-        assert_eq!(glyphs[0].cluster_end, 16);
-        assert_eq!(glyphs[1].face_idx, 1);
-        assert_eq!(glyphs[1].id, 1895);
-        assert_eq!(glyphs[1].cluster_start, 16);
-        assert_eq!(glyphs[1].cluster_end, 32);
-    }
-
-    #[test]
-    fn mixed_shape_test() {
-        //let text = "日本語";
-        //let text = "hel 日本語\u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f} ally";
-
-        let mut faces = load_faces();
-        let text = "hel \u{01f3f3}\u{fe0f}\u{200d}\u{26a7}\u{fe0f} 123 X\u{01f44d}\u{01f3fe}X br";
-        let glyphs = shape(&mut faces, text);
-
-        assert_eq!(glyphs[0].face_idx, 0);
-        assert_eq!(glyphs[0].id, 11);
-        assert_eq!(glyphs[0].cluster_start, 0);
-        assert_eq!(glyphs[0].cluster_end, 1);
-        assert_eq!(glyphs[1].face_idx, 0);
-        assert_eq!(glyphs[1].id, 6);
-        assert_eq!(glyphs[1].cluster_start, 1);
-        assert_eq!(glyphs[1].cluster_end, 2);
-        assert_eq!(glyphs[1].cluster_start, glyphs[0].cluster_end);
-        assert_eq!(glyphs[2].face_idx, 0);
-        assert_eq!(glyphs[2].id, 15);
-        assert_eq!(glyphs[2].cluster_start, 2);
-        assert_eq!(glyphs[2].cluster_end, 3);
-        assert_eq!(glyphs[2].cluster_start, glyphs[1].cluster_end);
-        assert_eq!(glyphs[3].face_idx, 0);
-        assert_eq!(glyphs[3].id, 1099);
-        assert_eq!(glyphs[3].cluster_start, 3);
-        assert_eq!(glyphs[3].cluster_end, 4);
-        assert_eq!(glyphs[3].cluster_start, glyphs[2].cluster_end);
-        assert_eq!(glyphs[4].face_idx, 1);
-        assert_eq!(glyphs[4].id, 1895);
-        assert_eq!(glyphs[4].cluster_start, 4);
-        assert_eq!(glyphs[4].cluster_end, 20);
-        assert_eq!(glyphs[4].cluster_start, glyphs[3].cluster_end);
-        assert_eq!(glyphs[5].face_idx, 0);
-        assert_eq!(glyphs[5].id, 1099);
-        assert_eq!(glyphs[5].cluster_start, 20);
-        assert_eq!(glyphs[5].cluster_end, 21);
-        assert_eq!(glyphs[5].cluster_start, glyphs[4].cluster_end);
-        assert_eq!(glyphs[6].face_idx, 0);
-        assert_eq!(glyphs[6].id, 59);
-        assert_eq!(glyphs[6].cluster_start, 21);
-        assert_eq!(glyphs[6].cluster_end, 22);
-        assert_eq!(glyphs[6].cluster_start, glyphs[5].cluster_end);
-        assert_eq!(glyphs[7].face_idx, 0);
-        assert_eq!(glyphs[7].id, 60);
-        assert_eq!(glyphs[7].cluster_start, 22);
-        assert_eq!(glyphs[7].cluster_end, 23);
-        assert_eq!(glyphs[7].cluster_start, glyphs[6].cluster_end);
-        assert_eq!(glyphs[8].face_idx, 0);
-        assert_eq!(glyphs[8].id, 61);
-        assert_eq!(glyphs[8].cluster_start, 23);
-        assert_eq!(glyphs[8].cluster_end, 24);
-        assert_eq!(glyphs[8].cluster_start, glyphs[7].cluster_end);
-        assert_eq!(glyphs[9].face_idx, 0);
-        assert_eq!(glyphs[9].id, 1099);
-        assert_eq!(glyphs[9].cluster_start, 24);
-        assert_eq!(glyphs[9].cluster_end, 25);
-        assert_eq!(glyphs[9].cluster_start, glyphs[8].cluster_end);
-        assert_eq!(glyphs[10].face_idx, 0);
-        assert_eq!(glyphs[10].id, 53);
-        assert_eq!(glyphs[10].cluster_start, 25);
-        assert_eq!(glyphs[10].cluster_end, 26);
-        assert_eq!(glyphs[10].cluster_start, glyphs[9].cluster_end);
-        assert_eq!(glyphs[11].face_idx, 1);
-        assert_eq!(glyphs[11].id, 1955);
-        assert_eq!(glyphs[11].cluster_start, 26);
-        assert_eq!(glyphs[11].cluster_end, 34);
-        assert_eq!(glyphs[11].cluster_start, glyphs[10].cluster_end);
-        assert_eq!(glyphs[12].face_idx, 0);
-        assert_eq!(glyphs[12].id, 53);
-        assert_eq!(glyphs[12].cluster_start, 34);
-        assert_eq!(glyphs[12].cluster_end, 35);
-        assert_eq!(glyphs[12].cluster_start, glyphs[11].cluster_end);
-        assert_eq!(glyphs[13].face_idx, 0);
-        assert_eq!(glyphs[13].id, 1099);
-        assert_eq!(glyphs[13].cluster_start, 35);
-        assert_eq!(glyphs[13].cluster_end, 36);
-        assert_eq!(glyphs[13].cluster_start, glyphs[12].cluster_end);
-        assert_eq!(glyphs[14].face_idx, 0);
-        assert_eq!(glyphs[14].id, 3);
-        assert_eq!(glyphs[14].cluster_start, 36);
-        assert_eq!(glyphs[14].cluster_end, 37);
-        assert_eq!(glyphs[14].cluster_start, glyphs[13].cluster_end);
-        assert_eq!(glyphs[15].face_idx, 0);
-        assert_eq!(glyphs[15].id, 21);
-        assert_eq!(glyphs[15].cluster_start, 37);
-        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);
-    }
-
-    /*
-    #[test]
-    fn weird_stuff() {
-        let mut faces = load_faces();
-        //let text = "( \u{361}° \u{35c}ʖ \u{361}°)";
-        let text = "\u{35c}ʖ \u{361}a";
-        let glyphs = shape(&mut faces, text);
-    }
-    */
-}

+ 0 - 295
bin/app/src/text/wrap.rs

@@ -1,295 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software 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 super::{Glyph, GlyphPositionIter};
-
-#[derive(Debug, PartialEq)]
-#[repr(u8)]
-enum TokenType {
-    Null,
-    Word,
-    Whitespace,
-}
-
-pub struct Token {
-    token_type: TokenType,
-    lhs: f32,
-    rhs: f32,
-    glyphs: Vec<Glyph>,
-}
-
-impl Token {
-    #[allow(dead_code)]
-    fn as_str(&self) -> String {
-        glyph_str(&self.glyphs)
-    }
-}
-
-impl std::fmt::Debug for Token {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self.token_type {
-            TokenType::Null => write!(f, "Token::Null")?,
-            TokenType::Word => write!(f, "Token::Word")?,
-            TokenType::Whitespace => write!(f, "Token::Whitespace")?,
-        }
-        write!(f, "({})", self.as_str())
-    }
-}
-
-/// Get the string represented by a vec of glyphs. Useful for debugging.
-pub fn glyph_str(glyphs: &[Glyph]) -> String {
-    glyphs.iter().map(|g| g.substr.as_str()).collect::<Vec<_>>().join("")
-}
-
-fn tokenize(font_size: f32, window_scale: f32, glyphs: &Vec<Glyph>) -> Vec<Token> {
-    let glyph_pos_iter = GlyphPositionIter::new(font_size, window_scale, glyphs, 0.);
-
-    let mut tokens = vec![];
-    let mut token_glyphs = vec![];
-    let mut lhs = -1.;
-    let mut rhs = 0.;
-
-    let mut token_type = TokenType::Null;
-
-    for (pos, glyph) in glyph_pos_iter.zip(glyphs.iter()) {
-        let new_type = if glyph.substr.chars().all(char::is_whitespace) {
-            TokenType::Whitespace
-        } else {
-            TokenType::Word
-        };
-
-        // This is the initial token so lets begin
-        // Just assume the token_type
-        if token_type == TokenType::Null {
-            assert!(token_glyphs.is_empty());
-            token_type = new_type;
-        } else if new_type != token_type {
-            // We just changed from one token type to another
-            assert!(!token_glyphs.is_empty());
-
-            // We have a non-empty word to push
-            let token = Token { token_type, lhs, rhs, glyphs: std::mem::take(&mut token_glyphs) };
-            tokens.push(token);
-
-            // Reset ruler
-            lhs = -1.;
-            //rhs = 0.;
-            // take() blanked token_glyphs above
-
-            token_type = new_type;
-        }
-
-        // LHS is uninitialized so this is the first glyph in the word
-        if lhs < 0. {
-            lhs = pos.x;
-        }
-
-        // RHS should always be the max
-        rhs = pos.x + pos.w;
-
-        // Update word
-        token_glyphs.push(glyph.clone());
-    }
-
-    if !token_glyphs.is_empty() {
-        let token = Token { token_type, lhs, rhs, glyphs: std::mem::take(&mut token_glyphs) };
-        tokens.push(token);
-    }
-
-    tokens
-}
-
-/// Given a series of words, apply wrapping.
-/// Whitespace is completely perserved.
-fn apply_wrap(line_width: f32, mut tokens: Vec<Token>) -> Vec<Vec<Glyph>> {
-    //debug!(target: "text::wrap", "apply_wrap({line_width}, {tokens:?})");
-
-    let mut lines = vec![];
-    let mut line = vec![];
-    let mut start = 0.;
-
-    let mut tokens_iter = tokens.iter_mut().peekable();
-    while let Some(token) = tokens_iter.next() {
-        assert!(token.token_type != TokenType::Null);
-
-        // Does this token cross over the end of the line?
-        if token.rhs > start + line_width {
-            // Whitespace tokens that cause wrapping are prepended to the current line before
-            // making the line break.
-            if token.token_type == TokenType::Whitespace {
-                line.append(&mut token.glyphs);
-            }
-
-            // Start a new line
-            let line = std::mem::take(&mut line);
-            //debug!(target: "text::apply_wrap", "adding line: {}", glyph_str(&line));
-            // This can happen if this token is very long and crosses the line boundary
-            if !line.is_empty() {
-                lines.push(line);
-            }
-
-            // Move to the next token if this is whitespace
-            if token.token_type == TokenType::Whitespace {
-                // Load LHS from next token in loop
-                if let Some(next_token) = tokens_iter.peek() {
-                    start = next_token.lhs;
-                }
-            } else {
-                start = token.lhs;
-            }
-        }
-
-        line.append(&mut token.glyphs);
-    }
-
-    // Handle the remainders
-    if !line.is_empty() {
-        let line = std::mem::take(&mut line);
-        //debug!(target: "text::apply_wrap", "adding rem line: {}", glyph_str(&line));
-        lines.push(line);
-    }
-
-    lines
-}
-
-/// Splits any Word token that exceeds the line width.
-/// So Word("aaaaaaaaaaaaaaa") => [Word("aaaaaaaa"), Word("aaaaaaa")].
-pub fn restrict_word_len(
-    font_size: f32,
-    window_scale: f32,
-    raw_tokens: Vec<Token>,
-    line_width: f32,
-) -> Vec<Token> {
-    let mut tokens = vec![];
-    for token in raw_tokens {
-        match token.token_type {
-            TokenType::Word => {
-                assert!(!token.glyphs.is_empty());
-                let token_width = token.rhs - token.lhs;
-                // No change required. This is the usual code path
-                if token_width < line_width {
-                    tokens.push(token);
-                    continue
-                }
-            }
-            _ => {
-                tokens.push(token);
-                continue
-            }
-        }
-
-        // OK we have encountered a Word that is very long. Lets split it up
-        // into multiple Words each under line_width.
-
-        let glyphs2 = token.glyphs.clone();
-        let glyph_pos_iter = GlyphPositionIter::new(font_size, window_scale, &glyphs2, 0.);
-        let mut token_glyphs = vec![];
-        let mut lhs = -1.;
-        let mut rhs = 0.;
-
-        // Just loop through each glyph. When the running total exceeds line_width
-        // then push the buffer, and start again.
-        // Very basic stuff.
-        for (pos, glyph) in glyph_pos_iter.zip(token.glyphs.into_iter()) {
-            if lhs < 0. {
-                lhs = pos.x;
-            }
-            rhs = pos.x + pos.w;
-
-            token_glyphs.push(glyph);
-
-            let curr_width = rhs - lhs;
-            // Line width exceeded. Do our thing.
-            if curr_width > line_width {
-                let token = Token {
-                    token_type: TokenType::Word,
-                    lhs,
-                    rhs,
-                    glyphs: std::mem::take(&mut token_glyphs),
-                };
-                tokens.push(token);
-                lhs = -1.;
-            }
-        }
-
-        // Take care of any remainders left over.
-        if !token_glyphs.is_empty() {
-            let token = Token {
-                token_type: TokenType::Word,
-                lhs,
-                rhs,
-                glyphs: std::mem::take(&mut token_glyphs),
-            };
-            tokens.push(token);
-        }
-    }
-    tokens
-}
-
-pub fn wrap(
-    line_width: f32,
-    font_size: f32,
-    window_scale: f32,
-    glyphs: &Vec<Glyph>,
-) -> Vec<Vec<Glyph>> {
-    let tokens = tokenize(font_size, window_scale, glyphs);
-
-    //debug!(target: "text::wrap", "tokenized words {:?}",
-    //       words.iter().map(|w| w.as_str()).collect::<Vec<_>>());
-
-    let tokens = restrict_word_len(font_size, window_scale, tokens, line_width);
-
-    let lines = apply_wrap(line_width, tokens);
-
-    //if lines.len() > 1 {
-    //    debug!(target: "text::wrap", "wrapped line: {}", glyph_str(glyphs));
-    //    for line in &lines {
-    //        debug!(target: "text::wrap", "-> {}", glyph_str(line));
-    //    }
-    //}
-
-    lines
-}
-
-#[cfg(test)]
-mod tests {
-    use super::{super::*, *};
-
-    #[test]
-    fn wrap_simple() {
-        let shaper = TextShaper::new();
-        let glyphs = shaper.shape("hello world 123".to_string(), 32., 1.);
-
-        let wrapped = wrap(200., 32., 1., &glyphs);
-        assert_eq!(wrapped.len(), 3);
-        assert_eq!(glyph_str(&wrapped[0]), "hello ");
-        assert_eq!(glyph_str(&wrapped[1]), "world ");
-        assert_eq!(glyph_str(&wrapped[2]), "123");
-    }
-
-    #[test]
-    fn wrap_long() {
-        let shaper = TextShaper::new();
-        let glyphs = shaper.shape("aaaaaaaaaaaaaaa".to_string(), 32., 1.);
-
-        let wrapped = wrap(200., 32., 1., &glyphs);
-        assert_eq!(wrapped.len(), 2);
-        assert_eq!(glyph_str(&wrapped[0]), "aaaaaaaa");
-        assert_eq!(glyph_str(&wrapped[1]), "aaaaaaa");
-    }
-}