/* 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 . */ 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::os; 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 { 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 { &text[self.cluster_start..self.cluster_end] } } struct ShapedGlyphs { glyphs: Vec, } impl ShapedGlyphs { fn new(glyphs: Vec) -> Self { Self { glyphs } } fn surgery(&mut self, idx: usize, glyphs: Vec) { let tail = self.glyphs.split_off(idx); let mut tail_iter = tail.into_iter().peekable(); for glyph in glyphs { // We have a glyph. Lets consume tail. // We continue while the glyphs are before this glyph's end. while let Some(tail_glyph) = tail_iter.peek() && tail_glyph.cluster_start < glyph.cluster_end { tail_iter.next(); } self.glyphs.push(glyph); // Only continue while the tail starts with 0 if let Some(tail_glyph) = tail_iter.peek() { if tail_glyph.id != 0 { break } } } self.glyphs.extend(tail_iter); } fn scan_zero(&self, start_idx: usize) -> Option<(usize, usize)> { let mut glyphs_iter = self.glyphs.iter().enumerate(); if glyphs_iter.advance_by(start_idx).is_err() { return None } for (i, glyph) in glyphs_iter { if glyph.id == 0 { return Some((i, glyph.cluster_start)) } } None } } /// Count the number of leading zeros fn count_leading_null_glyphs(glyphs: &Vec) -> usize { let mut cnt = 0; for glyph in glyphs { if glyph.id != 0 { break } cnt += 1; } cnt } /* fn print_glyphs(ctx: &str, glyphs: &Vec) { println!("{} ------------------", ctx); for (i, glyph) in glyphs.iter().enumerate() { println!( "{i}: {}/{} [{}, {}]", glyph.face_idx, glyph.id, glyph.cluster_start, glyph.cluster_end ); } println!("---------------------"); } */ fn face_shape(face: &mut FreetypeFace, text: &str, off: usize, face_idx: usize) -> Vec { let mut glyphs: Vec = 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}"); let remain_text = &text[cluster..]; //println!(" remain_text='{remain_text}'"); if i != 0 { glyphs.last_mut().unwrap().cluster_end = cluster + off; } glyphs.push(GlyphInfo { face_idx, id: glyph_id, cluster_start: cluster + off, 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() + off; } 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, text: &str) -> Vec { let glyphs = face_shape(&mut faces[0], text, 0, 0); let mut shaped = ShapedGlyphs::new(glyphs); // Go down successively in our fallbacks for face_idx in 1..faces.len() { // We attempt to replace each zero once. This idx keeps track so we don't // keep repeating zeros we already tried to replace. let mut last_idx = 0; // Find the next zero while let Some((off, cluster_start)) = shaped.scan_zero(last_idx) { let remain_text = &text[cluster_start..]; let glyphs = face_shape(&mut faces[face_idx], remain_text, cluster_start, face_idx); // We weren't successful shaping with this fallback font, so skip over these glyphs. let leading_zeros = count_leading_null_glyphs(&glyphs); last_idx = off + leading_zeros; // Perform bottom surgery if leading_zeros == 0 { shaped.surgery(off, glyphs); } } } shaped.glyphs } #[cfg(test)] mod tests { use super::*; fn load_faces() -> Vec { 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); } }