Files
luma-home/src/font.rs
T
Kaloyan DanchevandClaude Opus 4.6 cbea5a322f
CI / Identify a merged version PR (push) Canceled after 0s
CI / Rust checks, rendering, and native package (push) Canceled after 0s
CI / Publish release and Homebrew feed (push) Canceled after 0s
Release PR / Maintain Version Packages PR (push) Canceled after 0s
Merge Luma launcher fork into monorepo
Move trampoline files (boot-hook, watcher, overlay) into trampoline/
subdirectory. Add upstream Luma v2.3.1 source at repo root with our
additions:

- Page::Ha (4th nav tab) with placeholder empty state
- HA tab config schema and default entity mapping (ha-home/)
- Nav keys renamed: "home" → "start", new "ha" tab between Apps and Settings
- All state.rs/view.rs/main.rs/tests.rs updated for 4-tab navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-24 11:52:55 +03:00

127 lines
3.9 KiB
Rust

//! Lazy TTF metrics and individual outlines; never preprocess an entire font.
use geometry::lyon_tessellation::{
FillRule,
math::point,
path::{Path, path::Builder},
};
use luma_vector_geometry::{self as geometry, Vertex};
use owned_ttf_parser::{AsFaceRef, GlyphId, OutlineBuilder, OwnedFace};
pub struct Font {
face: OwnedFace,
}
pub struct LineMetrics {
pub ascent: f32,
pub descent: f32,
}
pub struct Metrics {
pub advance_width: f32,
}
impl Font {
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, String> {
OwnedFace::from_vec(bytes, 0)
.map(|face| Self { face })
.map_err(|e| e.to_string())
}
pub fn lookup_glyph_index(&self, character: char) -> u16 {
self.face
.as_face_ref()
.glyph_index(character)
.map_or(0, |id| id.0)
}
pub fn horizontal_line_metrics(&self, size: f32) -> Option<LineMetrics> {
let face = self.face.as_face_ref();
let scale = size / f32::from(face.units_per_em());
Some(LineMetrics {
ascent: f32::from(face.ascender()) * scale,
descent: f32::from(face.descender()) * scale,
})
}
pub fn metrics(&self, character: char, size: f32) -> Metrics {
let face = self.face.as_face_ref();
let glyph = GlyphId(self.lookup_glyph_index(character));
Metrics {
advance_width: f32::from(face.glyph_hor_advance(glyph).unwrap_or(0)) * size
/ f32::from(face.units_per_em()),
}
}
pub fn horizontal_kern(&self, left: char, right: char, size: f32) -> Option<f32> {
let face = self.face.as_face_ref();
let left = face.glyph_index(left)?;
let right = face.glyph_index(right)?;
for table in face.tables().kern?.subtables {
if table.horizontal
&& !table.has_cross_stream
&& let Some(value) = table.glyphs_kerning(left, right)
{
return Some(f32::from(value) * size / f32::from(face.units_per_em()));
}
}
None
}
pub fn geometry(&self, character: char) -> Result<Vec<Vertex>, String> {
let face = self.face.as_face_ref();
let mut outline = GlyphOutline {
builder: Path::builder(),
scale: 1. / f32::from(face.units_per_em()),
open: false,
};
let glyph = GlyphId(self.lookup_glyph_index(character));
if face.outline_glyph(glyph, &mut outline).is_none() {
return Ok(Vec::new());
}
if outline.open {
outline.builder.end(true);
}
// Flatten at subpixel precision for glyphs up to 256 physical px/em,
// covering the current 1080p and 4K typography with the same geometry.
geometry::tessellate(&outline.builder.build(), 0.15 / 256., FillRule::NonZero)
}
}
struct GlyphOutline {
builder: Builder,
scale: f32,
open: bool,
}
impl OutlineBuilder for GlyphOutline {
fn move_to(&mut self, x: f32, y: f32) {
if self.open {
self.builder.end(true);
}
self.builder.begin(point(x * self.scale, -y * self.scale));
self.open = true;
}
fn line_to(&mut self, x: f32, y: f32) {
self.builder.line_to(point(x * self.scale, -y * self.scale));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.builder.quadratic_bezier_to(
point(x1 * self.scale, -y1 * self.scale),
point(x * self.scale, -y * self.scale),
);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.builder.cubic_bezier_to(
point(x1 * self.scale, -y1 * self.scale),
point(x2 * self.scale, -y2 * self.scale),
point(x * self.scale, -y * self.scale),
);
}
fn close(&mut self) {
if self.open {
self.builder.end(true);
self.open = false;
}
}
}