| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- /* 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 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<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 {
- &text[self.cluster_start..self.cluster_end]
- }
- }
- struct ShapedGlyphs {
- glyphs: Vec<GlyphInfo>,
- }
- impl ShapedGlyphs {
- fn new(glyphs: Vec<GlyphInfo>) -> Self {
- Self { glyphs }
- }
- fn surgery(&mut self, idx: usize, glyphs: Vec<GlyphInfo>) {
- 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<GlyphInfo>) -> usize {
- let mut cnt = 0;
- for glyph in glyphs {
- if glyph.id != 0 {
- break
- }
- cnt += 1;
- }
- cnt
- }
- /*
- fn print_glyphs(ctx: &str, glyphs: &Vec<GlyphInfo>) {
- 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<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}");
- 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<FreetypeFace>, text: &str) -> Vec<GlyphInfo> {
- 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<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);
- }
- }
|