Files
darkfi/bin/app/src/text/ft.rs
2026-01-01 11:40:45 +00:00

144 lines
4.6 KiB
Rust

/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2026 Dyne.org foundation
*
* This program is free sofreetypeware: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Sofreetypeware 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::face::LoadFlag as FtLoadFlag;
use std::sync::Arc;
pub type FreetypeFace = freetype::Face<&'static [u8]>;
pub type SpritePtr = Arc<Sprite>;
pub struct Sprite {
pub bmp: Vec<u8>,
pub bmp_width: usize,
pub bmp_height: usize,
pub bearing_x: f32,
pub bearing_y: f32,
pub has_fixed_sizes: bool,
pub has_color: bool,
}
fn load_ft_glyph<'a>(
face: &'a FreetypeFace,
glyph_id: u32,
flags: FtLoadFlag,
) -> Option<&'a freetype::GlyphSlot> {
//debug!("load_glyph {} flags={flags:?}", glyph_id);
if let Err(err) = face.load_glyph(glyph_id, flags) {
error!(target: "text", "error loading glyph {glyph_id}: {err}");
return None
}
//debug!("load_glyph {} [done]", glyph_id);
// https://gist.github.com/jokertarot/7583938?permalink_comment_id=3327566#gistcomment-3327566
let glyph = face.glyph();
glyph.render_glyph(freetype::RenderMode::Normal).ok()?;
Some(glyph)
}
pub fn render_glyph(face: &FreetypeFace, glyph_id: u32) -> Option<Sprite> {
// If color is available then attempt to load it.
// Otherwise fallback to black and white.
let glyph = if face.has_color() {
match load_ft_glyph(face, glyph_id, FtLoadFlag::DEFAULT | FtLoadFlag::COLOR) {
Some(glyph) => glyph,
None => load_ft_glyph(face, glyph_id, FtLoadFlag::DEFAULT)?,
}
} else {
load_ft_glyph(face, glyph_id, FtLoadFlag::DEFAULT)?
};
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 has_fixed_sizes = face.has_fixed_sizes();
let pixel_mode = bmp.pixel_mode().unwrap();
let bmp = match pixel_mode {
freetype::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
}
freetype::bitmap::PixelMode::Gray => {
// Convert from greyscale to RGBA8
let tdata: Vec<_> =
buffer.iter().flat_map(|coverage| vec![255, 255, 255, *coverage]).collect();
tdata
}
freetype::bitmap::PixelMode::Mono => {
// Convert from mono to RGBA8
let tdata: Vec<_> =
buffer.iter().flat_map(|coverage| vec![255, 255, 255, *coverage]).collect();
tdata
}
_ => panic!("unsupport pixel mode: {:?}", pixel_mode),
};
Some(Sprite {
bmp,
bmp_width,
bmp_height,
bearing_x,
bearing_y,
has_fixed_sizes,
has_color: face.has_color(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_simple() {
let ftlib = freetype::Library::init().unwrap();
let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
let face = ftlib.new_memory_face2(font_data, 0).unwrap();
// glyph 11 in IBM plex mono regular is 'h'
let glyph = render_glyph(&face, 11).unwrap();
}
#[test]
fn render_custom_glyph() {
let ftlib = freetype::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 glyph = render_glyph(&face, 4).unwrap();
}
}