use crate::fitz::error::{Error, Result};
use crate::fitz::geometry::{Matrix, Point, Rect};
use crate::fitz::path::Path;
use crate::fitz::pixmap::Pixmap;
use crate::fitz::render::Rasterizer;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GlyphId(pub u16);
impl GlyphId {
pub fn new(id: u16) -> Self {
Self(id)
}
pub fn value(&self) -> u16 {
self.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphMetrics {
pub advance_width: f32,
pub advance_height: f32,
pub lsb: f32,
pub tsb: f32,
pub bbox: Rect,
}
impl Default for GlyphMetrics {
fn default() -> Self {
Self {
advance_width: 1.0,
advance_height: 1.0,
lsb: 0.0,
tsb: 0.0,
bbox: Rect::new(0.0, 0.0, 1.0, 1.0),
}
}
}
#[derive(Debug, Clone)]
pub struct GlyphOutline {
pub gid: GlyphId,
pub path: Path,
pub metrics: GlyphMetrics,
}
impl GlyphOutline {
pub fn new(gid: GlyphId, path: Path, metrics: GlyphMetrics) -> Self {
Self { gid, path, metrics }
}
pub fn transform(&mut self, ctm: &Matrix) {
self.path.transform(|p| ctm.transform_point(p));
let p0 = ctm.transform_point(Point::new(0.0, 0.0));
let p1 = ctm.transform_point(Point::new(self.metrics.advance_width, 0.0));
self.metrics.advance_width = (p1.x - p0.x).abs();
let p2 = ctm.transform_point(Point::new(0.0, self.metrics.advance_height));
self.metrics.advance_height = (p2.y - p0.y).abs();
let bbox = self.metrics.bbox;
let corners = [
ctm.transform_point(Point::new(bbox.x0, bbox.y0)),
ctm.transform_point(Point::new(bbox.x1, bbox.y0)),
ctm.transform_point(Point::new(bbox.x1, bbox.y1)),
ctm.transform_point(Point::new(bbox.x0, bbox.y1)),
];
let min_x = corners.iter().map(|p| p.x).fold(f32::INFINITY, f32::min);
let min_y = corners.iter().map(|p| p.y).fold(f32::INFINITY, f32::min);
let max_x = corners
.iter()
.map(|p| p.x)
.fold(f32::NEG_INFINITY, f32::max);
let max_y = corners
.iter()
.map(|p| p.y)
.fold(f32::NEG_INFINITY, f32::max);
self.metrics.bbox = Rect::new(min_x, min_y, max_x, max_y);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct GlyphCacheKey {
gid: GlyphId,
size: u32, subpixel_x: u8, subpixel_y: u8, }
pub struct GlyphCache {
cache: Arc<Mutex<HashMap<GlyphCacheKey, Arc<Pixmap>>>>,
max_size: usize,
current_size: Arc<Mutex<usize>>,
}
impl GlyphCache {
pub fn new(max_size_mb: usize) -> Self {
Self {
cache: Arc::new(Mutex::new(HashMap::new())),
max_size: max_size_mb * 1024 * 1024,
current_size: Arc::new(Mutex::new(0)),
}
}
pub fn get(
&self,
gid: GlyphId,
size: f32,
subpixel_x: f32,
subpixel_y: f32,
) -> Option<Arc<Pixmap>> {
let key = GlyphCacheKey {
gid,
size: (size * 64.0) as u32,
subpixel_x: (subpixel_x * 64.0) as u8,
subpixel_y: (subpixel_y * 64.0) as u8,
};
self.cache.lock().unwrap().get(&key).cloned()
}
pub fn insert(
&self,
gid: GlyphId,
size: f32,
subpixel_x: f32,
subpixel_y: f32,
pixmap: Pixmap,
) {
let key = GlyphCacheKey {
gid,
size: (size * 64.0) as u32,
subpixel_x: (subpixel_x * 64.0) as u8,
subpixel_y: (subpixel_y * 64.0) as u8,
};
let pixmap_size = pixmap.samples().len();
let pixmap = Arc::new(pixmap);
let mut current = self.current_size.lock().unwrap();
if *current + pixmap_size > self.max_size {
self.clear();
*current = 0;
}
self.cache.lock().unwrap().insert(key, pixmap);
*current += pixmap_size;
}
pub fn clear(&self) {
self.cache.lock().unwrap().clear();
*self.current_size.lock().unwrap() = 0;
}
pub fn stats(&self) -> (usize, usize, usize) {
let cache = self.cache.lock().unwrap();
let size = *self.current_size.lock().unwrap();
(cache.len(), size, self.max_size)
}
}
impl Default for GlyphCache {
fn default() -> Self {
Self::new(16) }
}
pub struct GlyphRasterizer {
rasterizer: Rasterizer,
cache: GlyphCache,
}
impl GlyphRasterizer {
pub fn new() -> Self {
let clip = Rect::new(0.0, 0.0, 1024.0, 1024.0);
Self {
rasterizer: Rasterizer::new(1024, 1024, clip),
cache: GlyphCache::default(),
}
}
pub fn with_cache_size(cache_size_mb: usize) -> Self {
let clip = Rect::new(0.0, 0.0, 1024.0, 1024.0);
Self {
rasterizer: Rasterizer::new(1024, 1024, clip),
cache: GlyphCache::new(cache_size_mb),
}
}
pub fn rasterize_glyph(
&self,
outline: &GlyphOutline,
font_size: f32,
subpixel_x: f32,
subpixel_y: f32,
) -> Result<Pixmap> {
if let Some(pixmap) = self
.cache
.get(outline.gid, font_size, subpixel_x, subpixel_y)
{
return Ok((*pixmap).clone());
}
let scale = font_size / 1000.0; let ctm = Matrix::new(
scale, 0.0, 0.0, -scale, subpixel_x, subpixel_y,
);
let bbox = outline.metrics.bbox;
let transformed_bbox = bbox.transform(&ctm);
let width = (transformed_bbox.width().ceil() as i32).max(1);
let height = (transformed_bbox.height().ceil() as i32).max(1);
let mut pixmap = Pixmap::new(None, width, height, true)?;
let colorspace = crate::fitz::colorspace::Colorspace::device_gray();
let color = vec![1.0]; let alpha = 1.0;
self.rasterizer.fill_path(
&outline.path,
false, &ctm,
&colorspace,
&color,
alpha,
&mut pixmap,
);
self.cache.insert(
outline.gid,
font_size,
subpixel_x,
subpixel_y,
pixmap.clone(),
);
Ok(pixmap)
}
pub fn rasterize_glyphs(
&self,
outlines: &[&GlyphOutline],
font_size: f32,
) -> Result<Vec<Pixmap>> {
outlines
.iter()
.map(|outline| self.rasterize_glyph(outline, font_size, 0.0, 0.0))
.collect()
}
pub fn clear_cache(&self) {
self.cache.clear();
}
pub fn cache_stats(&self) -> (usize, usize, usize) {
self.cache.stats()
}
}
impl Default for GlyphRasterizer {
fn default() -> Self {
Self::new()
}
}
pub fn create_missing_glyph_outline(gid: GlyphId, advance: f32) -> GlyphOutline {
let mut path = Path::new();
let width = advance * 0.8;
let height = advance * 1.0;
let margin = advance * 0.1;
path.move_to(Point::new(margin, margin));
path.line_to(Point::new(width, margin));
path.line_to(Point::new(width, height));
path.line_to(Point::new(margin, height));
path.close();
let inner_margin = margin * 2.0;
path.move_to(Point::new(inner_margin, inner_margin));
path.line_to(Point::new(width - inner_margin, inner_margin));
path.line_to(Point::new(width - inner_margin, height - inner_margin));
path.line_to(Point::new(inner_margin, height - inner_margin));
path.close();
let metrics = GlyphMetrics {
advance_width: advance,
advance_height: height,
lsb: margin,
tsb: margin,
bbox: Rect::new(margin, margin, width, height),
};
GlyphOutline::new(gid, path, metrics)
}
pub struct TrueTypeLoader {
data: Vec<u8>,
units_per_em: u16,
num_glyphs: u16,
}
impl TrueTypeLoader {
pub fn new(data: Vec<u8>) -> Result<Self> {
if data.len() < 12 {
return Err(Error::Generic("Invalid TrueType font data".into()));
}
let face = ttf_parser::Face::parse(&data, 0)
.map_err(|e| Error::Generic(format!("Failed to parse TrueType font: {}", e)))?;
let units_per_em = face.units_per_em();
let num_glyphs = face.number_of_glyphs();
Ok(Self {
data,
units_per_em,
num_glyphs,
})
}
pub fn num_glyphs(&self) -> u16 {
self.num_glyphs
}
pub fn units_per_em(&self) -> u16 {
self.units_per_em
}
pub fn load_glyph(&self, gid: GlyphId) -> Result<GlyphOutline> {
let face = ttf_parser::Face::parse(&self.data, 0)
.map_err(|e| Error::Generic(format!("Failed to parse font: {}", e)))?;
let ttf_gid = ttf_parser::GlyphId(gid.value());
let mut path = Path::new();
struct OutlineBuilder<'a> {
path: &'a mut Path,
}
impl ttf_parser::OutlineBuilder for OutlineBuilder<'_> {
fn move_to(&mut self, x: f32, y: f32) {
self.path.move_to(Point::new(x, y));
}
fn line_to(&mut self, x: f32, y: f32) {
self.path.line_to(Point::new(x, y));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.path.quad_to(Point::new(x1, y1), Point::new(x, y));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.path.curve_to(
Point::new(x1, y1),
Point::new(x2, y2),
Point::new(x, y),
);
}
fn close(&mut self) {
self.path.close();
}
}
let mut builder = OutlineBuilder { path: &mut path };
let bbox_opt = face.outline_glyph(ttf_gid, &mut builder);
let metrics = self.glyph_metrics(gid)?;
let glyph_bbox = if let Some(bbox) = bbox_opt {
Rect::new(
bbox.x_min as f32,
bbox.y_min as f32,
bbox.x_max as f32,
bbox.y_max as f32,
)
} else {
metrics.bbox
};
Ok(GlyphOutline::new(
gid,
path,
GlyphMetrics {
bbox: glyph_bbox,
..metrics
},
))
}
pub fn glyph_metrics(&self, gid: GlyphId) -> Result<GlyphMetrics> {
let face = ttf_parser::Face::parse(&self.data, 0)
.map_err(|e| Error::Generic(format!("Failed to parse font: {}", e)))?;
let ttf_gid = ttf_parser::GlyphId(gid.value());
let upem = self.units_per_em as f32;
let advance_width = face
.glyph_hor_advance(ttf_gid)
.map(|a| a as f32)
.unwrap_or(upem);
let advance_height = face
.glyph_ver_advance(ttf_gid)
.map(|a| a as f32)
.unwrap_or(upem);
let lsb = face
.glyph_hor_side_bearing(ttf_gid)
.map(|b| b as f32)
.unwrap_or(0.0);
let tsb = face
.glyph_ver_side_bearing(ttf_gid)
.map(|b| b as f32)
.unwrap_or(0.0);
let bbox = face
.glyph_bounding_box(ttf_gid)
.map(|b| {
Rect::new(
b.x_min as f32,
b.y_min as f32,
b.x_max as f32,
b.y_max as f32,
)
})
.unwrap_or(Rect::new(0.0, 0.0, advance_width, upem));
Ok(GlyphMetrics {
advance_width,
advance_height,
lsb,
tsb,
bbox,
})
}
}
pub struct Type1Loader {
data: Vec<u8>,
}
impl Type1Loader {
pub fn new(data: Vec<u8>) -> Result<Self> {
if data.len() < 16 {
return Err(Error::Generic("Invalid Type1 font data".into()));
}
let header = String::from_utf8_lossy(&data[0..14.min(data.len())]);
if !header.starts_with("%!") {
return Err(Error::Generic("Invalid Type1 signature".into()));
}
Ok(Self { data })
}
pub fn load_glyph_by_name(&self, name: &str) -> Result<GlyphOutline> {
Err(Error::Unsupported(format!(
"Type1 charstring interpretation for glyph '{}' requires a PostScript interpreter \
which is not yet available; use TrueType/OpenType fonts instead",
name
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glyph_id() {
let gid = GlyphId::new(42);
assert_eq!(gid.value(), 42);
}
#[test]
fn test_glyph_metrics_default() {
let metrics = GlyphMetrics::default();
assert_eq!(metrics.advance_width, 1.0);
assert_eq!(metrics.lsb, 0.0);
}
#[test]
fn test_glyph_cache_creation() {
let cache = GlyphCache::new(16);
let (count, size, max) = cache.stats();
assert_eq!(count, 0);
assert_eq!(size, 0);
assert_eq!(max, 16 * 1024 * 1024);
}
#[test]
fn test_glyph_cache_insert_get() {
let cache = GlyphCache::new(16);
let pixmap = Pixmap::new(None, 10, 10, true).unwrap();
let gid = GlyphId::new(42);
cache.insert(gid, 12.0, 0.0, 0.0, pixmap);
let retrieved = cache.get(gid, 12.0, 0.0, 0.0);
assert!(retrieved.is_some());
let (count, _, _) = cache.stats();
assert_eq!(count, 1);
}
#[test]
fn test_glyph_cache_clear() {
let cache = GlyphCache::new(16);
let pixmap = Pixmap::new(None, 10, 10, true).unwrap();
cache.insert(GlyphId::new(1), 12.0, 0.0, 0.0, pixmap);
cache.clear();
let (count, size, _) = cache.stats();
assert_eq!(count, 0);
assert_eq!(size, 0);
}
#[test]
fn test_create_missing_glyph_outline() {
let gid = GlyphId::new(0);
let outline = create_missing_glyph_outline(gid, 500.0);
assert_eq!(outline.gid, gid);
assert_eq!(outline.metrics.advance_width, 500.0);
assert!(!outline.path.elements().is_empty());
}
#[test]
fn test_glyph_rasterizer_creation() {
let rasterizer = GlyphRasterizer::new();
let (count, _, _) = rasterizer.cache_stats();
assert_eq!(count, 0);
}
#[test]
fn test_glyph_rasterizer_with_cache_size() {
let rasterizer = GlyphRasterizer::with_cache_size(32);
let (_, _, max) = rasterizer.cache_stats();
assert_eq!(max, 32 * 1024 * 1024);
}
}