use crate::platform::{prelude::*, Arc, RwLock};
#[cfg(feature = "font-loading")]
use font_kit::{
family_name::FamilyName,
handle::Handle,
properties::{Properties, Stretch, Style, Weight},
source::SystemSource,
};
use hashbrown::HashMap;
use rustybuzz::{
ttf_parser::{GlyphId, OutlineBuilder},
Face, Feature, Tag, UnicodeBuffer, Variation,
};
use super::{
font::{TEXT_FONT, TIMER_FONT},
FontKind, PathBuilder, Rgba, SharedOwnership,
};
use crate::settings::{self, FontStretch, FontStyle, FontWeight};
use self::color_font::{iter_colored_glyphs, ColorTables};
mod color_font;
pub struct TextEngine {
#[cfg(feature = "font-loading")]
source: SystemSource,
buffer: Option<UnicodeBuffer>,
}
impl Default for TextEngine {
fn default() -> Self {
Self::new()
}
}
impl TextEngine {
pub fn new() -> Self {
Self {
#[cfg(feature = "font-loading")]
source: SystemSource::new(),
buffer: None,
}
}
pub fn create_font<P>(
&mut self,
#[allow(unused)] font: Option<&settings::Font>,
kind: FontKind,
) -> Font<P> {
#[cfg(feature = "font-loading")]
if let Some(font) = font {
if let Some(font) = Font::try_load_font(&mut self.source, font, kind) {
return font;
}
}
#[cfg(not(feature = "font-loading"))]
let _ = font;
let (font_data, style, weight, stretch) = match kind {
FontKind::Timer => (
TIMER_FONT,
FontStyle::Normal,
FontWeight::Bold,
FontStretch::Normal,
),
FontKind::Times => (
TEXT_FONT,
FontStyle::Normal,
FontWeight::Bold,
FontStretch::Normal,
),
FontKind::Text => (
TEXT_FONT,
FontStyle::Normal,
FontWeight::Normal,
FontStretch::Normal,
),
};
Font::from_slice(font_data, 0, style, weight, stretch, kind).unwrap()
}
pub fn create_label<PB: PathBuilder>(
&mut self,
path_builder: impl FnMut() -> PB,
text: &str,
font: &mut Font<PB::Path>,
max_width: Option<f32>,
) -> Label<PB::Path> {
let mut label = Arc::new(RwLock::new(LockedLabel {
width: 0.0,
width_without_max_width: 0.0,
scale: 0.0,
glyphs: Vec::new(),
}));
self.update_label(path_builder, &mut label, text, font, max_width);
label
}
pub fn update_label<PB: PathBuilder>(
&mut self,
mut path_builder: impl FnMut() -> PB,
label: &mut Label<PB::Path>,
text: &str,
font: &mut Font<PB::Path>,
max_width: Option<f32>,
) {
let mut label = label.write().unwrap();
let label = &mut *label;
let mut buffer = self.buffer.take().unwrap_or_else(UnicodeBuffer::new);
buffer.push_str(text);
let features = font
.monotonic
.as_ref()
.map(|m| &m.features[..])
.unwrap_or_default();
let buffer = rustybuzz::shape(&font.face, features, buffer);
let iter = Iterator::zip(buffer.glyph_infos().iter(), buffer.glyph_positions().iter());
let (mut x, mut y) = (0.0, 0.0);
label.glyphs.clear();
if let Some(monotonic) = &font.monotonic {
iter.for_each(|(info, pos)| {
let glyph = GlyphId(info.glyph_id as _);
let layer_glyphs = font.glyph_cache.entry(glyph).or_insert_with(|| {
let mut glyphs = Vec::new();
iter_colored_glyphs(&font.color_tables, 0, glyph, |glyph, color| {
let mut builder = GlyphBuilder(path_builder());
font.face.outline_glyph(glyph, &mut builder);
let path = builder.0.finish();
glyphs.push((color.map(|c| c.to_array()), path));
});
glyphs
});
let (x_advance, x_offset) = if monotonic.digit_glyphs.contains(&glyph) {
(
monotonic.digit_width,
0.5 * (monotonic.digit_width - pos.x_advance as f32) + pos.x_offset as f32,
)
} else {
(pos.x_advance as f32, pos.x_offset as f32)
};
let (glyph_x, glyph_y) = (x + x_offset, y + pos.y_offset as f32);
x += x_advance;
y += pos.y_advance as f32;
label
.glyphs
.extend(layer_glyphs.iter().map(|(color, path)| Glyph {
color: *color,
x: glyph_x,
y: glyph_y,
path: path.share(),
}));
});
} else {
iter.for_each(|(info, pos)| {
let glyph = GlyphId(info.glyph_id as _);
let layer_glyphs = font.glyph_cache.entry(glyph).or_insert_with(|| {
let mut glyphs = Vec::new();
iter_colored_glyphs(&font.color_tables, 0, glyph, |glyph, color| {
let mut builder = GlyphBuilder(path_builder());
font.face.outline_glyph(glyph, &mut builder);
let path = builder.0.finish();
glyphs.push((color.map(|c| c.to_array()), path));
});
glyphs
});
let (glyph_x, glyph_y) = (x + pos.x_offset as f32, y + pos.y_offset as f32);
x += pos.x_advance as f32;
y += pos.y_advance as f32;
label
.glyphs
.extend(layer_glyphs.iter().map(|(color, path)| Glyph {
color: *color,
x: glyph_x,
y: glyph_y,
path: path.share(),
}));
});
};
label.width_without_max_width = x * font.scale_factor;
if let Some(max_width) = max_width {
let max_width = max_width / font.scale_factor;
if x > max_width {
let (ellipsis, ellipsis_width) = font.ellipsis;
let x_to_look_for = max_width - ellipsis_width;
let last_index = label
.glyphs
.iter()
.enumerate()
.rfind(|(_, g)| {
x = g.x;
y = g.y;
g.x <= x_to_look_for
})
.map(|(i, _)| i)
.unwrap_or_default();
label.glyphs.drain(last_index..);
let layer_glyphs = font.glyph_cache.entry(ellipsis).or_insert_with(|| {
let mut glyphs = Vec::new();
iter_colored_glyphs(&font.color_tables, 0, ellipsis, |glyph, color| {
let mut builder = GlyphBuilder(path_builder());
font.face.outline_glyph(glyph, &mut builder);
let path = builder.0.finish();
glyphs.push((color.map(|c| c.to_array()), path));
});
glyphs
});
label
.glyphs
.extend(layer_glyphs.iter().map(|(color, path)| Glyph {
color: *color,
x,
y,
path: path.share(),
}));
x += ellipsis_width;
}
}
self.buffer = Some(buffer.clear());
label.width = x * font.scale_factor;
label.scale = font.scale_factor;
}
}
struct MonotonicInfo {
digit_glyphs: [GlyphId; 10],
digit_width: f32,
features: [Feature; 1],
}
pub struct Font<P> {
face: Face<'static>,
color_tables: Option<ColorTables<'static>>,
scale_factor: f32,
monotonic: Option<MonotonicInfo>,
ellipsis: (GlyphId, f32),
glyph_cache: HashMap<GlyphId, Vec<(Option<Rgba>, P)>>,
#[cfg(feature = "font-loading")]
_buf: Option<Box<[u8]>>,
}
impl<P> Font<P> {
#[cfg(feature = "font-loading")]
fn try_load_font(
source: &mut SystemSource,
font: &settings::Font,
kind: FontKind,
) -> Option<Self> {
let handle = source
.select_best_match(
&[FamilyName::Title(font.family.clone())],
&Properties {
style: match font.style {
FontStyle::Normal => Style::Normal,
FontStyle::Italic => Style::Italic,
},
weight: Weight(font.weight.value()),
stretch: Stretch(font.stretch.factor()),
},
)
.ok()?;
let (buf, font_index) = match handle {
Handle::Path { path, font_index } => (std::fs::read(path).ok()?, font_index),
Handle::Memory { bytes, font_index } => (
Arc::try_unwrap(bytes).unwrap_or_else(|bytes| (*bytes).clone()),
font_index,
),
};
let buf = buf.into_boxed_slice();
unsafe {
let slice: *const [u8] = &*buf;
let mut font = Font::from_slice(
&*slice,
font_index,
font.style,
font.weight,
font.stretch,
kind,
)?;
font._buf = Some(buf);
Some(font)
}
}
fn from_slice(
data: &'static [u8],
index: u32,
style: FontStyle,
weight: FontWeight,
stretch: FontStretch,
kind: FontKind,
) -> Option<Self> {
let mut face = Face::from_slice(data, index)?;
let italic = style.value_for_italic();
let weight = weight.value();
let stretch = stretch.percentage();
face.set_variations(&[
Variation {
tag: Tag::from_bytes(b"ital"),
value: italic,
},
Variation {
tag: Tag::from_bytes(b"wght"),
value: weight,
},
Variation {
tag: Tag::from_bytes(b"wdth"),
value: stretch,
},
]);
let monotonic = kind.is_monospaced().then(|| {
let mut digit_glyphs = [GlyphId(0); 10];
let mut digit_width = 0;
for (digit, glyph) in digit_glyphs.iter_mut().enumerate() {
let (glyph_id, width) =
glyph_width(&face, char::from(digit as u8 + b'0')).unwrap_or_default();
*glyph = glyph_id;
if width > digit_width {
digit_width = width;
}
}
MonotonicInfo {
digit_glyphs,
digit_width: digit_width as f32,
features: [
Feature::new(Tag::from_bytes(b"tnum"), 1, ..),
],
}
});
let (ellipsis, ellipsis_width) = glyph_width(&face, '…').unwrap_or_default();
Some(Self {
scale_factor: 1.0 / face.height() as f32,
color_tables: ColorTables::new(&face),
face,
#[cfg(feature = "font-loading")]
_buf: None,
monotonic,
ellipsis: (ellipsis, ellipsis_width as f32),
glyph_cache: HashMap::new(),
})
}
}
struct GlyphBuilder<PB>(PB);
impl<PB: PathBuilder> OutlineBuilder for GlyphBuilder<PB> {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to(x, -y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to(x, -y);
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.0.quad_to(x1, -y1, x, -y);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.0.curve_to(x1, -y1, x2, -y2, x, -y);
}
fn close(&mut self) {
self.0.close();
}
}
pub type Label<P> = Arc<RwLock<LockedLabel<P>>>;
pub struct LockedLabel<P> {
width: f32,
width_without_max_width: f32,
scale: f32,
glyphs: Vec<Glyph<P>>,
}
impl<P> LockedLabel<P> {
pub const fn scale(&self) -> f32 {
self.scale
}
pub fn glyphs(&self) -> &[Glyph<P>] {
&self.glyphs
}
}
impl<P> super::Label for Label<P> {
fn width(&self, scale: f32) -> f32 {
self.read().unwrap().width * scale
}
fn width_without_max_width(&self, scale: f32) -> f32 {
self.read().unwrap().width_without_max_width * scale
}
}
pub struct Glyph<P> {
pub color: Option<Rgba>,
pub x: f32,
pub y: f32,
pub path: P,
}
fn glyph_width(face: &Face<'_>, c: char) -> Option<(GlyphId, u16)> {
let glyph_id = face.glyph_index(c)?;
Some((glyph_id, face.glyph_hor_advance(glyph_id)?))
}