Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-renderer/src/font_config.rs
StratoSDK / crates / strato-renderer / src / font_config.rs
1use cosmic_text::{fontdb, FontSystem as CosmicFontSystem};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::Path;
5use sys_locale::get_locale;
6 
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct FontConfig {
9 pub safe_fonts: HashMap<String, Vec<String>>,
10 pub fallback_fonts: Vec<String>,
11}
12 
13impl Default for FontConfig {
14 fn default() -> Self {
15 let mut safe_fonts = HashMap::new();
16 
17 // Windows safe fonts
18 safe_fonts.insert(
19 "windows".to_string(),
20 vec![
21 "segoeui.ttf".to_string(),
22 "arial.ttf".to_string(),
23 "tahoma.ttf".to_string(),
24 "calibri.ttf".to_string(),
25 "verdana.ttf".to_string(),
26 ],
27 );
28 
29 // macOS safe fonts
30 safe_fonts.insert(
31 "macos".to_string(),
32 vec![
33 "SF-Pro-Display-Regular.otf".to_string(),
34 "HelveticaNeue.ttc".to_string(),
35 "Arial.ttf".to_string(),
36 "Helvetica.ttc".to_string(),
37 ],
38 );
39 
40 // Linux safe fonts
41 safe_fonts.insert(
42 "linux".to_string(),
43 vec![
44 "DejaVuSans.ttf".to_string(),
45 "LiberationSans-Regular.ttf".to_string(),
46 "NotoSans-Regular.ttf".to_string(),
47 "Ubuntu-R.ttf".to_string(),
48 ],
49 );
50 
51 Self {
52 safe_fonts,
53 fallback_fonts: vec![
54 "sans-serif".to_string(),
55 "Arial".to_string(),
56 "Helvetica".to_string(),
57 ],
58 }
59 }
60}
61 
62impl FontConfig {
63 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
64 let content = std::fs::read_to_string(path)?;
65 let config: FontConfig = serde_json::from_str(&content)?;
66 Ok(config)
67 }
68 
69 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
70 let content = serde_json::to_string_pretty(self)?;
71 std::fs::write(path, content)?;
72 Ok(())
73 }
74 
75 pub fn get_platform_fonts(&self) -> Vec<String> {
76 let platform = if cfg!(target_os = "windows") {
77 "windows"
78 } else if cfg!(target_os = "macos") {
79 "macos"
80 } else {
81 "linux"
82 };
83 
84 self.safe_fonts
85 .get(platform)
86 .cloned()
87 .unwrap_or_else(|| self.fallback_fonts.clone())
88 }
89}
90 
91/// Creates a safe font system that loads only specific fonts to avoid problematic system fonts
92pub fn create_safe_font_system() -> CosmicFontSystem {
93 let config = FontConfig::load_from_file("safe_fonts.json").unwrap_or_else(|_| {
94 log::warn!("Could not load safe_fonts.json, using default configuration");
95 FontConfig::default()
96 });
97 
98 let mut db = fontdb::Database::new();
99 
100 if cfg!(target_os = "windows") {
101 // Load only safe fonts on Windows to avoid problematic fonts like mstmc.ttf
102 let safe_fonts = config.get_platform_fonts();
103 let system_fonts_dir = std::env::var("WINDIR")
104 .map(|windir| format!("{}\\Fonts", windir))
105 .unwrap_or_else(|_| "C:\\Windows\\Fonts".to_string());
106 
107 for font_name in safe_fonts {
108 let font_path = format!("{}\\{}", system_fonts_dir, font_name);
109 if std::path::Path::new(&font_path).exists() {
110 db.load_font_file(&font_path).ok();
111 }
112 }
113 
114 // If no fonts were loaded, fall back to a minimal set
115 if db.faces().count() == 0 {
116 log::warn!("No safe fonts found, loading minimal fallback fonts");
117 db.load_font_file(format!("{}\\arial.ttf", system_fonts_dir))
118 .ok();
119 db.load_font_file(format!("{}\\tahoma.ttf", system_fonts_dir))
120 .ok();
121 }
122 } else {
123 // On macOS and Linux, load system fonts normally
124 db.load_system_fonts();
125 
126 // Explicitly load emoji font on macOS to ensure it's available
127 if cfg!(target_os = "macos") {
128 let emoji_path = "/System/Library/Fonts/Apple Color Emoji.ttc";
129 if std::path::Path::new(emoji_path).exists() {
130 db.load_font_file(emoji_path).ok();
131 }
132 }
133 }
134 
135 let locale = get_locale().unwrap_or_else(|| "en-US".to_string());
136 CosmicFontSystem::new_with_locale_and_db(locale, db)
137}
138