Start work on wrapping lines without text shaping

This commit is contained in:
Max Brunsfeld 2021-07-21 17:23:05 -07:00
parent f169f8e358
commit 830f5336b9

View file

@ -11,14 +11,19 @@ use crate::{
util::Bias,
Settings,
};
use gpui::{AppContext, FontCache, FontSystem, Task};
use gpui::{fonts::FontId, AppContext, FontCache, FontSystem, Task};
use parking_lot::Mutex;
use postage::{
prelude::{Sink, Stream},
watch,
};
use smol::channel;
use std::{collections::VecDeque, ops::Range, sync::Arc, time::Duration};
use std::{
collections::{HashMap, VecDeque},
ops::Range,
sync::Arc,
time::Duration,
};
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct OutputPoint(super::Point);
@ -440,11 +445,18 @@ impl WrapMap {
}
struct BackgroundWrapper {
settings: Settings,
wrap_width: Option<f32>,
font_cache: Arc<FontCache>,
font_system: Arc<dyn FontSystem>,
snapshot: Snapshot,
line_wrapper: LineWrapper,
}
struct LineWrapper {
font_system: Arc<dyn FontSystem>,
font_cache: Arc<FontCache>,
font_id: FontId,
font_size: f32,
cached_ascii_char_widths: [f32; 128],
cached_other_char_widths: HashMap<char, f32>,
}
enum Change {
@ -464,11 +476,9 @@ impl BackgroundWrapper {
font_system: Arc<dyn FontSystem>,
) -> Self {
Self {
settings,
wrap_width,
font_cache,
font_system,
snapshot,
line_wrapper: LineWrapper::new(font_system, font_cache, settings),
}
}
@ -516,12 +526,6 @@ impl BackgroundWrapper {
return;
}
let font_id = self
.font_cache
.select_font(self.settings.buffer_font_family, &Default::default())
.unwrap();
let font_size = self.settings.buffer_font_size;
#[derive(Debug)]
struct RowEdit {
old_rows: Range<u32>,
@ -591,8 +595,8 @@ impl BackgroundWrapper {
let mut prev_boundary_ix = 0;
if let Some(wrap_width) = self.wrap_width {
for boundary_ix in self
.font_system
.wrap_line(&line, font_id, font_size, wrap_width)
.line_wrapper
.wrap_line_without_shaping(&line, wrap_width)
{
let wrapped = &line[prev_boundary_ix..boundary_ix];
new_transforms
@ -654,6 +658,100 @@ impl BackgroundWrapper {
}
}
impl LineWrapper {
fn new(
font_system: Arc<dyn FontSystem>,
font_cache: Arc<FontCache>,
settings: Settings,
) -> Self {
let font_id = font_cache
.select_font(settings.buffer_font_family, &Default::default())
.unwrap();
let font_size = settings.buffer_font_size;
Self {
font_cache,
font_system,
font_id,
font_size,
cached_ascii_char_widths: [f32::NAN; 128],
cached_other_char_widths: HashMap::new(),
}
}
fn wrap_line_with_shaping(&mut self, line: &str, wrap_width: f32) -> Vec<usize> {
self.font_system
.wrap_line(line, self.font_id, self.font_size, wrap_width)
}
fn wrap_line_without_shaping(&mut self, line: &str, wrap_width: f32) -> Vec<usize> {
let mut width = 0.0;
let mut result = Vec::new();
let mut last_boundary_ix = 0;
let mut last_boundary_width = 0.0;
let mut prev_c = '\0';
for (ix, c) in line.char_indices() {
if self.is_boundary(prev_c, c) {
last_boundary_ix = ix;
last_boundary_width = width;
}
let char_width = self.width_for_char(c);
width += char_width;
if width > wrap_width {
if last_boundary_ix > 0 {
result.push(last_boundary_ix);
width -= last_boundary_width;
last_boundary_ix = 0;
} else {
result.push(ix);
width = char_width;
}
}
prev_c = c;
}
result
}
fn is_boundary(&self, prev: char, next: char) -> bool {
if prev == ' ' || next == ' ' {
return true;
}
false
}
fn width_for_char(&mut self, c: char) -> f32 {
if (c as u32) < 128 {
let mut width = self.cached_ascii_char_widths[c as usize];
if width.is_nan() {
width = self.compute_width_for_char(c);
self.cached_ascii_char_widths[c as usize] = width;
}
width
} else {
let mut width = self
.cached_other_char_widths
.get(&c)
.copied()
.unwrap_or(f32::NAN);
if width.is_nan() {
width = self.compute_width_for_char(c);
self.cached_other_char_widths.insert(c, width);
}
width
}
}
fn compute_width_for_char(&self, c: char) -> f32 {
self.font_system
.layout_line(
&c.to_string(),
self.font_size,
&[(1, self.font_id, Default::default())],
)
.width
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct Transform {
summary: TransformSummary,
@ -761,6 +859,38 @@ mod tests {
use rand::prelude::*;
use std::env;
#[gpui::test]
fn test_line_wrapper(cx: &mut gpui::MutableAppContext) {
let font_cache = cx.font_cache().clone();
let font_system = cx.platform().fonts();
let settings = Settings {
tab_size: 4,
buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(),
buffer_font_size: 16.0,
..Settings::new(&font_cache).unwrap()
};
let mut wrapper = LineWrapper::new(font_system, font_cache, settings);
assert_eq!(
wrapper.wrap_line_with_shaping("aa bbb cccc ddddd eeee", 72.0),
&[7, 12, 18],
);
assert_eq!(
wrapper.wrap_line_without_shaping("aa bbb cccc ddddd eeee", 72.0),
&[7, 12, 18],
);
assert_eq!(
wrapper.wrap_line_with_shaping("aaa aaaaaaaaaaaaaaaaaa", 72.0),
&[4, 11, 18],
);
assert_eq!(
wrapper.wrap_line_without_shaping("aaa aaaaaaaaaaaaaaaaaa", 72.0),
&[4, 11, 18],
);
}
#[gpui::test]
fn test_random_wraps(cx: &mut gpui::MutableAppContext) {
let iterations = env::var("ITERATIONS")