| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 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 crate::gfx::{FreetypeFace, Rectangle};
- // 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 an async Mutex
- // * Glyph cache. Key is (glyph_id, font_size)
- // * Glyph texture cache: (glyph_id, font_size, color)
- #[derive(Clone)]
- pub struct Glyph {
- pub id: u32,
- // Substring this glyph corresponds to
- pub substr: String,
- // The texture
- pub bmp: Vec<u8>,
- pub bmp_width: u16,
- pub bmp_height: u16,
- pub pos: Rectangle<f32>,
- }
- pub struct TextShaper {
- pub font_faces: Vec<FreetypeFace>,
- }
- unsafe impl Send for TextShaper {}
- unsafe impl Sync for TextShaper {}
- impl TextShaper {
- fn split_into_substrs(&self, text: String) -> Vec<(usize, String)> {
- let mut current_idx = 0;
- let mut current_str = String::new();
- let mut substrs = vec![];
- 'next_char: for chr in text.chars() {
- let idx = 'get_idx: {
- for i in 0..self.font_faces.len() {
- let ft_face = &self.font_faces[i];
- if ft_face.get_char_index(chr as usize).is_some() {
- break 'get_idx i
- }
- }
- warn!("no font fallback for char: '{}'", chr);
- // Skip this char
- continue 'next_char
- };
- if current_idx != idx {
- if !current_str.is_empty() {
- // Push
- substrs.push((current_idx, current_str.clone()));
- }
- current_str.clear();
- current_idx = idx;
- }
- current_str.push(chr);
- }
- if !current_str.is_empty() {
- // Push
- substrs.push((current_idx, current_str));
- }
- substrs
- }
- pub fn shape(&self, text: String, font_size: f32, text_color: [f32; 4]) -> Vec<Glyph> {
- let substrs = self.split_into_substrs(text.clone());
- let mut glyphs: Vec<Glyph> = vec![];
- let mut current_x = 0.;
- let mut current_y = 0.;
- for (face_idx, text) in substrs {
- //debug!("substr {}", text);
- let face = &self.font_faces[face_idx];
- if face.has_fixed_sizes() {
- // emojis required a fixed size
- //face.set_char_size(109 * 64, 0, 72, 72).unwrap();
- face.select_size(0).unwrap();
- } else {
- face.set_char_size(font_size as isize * 64, 0, 72, 72).unwrap();
- }
- let hb_font = harfbuzz_rs::Font::from_freetype_face(face.clone());
- let buffer = harfbuzz_rs::UnicodeBuffer::new()
- .set_cluster_level(harfbuzz_rs::ClusterLevel::MonotoneCharacters)
- .add_str(&text);
- let output = harfbuzz_rs::shape(&hb_font, buffer, &[]);
- let positions = output.get_glyph_positions();
- let infos = output.get_glyph_infos();
- let mut prev_cluster = 0;
- for (i, (position, info)) in positions.iter().zip(infos).enumerate() {
- let gid = info.codepoint;
- // Index within this substr
- let curr_cluster = info.cluster as usize;
- // Skip first time
- if i != 0 {
- let substr = text[prev_cluster..curr_cluster].to_string();
- glyphs.last_mut().unwrap().substr = substr;
- }
- prev_cluster = curr_cluster;
- let mut flags = ft::face::LoadFlag::DEFAULT;
- if face.has_color() {
- flags |= ft::face::LoadFlag::COLOR;
- }
- // FIXME: glyph 884 hangs on android
- // For now just avoid using emojis on android
- //debug!("load_glyph {}", gid);
- face.load_glyph(gid, flags).unwrap();
- //debug!("load_glyph {} [done]", gid);
- 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 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| {
- let r = (255. * text_color[0]) as u8;
- let g = (255. * text_color[1]) as u8;
- let b = (255. * text_color[2]) as u8;
- let α = ((*coverage as f32) * text_color[3]) as u8;
- vec![r, g, b, α]
- })
- .collect();
- tdata
- }
- _ => panic!("unsupport pixel mode: {:?}", pixel_mode),
- };
- let pos = if face.has_fixed_sizes() {
- // Downscale by height
- let w = (bmp_width as f32 * font_size) / bmp_height as f32;
- let h = font_size;
- // Shouldn't this use the bearing?
- let x = current_x;
- let y = current_y - h;
- current_x += w;
- Rectangle { x, y, w, h }
- } else {
- let (w, h) = (bmp_width as f32, bmp_height as f32);
- let off_x = position.x_offset as f32 / 64.;
- let off_y = position.y_offset as f32 / 64.;
- let x = current_x + off_x + bearing_x;
- let y = current_y - off_y - bearing_y;
- let x_advance = position.x_advance as f32 / 64.;
- let y_advance = position.y_advance as f32 / 64.;
- current_x += x_advance;
- current_y += y_advance;
- Rectangle { x, y, w, h }
- };
- let glyph = Glyph {
- id: gid,
- substr: String::new(),
- bmp,
- bmp_width: bmp_width as u16,
- bmp_height: bmp_height as u16,
- pos,
- };
- glyphs.push(glyph);
- }
- let substr = text[prev_cluster..].to_string();
- glyphs.last_mut().unwrap().substr = substr;
- }
- glyphs
- }
- }
|