Skip to content

Instantly share code, notes, and snippets.

@airstrike
Created January 31, 2025 02:43
Show Gist options
  • Save airstrike/be012bfb5fd4f80ac6d837531ca94a40 to your computer and use it in GitHub Desktop.
Save airstrike/be012bfb5fd4f80ac6d837531ca94a40 to your computer and use it in GitHub Desktop.
iced font sampler
use iced::Alignment::Center;
use iced::Length::Fill;
use iced::font::{self, Font};
use iced::widget::{column, horizontal_rule, pick_list, row, scrollable, text, text_input};
use iced::{Element, Size, Task};
use std::collections::HashMap;
// Font candidates with their names and possible paths
const FONT_CANDIDATES: &[(&str, &[&str])] = &[
("Menlo", &[
"/System/Library/Fonts/Menlo.ttc",
"/Library/Fonts/Menlo.ttc",
]),
("JuliaMono", &[
"/Library/Fonts/JuliaMono-Regular.ttf",
"/Library/Fonts/JuliaMono-RegularLatin.ttf",
]),
("Cascadia Code", &[
"/Library/Fonts/CascadiaCode.ttf",
"/Library/Fonts/CascadiaCodePL.ttf",
]),
("Monaco", &["/System/Library/Fonts/Monaco.ttf"]),
("Courier", &["/System/Library/Fonts/Courier.ttc"]),
];
#[derive(Debug, Clone)]
enum Message {
Input(String),
FontLoaded(&'static str, Result<(), font::Error>),
FontSelected(FontFamily),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum FontFamily {
DefaultMonospace,
Custom(&'static str),
}
impl FontFamily {
fn to_font(&self) -> Font {
match self {
FontFamily::DefaultMonospace => Font::MONOSPACE,
FontFamily::Custom(name) => Font::with_name(name),
}
}
}
impl std::fmt::Display for FontFamily {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FontFamily::DefaultMonospace => write!(f, "System Monospace"),
FontFamily::Custom(name) => write!(f, "{}", name),
}
}
}
struct App {
input: String,
selected_font: FontFamily,
available_fonts: Vec<FontFamily>,
font_load_status: HashMap<&'static str, bool>,
}
impl App {
fn new() -> (Self, Task<Message>) {
let mut app = Self {
input: "Centered. Amazingly few discotheques provide jukeboxes.".to_string(),
selected_font: FontFamily::DefaultMonospace,
available_fonts: vec![FontFamily::DefaultMonospace],
font_load_status: HashMap::new(),
};
// Create tasks for loading each font
let mut font_tasks = Vec::new();
if let Ok(home) = std::env::var("HOME") {
for &(name, paths) in FONT_CANDIDATES {
// Try user's font directory first
let user_path = format!(
"{}/Library/Fonts/{}",
home,
paths[0].split('/').last().unwrap_or_default()
);
if let Ok(bytes) = std::fs::read(&user_path) {
font_tasks.push(
font::load(bytes).map(move |result| Message::FontLoaded(name, result)),
);
continue;
}
// Try system paths if user path failed
for &path in paths {
if let Ok(bytes) = std::fs::read(path) {
font_tasks.push(
font::load(bytes).map(move |result| Message::FontLoaded(name, result)),
);
break;
}
}
}
}
// Initialize font status as pending
for &(name, _) in FONT_CANDIDATES {
app.font_load_status.insert(name, false);
}
(app, Task::batch(font_tasks))
}
fn theme(&self) -> iced::Theme {
iced::Theme::Light
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Input(value) => {
self.input = value;
}
Message::FontLoaded(name, Ok(())) => {
println!("Font loaded successfully: {}", name);
self.font_load_status.insert(name, true);
self.available_fonts.push(FontFamily::Custom(name));
}
Message::FontLoaded(name, Err(e)) => {
eprintln!("Failed to load font {} from paths:", name);
if let Some(paths) = FONT_CANDIDATES
.iter()
.find(|(n, _)| *n == name)
.map(|(_, p)| p)
{
for path in *paths {
eprintln!(
" {}: {}",
path,
if std::path::Path::new(path).exists() {
"exists"
} else {
"not found"
}
);
}
}
eprintln!("Error: {:?}", e);
self.font_load_status.insert(name, false);
}
Message::FontSelected(font) => {
self.selected_font = font;
}
}
Task::none()
}
fn view(&self) -> Element<Message> {
let samples = (8..=24)
.map(|size| {
column![
row![
text(format!("Size: {}", size)).size(16).width(60),
text(&self.input)
.size(size)
.font(self.selected_font.to_font())
]
.height(30)
.align_y(Center)
.spacing(5),
horizontal_rule(1)
]
.into()
})
.collect::<Vec<_>>();
let font_picker = pick_list(
&self.available_fonts[..],
Some(self.selected_font),
Message::FontSelected,
)
.width(Fill);
let status_text = self
.font_load_status
.iter()
.filter(|&(_, &loaded)| loaded)
.count()
+ 1;
column![
row![
text(format!(
"Loaded Fonts ({}/{}): ",
status_text,
FONT_CANDIDATES.len() + 1
))
.width(Fill),
font_picker,
]
.align_y(Center),
text_input("", &self.input)
.on_input(Message::Input)
.padding(5),
scrollable(column(samples).spacing(5)),
]
.padding(20)
.spacing(20)
.into()
}
}
fn main() -> iced::Result {
iced::application("iced • font sampler", App::update, App::view)
.theme(App::theme)
.window_size(Size::new(900.0, 700.0))
.centered()
.run_with(App::new)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment