use ahash::AHashMap;
pub(crate) fn estimate_text_width(text: &str, font_size: f64) -> f64 {
let mut narrow_chars = 0;
let mut uppercase_chars = 0;
let mut other_chars = 0;
for c in text.chars() {
if matches!(
c,
'.' | ',' | ':' | ';' | '!' | 'i' | 'j' | 'l' | '-' | '|' | '1' | 't' | 'f' | 'r'
) {
narrow_chars += 1;
} else if c.is_ascii_uppercase() {
uppercase_chars += 1;
} else {
other_chars += 1;
}
}
(narrow_chars as f64 * 0.3 + uppercase_chars as f64 * 0.65 + other_chars as f64 * 0.55)
* font_size
}
#[cfg(feature = "pdf")]
use std::sync::{Arc, OnceLock};
#[cfg(feature = "pdf")]
static GLOBAL_FONT_DB: OnceLock<Arc<svg2pdf::usvg::fontdb::Database>> = OnceLock::new();
#[cfg(feature = "pdf")]
pub(crate) fn get_font_db() -> Arc<svg2pdf::usvg::fontdb::Database> {
GLOBAL_FONT_DB
.get_or_init(|| {
let mut fontdb = svg2pdf::usvg::fontdb::Database::new();
fontdb.load_system_fonts();
let default_font_data = include_bytes!("../../assets/fonts/Inter-Regular.ttf");
fontdb.load_font_data(default_font_data.to_vec());
fontdb.set_sans_serif_family("Inter");
Arc::new(fontdb)
})
.clone()
}
#[cfg(feature = "png")]
use ab_glyph::FontArc;
#[cfg(feature = "png")]
use std::sync::{OnceLock, RwLock};
#[cfg(feature = "png")]
static RASTER_FONT_REGISTRY: OnceLock<RwLock<AHashMap<String, FontArc>>> = OnceLock::new();
#[cfg(feature = "png")]
static SYSTEM_FONT_DB: OnceLock<fontdb::Database> = OnceLock::new();
#[cfg(feature = "png")]
fn get_system_font_db() -> &'static fontdb::Database {
SYSTEM_FONT_DB.get_or_init(|| {
let mut db = fontdb::Database::new();
db.load_system_fonts();
db
})
}
#[cfg(feature = "png")]
fn get_raster_registry() -> &'static RwLock<AHashMap<String, FontArc>> {
RASTER_FONT_REGISTRY.get_or_init(|| {
let mut map = AHashMap::new();
let default_font_data = include_bytes!("../../assets/fonts/Inter-Regular.ttf");
if let Ok(font) = FontArc::try_from_slice(default_font_data) {
map.insert("inter".to_string(), font.clone());
map.insert("sans-serif".to_string(), font);
} else {
eprintln!("Warning: Failed to load default Inter font for raster rendering.");
}
RwLock::new(map)
})
}
#[cfg(feature = "png")]
pub(crate) fn get_raster_font(family: &str) -> FontArc {
let registry = get_raster_registry();
{
let map = registry.read().expect("Failed to read font registry");
if let Some(font) = map.get(&family.to_lowercase()) {
return font.clone();
}
}
let mut map = registry.write().expect("Failed to write to font registry");
if let Some(font) = map.get(&family.to_lowercase()) {
return font.clone();
}
let sys_db = get_system_font_db();
let families = [fontdb::Family::Name(family)];
let query = fontdb::Query {
families: &families,
weight: fontdb::Weight::NORMAL,
stretch: fontdb::Stretch::Normal,
style: fontdb::Style::Normal,
};
if let Some(id) = sys_db.query(&query) {
if let Some(face_info) = sys_db.face(id) {
if let fontdb::Source::File(ref path) = face_info.source {
if let Ok(font_data) = std::fs::read(path) {
if let Ok(font) = FontArc::try_from_vec(font_data) {
map.insert(family.to_lowercase(), font.clone());
return font;
}
}
}
}
}
if let Some(font) = map.get("sans-serif") {
return font.clone();
}
panic!(
"No fonts available. Default 'Inter' font failed to load and '{}' not found in system.",
family
);
}
#[cfg(feature = "png")]
pub fn register_raster_font(name: &str, data: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
let font = FontArc::try_from_vec(data)?;
let registry = get_raster_registry();
let mut map = registry.write().expect("Failed to write to font registry");
map.insert(name.to_lowercase(), font);
Ok(())
}
#[cfg(feature = "parallel")]
use rayon::prelude::*;
pub trait IntoParallelizable {
type Item;
#[cfg(feature = "parallel")]
type Iter: ParallelIterator<Item = Self::Item>;
#[cfg(not(feature = "parallel"))]
type Iter: Iterator<Item = Self::Item>;
fn maybe_into_par_iter(self) -> Self::Iter;
}
pub trait Parallelizable {
type Item;
#[cfg(feature = "parallel")]
type Iter: ParallelIterator<Item = Self::Item>;
#[cfg(not(feature = "parallel"))]
type Iter: Iterator<Item = Self::Item>;
fn maybe_par_iter(self) -> Self::Iter;
}
impl<'a, T: Sync + Send + 'a> Parallelizable for &'a Vec<T> {
type Item = &'a T;
#[cfg(feature = "parallel")]
type Iter = rayon::slice::Iter<'a, T>;
#[cfg(not(feature = "parallel"))]
type Iter = std::slice::Iter<'a, T>;
fn maybe_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.par_iter()
}
#[cfg(not(feature = "parallel"))]
{
self.iter()
}
}
}
impl<'a, T: Sync + Send + 'a> Parallelizable for &'a [T] {
type Item = &'a T;
#[cfg(feature = "parallel")]
type Iter = rayon::slice::Iter<'a, T>;
#[cfg(not(feature = "parallel"))]
type Iter = std::slice::Iter<'a, T>;
fn maybe_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.par_iter()
}
#[cfg(not(feature = "parallel"))]
{
self.iter()
}
}
}
impl<'a, T: Sync + Send + 'a> Parallelizable for &'a mut Vec<T> {
type Item = &'a mut T;
#[cfg(feature = "parallel")]
type Iter = rayon::slice::IterMut<'a, T>;
#[cfg(not(feature = "parallel"))]
type Iter = std::slice::IterMut<'a, T>;
fn maybe_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.par_iter_mut()
}
#[cfg(not(feature = "parallel"))]
{
self.iter_mut()
}
}
}
impl<'a, K: Sync + Send + 'a, V: Sync + Send + 'a> Parallelizable for &'a AHashMap<K, V> {
type Item = (&'a K, &'a V);
#[cfg(feature = "parallel")]
type Iter = rayon::collections::hash_map::Iter<'a, K, V>;
#[cfg(not(feature = "parallel"))]
type Iter = std::collections::hash_map::Iter<'a, K, V>;
fn maybe_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.par_iter()
}
#[cfg(not(feature = "parallel"))]
{
self.iter()
}
}
}
impl<'a, K: Sync + Send + 'a, V: Sync + Send + 'a> Parallelizable for &'a mut AHashMap<K, V> {
type Item = (&'a K, &'a mut V);
#[cfg(feature = "parallel")]
type Iter = rayon::collections::hash_map::IterMut<'a, K, V>;
#[cfg(not(feature = "parallel"))]
type Iter = std::collections::hash_map::IterMut<'a, K, V>;
fn maybe_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.par_iter_mut()
}
#[cfg(not(feature = "parallel"))]
{
self.iter_mut()
}
}
}
impl<T: Send + Sync> IntoParallelizable for Vec<T> {
type Item = T;
#[cfg(feature = "parallel")]
type Iter = rayon::vec::IntoIter<T>;
#[cfg(not(feature = "parallel"))]
type Iter = std::vec::IntoIter<T>;
fn maybe_into_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.into_par_iter()
}
#[cfg(not(feature = "parallel"))]
{
self.into_iter()
}
}
}
impl IntoParallelizable for std::ops::Range<usize> {
type Item = usize;
#[cfg(feature = "parallel")]
type Iter = rayon::range::Iter<usize>;
#[cfg(not(feature = "parallel"))]
type Iter = std::ops::Range<usize>;
fn maybe_into_par_iter(self) -> Self::Iter {
#[cfg(feature = "parallel")]
{
self.into_par_iter()
}
#[cfg(not(feature = "parallel"))]
{
self.into_iter()
}
}
}