mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-27 04:44:30 +00:00
91c2b5825e
This allows us to perform wrapping based on glyph positions in an already-shaped line. We plan to use this in the new Text element, because there we'll already need to do text shaping as part of layout. This text isn't editable so it won't need to be rewrapped with the same frequency as the text editor's content. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
257 lines
6.7 KiB
Rust
257 lines
6.7 KiB
Rust
use crate::{
|
|
color::Color,
|
|
fonts::{FontId, GlyphId},
|
|
geometry::{
|
|
rect::RectF,
|
|
vector::{vec2f, Vector2F},
|
|
},
|
|
platform, scene, PaintContext,
|
|
};
|
|
use ordered_float::OrderedFloat;
|
|
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
|
use smallvec::SmallVec;
|
|
use std::{
|
|
borrow::Borrow,
|
|
collections::HashMap,
|
|
hash::{Hash, Hasher},
|
|
sync::Arc,
|
|
};
|
|
|
|
pub struct TextLayoutCache {
|
|
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
|
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
|
|
fonts: Arc<dyn platform::FontSystem>,
|
|
}
|
|
|
|
impl TextLayoutCache {
|
|
pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
|
|
Self {
|
|
prev_frame: Mutex::new(HashMap::new()),
|
|
curr_frame: RwLock::new(HashMap::new()),
|
|
fonts,
|
|
}
|
|
}
|
|
|
|
pub fn finish_frame(&self) {
|
|
let mut prev_frame = self.prev_frame.lock();
|
|
let mut curr_frame = self.curr_frame.write();
|
|
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
|
curr_frame.clear();
|
|
}
|
|
|
|
pub fn layout_str<'a>(
|
|
&'a self,
|
|
text: &'a str,
|
|
font_size: f32,
|
|
runs: &'a [(usize, FontId, Color)],
|
|
) -> Line {
|
|
let key = &CacheKeyRef {
|
|
text,
|
|
font_size: OrderedFloat(font_size),
|
|
runs,
|
|
} as &dyn CacheKey;
|
|
let curr_frame = self.curr_frame.upgradable_read();
|
|
if let Some(layout) = curr_frame.get(key) {
|
|
return Line::new(layout.clone(), runs);
|
|
}
|
|
|
|
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
|
|
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
|
|
curr_frame.insert(key, layout.clone());
|
|
Line::new(layout.clone(), runs)
|
|
} else {
|
|
let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
|
|
let key = CacheKeyValue {
|
|
text: text.into(),
|
|
font_size: OrderedFloat(font_size),
|
|
runs: SmallVec::from(runs),
|
|
};
|
|
curr_frame.insert(key, layout.clone());
|
|
Line::new(layout, runs)
|
|
}
|
|
}
|
|
}
|
|
|
|
trait CacheKey {
|
|
fn key<'a>(&'a self) -> CacheKeyRef<'a>;
|
|
}
|
|
|
|
impl<'a> PartialEq for (dyn CacheKey + 'a) {
|
|
fn eq(&self, other: &dyn CacheKey) -> bool {
|
|
self.key() == other.key()
|
|
}
|
|
}
|
|
|
|
impl<'a> Eq for (dyn CacheKey + 'a) {}
|
|
|
|
impl<'a> Hash for (dyn CacheKey + 'a) {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.key().hash(state)
|
|
}
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
struct CacheKeyValue {
|
|
text: String,
|
|
font_size: OrderedFloat<f32>,
|
|
runs: SmallVec<[(usize, FontId, Color); 1]>,
|
|
}
|
|
|
|
impl CacheKey for CacheKeyValue {
|
|
fn key<'a>(&'a self) -> CacheKeyRef<'a> {
|
|
CacheKeyRef {
|
|
text: &self.text.as_str(),
|
|
font_size: self.font_size,
|
|
runs: self.runs.as_slice(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Hash for CacheKeyValue {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.key().hash(state);
|
|
}
|
|
}
|
|
|
|
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
|
|
fn borrow(&self) -> &(dyn CacheKey + 'a) {
|
|
self as &dyn CacheKey
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
|
struct CacheKeyRef<'a> {
|
|
text: &'a str,
|
|
font_size: OrderedFloat<f32>,
|
|
runs: &'a [(usize, FontId, Color)],
|
|
}
|
|
|
|
impl<'a> CacheKey for CacheKeyRef<'a> {
|
|
fn key<'b>(&'b self) -> CacheKeyRef<'b> {
|
|
*self
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct Line {
|
|
layout: Arc<LineLayout>,
|
|
color_runs: SmallVec<[(u32, Color); 32]>,
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct LineLayout {
|
|
pub width: f32,
|
|
pub ascent: f32,
|
|
pub descent: f32,
|
|
pub runs: Vec<Run>,
|
|
pub len: usize,
|
|
pub font_size: f32,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Run {
|
|
pub font_id: FontId,
|
|
pub glyphs: Vec<Glyph>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Glyph {
|
|
pub id: GlyphId,
|
|
pub position: Vector2F,
|
|
pub index: usize,
|
|
}
|
|
|
|
impl Line {
|
|
fn new(layout: Arc<LineLayout>, runs: &[(usize, FontId, Color)]) -> Self {
|
|
let mut color_runs = SmallVec::new();
|
|
for (len, _, color) in runs {
|
|
color_runs.push((*len as u32, *color));
|
|
}
|
|
Self { layout, color_runs }
|
|
}
|
|
|
|
pub fn runs(&self) -> &[Run] {
|
|
&self.layout.runs
|
|
}
|
|
|
|
pub fn width(&self) -> f32 {
|
|
self.layout.width
|
|
}
|
|
|
|
pub fn x_for_index(&self, index: usize) -> f32 {
|
|
for run in &self.layout.runs {
|
|
for glyph in &run.glyphs {
|
|
if glyph.index == index {
|
|
return glyph.position.x();
|
|
}
|
|
}
|
|
}
|
|
self.layout.width
|
|
}
|
|
|
|
pub fn index_for_x(&self, x: f32) -> Option<usize> {
|
|
if x >= self.layout.width {
|
|
None
|
|
} else {
|
|
for run in self.layout.runs.iter().rev() {
|
|
for glyph in run.glyphs.iter().rev() {
|
|
if glyph.position.x() <= x {
|
|
return Some(glyph.index);
|
|
}
|
|
}
|
|
}
|
|
Some(0)
|
|
}
|
|
}
|
|
|
|
pub fn paint(&self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
|
|
let padding_top = (visible_bounds.height() - self.layout.ascent - self.layout.descent) / 2.;
|
|
let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
|
|
|
|
let mut color_runs = self.color_runs.iter();
|
|
let mut color_end = 0;
|
|
let mut color = Color::black();
|
|
|
|
for run in &self.layout.runs {
|
|
let max_glyph_width = cx
|
|
.font_cache
|
|
.bounding_box(run.font_id, self.layout.font_size)
|
|
.x();
|
|
|
|
for glyph in &run.glyphs {
|
|
let glyph_origin = baseline_origin + glyph.position;
|
|
|
|
if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() {
|
|
continue;
|
|
}
|
|
if glyph_origin.x() > visible_bounds.upper_right().x() {
|
|
break;
|
|
}
|
|
|
|
if glyph.index >= color_end {
|
|
if let Some(next_run) = color_runs.next() {
|
|
color_end += next_run.0 as usize;
|
|
color = next_run.1;
|
|
} else {
|
|
color_end = self.layout.len;
|
|
color = Color::black();
|
|
}
|
|
}
|
|
|
|
cx.scene.push_glyph(scene::Glyph {
|
|
font_id: run.font_id,
|
|
font_size: self.layout.font_size,
|
|
id: glyph.id,
|
|
origin: origin + glyph_origin,
|
|
color,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Run {
|
|
pub fn glyphs(&self) -> &[Glyph] {
|
|
&self.glyphs
|
|
}
|
|
}
|