use collections::FxHashMap;
use gpui_engine::{
FontRun, LineLayout, LineLayoutIndex, PlatformTextSystem, WrapBoundary, WrappedLineLayout,
};
use gpui_shared_string::SharedString;
use gpui_types::{Pixels, px};
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
hash::{Hash, Hasher},
ops::Range,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
};
use gpui_engine::LineWrapper;
pub(crate) fn compute_wrap_boundaries(
layout: &LineLayout,
text: &str,
wrap_width: Pixels,
max_lines: Option<usize>,
) -> SmallVec<[WrapBoundary; 1]> {
let mut boundaries = SmallVec::new();
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = px(0.);
let mut last_boundary = WrapBoundary {
run_ix: 0,
glyph_ix: 0,
};
let mut last_boundary_x = px(0.);
let mut prev_ch = '\0';
let mut glyphs = layout
.runs
.iter()
.enumerate()
.flat_map(move |(run_ix, run)| {
run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
let character = text[glyph.index..].chars().next().unwrap();
(
WrapBoundary { run_ix, glyph_ix },
character,
glyph.position.x,
)
})
})
.peekable();
while let Some((boundary, ch, x)) = glyphs.next() {
if ch == '\n' {
continue;
}
if LineWrapper::is_word_char(ch) {
if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = Some(boundary);
last_candidate_x = x;
}
} else {
if ch != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = Some(boundary);
last_candidate_x = x;
}
}
if ch != ' ' && first_non_whitespace_ix.is_none() {
first_non_whitespace_ix = Some(boundary);
}
let next_x = glyphs.peek().map_or(layout.width, |(_, _, x)| *x);
let width = next_x - last_boundary_x;
if width > wrap_width && boundary > last_boundary {
if let Some(max_lines) = max_lines
&& boundaries.len() >= max_lines.saturating_sub(1)
{
break;
}
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_boundary = last_candidate_ix;
last_boundary_x = last_candidate_x;
} else {
last_boundary = boundary;
last_boundary_x = x;
}
boundaries.push(last_boundary);
}
prev_ch = ch;
}
boundaries
}
pub struct LineLayoutCache {
previous_frame: Mutex<FrameCache>,
current_frame: RwLock<FrameCache>,
platform_text_system: Arc<dyn PlatformTextSystem>,
font_generation: Arc<AtomicUsize>,
cached_font_generation: AtomicUsize,
}
#[derive(Default)]
struct FrameCache {
lines: FxHashMap<Arc<CacheKey>, Arc<LineLayout>>,
wrapped_lines: FxHashMap<Arc<CacheKey>, Arc<WrappedLineLayout>>,
used_lines: Vec<Arc<CacheKey>>,
used_wrapped_lines: Vec<Arc<CacheKey>>,
lines_by_hash: FxHashMap<Arc<HashedCacheKey>, Arc<LineLayout>>,
wrapped_lines_by_hash: FxHashMap<Arc<HashedCacheKey>, Arc<WrappedLineLayout>>,
used_lines_by_hash: Vec<Arc<HashedCacheKey>>,
used_wrapped_lines_by_hash: Vec<Arc<HashedCacheKey>>,
}
impl LineLayoutCache {
pub fn new(
platform_text_system: Arc<dyn PlatformTextSystem>,
font_generation: Arc<AtomicUsize>,
) -> Self {
let cached_font_generation = font_generation.load(Ordering::Acquire);
Self {
previous_frame: Mutex::default(),
current_frame: RwLock::default(),
platform_text_system,
font_generation,
cached_font_generation: AtomicUsize::new(cached_font_generation),
}
}
pub fn layout_index(&self) -> LineLayoutIndex {
let font_generation = self.clear_if_font_generation_changed();
let frame = self.current_frame.read();
LineLayoutIndex {
font_generation,
lines_index: frame.used_lines.len(),
wrapped_lines_index: frame.used_wrapped_lines.len(),
lines_by_hash_index: frame.used_lines_by_hash.len(),
wrapped_lines_by_hash_index: frame.used_wrapped_lines_by_hash.len(),
}
}
pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
let font_generation = self.clear_if_font_generation_changed();
if range.start.font_generation != font_generation
|| range.end.font_generation != font_generation
{
return;
}
let current_frame = &mut *self.current_frame.write();
let previous_frame = &mut *self.previous_frame.lock();
for key in &previous_frame.used_lines[range.start.lines_index..range.end.lines_index] {
if let Some((key, line)) = previous_frame.lines.remove_entry(key) {
current_frame.lines.insert(key, line);
}
current_frame.used_lines.push(key.clone());
}
for key in &previous_frame.used_wrapped_lines
[range.start.wrapped_lines_index..range.end.wrapped_lines_index]
{
if let Some((key, line)) = previous_frame.wrapped_lines.remove_entry(key) {
current_frame.wrapped_lines.insert(key, line);
}
current_frame.used_wrapped_lines.push(key.clone());
}
for key in &previous_frame.used_lines_by_hash
[range.start.lines_by_hash_index..range.end.lines_by_hash_index]
{
if let Some((key, line)) = previous_frame.lines_by_hash.remove_entry(key) {
current_frame.lines_by_hash.insert(key, line);
}
current_frame.used_lines_by_hash.push(key.clone());
}
for key in &previous_frame.used_wrapped_lines_by_hash
[range.start.wrapped_lines_by_hash_index..range.end.wrapped_lines_by_hash_index]
{
if let Some((key, line)) = previous_frame.wrapped_lines_by_hash.remove_entry(key) {
current_frame.wrapped_lines_by_hash.insert(key, line);
}
current_frame.used_wrapped_lines_by_hash.push(key.clone());
}
}
pub fn truncate_layouts(&self, index: LineLayoutIndex) {
let font_generation = self.clear_if_font_generation_changed();
if index.font_generation != font_generation {
return;
}
let current_frame = &mut *self.current_frame.write();
current_frame.used_lines.truncate(index.lines_index);
current_frame
.used_wrapped_lines
.truncate(index.wrapped_lines_index);
current_frame
.used_lines_by_hash
.truncate(index.lines_by_hash_index);
current_frame
.used_wrapped_lines_by_hash
.truncate(index.wrapped_lines_by_hash_index);
}
pub fn finish_frame(&self) {
let _font_generation = self.clear_if_font_generation_changed();
let mut curr_frame = self.current_frame.write();
let mut prev_frame = self.previous_frame.lock();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
curr_frame.lines.clear();
curr_frame.wrapped_lines.clear();
curr_frame.used_lines.clear();
curr_frame.used_wrapped_lines.clear();
curr_frame.lines_by_hash.clear();
curr_frame.wrapped_lines_by_hash.clear();
curr_frame.used_lines_by_hash.clear();
curr_frame.used_wrapped_lines_by_hash.clear();
}
pub fn layout_wrapped_line<Text>(
&self,
text: Text,
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
max_lines: Option<usize>,
) -> Arc<WrappedLineLayout>
where
Text: AsRef<str>,
SharedString: From<Text>,
{
let _font_generation = self.clear_if_font_generation_changed();
let key = &CacheKeyRef {
text: text.as_ref(),
font_size,
runs,
wrap_width,
force_width: None,
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame.upgradable_read();
if let Some(layout) = current_frame.wrapped_lines.get(key) {
return layout.clone();
}
let previous_frame_entry = self.previous_frame.lock().wrapped_lines.remove_entry(key);
if let Some((key, layout)) = previous_frame_entry {
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
current_frame
.wrapped_lines
.insert(key.clone(), layout.clone());
current_frame.used_wrapped_lines.push(key);
layout
} else {
drop(current_frame);
let text = SharedString::from(text);
let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs, None);
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
compute_wrap_boundaries(&unwrapped_layout, text.as_ref(), wrap_width, max_lines)
} else {
SmallVec::new()
};
let layout = Arc::new(WrappedLineLayout {
unwrapped_layout,
wrap_boundaries,
wrap_width,
});
let key = Arc::new(CacheKey {
text,
font_size,
runs: SmallVec::from(runs),
wrap_width,
force_width: None,
});
let mut current_frame = self.current_frame.write();
current_frame
.wrapped_lines
.insert(key.clone(), layout.clone());
current_frame.used_wrapped_lines.push(key);
layout
}
}
pub fn layout_line<Text>(
&self,
text: Text,
font_size: Pixels,
runs: &[FontRun],
force_width: Option<Pixels>,
) -> Arc<LineLayout>
where
Text: AsRef<str>,
SharedString: From<Text>,
{
let _font_generation = self.clear_if_font_generation_changed();
let key = &CacheKeyRef {
text: text.as_ref(),
font_size,
runs,
wrap_width: None,
force_width,
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame.upgradable_read();
if let Some(layout) = current_frame.lines.get(key) {
return layout.clone();
}
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
current_frame.lines.insert(key.clone(), layout.clone());
current_frame.used_lines.push(key);
layout
} else {
let text = SharedString::from(text);
let mut layout = self
.platform_text_system
.layout_line(&text, font_size, runs);
if let Some(force_width) = force_width {
apply_force_width_to_layout(&mut layout, force_width);
}
let key = Arc::new(CacheKey {
text,
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
force_width,
});
let layout = Arc::new(layout);
current_frame.lines.insert(key.clone(), layout.clone());
current_frame.used_lines.push(key);
layout
}
}
pub fn try_layout_line_by_hash(
&self,
text_hash: u64,
text_len: usize,
font_size: Pixels,
runs: &[FontRun],
force_width: Option<Pixels>,
) -> Option<Arc<LineLayout>> {
let _font_generation = self.clear_if_font_generation_changed();
let key_ref = HashedCacheKeyRef {
text_hash,
text_len,
font_size,
runs,
wrap_width: None,
force_width,
};
let current_frame = self.current_frame.read();
if let Some((_, layout)) = current_frame.lines_by_hash.iter().find(|(key, _)| {
HashedCacheKeyRef {
text_hash: key.text_hash,
text_len: key.text_len,
font_size: key.font_size,
runs: key.runs.as_slice(),
wrap_width: key.wrap_width,
force_width: key.force_width,
} == key_ref
}) {
return Some(layout.clone());
}
let previous_frame = self.previous_frame.lock();
if let Some((_, layout)) = previous_frame.lines_by_hash.iter().find(|(key, _)| {
HashedCacheKeyRef {
text_hash: key.text_hash,
text_len: key.text_len,
font_size: key.font_size,
runs: key.runs.as_slice(),
wrap_width: key.wrap_width,
force_width: key.force_width,
} == key_ref
}) {
return Some(layout.clone());
}
None
}
pub fn layout_line_by_hash(
&self,
text_hash: u64,
text_len: usize,
font_size: Pixels,
runs: &[FontRun],
force_width: Option<Pixels>,
materialize_text: impl FnOnce() -> SharedString,
) -> Arc<LineLayout> {
let _font_generation = self.clear_if_font_generation_changed();
let key_ref = HashedCacheKeyRef {
text_hash,
text_len,
font_size,
runs,
wrap_width: None,
force_width,
};
let current_frame = self.current_frame.upgradable_read();
if let Some((_, layout)) = current_frame.lines_by_hash.iter().find(|(key, _)| {
HashedCacheKeyRef {
text_hash: key.text_hash,
text_len: key.text_len,
font_size: key.font_size,
runs: key.runs.as_slice(),
wrap_width: key.wrap_width,
force_width: key.force_width,
} == key_ref
}) {
return layout.clone();
}
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
let mut previous_frame = self.previous_frame.lock();
if let Some(existing_key) = previous_frame
.used_lines_by_hash
.iter()
.find(|key| {
HashedCacheKeyRef {
text_hash: key.text_hash,
text_len: key.text_len,
font_size: key.font_size,
runs: key.runs.as_slice(),
wrap_width: key.wrap_width,
force_width: key.force_width,
} == key_ref
})
.cloned()
{
if let Some((key, layout)) = previous_frame.lines_by_hash.remove_entry(&existing_key) {
current_frame
.lines_by_hash
.insert(key.clone(), layout.clone());
current_frame.used_lines_by_hash.push(key);
return layout;
}
}
let text = materialize_text();
let mut layout = self
.platform_text_system
.layout_line(&text, font_size, runs);
if let Some(force_width) = force_width {
apply_force_width_to_layout(&mut layout, force_width);
}
let key = Arc::new(HashedCacheKey {
text_hash,
text_len,
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
force_width,
});
let layout = Arc::new(layout);
current_frame
.lines_by_hash
.insert(key.clone(), layout.clone());
current_frame.used_lines_by_hash.push(key);
layout
}
fn clear_if_font_generation_changed(&self) -> usize {
let font_generation = self.font_generation.load(Ordering::Acquire);
if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
return font_generation;
}
let mut current_frame = self.current_frame.write();
if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
return font_generation;
}
*current_frame = FrameCache::default();
*self.previous_frame.lock() = FrameCache::default();
self.cached_font_generation
.store(font_generation, Ordering::Release);
font_generation
}
}
fn apply_force_width_to_layout(layout: &mut LineLayout, force_width: Pixels) {
let mut glyph_pos: usize = 0;
let mut last_base_shaped_x = px(f32::NEG_INFINITY);
let mut last_base_actual_x = px(0.);
for run in layout.runs.iter_mut() {
for glyph in run.glyphs.iter_mut() {
let shaped_x = glyph.position.x;
if shaped_x > last_base_shaped_x + force_width * 0.5 {
let forced_x = glyph_pos * force_width;
if (shaped_x - forced_x).abs() > px(1.) {
glyph.position.x = forced_x;
}
last_base_shaped_x = shaped_x;
last_base_actual_x = glyph.position.x;
glyph_pos += 1;
} else {
glyph.position.x = last_base_actual_x + (shaped_x - last_base_shaped_x);
}
}
}
}
trait AsCacheKeyRef {
fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
}
#[derive(Clone, Debug, Eq)]
struct CacheKey {
text: SharedString,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,
force_width: Option<Pixels>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: Pixels,
runs: &'a [FontRun],
wrap_width: Option<Pixels>,
force_width: Option<Pixels>,
}
#[derive(Clone, Debug)]
struct HashedCacheKey {
text_hash: u64,
text_len: usize,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,
force_width: Option<Pixels>,
}
#[derive(Copy, Clone)]
struct HashedCacheKeyRef<'a> {
text_hash: u64,
text_len: usize,
font_size: Pixels,
runs: &'a [FontRun],
wrap_width: Option<Pixels>,
force_width: Option<Pixels>,
}
impl PartialEq for dyn AsCacheKeyRef + '_ {
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
self.as_cache_key_ref() == other.as_cache_key_ref()
}
}
impl PartialEq for HashedCacheKey {
fn eq(&self, other: &Self) -> bool {
self.text_hash == other.text_hash
&& self.text_len == other.text_len
&& self.font_size == other.font_size
&& self.runs.as_slice() == other.runs.as_slice()
&& self.wrap_width == other.wrap_width
&& self.force_width == other.force_width
}
}
impl Eq for HashedCacheKey {}
impl Hash for HashedCacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text_hash.hash(state);
self.text_len.hash(state);
self.font_size.hash(state);
self.runs.as_slice().hash(state);
self.wrap_width.hash(state);
self.force_width.hash(state);
}
}
impl PartialEq for HashedCacheKeyRef<'_> {
fn eq(&self, other: &Self) -> bool {
self.text_hash == other.text_hash
&& self.text_len == other.text_len
&& self.font_size == other.font_size
&& self.runs == other.runs
&& self.wrap_width == other.wrap_width
&& self.force_width == other.force_width
}
}
impl Eq for HashedCacheKeyRef<'_> {}
impl Hash for HashedCacheKeyRef<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.text_hash.hash(state);
self.text_len.hash(state);
self.font_size.hash(state);
self.runs.hash(state);
self.wrap_width.hash(state);
self.force_width.hash(state);
}
}
impl Eq for dyn AsCacheKeyRef + '_ {}
impl Hash for dyn AsCacheKeyRef + '_ {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state)
}
}
impl AsCacheKeyRef for CacheKey {
fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
CacheKeyRef {
text: &self.text,
font_size: self.font_size,
runs: self.runs.as_slice(),
wrap_width: self.wrap_width,
force_width: self.force_width,
}
}
}
impl PartialEq for CacheKey {
fn eq(&self, other: &Self) -> bool {
self.as_cache_key_ref().eq(&other.as_cache_key_ref())
}
}
impl Hash for CacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state);
}
}
impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
self.as_ref() as &dyn AsCacheKeyRef
}
}
impl AsCacheKeyRef for CacheKeyRef<'_> {
fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
*self
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui_engine::{FontId, GlyphId, ShapedGlyph, ShapedRun};
use gpui_types::point;
fn glyph_at(x: f32, index: usize) -> ShapedGlyph {
ShapedGlyph {
id: GlyphId(0),
position: point(px(x), px(0.)),
index,
is_emoji: false,
}
}
fn make_layout(glyphs: Vec<ShapedGlyph>) -> LineLayout {
LineLayout {
font_size: px(16.),
width: px(100.),
ascent: px(12.),
descent: px(4.),
runs: vec![ShapedRun {
font_id: FontId(0),
glyphs,
}],
len: 0,
}
}
fn glyph_x_positions(layout: &LineLayout) -> Vec<f32> {
layout.runs[0]
.glyphs
.iter()
.map(|g| f32::from(g.position.x))
.collect()
}
#[test]
fn test_force_width_latin_unchanged() {
let cell_width = px(8.);
let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(8., 1), glyph_at(16., 2)]);
apply_force_width_to_layout(&mut layout, cell_width);
let positions = glyph_x_positions(&layout);
assert_eq!(positions, vec![0., 8., 16.]);
}
#[test]
fn test_force_width_combining_marks_not_advanced() {
let cell_width = px(8.);
let mut layout = make_layout(vec![
glyph_at(0., 0), glyph_at(0., 3), ]);
apply_force_width_to_layout(&mut layout, cell_width);
let positions = glyph_x_positions(&layout);
assert_eq!(positions, vec![0., 0.]);
}
#[test]
fn test_force_width_base_after_combining_mark() {
let cell_width = px(8.);
let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(0., 3), glyph_at(8., 6)]);
apply_force_width_to_layout(&mut layout, cell_width);
let positions = glyph_x_positions(&layout);
assert_eq!(positions, vec![0., 0., 8.]);
}
#[test]
fn test_force_width_multiple_combining_marks() {
let cell_width = px(8.);
let mut layout = make_layout(vec![
glyph_at(0., 0), glyph_at(0., 3), glyph_at(0., 6), glyph_at(8., 9), ]);
apply_force_width_to_layout(&mut layout, cell_width);
let positions = glyph_x_positions(&layout);
assert_eq!(positions, vec![0., 0., 0., 8.]);
}
#[test]
fn test_force_width_corrects_drifted_base_positions() {
let cell_width = px(8.);
let mut layout = make_layout(vec![
glyph_at(0.5, 0), glyph_at(10.2, 1), glyph_at(19.8, 2), ]);
apply_force_width_to_layout(&mut layout, cell_width);
let positions = glyph_x_positions(&layout);
assert_eq!(positions, vec![0.5, 8., 16.]);
}
#[test]
fn test_force_width_combining_mark_after_within_tolerance_base() {
let cell_width = px(8.);
let mut layout = make_layout(vec![glyph_at(0.5, 0), glyph_at(0.5, 3)]);
apply_force_width_to_layout(&mut layout, cell_width);
let positions = glyph_x_positions(&layout);
assert_eq!(positions, vec![0.5, 0.5]);
}
}