use ahash::{AHashMap, AHasher};
use cosmic_text::{
Attrs, Buffer, CacheKey, Family, FontSystem, Metrics, Shaping, SwashCache, SwashContent,
};
use once_cell::sync::OnceCell;
use std::sync::atomic::{AtomicU64, Ordering};
use std::{
collections::{HashMap, VecDeque},
hash::{Hash, Hasher},
sync::Mutex,
};
use unicode_segmentation::UnicodeSegmentation;
static FRAME_COUNTER: AtomicU64 = AtomicU64::new(0);
pub fn begin_frame() {
FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
}
pub fn current_frame() -> u64 {
FRAME_COUNTER.load(Ordering::Relaxed)
}
const WRAP_CACHE_CAP: usize = 1024;
const ELLIP_CACHE_CAP: usize = 2048;
static METRICS_LRU: OnceCell<Mutex<Lru<(u64, u32, u64), TextMetrics>>> = OnceCell::new();
fn metrics_cache() -> &'static Mutex<Lru<(u64, u32, u64), TextMetrics>> {
METRICS_LRU.get_or_init(|| Mutex::new(Lru::new(4096)))
}
struct Lru<K, V> {
map: AHashMap<K, V>,
order: VecDeque<K>,
cap: usize,
}
impl<K: std::hash::Hash + Eq + Clone, V> Lru<K, V> {
fn new(cap: usize) -> Self {
Self {
map: AHashMap::new(),
order: VecDeque::new(),
cap,
}
}
fn get(&mut self, k: &K) -> Option<&V> {
if self.map.contains_key(k) {
if let Some(pos) = self.order.iter().position(|x| x == k) {
let key = self.order.remove(pos).unwrap();
self.order.push_back(key);
}
}
self.map.get(k)
}
fn put(&mut self, k: K, v: V) {
if self.map.contains_key(&k) {
self.map.insert(k.clone(), v);
if let Some(pos) = self.order.iter().position(|x| x == &k) {
let key = self.order.remove(pos).unwrap();
self.order.push_back(key);
}
return;
}
if self.map.len() >= self.cap
&& let Some(old) = self.order.pop_front()
{
self.map.remove(&old);
}
self.order.push_back(k.clone());
self.map.insert(k, v);
}
}
static WRAP_LRU: OnceCell<Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<String>, bool)>>> =
OnceCell::new();
static WRAP_RANGES_LRU: OnceCell<
Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<(usize, usize)>, bool)>>,
> = OnceCell::new();
static ELLIP_LRU: OnceCell<Mutex<Lru<(u64, u32, u32), String>>> = OnceCell::new();
fn wrap_cache() -> &'static Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<String>, bool)>> {
WRAP_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
}
fn wrap_ranges_cache()
-> &'static Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<(usize, usize)>, bool)>> {
WRAP_RANGES_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
}
fn ellip_cache() -> &'static Mutex<Lru<(u64, u32, u32), String>> {
ELLIP_LRU.get_or_init(|| Mutex::new(Lru::new(ELLIP_CACHE_CAP)))
}
fn fast_hash(s: &str) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = AHasher::default();
s.hash(&mut h);
h.finish()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct GlyphKey(pub u64);
pub struct ShapedGlyph {
pub key: GlyphKey,
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
pub bearing_x: f32,
pub bearing_y: f32,
pub advance: f32,
}
pub struct GlyphBitmap {
pub key: GlyphKey,
pub w: u32,
pub h: u32,
pub content: SwashContent,
pub data: Vec<u8>, }
struct Engine {
fs: FontSystem,
cache: SwashCache,
key_map: HashMap<GlyphKey, CacheKey>,
}
impl Engine {
fn get_image(&mut self, key: CacheKey) -> Option<cosmic_text::SwashImage> {
self.cache.get_image(&mut self.fs, key).clone()
}
}
static ENGINE: OnceCell<Mutex<Engine>> = OnceCell::new();
fn engine() -> &'static Mutex<Engine> {
ENGINE.get_or_init(|| {
#[allow(unused_mut)]
let mut fs = FontSystem::new();
let cache = SwashCache::new();
{
static FALLBACK_TTF: &[u8] = include_bytes!("assets/OpenSans-Regular.ttf"); static FALLBACK_EMOJI_TTF: &[u8] = include_bytes!("assets/NotoColorEmoji-Regular.ttf"); static FALLBACK_SYMBOLS_TTF: &[u8] =
include_bytes!("assets/NotoSansSymbols2-Regular.ttf"); static MATERIAL_SYMBOLS_TTF: &[u8] =
include_bytes!("assets/MaterialSymbolsOutlined.ttf"); {
let db = fs.db_mut();
db.load_font_data(FALLBACK_TTF.to_vec());
db.set_sans_serif_family("Open Sans".to_string());
db.load_font_data(FALLBACK_SYMBOLS_TTF.to_vec());
db.load_font_data(FALLBACK_EMOJI_TTF.to_vec());
db.load_font_data(MATERIAL_SYMBOLS_TTF.to_vec());
}
}
Mutex::new(Engine {
fs,
cache,
key_map: HashMap::new(),
})
})
}
pub fn register_font_data(bytes: &'static [u8]) {
let mut eng = engine().lock().unwrap();
eng.fs.db_mut().load_font_data(bytes.to_vec());
}
fn key_from_cachekey(k: &CacheKey) -> GlyphKey {
let mut h = AHasher::default();
k.hash(&mut h);
GlyphKey(h.finish())
}
pub fn shape_line(text: &str, px: f32, font_family: Option<&str>) -> Vec<ShapedGlyph> {
let mut eng = engine().lock().unwrap();
let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
{
let mut b = buf.borrow_with(&mut eng.fs);
b.set_size(None, None);
let attrs = match font_family {
Some(family) => Attrs::new().family(Family::Name(family)),
None => Attrs::new(),
};
b.set_text(text, &attrs, Shaping::Advanced, None);
b.shape_until_scroll(true);
}
let mut out = Vec::new();
for run in buf.layout_runs() {
for g in run.glyphs {
let phys = g.physical((0.0, run.line_y), 1.0);
let key = key_from_cachekey(&phys.cache_key);
eng.key_map.insert(key, phys.cache_key);
let img_opt = eng.get_image(phys.cache_key);
let (w, h, left, top) = if let Some(img) = img_opt.as_ref() {
(
img.placement.width as f32,
img.placement.height as f32,
img.placement.left as f32,
img.placement.top as f32,
)
} else {
(0.0, 0.0, 0.0, 0.0)
};
out.push(ShapedGlyph {
key,
x: g.x + g.x_offset, y: run.line_y, w,
h,
bearing_x: left,
bearing_y: top,
advance: g.w,
});
}
}
out
}
pub fn rasterize(key: GlyphKey, _px: f32) -> Option<GlyphBitmap> {
let mut eng = engine().lock().unwrap();
let &ck = eng.key_map.get(&key)?;
let img = eng.get_image(ck).as_ref()?.clone();
Some(GlyphBitmap {
key,
w: img.placement.width,
h: img.placement.height,
content: img.content,
data: img.data, })
}
#[derive(Clone)]
pub struct TextMetrics {
pub positions: Vec<f32>, pub byte_offsets: Vec<usize>, }
pub fn metrics_for_textfield(text: &str, px: f32, font_family: Option<&str>) -> TextMetrics {
let family_hash = font_family.map(fast_hash).unwrap_or(0);
let key = (fast_hash(text), (px * 100.0) as u32, family_hash);
if let Some(m) = metrics_cache().lock().unwrap().get(&key).cloned() {
return m;
}
let mut eng = engine().lock().unwrap();
let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
{
let mut b = buf.borrow_with(&mut eng.fs);
b.set_size(None, None);
let attrs = match font_family {
Some(family) => Attrs::new().family(Family::Name(family)),
None => Attrs::new(),
};
b.set_text(text, &attrs, Shaping::Advanced, None);
b.shape_until_scroll(true);
}
let mut edges: Vec<(usize, f32)> = Vec::new();
let mut last_x = 0.0f32;
for run in buf.layout_runs() {
for g in run.glyphs {
let right = g.x + g.w;
last_x = right.max(last_x);
edges.push((g.end, right));
}
}
if edges.last().map(|e| e.0) != Some(text.len()) {
edges.push((text.len(), last_x));
}
let mut positions = Vec::with_capacity(text.graphemes(true).count() + 1);
let mut byte_offsets = Vec::with_capacity(positions.capacity());
positions.push(0.0);
byte_offsets.push(0);
let mut last_byte = 0usize;
for (b, _) in text.grapheme_indices(true) {
positions
.push(positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, b));
byte_offsets.push(b);
last_byte = b;
}
if *byte_offsets.last().unwrap_or(&0) != text.len() {
positions.push(
positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, text.len()),
);
byte_offsets.push(text.len());
}
let m = TextMetrics {
positions,
byte_offsets,
};
metrics_cache().lock().unwrap().put(key, m.clone());
m
}
fn width_between(edges: &[(usize, f32)], start_b: usize, end_b: usize) -> f32 {
let x0 = lookup_right(edges, start_b);
let x1 = lookup_right(edges, end_b);
(x1 - x0).max(0.0)
}
fn lookup_right(edges: &[(usize, f32)], b: usize) -> f32 {
match edges.binary_search_by_key(&b, |e| e.0) {
Ok(i) => edges[i].1,
Err(i) => {
if i == 0 {
0.0
} else {
edges[i - 1].1
}
}
}
}
pub fn wrap_lines(
text: &str,
px: f32,
max_width: f32,
max_lines: Option<usize>,
soft_wrap: bool,
) -> (Vec<String>, bool) {
if text.is_empty() || max_width <= 0.0 {
return (vec![String::new()], false);
}
if !soft_wrap {
return (vec![text.to_string()], false);
}
let max_lines_key: u16 = match max_lines {
None => 0,
Some(n) => {
let n = n.min(u16::MAX as usize - 1) as u16;
n.saturating_add(1)
}
};
let key = (
fast_hash(text),
(px * 100.0) as u32,
(max_width * 100.0) as u32,
max_lines_key,
soft_wrap,
);
if let Some(h) = wrap_cache().lock().unwrap().get(&key).cloned() {
return h;
}
let m = metrics_for_textfield(text, px, None);
if let Some(&last) = m.positions.last()
&& last <= max_width + 0.5
{
return (vec![text.to_string()], false);
}
let width_of = |start_b: usize, end_b: usize| -> f32 {
let i0 = match m.byte_offsets.binary_search(&start_b) {
Ok(i) | Err(i) => i,
};
let i1 = match m.byte_offsets.binary_search(&end_b) {
Ok(i) | Err(i) => i,
};
(m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
.max(0.0)
};
let mut out: Vec<String> = Vec::new();
let mut truncated = false;
let mut line_start = 0usize; let mut best_break = line_start;
for tok in text.split_word_bounds() {
let tok_start = best_break;
let tok_end = tok_start + tok.len();
let w = width_of(line_start, tok_end);
if w <= max_width + 0.5 {
best_break = tok_end;
continue;
}
if best_break > line_start {
out.push(text[line_start..best_break].trim_end().to_string());
line_start = best_break;
} else {
let mut cut = tok_start;
for g in tok.grapheme_indices(true) {
let next = tok_start + g.0 + g.1.len();
if width_of(line_start, next) <= max_width + 0.5 {
cut = next;
} else {
break;
}
}
if cut == line_start {
if let Some((ofs, grapheme)) = tok.grapheme_indices(true).next() {
cut = tok_start + ofs + grapheme.len();
}
}
out.push(text[line_start..cut].to_string());
line_start = cut;
}
if let Some(ml) = max_lines
&& out.len() >= ml
{
truncated = true;
line_start = line_start.min(text.len());
break;
}
best_break = line_start;
if line_start < tok_end {
if width_of(line_start, tok_end) <= max_width + 0.5 {
best_break = tok_end;
} else {
}
}
}
if line_start < text.len() && max_lines.is_none_or(|ml| out.len() < ml) {
out.push(text[line_start..].trim_end().to_string());
}
let res = (out, truncated);
wrap_cache().lock().unwrap().put(key, res.clone());
res
}
pub fn wrap_line_ranges(
text: &str,
px: f32,
max_width: f32,
max_lines: Option<usize>,
soft_wrap: bool,
) -> (Vec<(usize, usize)>, bool) {
if text.is_empty() || max_width <= 0.0 {
return (vec![(0, 0)], false);
}
if !soft_wrap {
let mut out = Vec::new();
let mut start = 0usize;
for (i, ch) in text.char_indices() {
if ch == '\n' {
out.push((start, i));
start = i + 1;
}
}
out.push((start, text.len()));
return (out, false);
}
let max_lines_key: u16 = match max_lines {
None => 0,
Some(n) => {
let n = n.min(u16::MAX as usize - 1) as u16;
n.saturating_add(1)
}
};
let key = (
fast_hash(text),
(px * 100.0) as u32,
(max_width * 100.0) as u32,
max_lines_key,
soft_wrap,
);
if let Some(v) = wrap_ranges_cache().lock().unwrap().get(&key).cloned() {
return v;
}
let m = metrics_for_textfield(text, px, None);
let width_of = |start_b: usize, end_b: usize| -> f32 {
let i0 = match m.byte_offsets.binary_search(&start_b) {
Ok(i) | Err(i) => i,
};
let i1 = match m.byte_offsets.binary_search(&end_b) {
Ok(i) | Err(i) => i,
};
(m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
.max(0.0)
};
let mut out: Vec<(usize, usize)> = Vec::new();
let mut truncated = false;
let mut line0_start = 0usize;
for (i, ch) in text.char_indices() {
if ch == '\n' {
let (mut ranges, tr) = wrap_one_hard_line_ranges(
text,
line0_start,
i,
max_width,
max_lines.map(|ml| ml.saturating_sub(out.len())),
&width_of,
);
out.append(&mut ranges);
if tr {
truncated = true;
break;
}
line0_start = i + 1;
if let Some(ml) = max_lines {
if out.len() >= ml {
truncated = true;
break;
}
}
}
}
if !truncated {
let (mut ranges, tr) = wrap_one_hard_line_ranges(
text,
line0_start,
text.len(),
max_width,
max_lines.map(|ml| ml.saturating_sub(out.len())),
&width_of,
);
out.append(&mut ranges);
truncated = tr;
}
if out.is_empty() {
out.push((0, 0));
}
let res = (out, truncated);
wrap_ranges_cache().lock().unwrap().put(key, res.clone());
res
}
fn wrap_one_hard_line_ranges(
text: &str,
start: usize,
end: usize,
max_width: f32,
max_lines: Option<usize>,
width_of: &dyn Fn(usize, usize) -> f32,
) -> (Vec<(usize, usize)>, bool) {
let mut out = Vec::new();
let mut t = false;
if start >= end {
out.push((start, start));
return (out, false);
}
if width_of(start, end) <= max_width + 0.5 {
out.push((start, end));
return (out, false);
}
let mut line_start = start;
let mut best_break = line_start;
let mut unconsumed_start = start;
for tok in text[line_start..end].split_word_bounds() {
let tok_abs_start = unconsumed_start;
let tok_abs_end = tok_abs_start + tok.len();
unconsumed_start = tok_abs_end;
let w = width_of(line_start, tok_abs_end);
if w <= max_width + 0.5 {
best_break = tok_abs_end;
continue;
}
if best_break > line_start {
out.push((line_start, best_break));
line_start = best_break;
} else {
let mut cut = tok_abs_start;
for (ofs, g) in tok.grapheme_indices(true) {
let next = tok_abs_start + ofs + g.len();
if width_of(line_start, next) <= max_width + 0.5 {
cut = next;
} else {
break;
}
}
if cut == line_start {
if let Some((ofs, gr)) = tok.grapheme_indices(true).next() {
cut = tok_abs_start + ofs + gr.len();
}
}
out.push((line_start, cut));
line_start = cut;
}
if let Some(ml) = max_lines {
if out.len() >= ml {
t = true;
break;
}
}
best_break = line_start;
}
if !t && line_start < end && max_lines.is_none_or(|ml| out.len() < ml) {
out.push((line_start, end));
}
(out, t)
}
pub fn ellipsize_line(text: &str, px: f32, max_width: f32) -> String {
if text.is_empty() || max_width <= 0.0 {
return String::new();
}
let key = (
fast_hash(text),
(px * 100.0) as u32,
(max_width * 100.0) as u32,
);
if let Some(s) = ellip_cache().lock().unwrap().get(&key).cloned() {
return s;
}
let m = metrics_for_textfield(text, px, None);
if let Some(&last) = m.positions.last()
&& last <= max_width + 0.5
{
return text.to_string();
}
let _el = "…";
let e_w = ellipsis_width(px);
if e_w >= max_width {
return String::new();
}
let mut cut_i = 0usize;
for i in 0..m.positions.len() {
if m.positions[i] + e_w <= max_width {
cut_i = i;
} else {
break;
}
}
let byte = m
.byte_offsets
.get(cut_i)
.copied()
.unwrap_or(0)
.min(text.len());
let mut out = String::with_capacity(byte + 3);
out.push_str(&text[..byte]);
out.push('…');
let s = out;
ellip_cache().lock().unwrap().put(key, s.clone());
s
}
fn ellipsis_width(px: f32) -> f32 {
static ELLIP_W_LRU: OnceCell<Mutex<Lru<u32, f32>>> = OnceCell::new();
let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
let key = (px * 100.0) as u32;
if let Some(w) = cache.lock().unwrap().get(&key).copied() {
return w;
}
let w = if let Some(g) = crate::shape_line("…", px, None).last() {
g.x + g.advance
} else {
0.0
};
cache.lock().unwrap().put(key, w);
w
}