mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-12 05:15:00 +00:00
Add LineWrapper::wrap_shaped_line
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>
This commit is contained in:
parent
0187ac8fde
commit
91c2b5825e
2 changed files with 150 additions and 3 deletions
|
@ -170,6 +170,10 @@ impl Line {
|
|||
Self { layout, color_runs }
|
||||
}
|
||||
|
||||
pub fn runs(&self) -> &[Run] {
|
||||
&self.layout.runs
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.layout.width
|
||||
}
|
||||
|
@ -245,3 +249,9 @@ impl Line {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Run {
|
||||
pub fn glyphs(&self) -> &[Glyph] {
|
||||
&self.glyphs
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Settings;
|
||||
use gpui::{fonts::FontId, FontCache, FontSystem};
|
||||
use gpui::{fonts::FontId, text_layout::Line, FontCache, FontSystem};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
|
@ -19,6 +19,12 @@ pub struct Boundary {
|
|||
pub next_indent: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ShapedBoundary {
|
||||
pub run_ix: usize,
|
||||
pub glyph_ix: usize,
|
||||
}
|
||||
|
||||
impl Boundary {
|
||||
fn new(ix: usize, next_indent: u32) -> Self {
|
||||
Self { ix, next_indent }
|
||||
|
@ -135,6 +141,74 @@ impl LineWrapper {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn wrap_shaped_line<'a>(
|
||||
&'a mut self,
|
||||
str: &'a str,
|
||||
line: &'a Line,
|
||||
wrap_width: f32,
|
||||
) -> impl Iterator<Item = ShapedBoundary> + 'a {
|
||||
let mut width = 0.0;
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut last_candidate_ix = None;
|
||||
let mut last_candidate_x = 0.0;
|
||||
let mut last_wrap_ix = ShapedBoundary {
|
||||
run_ix: 0,
|
||||
glyph_ix: 0,
|
||||
};
|
||||
let mut last_wrap_x = 0.;
|
||||
let mut prev_c = '\0';
|
||||
let mut glyphs = line
|
||||
.runs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(run_ix, run)| {
|
||||
run.glyphs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(glyph_ix, glyph)| {
|
||||
let character = str[glyph.index..].chars().next().unwrap();
|
||||
(
|
||||
ShapedBoundary { run_ix, glyph_ix },
|
||||
character,
|
||||
glyph.position.x(),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
iter::from_fn(move || {
|
||||
while let Some((ix, c, x)) = glyphs.next() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = Some(ix);
|
||||
last_candidate_x = x;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let width = x - last_wrap_x;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let Some(last_candidate_ix) = last_candidate_ix.take() {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
last_wrap_x = last_candidate_x;
|
||||
} else {
|
||||
last_wrap_ix = ix;
|
||||
last_wrap_x = x;
|
||||
}
|
||||
|
||||
return Some(last_wrap_ix);
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn is_boundary(&self, prev: char, next: char) -> bool {
|
||||
(prev == ' ') && (next != ' ')
|
||||
}
|
||||
|
@ -163,7 +237,6 @@ impl LineWrapper {
|
|||
}
|
||||
|
||||
fn compute_width_for_char(&self, c: char) -> f32 {
|
||||
log::info!("cache miss {}", c);
|
||||
self.font_system
|
||||
.layout_line(
|
||||
&c.to_string(),
|
||||
|
@ -200,9 +273,14 @@ impl DerefMut for LineWrapperHandle {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{
|
||||
color::Color,
|
||||
fonts::{Properties, Weight},
|
||||
TextLayoutCache,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_line_wrapper(cx: &mut gpui::MutableAppContext) {
|
||||
fn test_wrap_line(cx: &mut gpui::MutableAppContext) {
|
||||
let font_cache = cx.font_cache().clone();
|
||||
let font_system = cx.platform().fonts();
|
||||
let settings = Settings {
|
||||
|
@ -263,4 +341,63 @@ mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_wrap_layout_line(cx: &mut gpui::MutableAppContext) {
|
||||
let font_cache = cx.font_cache().clone();
|
||||
let font_system = cx.platform().fonts();
|
||||
let text_layout_cache = TextLayoutCache::new(font_system.clone());
|
||||
|
||||
let family = font_cache.load_family(&["Helvetica"]).unwrap();
|
||||
let settings = Settings {
|
||||
tab_size: 4,
|
||||
buffer_font_family: family,
|
||||
buffer_font_size: 16.0,
|
||||
..Settings::new(&font_cache).unwrap()
|
||||
};
|
||||
let normal = font_cache.select_font(family, &Default::default()).unwrap();
|
||||
let bold = font_cache
|
||||
.select_font(
|
||||
family,
|
||||
&Properties {
|
||||
weight: Weight::BOLD,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let text = "aa bbb cccc ddddd eeee";
|
||||
let line = text_layout_cache.layout_str(
|
||||
text,
|
||||
16.0,
|
||||
&[
|
||||
(4, normal, Color::default()),
|
||||
(5, bold, Color::default()),
|
||||
(6, normal, Color::default()),
|
||||
(1, bold, Color::default()),
|
||||
(7, normal, Color::default()),
|
||||
],
|
||||
);
|
||||
|
||||
let mut wrapper = LineWrapper::new(font_system, &font_cache, settings);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_shaped_line(&text, &line, 72.0)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
ShapedBoundary {
|
||||
run_ix: 1,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 2,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 4,
|
||||
glyph_ix: 2
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue