use crate::geometry::Color;
use crate::painter::Painter;
pub struct Font {
inner: fontdue::Font,
}
impl Font {
pub fn load_system() -> Option<Self> {
const SANS_FAMILIES: &[&str] = &[
"MS Sans Serif",
"Microsoft Sans Serif",
"Tahoma",
"Segoe UI",
"Arial",
"Helvetica",
"Geneva",
"DejaVu Sans",
"Liberation Sans",
];
load_family_chain(SANS_FAMILIES, false)
}
pub fn load_monospace() -> Option<Self> {
const MONO_FAMILIES: &[&str] = &[
"Lucida Console",
"Consolas",
"Courier New",
"Courier",
"Liberation Mono",
"DejaVu Sans Mono",
"Menlo",
"Monaco",
];
load_family_chain(MONO_FAMILIES, true)
}
pub fn from_bytes(data: Vec<u8>) -> Option<Self> {
fontdue::Font::from_bytes(data, fontdue::FontSettings::default())
.ok()
.map(|inner| Self { inner })
}
pub fn measure(&self, text: &str, size: f32) -> (f32, f32) {
let mut width = 0.0_f32;
let mut height = 0.0_f32;
for ch in text.chars() {
let m = self.inner.metrics(ch, size);
width += m.advance_width;
height = height.max(m.height as f32);
}
(width, size * 1.2)
}
pub(crate) fn draw_phys(
&self,
painter: &mut Painter,
text: &str,
x: f32,
y: f32,
size_phys: f32,
color: Color,
) -> f32 {
let baseline = y + size_phys;
let mut pen_x = x;
for ch in text.chars() {
let (metrics, bitmap) = self.inner.rasterize(ch, size_phys);
let glyph_x = pen_x + metrics.xmin as f32;
let glyph_y = baseline - metrics.ymin as f32 - metrics.height as f32;
for row in 0..metrics.height {
let dy = glyph_y as i32 + row as i32;
for col in 0..metrics.width {
let alpha = bitmap[row * metrics.width + col];
if alpha == 0 {
continue;
}
let dx = glyph_x as i32 + col as i32;
painter.blend_pixel_phys(dx, dy, color, alpha);
}
}
pen_x += metrics.advance_width;
}
pen_x
}
}
fn load_face(db: &fontdb::Database, id: fontdb::ID) -> Option<fontdue::Font> {
let mut data: Option<Vec<u8>> = None;
db.with_face_data(id, |bytes, _| data = Some(bytes.to_vec()));
let data = data?;
fontdue::Font::from_bytes(data, fontdue::FontSettings::default()).ok()
}
fn load_family_chain(families: &[&str], monospace_fallback: bool) -> Option<Font> {
let mut db = fontdb::Database::new();
db.load_system_fonts();
if db.faces().next().is_none() {
db.load_fonts_dir("/usr/local/share/fonts");
}
for family in families {
let query = fontdb::Query {
families: &[fontdb::Family::Name(family)],
weight: fontdb::Weight::NORMAL,
stretch: fontdb::Stretch::Normal,
style: fontdb::Style::Normal,
};
if let Some(id) = db.query(&query)
&& let Some(font) = load_face(&db, id)
{
return Some(Font { inner: font });
}
}
if monospace_fallback {
for face in db.faces() {
if face.monospaced
&& let Some(font) = load_face(&db, face.id)
{
return Some(Font { inner: font });
}
}
}
for face in db.faces() {
if let Some(font) = load_face(&db, face.id) {
return Some(Font { inner: font });
}
}
None
}