iris: make font families application-named strings

This commit is contained in:
iris committed 2026-09-12 13:09:31 -04:00
1 parent beaf24c75d
commit 7c23c5f146
4 files changed
+38 -67

No files matched your search

+31 -60
View File
@@ -1,7 +1,7 @@
use crate::{Align, GlyphAtlas, GlyphKey, PaintId, PlacedGlyph, RegionAlign, Textures, util::Vec2}; use crate::{Align, GlyphAtlas, GlyphKey, PaintId, PlacedGlyph, RegionAlign, Textures, util::Vec2};
use parley::{ use parley::{
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, FontStyle, FontWeight, Alignment, AlignmentOptions, FontContext, FontFamily, FontStyle, FontWeight, GenericFamily,
GenericFamily, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
fontique::{Blob, FontInfoOverride}, fontique::{Blob, FontInfoOverride},
}; };
use std::{collections::HashMap, fmt, ops::Range, sync::Arc}; use std::{collections::HashMap, fmt, ops::Range, sync::Arc};
@@ -24,19 +24,14 @@ pub struct FontDiagnostics {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum FontRegistrationError { pub enum FontRegistrationError {
UnsupportedFamily(Family), AlreadyRegistered(String),
AlreadyRegistered(Family),
TextAlreadyShaped, TextAlreadyShaped,
InvalidFont(Family), InvalidFont(String),
} }
impl fmt::Display for FontRegistrationError { impl fmt::Display for FontRegistrationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::UnsupportedFamily(family) => write!(
f,
"cannot register font data for {family:?}; use Family::Icons or Family::Named"
),
Self::AlreadyRegistered(family) => { Self::AlreadyRegistered(family) => {
write!(f, "font data is already registered for {family:?}") write!(f, "font data is already registered for {family:?}")
} }
@@ -72,7 +67,7 @@ pub struct TextData {
/// truth would mean carrying a `Painter` (or output size) into every /// truth would mean carrying a `Painter` (or output size) into every
/// input handler for the sake of one field. /// input handler for the sake of one field.
pub density: f32, pub density: f32,
registered_families: HashMap<Family, String>, registered_families: HashMap<String, String>,
next_registered_family: u64, next_registered_family: u64,
shaping_started: bool, shaping_started: bool,
} }
@@ -169,12 +164,10 @@ fn patch_android_monospace(_font_cx: &mut FontContext) {}
impl TextData { impl TextData {
pub(crate) fn register_font( pub(crate) fn register_font(
&mut self, &mut self,
family: Family, family: impl AsRef<str>,
data: impl AsRef<[u8]> + Send + Sync + 'static, data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), FontRegistrationError> { ) -> Result<(), FontRegistrationError> {
if !matches!(family, Family::Icons | Family::Named(_)) { let family = family.as_ref().to_owned();
return Err(FontRegistrationError::UnsupportedFamily(family));
}
if self.shaping_started { if self.shaping_started {
return Err(FontRegistrationError::TextAlreadyShaped); return Err(FontRegistrationError::TextAlreadyShaped);
} }
@@ -201,23 +194,18 @@ impl TextData {
Ok(()) Ok(())
} }
pub(crate) fn is_font_registered(&self, family: &Family) -> bool { pub(crate) fn is_font_registered(&self, family: impl AsRef<str>) -> bool {
self.registered_families.contains_key(family) self.registered_families.contains_key(family.as_ref())
} }
/// Cloned rather than borrowed because the caller needs it while the /// Cloned rather than borrowed because the caller needs it while the
/// layout builder holds `&mut self` -- a `String` per shaped registered /// layout builder holds `&mut self` -- a `String` per shaped registered
/// run, paid only when the layout is rebuilt. /// run, paid only when the layout is rebuilt.
pub fn resolve_family(&self, family: &Family) -> Family { pub fn resolve_family(&self, family: &str) -> String {
if let Some(name) = self.registered_families.get(family) { if let Some(name) = self.registered_families.get(family) {
return Family::Named(name.clone()); return name.clone();
}
match family {
Family::Icons => panic!(
"Family::Icons has no font; register application font data with Ui::register_font before the first draw"
),
_ => family.clone(),
} }
family.to_owned()
} }
pub fn font_diagnostics(&mut self) -> FontDiagnostics { pub fn font_diagnostics(&mut self) -> FontDiagnostics {
@@ -389,37 +377,15 @@ impl TextData {
} }
} }
/// Which family to ask for. Kept as an owned name rather than parley's pub const SANS_SERIF: &str = "sans-serif";
/// borrowed `FontFamily<'_>` so that a widget can hold one without a lifetime. pub const SERIF: &str = "serif";
#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub const MONOSPACE: &str = "monospace";
pub enum Family {
SansSerif,
Serif,
Monospace,
/// The icon font supplied by the application through
/// [`crate::Ui::register_font`].
Icons,
Named(String),
}
impl Family {
fn family(&self) -> FontFamily<'_> {
let name = match self {
Self::SansSerif => FontFamilyName::Generic(GenericFamily::SansSerif),
Self::Serif => FontFamilyName::Generic(GenericFamily::Serif),
Self::Monospace => FontFamilyName::Generic(GenericFamily::Monospace),
Self::Icons => FontFamilyName::Generic(GenericFamily::SansSerif),
Self::Named(name) => FontFamilyName::Named(name.as_str().into()),
};
FontFamily::Single(name)
}
}
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct SpanStyle { pub struct SpanStyle {
pub range: Range<usize>, pub range: Range<usize>,
pub color: Option<PaintId>, pub color: Option<PaintId>,
pub family: Option<Family>, pub family: Option<String>,
pub font_size: Option<f32>, pub font_size: Option<f32>,
pub bold: bool, pub bold: bool,
pub italic: bool, pub italic: bool,
@@ -442,8 +408,8 @@ impl SpanStyle {
self.color = Some(color); self.color = Some(color);
self self
} }
pub fn family(mut self, family: Family) -> Self { pub fn family(mut self, family: impl AsRef<str>) -> Self {
self.family = Some(family); self.family = Some(family.as_ref().to_owned());
self self
} }
pub fn font_size(mut self, size: f32) -> Self { pub fn font_size(mut self, size: f32) -> Self {
@@ -469,7 +435,7 @@ pub struct TextAttrs {
pub color: PaintId, pub color: PaintId,
pub font_size: f32, pub font_size: f32,
pub line_height: f32, pub line_height: f32,
pub family: Family, pub family: String,
pub wrap: bool, pub wrap: bool,
pub align: RegionAlign, pub align: RegionAlign,
} }
@@ -483,7 +449,7 @@ impl Default for TextAttrs {
color: PaintId::WHITE, color: PaintId::WHITE,
font_size: size, font_size: size,
line_height: size * LINE_HEIGHT_MULT, line_height: size * LINE_HEIGHT_MULT,
family: Family::SansSerif, family: SANS_SERIF.to_owned(),
wrap: false, wrap: false,
align: Align::CENTER_LEFT, align: Align::CENTER_LEFT,
} }
@@ -562,7 +528,7 @@ impl TextBuffer {
return; return;
} }
let base_family = data.resolve_family(&attrs.family); let base_family = data.resolve_family(&attrs.family);
let span_families: Vec<Option<Family>> = self let span_families: Vec<Option<String>> = self
.spans .spans
.iter() .iter()
.map(|span| span.family.as_ref().map(|f| data.resolve_family(f))) .map(|span| span.family.as_ref().map(|f| data.resolve_family(f)))
@@ -570,7 +536,9 @@ impl TextBuffer {
let mut builder = data let mut builder = data
.layout_cx .layout_cx
.ranged_builder(&mut data.font_cx, &self.text, 1.0, true); .ranged_builder(&mut data.font_cx, &self.text, 1.0, true);
builder.push_default(StyleProperty::FontFamily(base_family.family())); builder.push_default(StyleProperty::FontFamily(FontFamily::from(
base_family.as_str(),
)));
builder.push_default(StyleProperty::FontSize(attrs.font_size * density)); builder.push_default(StyleProperty::FontSize(attrs.font_size * density));
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute( builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
attrs.line_height * density, attrs.line_height * density,
@@ -582,7 +550,10 @@ impl TextBuffer {
builder.push(StyleProperty::Brush(color.clone()), range.clone()); builder.push(StyleProperty::Brush(color.clone()), range.clone());
} }
if let Some(family) = family { if let Some(family) = family {
builder.push(StyleProperty::FontFamily(family.family()), range.clone()); builder.push(
StyleProperty::FontFamily(FontFamily::from(family.as_str())),
range.clone(),
);
} }
if let Some(size) = span.font_size { if let Some(size) = span.font_size {
builder.push(StyleProperty::FontSize(size * density), range.clone()); builder.push(StyleProperty::FontSize(size * density), range.clone());
@@ -642,8 +613,8 @@ mod tests {
fn invalid_font_data_is_reported() { fn invalid_font_data_is_reported() {
let mut data = TextData::default(); let mut data = TextData::default();
assert_eq!( assert_eq!(
data.register_font(Family::Icons, b"not a font" as &'static [u8]), data.register_font("icons", b"not a font" as &'static [u8]),
Err(FontRegistrationError::InvalidFont(Family::Icons)) Err(FontRegistrationError::InvalidFont("icons".to_owned()))
); );
} }
@@ -653,7 +624,7 @@ mod tests {
let mut buffer = TextBuffer::new("ordinary platform text"); let mut buffer = TextBuffer::new("ordinary platform text");
buffer.shape(&mut data, &TextAttrs::default(), None, 1.0); buffer.shape(&mut data, &TextAttrs::default(), None, 1.0);
assert_eq!( assert_eq!(
data.register_font(Family::Icons, b"not a font" as &'static [u8]), data.register_font("icons", b"not a font" as &'static [u8]),
Err(FontRegistrationError::TextAlreadyShaped) Err(FontRegistrationError::TextAlreadyShaped)
); );
} }
+2 -2
View File
@@ -74,13 +74,13 @@ impl Ui {
#[track_caller] #[track_caller]
pub fn register_font( pub fn register_font(
&mut self, &mut self,
family: crate::Family, family: impl AsRef<str>,
data: impl AsRef<[u8]> + Send + Sync + 'static, data: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Result<(), crate::FontRegistrationError> { ) -> Result<(), crate::FontRegistrationError> {
self.data.text.register_font(family, data) self.data.text.register_font(family, data)
} }
pub fn is_font_registered(&self, family: &crate::Family) -> bool { pub fn is_font_registered(&self, family: impl AsRef<str>) -> bool {
self.data.text.is_font_registered(family) self.data.text.is_font_registered(family)
} }
+3 -3
View File
@@ -92,9 +92,9 @@ where
btext("hmm"), btext("hmm"),
btext("a"), btext("a"),
( (
btext("'").family(Family::Monospace).align(Align::TOP), btext("'").family(MONOSPACE).align(Align::TOP),
btext("'").family(Family::Monospace), btext("'").family(MONOSPACE),
btext(":gamer mode").family(Family::Monospace), btext(":gamer mode").family(MONOSPACE),
rect(PaintId::CYAN).sized((10, 10)).center(), rect(PaintId::CYAN).sized((10, 10)).center(),
rect(PaintId::RED).sized((100, 100)).center(), rect(PaintId::RED).sized((100, 100)).center(),
rect(PaintId::PURPLE).sized((50, 50)).align(Align::TOP), rect(PaintId::PURPLE).sized((50, 50)).align(Align::TOP),
+2 -2
View File
@@ -20,8 +20,8 @@ impl<State, O, H: WidgetOption<State>> TextBuilder<State, O, H> {
self.attrs.color = color; self.attrs.color = color;
self self
} }
pub fn family(mut self, family: Family) -> Self { pub fn family(mut self, family: impl AsRef<str>) -> Self {
self.attrs.family = family; self.attrs.family = family.as_ref().to_owned();
self self
} }
pub fn line_height(mut self, height: f32) -> Self { pub fn line_height(mut self, height: f32) -> Self {