//! 基于 FreeType 的中文文字光栅化 + OpenGL 纹理渲染(设备端 Live2D 字幕 overlay)。 //! //! 设计:每个字符首次出现时用 FreeType 渲染为灰度位图(经 `render_glyph` 拿到位图),转成 //! 白色字形(alpha=灰度)的 RGBA 纹理并缓存;绘制时按 pen 位置贴纹理即可。纹理创建需要 //! `Gl` 函数指针(与渲染器共用同一 EGL 上下文)。 use super::gl::gl as gl_const; use super::gl::Gl; use freetype::face::LoadFlag; use freetype::Face; use freetype::Library; use freetype::RenderMode; use std::collections::HashMap; use std::path::Path; use anyhow::Result; /// 单个字符的缓存字形 #[derive(Clone)] pub struct CachedGlyph { /// 字形纹理(0 表示该字符无可见像素,仅用于步进) pub tex: gl_const::GLuint, pub w: i32, pub h: i32, /// 字形位图相对 pen 原点的偏移 pub left: i32, pub top: i32, /// 26.6 定点像素步进(渲染端需 >> 6 转为像素) pub advance: i32, } pub struct TextRenderer { face: Face<'static>, font_px: u32, glyphs: HashMap, white_tex: Option, } // FreeType 的 Face 包裹裸指针,仅在本插件单一渲染线程使用。 unsafe impl Send for TextRenderer {} /// FT_LOAD_RENDER 标志位(0x4):加载字符的同时渲染为位图 const FT_LOAD_RENDER: i32 = 0x4; impl TextRenderer { pub fn new(font_path: &Path, font_px: u32) -> Result { let library = Library::init()?; // 让 Library 成为 'static(程序级单例),从而 Face<'static> 可长期持有而不会悬垂 let library: &'static Library = Box::leak(Box::new(library)); let face = library.new_face(font_path, 0)?; let tr = Self { face, font_px, glyphs: HashMap::new(), white_tex: None, }; tr.face.set_pixel_sizes(0, font_px)?; println!("[Live2D] 字幕字体就绪: {:?} ({}px)", font_path, font_px); Ok(tr) } /// 当前字号(像素) pub fn font_size(&self) -> u32 { self.font_px } /// 1x1 白色纹理(用于纯色背景框,alpha 恒为 1)。 fn white_tex(&mut self, gl: &Gl) -> gl_const::GLuint { if let Some(t) = self.white_tex { return t; } let mut tex = 0u32; unsafe { (gl.gl_gen_textures)(1, &mut tex); (gl.gl_bind_texture)(gl_const::GL_TEXTURE_2D, tex); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_MIN_FILTER, gl_const::GL_LINEAR as i32, ); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_MAG_FILTER, gl_const::GL_LINEAR as i32, ); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_WRAP_S, gl_const::GL_CLAMP_TO_EDGE as i32, ); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_WRAP_T, gl_const::GL_CLAMP_TO_EDGE as i32, ); let px: [u8; 4] = [255, 255, 255, 255]; (gl.gl_tex_image_2d)( gl_const::GL_TEXTURE_2D, 0, gl_const::GL_RGBA as i32, 1, 1, 0, gl_const::GL_RGBA, gl_const::GL_UNSIGNED_BYTE, px.as_ptr() as *const std::ffi::c_void, ); } self.white_tex = Some(tex); tex } /// 取 1x1 白纹理(供外部画背景框)。 pub fn white_texture(&mut self, gl: &Gl) -> gl_const::GLuint { self.white_tex(gl) } /// 估算字符像素宽度(用于换行布局,不创建纹理)。返回像素值。 pub fn measure_advance(&mut self, ch: char) -> i32 { if self .face .load_char(ch as usize, LoadFlag::from_bits_truncate(FT_LOAD_RENDER)) .is_ok() { (self.face.glyph().advance().x >> 6) as i32 } else { (self.font_px as f32 * 0.5) as i32 } } fn rasterize(&mut self, gl: &Gl, ch: char) -> Option { let half_advance_26_6 = (self.font_px as i32) << 5; // 半字宽,26.6 定点 if self .face .load_char(ch as usize, LoadFlag::from_bits_truncate(FT_LOAD_RENDER)) .is_err() { return Some(CachedGlyph { tex: 0, w: 0, h: 0, left: 0, top: 0, advance: half_advance_26_6, }); } let glyph = self.face.glyph(); if glyph.render_glyph(RenderMode::Normal).is_err() { let adv = glyph.advance().x as i32; // 26.6 定点 return Some(CachedGlyph { tex: 0, w: 0, h: 0, left: 0, top: 0, advance: adv, }); } let bitmap = glyph.bitmap(); let w = bitmap.width(); let h = bitmap.rows(); let left = glyph.bitmap_left(); let top = glyph.bitmap_top(); let advance = glyph.advance().x as i32; // 26.6 定点 if w == 0 || h == 0 { return Some(CachedGlyph { tex: 0, w: 0, h: 0, left, top, advance, }); } // 灰度 -> RGBA:白色字形,alpha=灰度 let buf = bitmap.buffer(); let mut rgba: Vec = Vec::with_capacity((w * h * 4) as usize); for &g in buf { rgba.push(255); rgba.push(255); rgba.push(255); rgba.push(g); } let mut tex = 0u32; unsafe { (gl.gl_gen_textures)(1, &mut tex); (gl.gl_bind_texture)(gl_const::GL_TEXTURE_2D, tex); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_MIN_FILTER, gl_const::GL_LINEAR as i32, ); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_MAG_FILTER, gl_const::GL_LINEAR as i32, ); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_WRAP_S, gl_const::GL_CLAMP_TO_EDGE as i32, ); (gl.gl_tex_parameteri)( gl_const::GL_TEXTURE_2D, gl_const::GL_TEXTURE_WRAP_T, gl_const::GL_CLAMP_TO_EDGE as i32, ); (gl.gl_tex_image_2d)( gl_const::GL_TEXTURE_2D, 0, gl_const::GL_RGBA as i32, w, h, 0, gl_const::GL_RGBA, gl_const::GL_UNSIGNED_BYTE, rgba.as_ptr() as *const std::ffi::c_void, ); } Some(CachedGlyph { tex, w, h, left, top, advance, }) } /// 获取(必要时光栅化并缓存)字符字形。 pub fn glyph(&mut self, gl: &Gl, ch: char) -> Option { if !self.glyphs.contains_key(&ch) { if let Some(g) = self.rasterize(gl, ch) { self.glyphs.insert(ch, g); } } self.glyphs.get(&ch).cloned() } }