text.rs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use freetype as ft;
  19. use crate::gfx::{FreetypeFace, Rectangle};
  20. // From https://sourceforge.net/projects/freetype/files/freetype2/2.6/
  21. //
  22. // * An `FT_Face' object can only be safely used from one thread at
  23. // a time.
  24. //
  25. // * An `FT_Library' object can now be used without modification
  26. // from multiple threads at the same time.
  27. //
  28. // * `FT_Face' creation and destruction with the same `FT_Library'
  29. // object can only be done from one thread at a time.
  30. //
  31. // One can use a single `FT_Library' object across threads as long
  32. // as a mutex lock is used around `FT_New_Face' and `FT_Done_Face'.
  33. // Any calls to `FT_Load_Glyph' and similar API are safe and do not
  34. // need the lock to be held as long as the same `FT_Face' is not
  35. // used from multiple threads at the same time.
  36. // Harfbuzz is threadsafe.
  37. // Notes:
  38. // * All ft init and face creation should happen at startup.
  39. // * FT faces protected behind an async Mutex
  40. // * Glyph cache. Key is (glyph_id, font_size)
  41. // * Glyph texture cache: (glyph_id, font_size, color)
  42. #[derive(Clone)]
  43. pub struct Glyph {
  44. pub id: u32,
  45. // Substring this glyph corresponds to
  46. pub substr: String,
  47. // The texture
  48. pub bmp: Vec<u8>,
  49. pub bmp_width: u16,
  50. pub bmp_height: u16,
  51. pub pos: Rectangle<f32>,
  52. }
  53. pub struct TextShaper {
  54. pub font_faces: Vec<FreetypeFace>,
  55. }
  56. unsafe impl Send for TextShaper {}
  57. unsafe impl Sync for TextShaper {}
  58. impl TextShaper {
  59. fn split_into_substrs(&self, text: String) -> Vec<(usize, String)> {
  60. let mut current_idx = 0;
  61. let mut current_str = String::new();
  62. let mut substrs = vec![];
  63. 'next_char: for chr in text.chars() {
  64. let idx = 'get_idx: {
  65. for i in 0..self.font_faces.len() {
  66. let ft_face = &self.font_faces[i];
  67. if ft_face.get_char_index(chr as usize).is_some() {
  68. break 'get_idx i
  69. }
  70. }
  71. warn!("no font fallback for char: '{}'", chr);
  72. // Skip this char
  73. continue 'next_char
  74. };
  75. if current_idx != idx {
  76. if !current_str.is_empty() {
  77. // Push
  78. substrs.push((current_idx, current_str.clone()));
  79. }
  80. current_str.clear();
  81. current_idx = idx;
  82. }
  83. current_str.push(chr);
  84. }
  85. if !current_str.is_empty() {
  86. // Push
  87. substrs.push((current_idx, current_str));
  88. }
  89. substrs
  90. }
  91. pub fn shape(&self, text: String, font_size: f32, text_color: [f32; 4]) -> Vec<Glyph> {
  92. let substrs = self.split_into_substrs(text.clone());
  93. let mut glyphs: Vec<Glyph> = vec![];
  94. let mut current_x = 0.;
  95. let mut current_y = 0.;
  96. for (face_idx, text) in substrs {
  97. //debug!("substr {}", text);
  98. let face = &self.font_faces[face_idx];
  99. if face.has_fixed_sizes() {
  100. // emojis required a fixed size
  101. //face.set_char_size(109 * 64, 0, 72, 72).unwrap();
  102. face.select_size(0).unwrap();
  103. } else {
  104. face.set_char_size(font_size as isize * 64, 0, 72, 72).unwrap();
  105. }
  106. let hb_font = harfbuzz_rs::Font::from_freetype_face(face.clone());
  107. let buffer = harfbuzz_rs::UnicodeBuffer::new()
  108. .set_cluster_level(harfbuzz_rs::ClusterLevel::MonotoneCharacters)
  109. .add_str(&text);
  110. let output = harfbuzz_rs::shape(&hb_font, buffer, &[]);
  111. let positions = output.get_glyph_positions();
  112. let infos = output.get_glyph_infos();
  113. let mut prev_cluster = 0;
  114. for (i, (position, info)) in positions.iter().zip(infos).enumerate() {
  115. let gid = info.codepoint;
  116. // Index within this substr
  117. let curr_cluster = info.cluster as usize;
  118. // Skip first time
  119. if i != 0 {
  120. let substr = text[prev_cluster..curr_cluster].to_string();
  121. glyphs.last_mut().unwrap().substr = substr;
  122. }
  123. prev_cluster = curr_cluster;
  124. let mut flags = ft::face::LoadFlag::DEFAULT;
  125. if face.has_color() {
  126. flags |= ft::face::LoadFlag::COLOR;
  127. }
  128. // FIXME: glyph 884 hangs on android
  129. // For now just avoid using emojis on android
  130. //debug!("load_glyph {}", gid);
  131. face.load_glyph(gid, flags).unwrap();
  132. //debug!("load_glyph {} [done]", gid);
  133. let glyph = face.glyph();
  134. glyph.render_glyph(ft::RenderMode::Normal).unwrap();
  135. let bmp = glyph.bitmap();
  136. let buffer = bmp.buffer();
  137. let bmp_width = bmp.width() as usize;
  138. let bmp_height = bmp.rows() as usize;
  139. let bearing_x = glyph.bitmap_left() as f32;
  140. let bearing_y = glyph.bitmap_top() as f32;
  141. let pixel_mode = bmp.pixel_mode().unwrap();
  142. let bmp = match pixel_mode {
  143. ft::bitmap::PixelMode::Bgra => {
  144. let mut tdata = vec![];
  145. tdata.resize(4 * bmp_width * bmp_height, 0);
  146. // Convert from BGRA to RGBA
  147. for i in 0..bmp_width * bmp_height {
  148. let idx = i * 4;
  149. let b = buffer[idx];
  150. let g = buffer[idx + 1];
  151. let r = buffer[idx + 2];
  152. let a = buffer[idx + 3];
  153. tdata[idx] = r;
  154. tdata[idx + 1] = g;
  155. tdata[idx + 2] = b;
  156. tdata[idx + 3] = a;
  157. }
  158. tdata
  159. }
  160. ft::bitmap::PixelMode::Gray => {
  161. // Convert from greyscale to RGBA8
  162. let tdata: Vec<_> = buffer
  163. .iter()
  164. .flat_map(|coverage| {
  165. let r = (255. * text_color[0]) as u8;
  166. let g = (255. * text_color[1]) as u8;
  167. let b = (255. * text_color[2]) as u8;
  168. let α = ((*coverage as f32) * text_color[3]) as u8;
  169. vec![r, g, b, α]
  170. })
  171. .collect();
  172. tdata
  173. }
  174. _ => panic!("unsupport pixel mode: {:?}", pixel_mode),
  175. };
  176. let pos = if face.has_fixed_sizes() {
  177. // Downscale by height
  178. let w = (bmp_width as f32 * font_size) / bmp_height as f32;
  179. let h = font_size;
  180. // Shouldn't this use the bearing?
  181. let x = current_x;
  182. let y = current_y - h;
  183. current_x += w;
  184. Rectangle { x, y, w, h }
  185. } else {
  186. let (w, h) = (bmp_width as f32, bmp_height as f32);
  187. let off_x = position.x_offset as f32 / 64.;
  188. let off_y = position.y_offset as f32 / 64.;
  189. let x = current_x + off_x + bearing_x;
  190. let y = current_y - off_y - bearing_y;
  191. let x_advance = position.x_advance as f32 / 64.;
  192. let y_advance = position.y_advance as f32 / 64.;
  193. current_x += x_advance;
  194. current_y += y_advance;
  195. Rectangle { x, y, w, h }
  196. };
  197. let glyph = Glyph {
  198. id: gid,
  199. substr: String::new(),
  200. bmp,
  201. bmp_width: bmp_width as u16,
  202. bmp_height: bmp_height as u16,
  203. pos,
  204. };
  205. glyphs.push(glyph);
  206. }
  207. let substr = text[prev_cluster..].to_string();
  208. glyphs.last_mut().unwrap().substr = substr;
  209. }
  210. glyphs
  211. }
  212. }