mod.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 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 harfbuzz_sys::{
  19. freetype::hb_ft_font_create_referenced, hb_buffer_add_utf8, hb_buffer_create,
  20. hb_buffer_destroy, hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions,
  21. hb_buffer_guess_segment_properties, hb_buffer_set_cluster_level, hb_buffer_set_content_type,
  22. hb_font_destroy, hb_glyph_info_t, hb_glyph_position_t, hb_shape,
  23. HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES, HB_BUFFER_CONTENT_TYPE_UNICODE,
  24. };
  25. use std::{
  26. collections::HashMap,
  27. ffi::OsStr,
  28. os,
  29. path::PathBuf,
  30. sync::{Arc, Mutex as SyncMutex, Weak},
  31. };
  32. use crate::gfx::Rectangle;
  33. mod atlas;
  34. pub use atlas::{make_texture_atlas, Atlas, RenderedAtlas};
  35. mod ft;
  36. use ft::{render_glyph, FreetypeFace, Sprite, SpritePtr};
  37. mod shape;
  38. use shape::{set_face_size, shape};
  39. mod wrap;
  40. pub use wrap::{glyph_str, wrap};
  41. // Upscale emoji relative to font size
  42. pub const EMOJI_SCALE_FACT: f32 = 1.6;
  43. // How much of the emoji is above baseline?
  44. pub const EMOJI_PROP_ABOVE_BASELINE: f32 = 0.8;
  45. #[cfg(target_os = "android")]
  46. fn custom_font_path() -> PathBuf {
  47. crate::android::get_external_storage_path().join("font")
  48. }
  49. #[cfg(not(target_os = "android"))]
  50. fn custom_font_path() -> PathBuf {
  51. dirs::data_local_dir().unwrap().join("darkfi/app/font")
  52. }
  53. // From https://sourceforge.net/projects/freetype/files/freetype2/2.6/
  54. //
  55. // * An `FT_Face' object can only be safely used from one thread at
  56. // a time.
  57. //
  58. // * An `FT_Library' object can now be used without modification
  59. // from multiple threads at the same time.
  60. //
  61. // * `FT_Face' creation and destruction with the same `FT_Library'
  62. // object can only be done from one thread at a time.
  63. //
  64. // One can use a single `FT_Library' object across threads as long
  65. // as a mutex lock is used around `FT_New_Face' and `FT_Done_Face'.
  66. // Any calls to `FT_Load_Glyph' and similar API are safe and do not
  67. // need the lock to be held as long as the same `FT_Face' is not
  68. // used from multiple threads at the same time.
  69. // Harfbuzz is threadsafe.
  70. // Notes:
  71. // * All ft init and face creation should happen at startup.
  72. // * FT faces protected behind a Mutex
  73. // * Glyph cache. Key is (glyph_id, font_size)
  74. // * Glyph texture cache: (glyph_id, font_size, color)
  75. #[derive(Clone)]
  76. pub struct GlyphPositionIter<'a> {
  77. font_size: f32,
  78. window_scale: f32,
  79. glyphs: &'a Vec<Glyph>,
  80. current_x: f32,
  81. current_y: f32,
  82. i: usize,
  83. }
  84. impl<'a> GlyphPositionIter<'a> {
  85. pub fn new(font_size: f32, window_scale: f32, glyphs: &'a Vec<Glyph>, baseline_y: f32) -> Self {
  86. let start_y = baseline_y * window_scale;
  87. Self { font_size, window_scale, glyphs, current_x: 0., current_y: start_y, i: 0 }
  88. }
  89. }
  90. impl<'a> Iterator for GlyphPositionIter<'a> {
  91. type Item = Rectangle;
  92. fn next(&mut self) -> Option<Self::Item> {
  93. assert!(self.i <= self.glyphs.len());
  94. if self.i == self.glyphs.len() {
  95. return None
  96. }
  97. let glyph = &self.glyphs[self.i];
  98. let sprite = &glyph.sprite;
  99. // current_x/y is scaled real coords
  100. // but the returned rect is unscaled
  101. let rect = if sprite.has_fixed_sizes {
  102. // Downscale by height
  103. let w = (sprite.bmp_width as f32 * EMOJI_SCALE_FACT * self.font_size) /
  104. sprite.bmp_height as f32;
  105. let h = EMOJI_SCALE_FACT * self.font_size;
  106. let x = self.current_x / self.window_scale;
  107. let y = self.current_y / self.window_scale - (EMOJI_PROP_ABOVE_BASELINE * h);
  108. self.current_x += w * self.window_scale;
  109. Rectangle { x, y, w, h }
  110. } else {
  111. let (w, h) = (sprite.bmp_width as f32, sprite.bmp_height as f32);
  112. let off_x = glyph.x_offset as f32 / 64.;
  113. let off_y = glyph.y_offset as f32 / 64.;
  114. let x = self.current_x + off_x + sprite.bearing_x;
  115. let y = self.current_y - off_y - sprite.bearing_y;
  116. let x_advance = glyph.x_advance;
  117. let y_advance = glyph.y_advance;
  118. self.current_x += x_advance;
  119. self.current_y += y_advance;
  120. // Downscale back again
  121. Rectangle { x, y, w, h } / self.window_scale
  122. };
  123. self.i += 1;
  124. Some(rect)
  125. }
  126. }
  127. struct TextShaperInternal {
  128. font_faces: FtFaces,
  129. cache: TextShaperCache,
  130. }
  131. impl TextShaperInternal {
  132. #[inline]
  133. fn faces<'a>(&'a mut self) -> &'a mut Vec<FreetypeFace> {
  134. &mut self.font_faces.0
  135. }
  136. #[inline]
  137. fn face<'a>(&'a mut self, idx: usize) -> &'a mut FreetypeFace {
  138. &mut self.font_faces.0[idx]
  139. }
  140. }
  141. pub struct TextShaper {
  142. intern: SyncMutex<TextShaperInternal>,
  143. fonts_data: Vec<Vec<u8>>,
  144. }
  145. impl TextShaper {
  146. pub fn new() -> Arc<Self> {
  147. let ftlib = freetype::Library::init().unwrap();
  148. let mut fonts_data = vec![];
  149. if let Ok(read_dir) = std::fs::read_dir(custom_font_path()) {
  150. for entry in read_dir {
  151. let Ok(entry) = entry else {
  152. warn!(target: "text", "Skipping unknown in custom font path");
  153. continue
  154. };
  155. let font_path = entry.path();
  156. if font_path.is_dir() {
  157. warn!(target: "text", "Skipping {font_path:?} in custom font path: is directory");
  158. continue
  159. }
  160. let Some(font_ext) = font_path.extension().and_then(OsStr::to_str) else {
  161. warn!(target: "text", "Skipping {font_path:?} in custom font path: missing file extension");
  162. continue
  163. };
  164. if !["ttf", "otf"].contains(&font_ext) {
  165. warn!(target: "text", "Skipping {font_path:?} in custom font path: unsupported file extension (supported: ttf, otf)");
  166. continue
  167. }
  168. let font_data: Vec<u8> = match std::fs::read(&font_path) {
  169. Ok(font_data) => font_data,
  170. Err(err) => {
  171. warn!(target: "text", "Unexpected error loading {font_path:?} in custom font path: {err}");
  172. continue
  173. }
  174. };
  175. info!(target: "text", "Loaded custom font: {font_path:?}");
  176. fonts_data.push(font_data);
  177. }
  178. }
  179. fonts_data.reserve_exact(fonts_data.len() + 2);
  180. let mut faces = vec![];
  181. let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
  182. let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
  183. faces.push(ft_face);
  184. for font_data in &fonts_data {
  185. let face = unsafe { Self::load_font_face(&ftlib, font_data) };
  186. faces.push(face);
  187. }
  188. let font_data = include_bytes!("../../NotoColorEmoji.ttf") as &[u8];
  189. let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
  190. faces.push(ft_face);
  191. Arc::new(Self {
  192. intern: SyncMutex::new(TextShaperInternal {
  193. font_faces: FtFaces(faces),
  194. cache: HashMap::new(),
  195. }),
  196. fonts_data,
  197. })
  198. }
  199. /// Beware: recasts font_data as static. Make sure data outlives the face.
  200. unsafe fn load_font_face(ftlib: &freetype::Library, font_data: &[u8]) -> FreetypeFace {
  201. let font_data = &*(font_data as *const _);
  202. let ft_face = ftlib.new_memory_face2(font_data, 0).unwrap();
  203. ft_face
  204. }
  205. pub fn shape(&self, mut text: String, font_size: f32, window_scale: f32) -> Vec<Glyph> {
  206. //debug!(target: "text", "shape('{}', {})", text, font_size);
  207. if text.is_empty() {
  208. return vec![]
  209. }
  210. let text = &text;
  211. // Freetype faces are not threadsafe
  212. let mut intern = self.intern.lock().unwrap();
  213. let size = font_size * window_scale;
  214. for face in intern.faces() {
  215. set_face_size(face, size);
  216. }
  217. let mut glyphs: Vec<Glyph> = vec![];
  218. 'next_glyph: for glyph_info in shape(intern.faces(), text) {
  219. let face_idx = glyph_info.face_idx;
  220. let face = intern.face(face_idx);
  221. let glyph_id = glyph_info.id;
  222. let substr = glyph_info.substr(text).to_string();
  223. let x_offset = glyph_info.x_offset as f32 / 64.;
  224. let y_offset = glyph_info.y_offset as f32 / 64.;
  225. let x_advance = glyph_info.x_advance as f32 / 64.;
  226. let y_advance = glyph_info.y_advance as f32 / 64.;
  227. // Check cache
  228. // If it exists in the cache then skip
  229. // Relevant info:
  230. // * glyph_id
  231. // * font_size (for non-fixed size faces)
  232. // * face_idx
  233. let cache_key = CacheKey {
  234. glyph_id,
  235. font_size: if face.has_fixed_sizes() {
  236. FontSize::Fixed
  237. } else {
  238. FontSize::from((font_size, window_scale))
  239. },
  240. face_idx,
  241. };
  242. //debug!(target: "text", "cache_key: {:?}", cache_key);
  243. 'load_sprite: {
  244. if let Some(sprite) = intern.cache.get(&cache_key) {
  245. let Some(sprite) = sprite.upgrade() else {
  246. break 'load_sprite;
  247. };
  248. //debug!(target: "text", "found glyph!");
  249. let glyph = Glyph {
  250. glyph_id,
  251. substr,
  252. sprite,
  253. x_offset,
  254. y_offset,
  255. x_advance,
  256. y_advance,
  257. };
  258. glyphs.push(glyph);
  259. continue 'next_glyph;
  260. }
  261. }
  262. let face = intern.face(face_idx);
  263. let Some(sprite) = render_glyph(&face, glyph_id) else { continue };
  264. let sprite = Arc::new(sprite);
  265. intern.cache.insert(cache_key, Arc::downgrade(&sprite));
  266. let glyph =
  267. Glyph { glyph_id, substr, sprite, x_offset, y_offset, x_advance, y_advance };
  268. //debug!(target: "text", "pushing glyph...");
  269. glyphs.push(glyph);
  270. }
  271. glyphs
  272. }
  273. }
  274. #[derive(Eq, Hash, PartialEq, Debug)]
  275. enum FontSize {
  276. Fixed,
  277. Size((u32, u32)),
  278. }
  279. impl FontSize {
  280. /// You can't use f32 in Hash and Eq impls
  281. fn from(size: (f32, f32)) -> Self {
  282. let font_size = (size.0 * 1000.).round() as u32;
  283. let scale = (size.1 * 1000.).round() as u32;
  284. Self::Size((font_size, scale))
  285. }
  286. }
  287. #[derive(Eq, Hash, PartialEq, Debug)]
  288. struct CacheKey {
  289. glyph_id: u32,
  290. font_size: FontSize,
  291. face_idx: usize,
  292. }
  293. #[derive(Clone)]
  294. pub struct Glyph {
  295. pub glyph_id: u32,
  296. // Substring this glyph corresponds to
  297. pub substr: String,
  298. pub sprite: SpritePtr,
  299. // Normally these are i32, we provide the conversions
  300. pub x_offset: f32,
  301. pub y_offset: f32,
  302. pub x_advance: f32,
  303. pub y_advance: f32,
  304. }
  305. impl std::fmt::Debug for Glyph {
  306. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  307. f.debug_struct("Glyph")
  308. .field("glyph_id", &self.glyph_id)
  309. .field("substr", &self.substr)
  310. .finish()
  311. }
  312. }
  313. struct FtFaces(Vec<FreetypeFace>);
  314. unsafe impl Send for FtFaces {}
  315. unsafe impl Sync for FtFaces {}
  316. pub type TextShaperPtr = Arc<TextShaper>;
  317. type TextShaperCache = HashMap<CacheKey, Weak<Sprite>>;