Optimize Buffer::set_rich_text for when the buffer is reconstructed
This commit is contained in:
parent
c751217020
commit
b68f4ad5c6
6 changed files with 421 additions and 69 deletions
|
|
@ -373,4 +373,11 @@ impl AttrsList {
|
||||||
}
|
}
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the attributes with new defaults.
|
||||||
|
pub(crate) fn reset(mut self, default: Attrs) -> Self {
|
||||||
|
self.defaults = AttrsOwned::new(default);
|
||||||
|
self.spans.clear();
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,8 @@ impl<'b> Iterator for LayoutRunIter<'b> {
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
while let Some(line) = self.buffer.lines.get(self.line_i) {
|
while let Some(line) = self.buffer.lines.get(self.line_i) {
|
||||||
let shape = line.shape_opt().as_ref()?;
|
let shape = line.shape_opt()?;
|
||||||
let layout = line.layout_opt().as_ref()?;
|
let layout = line.layout_opt()?;
|
||||||
while let Some(layout_line) = layout.get(self.layout_i) {
|
while let Some(layout_line) = layout.get(self.layout_i) {
|
||||||
self.layout_i += 1;
|
self.layout_i += 1;
|
||||||
|
|
||||||
|
|
@ -725,11 +725,8 @@ impl Buffer {
|
||||||
) where
|
) where
|
||||||
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
|
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
|
||||||
{
|
{
|
||||||
self.lines.clear();
|
|
||||||
|
|
||||||
let mut attrs_list = AttrsList::new(default_attrs);
|
|
||||||
let mut line_string = String::new();
|
|
||||||
let mut end = 0;
|
let mut end = 0;
|
||||||
|
// TODO: find a way to cache this string and vec for reuse
|
||||||
let (string, spans_data): (String, Vec<_>) = spans
|
let (string, spans_data): (String, Vec<_>) = spans
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(s, attrs)| {
|
.map(|(s, attrs)| {
|
||||||
|
|
@ -753,15 +750,32 @@ impl Buffer {
|
||||||
//TODO: set this based on information from spans
|
//TODO: set this based on information from spans
|
||||||
let line_ending = LineEnding::default();
|
let line_ending = LineEnding::default();
|
||||||
|
|
||||||
|
let mut line_count = 0;
|
||||||
|
let mut attrs_list = self
|
||||||
|
.lines
|
||||||
|
.get_mut(line_count)
|
||||||
|
.map(BufferLine::reclaim_attrs)
|
||||||
|
.unwrap_or_else(|| AttrsList::new(Attrs::new()))
|
||||||
|
.reset(default_attrs);
|
||||||
|
let mut line_string = self
|
||||||
|
.lines
|
||||||
|
.get_mut(line_count)
|
||||||
|
.map(BufferLine::reclaim_text)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (Some(line_range), Some((attrs, span_range))) = (&maybe_line, &maybe_span) else {
|
let (Some(line_range), Some((attrs, span_range))) = (&maybe_line, &maybe_span) else {
|
||||||
// this is reached only if this text is empty
|
// this is reached only if this text is empty
|
||||||
self.lines.push(BufferLine::new(
|
if self.lines.len() <= line_count {
|
||||||
|
self.lines.push(BufferLine::empty());
|
||||||
|
}
|
||||||
|
self.lines[line_count].reset_new(
|
||||||
String::new(),
|
String::new(),
|
||||||
line_ending,
|
line_ending,
|
||||||
AttrsList::new(default_attrs),
|
AttrsList::new(default_attrs),
|
||||||
shaping,
|
shaping,
|
||||||
));
|
);
|
||||||
|
line_count += 1;
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -790,22 +804,44 @@ impl Buffer {
|
||||||
maybe_line = lines_iter.next();
|
maybe_line = lines_iter.next();
|
||||||
if maybe_line.is_some() {
|
if maybe_line.is_some() {
|
||||||
// finalize this line and start a new line
|
// finalize this line and start a new line
|
||||||
let prev_attrs_list =
|
let next_attrs_list = self
|
||||||
core::mem::replace(&mut attrs_list, AttrsList::new(default_attrs));
|
.lines
|
||||||
let prev_line_string = core::mem::take(&mut line_string);
|
.get_mut(line_count + 1)
|
||||||
let buffer_line =
|
.map(BufferLine::reclaim_attrs)
|
||||||
BufferLine::new(prev_line_string, line_ending, prev_attrs_list, shaping);
|
.unwrap_or_else(|| AttrsList::new(Attrs::new()))
|
||||||
self.lines.push(buffer_line);
|
.reset(default_attrs);
|
||||||
|
let next_line_string = self
|
||||||
|
.lines
|
||||||
|
.get_mut(line_count + 1)
|
||||||
|
.map(BufferLine::reclaim_text)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let prev_attrs_list = core::mem::replace(&mut attrs_list, next_attrs_list);
|
||||||
|
let prev_line_string = core::mem::replace(&mut line_string, next_line_string);
|
||||||
|
if self.lines.len() <= line_count {
|
||||||
|
self.lines.push(BufferLine::empty());
|
||||||
|
}
|
||||||
|
self.lines[line_count].reset_new(
|
||||||
|
prev_line_string,
|
||||||
|
line_ending,
|
||||||
|
prev_attrs_list,
|
||||||
|
shaping,
|
||||||
|
);
|
||||||
|
line_count += 1;
|
||||||
} else {
|
} else {
|
||||||
// finalize the final line
|
// finalize the final line
|
||||||
let buffer_line =
|
if self.lines.len() <= line_count {
|
||||||
BufferLine::new(line_string, line_ending, attrs_list, shaping);
|
self.lines.push(BufferLine::empty());
|
||||||
self.lines.push(buffer_line);
|
}
|
||||||
|
self.lines[line_count].reset_new(line_string, line_ending, attrs_list, shaping);
|
||||||
|
line_count += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discard excess lines now that we have reused as much of the existing allocations as possible.
|
||||||
|
self.lines.truncate(line_count);
|
||||||
|
|
||||||
self.scroll = Scroll::default();
|
self.scroll = Scroll::default();
|
||||||
|
|
||||||
self.shape_until_scroll(font_system, false);
|
self.shape_until_scroll(font_system, false);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
use alloc::{string::String, vec::Vec};
|
use alloc::{string::String, vec::Vec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Align, AttrsList, FontSystem, LayoutLine, LineEnding, ShapeBuffer, ShapeLine, Shaping, Wrap,
|
Align, Attrs, AttrsList, Cached, FontSystem, LayoutLine, LineEnding, ShapeBuffer, ShapeLine,
|
||||||
|
Shaping, Wrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A line (or paragraph) of text that is shaped and laid out
|
/// A line (or paragraph) of text that is shaped and laid out
|
||||||
|
|
@ -12,8 +13,8 @@ pub struct BufferLine {
|
||||||
ending: LineEnding,
|
ending: LineEnding,
|
||||||
attrs_list: AttrsList,
|
attrs_list: AttrsList,
|
||||||
align: Option<Align>,
|
align: Option<Align>,
|
||||||
shape_opt: Option<ShapeLine>,
|
shape_opt: Cached<ShapeLine>,
|
||||||
layout_opt: Option<Vec<LayoutLine>>,
|
layout_opt: Cached<Vec<LayoutLine>>,
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
metadata: Option<usize>,
|
metadata: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
@ -33,13 +34,33 @@ impl BufferLine {
|
||||||
ending,
|
ending,
|
||||||
attrs_list,
|
attrs_list,
|
||||||
align: None,
|
align: None,
|
||||||
shape_opt: None,
|
shape_opt: Cached::Empty,
|
||||||
layout_opt: None,
|
layout_opt: Cached::Empty,
|
||||||
shaping,
|
shaping,
|
||||||
metadata: None,
|
metadata: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the current line with new internal values.
|
||||||
|
///
|
||||||
|
/// Avoids deallocating internal caches so they can be reused.
|
||||||
|
pub fn reset_new<T: Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
text: T,
|
||||||
|
ending: LineEnding,
|
||||||
|
attrs_list: AttrsList,
|
||||||
|
shaping: Shaping,
|
||||||
|
) {
|
||||||
|
self.text = text.into();
|
||||||
|
self.ending = ending;
|
||||||
|
self.attrs_list = attrs_list;
|
||||||
|
self.align = None;
|
||||||
|
self.shape_opt.set_unused();
|
||||||
|
self.layout_opt.set_unused();
|
||||||
|
self.shaping = shaping;
|
||||||
|
self.metadata = None;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get current text
|
/// Get current text
|
||||||
pub fn text(&self) -> &str {
|
pub fn text(&self) -> &str {
|
||||||
&self.text
|
&self.text
|
||||||
|
|
@ -172,13 +193,13 @@ impl BufferLine {
|
||||||
|
|
||||||
/// Reset shaping and layout caches
|
/// Reset shaping and layout caches
|
||||||
pub fn reset_shaping(&mut self) {
|
pub fn reset_shaping(&mut self) {
|
||||||
self.shape_opt = None;
|
self.shape_opt.set_unused();
|
||||||
self.reset_layout();
|
self.reset_layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset only layout cache
|
/// Reset only layout cache
|
||||||
pub fn reset_layout(&mut self) {
|
pub fn reset_layout(&mut self) {
|
||||||
self.layout_opt = None;
|
self.layout_opt.set_unused();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shape line, will cache results
|
/// Shape line, will cache results
|
||||||
|
|
@ -193,23 +214,28 @@ impl BufferLine {
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
tab_width: u16,
|
tab_width: u16,
|
||||||
) -> &ShapeLine {
|
) -> &ShapeLine {
|
||||||
if self.shape_opt.is_none() {
|
if self.shape_opt.is_unused() {
|
||||||
self.shape_opt = Some(ShapeLine::new_in_buffer(
|
let mut line = self
|
||||||
|
.shape_opt
|
||||||
|
.take_unused()
|
||||||
|
.unwrap_or_else(ShapeLine::empty);
|
||||||
|
line.build_in_buffer(
|
||||||
scratch,
|
scratch,
|
||||||
font_system,
|
font_system,
|
||||||
&self.text,
|
&self.text,
|
||||||
&self.attrs_list,
|
&self.attrs_list,
|
||||||
self.shaping,
|
self.shaping,
|
||||||
tab_width,
|
tab_width,
|
||||||
));
|
);
|
||||||
self.layout_opt = None;
|
self.shape_opt.set_used(line);
|
||||||
|
self.layout_opt.set_unused();
|
||||||
}
|
}
|
||||||
self.shape_opt.as_ref().expect("shape not found")
|
self.shape_opt.get().expect("shape not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get line shaping cache
|
/// Get line shaping cache
|
||||||
pub fn shape_opt(&self) -> &Option<ShapeLine> {
|
pub fn shape_opt(&self) -> Option<&ShapeLine> {
|
||||||
&self.shape_opt
|
self.shape_opt.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout line, will cache results
|
/// Layout line, will cache results
|
||||||
|
|
@ -244,10 +270,13 @@ impl BufferLine {
|
||||||
match_mono_width: Option<f32>,
|
match_mono_width: Option<f32>,
|
||||||
tab_width: u16,
|
tab_width: u16,
|
||||||
) -> &[LayoutLine] {
|
) -> &[LayoutLine] {
|
||||||
if self.layout_opt.is_none() {
|
if self.layout_opt.is_unused() {
|
||||||
let align = self.align;
|
let align = self.align;
|
||||||
|
let mut layout = self
|
||||||
|
.layout_opt
|
||||||
|
.take_unused()
|
||||||
|
.unwrap_or_else(|| Vec::with_capacity(1));
|
||||||
let shape = self.shape_in_buffer(scratch, font_system, tab_width);
|
let shape = self.shape_in_buffer(scratch, font_system, tab_width);
|
||||||
let mut layout = Vec::with_capacity(1);
|
|
||||||
shape.layout_to_buffer(
|
shape.layout_to_buffer(
|
||||||
scratch,
|
scratch,
|
||||||
font_size,
|
font_size,
|
||||||
|
|
@ -257,14 +286,14 @@ impl BufferLine {
|
||||||
&mut layout,
|
&mut layout,
|
||||||
match_mono_width,
|
match_mono_width,
|
||||||
);
|
);
|
||||||
self.layout_opt = Some(layout);
|
self.layout_opt.set_used(layout);
|
||||||
}
|
}
|
||||||
self.layout_opt.as_ref().expect("layout not found")
|
self.layout_opt.get().expect("layout not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get line layout cache
|
/// Get line layout cache
|
||||||
pub fn layout_opt(&self) -> &Option<Vec<LayoutLine>> {
|
pub fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
|
||||||
&self.layout_opt
|
self.layout_opt.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
|
/// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
|
||||||
|
|
@ -277,4 +306,36 @@ impl BufferLine {
|
||||||
pub fn set_metadata(&mut self, metadata: usize) {
|
pub fn set_metadata(&mut self, metadata: usize) {
|
||||||
self.metadata = Some(metadata);
|
self.metadata = Some(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes an empty buffer line.
|
||||||
|
///
|
||||||
|
/// The buffer line is in an invalid state after this is called. See [`Self::reclaim_new`].
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
text: String::default(),
|
||||||
|
ending: LineEnding::default(),
|
||||||
|
attrs_list: AttrsList::new(Attrs::new()),
|
||||||
|
align: None,
|
||||||
|
shape_opt: Cached::Empty,
|
||||||
|
layout_opt: Cached::Empty,
|
||||||
|
shaping: Shaping::Advanced,
|
||||||
|
metadata: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reclaim attributes list memory that isn't needed any longer.
|
||||||
|
///
|
||||||
|
/// The buffer line is in an invalid state after this is called. See [`Self::reclaim_new`].
|
||||||
|
pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
|
||||||
|
std::mem::replace(&mut self.attrs_list, AttrsList::new(Attrs::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reclaim text memory that isn't needed any longer.
|
||||||
|
///
|
||||||
|
/// The buffer line is in an invalid state after this is called. See [`Self::reclaim_new`].
|
||||||
|
pub(crate) fn reclaim_text(&mut self) -> String {
|
||||||
|
let mut text = std::mem::take(&mut self.text);
|
||||||
|
text.clear();
|
||||||
|
text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
src/cached.rs
Normal file
71
src/cached.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use core::fmt::Debug;
|
||||||
|
|
||||||
|
/// Helper for caching a value when the value is optionally present in the 'unused' state.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Cached<T: Clone + Debug> {
|
||||||
|
Empty,
|
||||||
|
Unused(T),
|
||||||
|
Used(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Debug> Cached<T> {
|
||||||
|
pub fn get(&self) -> Option<&T> {
|
||||||
|
match self {
|
||||||
|
Self::Empty | Self::Unused(_) => None,
|
||||||
|
Self::Used(t) => Some(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self) -> Option<&mut T> {
|
||||||
|
match self {
|
||||||
|
Self::Empty | Self::Unused(_) => None,
|
||||||
|
Self::Used(t) => Some(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_unused(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Empty | Self::Unused(_) => true,
|
||||||
|
Self::Used(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_used(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Empty | Self::Unused(_) => false,
|
||||||
|
Self::Used(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_unused(&mut self) -> Option<T> {
|
||||||
|
if matches!(*self, Self::Unused(_)) {
|
||||||
|
let Self::Unused(val) = std::mem::replace(self, Self::Empty) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
Some(val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_used(&mut self) -> Option<T> {
|
||||||
|
if matches!(*self, Self::Used(_)) {
|
||||||
|
let Self::Used(val) = std::mem::replace(self, Self::Empty) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
Some(val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_unused(&mut self) {
|
||||||
|
if matches!(*self, Self::Used(_)) {
|
||||||
|
*self = Self::Unused(self.take_used().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_used(&mut self, val: T) {
|
||||||
|
*self = Self::Used(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -108,6 +108,9 @@ mod buffer;
|
||||||
pub use self::buffer_line::*;
|
pub use self::buffer_line::*;
|
||||||
mod buffer_line;
|
mod buffer_line;
|
||||||
|
|
||||||
|
pub use self::cached::*;
|
||||||
|
mod cached;
|
||||||
|
|
||||||
pub use self::glyph_cache::*;
|
pub use self::glyph_cache::*;
|
||||||
mod glyph_cache;
|
mod glyph_cache;
|
||||||
|
|
||||||
|
|
|
||||||
238
src/shape.rs
238
src/shape.rs
|
|
@ -87,8 +87,18 @@ pub struct ShapeBuffer {
|
||||||
/// Temporary buffers for scripts.
|
/// Temporary buffers for scripts.
|
||||||
scripts: Vec<Script>,
|
scripts: Vec<Script>,
|
||||||
|
|
||||||
/// Buffer for visual lines.
|
/// Buffer for shape spans.
|
||||||
|
spans: Vec<ShapeSpan>,
|
||||||
|
|
||||||
|
/// Buffer for shape words.
|
||||||
|
words: Vec<ShapeWord>,
|
||||||
|
|
||||||
|
/// Buffers for visual lines.
|
||||||
visual_lines: Vec<VisualLine>,
|
visual_lines: Vec<VisualLine>,
|
||||||
|
cached_visual_lines: Vec<VisualLine>,
|
||||||
|
|
||||||
|
/// Buffer for sets of layout glyphs.
|
||||||
|
glyph_sets: Vec<Vec<LayoutGlyph>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ShapeBuffer {
|
impl fmt::Debug for ShapeBuffer {
|
||||||
|
|
@ -542,6 +552,16 @@ pub struct ShapeWord {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeWord {
|
impl ShapeWord {
|
||||||
|
/// Creates an empty word.
|
||||||
|
///
|
||||||
|
/// The returned word is in an invalid state until [`Self::build_in_buffer`] is called.
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
blank: true,
|
||||||
|
glyphs: Vec::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
line: &str,
|
line: &str,
|
||||||
|
|
@ -575,6 +595,35 @@ impl ShapeWord {
|
||||||
blank: bool,
|
blank: bool,
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let mut empty = Self::empty();
|
||||||
|
empty.build_in_buffer(
|
||||||
|
scratch,
|
||||||
|
font_system,
|
||||||
|
line,
|
||||||
|
attrs_list,
|
||||||
|
word_range,
|
||||||
|
level,
|
||||||
|
blank,
|
||||||
|
shaping,
|
||||||
|
);
|
||||||
|
empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`Self::new_in_buffer`].
|
||||||
|
///
|
||||||
|
/// Reuses as much of the pre-existing internal allocations as possible.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn build_in_buffer(
|
||||||
|
&mut self,
|
||||||
|
scratch: &mut ShapeBuffer,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
line: &str,
|
||||||
|
attrs_list: &AttrsList,
|
||||||
|
word_range: Range<usize>,
|
||||||
|
level: unicode_bidi::Level,
|
||||||
|
blank: bool,
|
||||||
|
shaping: Shaping,
|
||||||
|
) {
|
||||||
let word = &line[word_range.clone()];
|
let word = &line[word_range.clone()];
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
|
|
@ -583,7 +632,9 @@ impl ShapeWord {
|
||||||
word
|
word
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut glyphs = Vec::new();
|
let mut glyphs = std::mem::take(&mut self.glyphs);
|
||||||
|
glyphs.clear();
|
||||||
|
|
||||||
let span_rtl = level.is_rtl();
|
let span_rtl = level.is_rtl();
|
||||||
|
|
||||||
let mut start_run = word_range.start;
|
let mut start_run = word_range.start;
|
||||||
|
|
@ -620,7 +671,8 @@ impl ShapeWord {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { blank, glyphs }
|
self.blank = blank;
|
||||||
|
self.glyphs = glyphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the width of the [`ShapeWord`] in pixels, using the [`ShapeGlyph::width`] function.
|
/// Get the width of the [`ShapeWord`] in pixels, using the [`ShapeGlyph::width`] function.
|
||||||
|
|
@ -641,6 +693,16 @@ pub struct ShapeSpan {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeSpan {
|
impl ShapeSpan {
|
||||||
|
/// Creates an empty span.
|
||||||
|
///
|
||||||
|
/// The returned span is in an invalid state until [`Self::build_in_buffer`] is called.
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
level: unicode_bidi::Level::ltr(),
|
||||||
|
words: Vec::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
line: &str,
|
line: &str,
|
||||||
|
|
@ -673,6 +735,34 @@ impl ShapeSpan {
|
||||||
level: unicode_bidi::Level,
|
level: unicode_bidi::Level,
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let mut empty = Self::empty();
|
||||||
|
empty.build_in_buffer(
|
||||||
|
scratch,
|
||||||
|
font_system,
|
||||||
|
line,
|
||||||
|
attrs_list,
|
||||||
|
span_range,
|
||||||
|
line_rtl,
|
||||||
|
level,
|
||||||
|
shaping,
|
||||||
|
);
|
||||||
|
empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`Self::new_in_buffer`].
|
||||||
|
///
|
||||||
|
/// Reuses as much of the pre-existing internal allocations as possible.
|
||||||
|
pub fn build_in_buffer(
|
||||||
|
&mut self,
|
||||||
|
scratch: &mut ShapeBuffer,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
line: &str,
|
||||||
|
attrs_list: &AttrsList,
|
||||||
|
span_range: Range<usize>,
|
||||||
|
line_rtl: bool,
|
||||||
|
level: unicode_bidi::Level,
|
||||||
|
shaping: Shaping,
|
||||||
|
) {
|
||||||
let span = &line[span_range.start..span_range.end];
|
let span = &line[span_range.start..span_range.end];
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
|
|
@ -681,7 +771,17 @@ impl ShapeSpan {
|
||||||
span
|
span
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut words = Vec::new();
|
let mut words = std::mem::take(&mut self.words);
|
||||||
|
|
||||||
|
// Cache the shape words in reverse order so they can be popped for reuse in the same order.
|
||||||
|
let mut cached_words = std::mem::take(&mut scratch.words);
|
||||||
|
cached_words.clear();
|
||||||
|
if line_rtl != level.is_rtl() {
|
||||||
|
// Un-reverse previous words so the internal glyph counts match accurately when rewriting memory.
|
||||||
|
cached_words.extend(words.drain(..));
|
||||||
|
} else {
|
||||||
|
cached_words.extend(words.drain(..).rev());
|
||||||
|
}
|
||||||
|
|
||||||
let mut start_word = 0;
|
let mut start_word = 0;
|
||||||
for (end_lb, _) in unicode_linebreak::linebreaks(span) {
|
for (end_lb, _) in unicode_linebreak::linebreaks(span) {
|
||||||
|
|
@ -698,7 +798,8 @@ impl ShapeSpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start_word < start_lb {
|
if start_word < start_lb {
|
||||||
words.push(ShapeWord::new_in_buffer(
|
let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
|
||||||
|
word.build_in_buffer(
|
||||||
scratch,
|
scratch,
|
||||||
font_system,
|
font_system,
|
||||||
line,
|
line,
|
||||||
|
|
@ -707,12 +808,14 @@ impl ShapeSpan {
|
||||||
level,
|
level,
|
||||||
false,
|
false,
|
||||||
shaping,
|
shaping,
|
||||||
));
|
);
|
||||||
|
words.push(word);
|
||||||
}
|
}
|
||||||
if start_lb < end_lb {
|
if start_lb < end_lb {
|
||||||
for (i, c) in span[start_lb..end_lb].char_indices() {
|
for (i, c) in span[start_lb..end_lb].char_indices() {
|
||||||
// assert!(c.is_whitespace());
|
// assert!(c.is_whitespace());
|
||||||
words.push(ShapeWord::new_in_buffer(
|
let mut word = cached_words.pop().unwrap_or_else(ShapeWord::empty);
|
||||||
|
word.build_in_buffer(
|
||||||
scratch,
|
scratch,
|
||||||
font_system,
|
font_system,
|
||||||
line,
|
line,
|
||||||
|
|
@ -722,7 +825,8 @@ impl ShapeSpan {
|
||||||
level,
|
level,
|
||||||
true,
|
true,
|
||||||
shaping,
|
shaping,
|
||||||
));
|
);
|
||||||
|
words.push(word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
start_word = end_lb;
|
start_word = end_lb;
|
||||||
|
|
@ -740,7 +844,11 @@ impl ShapeSpan {
|
||||||
words.reverse();
|
words.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShapeSpan { level, words }
|
self.level = level;
|
||||||
|
self.words = words;
|
||||||
|
|
||||||
|
// Cache buffer for future reuse.
|
||||||
|
scratch.words = cached_words;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -762,7 +870,26 @@ struct VisualLine {
|
||||||
w: f32,
|
w: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VisualLine {
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.ranges.clear();
|
||||||
|
self.spaces = 0;
|
||||||
|
self.w = 0.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ShapeLine {
|
impl ShapeLine {
|
||||||
|
/// Creates an empty line.
|
||||||
|
///
|
||||||
|
/// The returned line is in an invalid state until [`Self::build_in_buffer`] is called.
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
rtl: false,
|
||||||
|
spans: Vec::default(),
|
||||||
|
metrics_opt: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Will panic if `line` contains more than one paragraph.
|
/// Will panic if `line` contains more than one paragraph.
|
||||||
|
|
@ -797,7 +924,29 @@ impl ShapeLine {
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
tab_width: u16,
|
tab_width: u16,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut spans = Vec::new();
|
let mut empty = Self::empty();
|
||||||
|
empty.build_in_buffer(scratch, font_system, line, attrs_list, shaping, tab_width);
|
||||||
|
empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`Self::new_in_buffer`].
|
||||||
|
///
|
||||||
|
/// Reuses as much of the pre-existing internal allocations as possible.
|
||||||
|
pub fn build_in_buffer(
|
||||||
|
&mut self,
|
||||||
|
scratch: &mut ShapeBuffer,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
line: &str,
|
||||||
|
attrs_list: &AttrsList,
|
||||||
|
shaping: Shaping,
|
||||||
|
tab_width: u16,
|
||||||
|
) {
|
||||||
|
let mut spans = std::mem::take(&mut self.spans);
|
||||||
|
|
||||||
|
// Cache the shape spans in reverse order so they can be popped for reuse in the same order.
|
||||||
|
let mut cached_spans = std::mem::take(&mut scratch.spans);
|
||||||
|
cached_spans.clear();
|
||||||
|
cached_spans.extend(spans.drain(..).rev());
|
||||||
|
|
||||||
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
||||||
let rtl = if bidi.paragraphs.is_empty() {
|
let rtl = if bidi.paragraphs.is_empty() {
|
||||||
|
|
@ -829,7 +978,8 @@ impl ShapeLine {
|
||||||
{
|
{
|
||||||
if new_level != run_level {
|
if new_level != run_level {
|
||||||
// End of the previous run, start of a new one.
|
// End of the previous run, start of a new one.
|
||||||
spans.push(ShapeSpan::new_in_buffer(
|
let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
|
||||||
|
span.build_in_buffer(
|
||||||
scratch,
|
scratch,
|
||||||
font_system,
|
font_system,
|
||||||
line,
|
line,
|
||||||
|
|
@ -838,12 +988,14 @@ impl ShapeLine {
|
||||||
line_rtl,
|
line_rtl,
|
||||||
run_level,
|
run_level,
|
||||||
shaping,
|
shaping,
|
||||||
));
|
);
|
||||||
|
spans.push(span);
|
||||||
start = i;
|
start = i;
|
||||||
run_level = new_level;
|
run_level = new_level;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spans.push(ShapeSpan::new_in_buffer(
|
let mut span = cached_spans.pop().unwrap_or_else(ShapeSpan::empty);
|
||||||
|
span.build_in_buffer(
|
||||||
scratch,
|
scratch,
|
||||||
font_system,
|
font_system,
|
||||||
line,
|
line,
|
||||||
|
|
@ -852,7 +1004,8 @@ impl ShapeLine {
|
||||||
line_rtl,
|
line_rtl,
|
||||||
run_level,
|
run_level,
|
||||||
shaping,
|
shaping,
|
||||||
));
|
);
|
||||||
|
spans.push(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust for tabs
|
// Adjust for tabs
|
||||||
|
|
@ -871,11 +1024,12 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
self.rtl = rtl;
|
||||||
rtl,
|
self.spans = spans;
|
||||||
spans,
|
self.metrics_opt = attrs_list.defaults().metrics_opt.map(|x| x.into());
|
||||||
metrics_opt: attrs_list.defaults().metrics_opt.map(|x| x.into()),
|
|
||||||
}
|
// Return the buffer for later reuse.
|
||||||
|
scratch.spans = cached_spans;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A modified version of first part of unicode_bidi::bidi_info::visual_run
|
// A modified version of first part of unicode_bidi::bidi_info::visual_run
|
||||||
|
|
@ -1029,11 +1183,21 @@ impl ShapeLine {
|
||||||
// For each visual line a list of (span index, and range of words in that span)
|
// For each visual line a list of (span index, and range of words in that span)
|
||||||
// Note that a BiDi visual line could have multiple spans or parts of them
|
// Note that a BiDi visual line could have multiple spans or parts of them
|
||||||
// let mut vl_range_of_spans = Vec::with_capacity(1);
|
// let mut vl_range_of_spans = Vec::with_capacity(1);
|
||||||
let mut visual_lines: Vec<VisualLine> = {
|
let mut visual_lines = mem::take(&mut scratch.visual_lines);
|
||||||
let mut visual_lines = mem::take(&mut scratch.visual_lines);
|
let mut cached_visual_lines = mem::take(&mut scratch.cached_visual_lines);
|
||||||
visual_lines.clear();
|
cached_visual_lines.clear();
|
||||||
visual_lines
|
cached_visual_lines.extend(visual_lines.drain(..).map(|mut l| {
|
||||||
};
|
l.clear();
|
||||||
|
l
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Cache glyph sets in reverse order so they will ideally be reused in exactly the same lines.
|
||||||
|
let mut cached_glyph_sets = mem::take(&mut scratch.glyph_sets);
|
||||||
|
cached_glyph_sets.clear();
|
||||||
|
cached_glyph_sets.extend(layout_lines.drain(..).rev().map(|mut v| {
|
||||||
|
v.glyphs.clear();
|
||||||
|
v.glyphs
|
||||||
|
}));
|
||||||
|
|
||||||
fn add_to_visual_line(
|
fn add_to_visual_line(
|
||||||
vl: &mut VisualLine,
|
vl: &mut VisualLine,
|
||||||
|
|
@ -1056,7 +1220,7 @@ impl ShapeLine {
|
||||||
// If one span is too large, this variable will hold the range of words inside that span
|
// If one span is too large, this variable will hold the range of words inside that span
|
||||||
// that fits on a line.
|
// that fits on a line.
|
||||||
// let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
|
// let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
|
||||||
let mut current_visual_line = VisualLine::default();
|
let mut current_visual_line = cached_visual_lines.pop().unwrap_or_default();
|
||||||
|
|
||||||
if wrap == Wrap::None {
|
if wrap == Wrap::None {
|
||||||
for (span_index, span) in self.spans.iter().enumerate() {
|
for (span_index, span) in self.spans.iter().enumerate() {
|
||||||
|
|
@ -1127,7 +1291,7 @@ impl ShapeLine {
|
||||||
);
|
);
|
||||||
|
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line = cached_visual_lines.pop().unwrap_or_default();
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
|
|
@ -1152,7 +1316,8 @@ impl ShapeLine {
|
||||||
number_of_blanks,
|
number_of_blanks,
|
||||||
);
|
);
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line =
|
||||||
|
cached_visual_lines.pop().unwrap_or_default();
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
word_range_width = glyph_width;
|
word_range_width = glyph_width;
|
||||||
|
|
@ -1192,7 +1357,7 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line = cached_visual_lines.pop().unwrap_or_default();
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1251,7 +1416,7 @@ impl ShapeLine {
|
||||||
);
|
);
|
||||||
|
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line = cached_visual_lines.pop().unwrap_or_default();
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
|
|
@ -1276,7 +1441,8 @@ impl ShapeLine {
|
||||||
number_of_blanks,
|
number_of_blanks,
|
||||||
);
|
);
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line =
|
||||||
|
cached_visual_lines.pop().unwrap_or_default();
|
||||||
|
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
word_range_width = glyph_width;
|
word_range_width = glyph_width;
|
||||||
|
|
@ -1314,7 +1480,7 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
current_visual_line = VisualLine::default();
|
current_visual_line = cached_visual_lines.pop().unwrap_or_default();
|
||||||
number_of_blanks = 0;
|
number_of_blanks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1341,6 +1507,9 @@ impl ShapeLine {
|
||||||
|
|
||||||
if !current_visual_line.ranges.is_empty() {
|
if !current_visual_line.ranges.is_empty() {
|
||||||
visual_lines.push(current_visual_line);
|
visual_lines.push(current_visual_line);
|
||||||
|
} else {
|
||||||
|
current_visual_line.clear();
|
||||||
|
cached_visual_lines.push(current_visual_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the LayoutLines using the ranges inside visual lines
|
// Create the LayoutLines using the ranges inside visual lines
|
||||||
|
|
@ -1371,7 +1540,9 @@ impl ShapeLine {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let new_order = self.reorder(&visual_line.ranges);
|
let new_order = self.reorder(&visual_line.ranges);
|
||||||
let mut glyphs = Vec::with_capacity(1);
|
let mut glyphs = cached_glyph_sets
|
||||||
|
.pop()
|
||||||
|
.unwrap_or_else(|| Vec::with_capacity(1));
|
||||||
let mut x = start_x;
|
let mut x = start_x;
|
||||||
let mut y = 0.;
|
let mut y = 0.;
|
||||||
let mut max_ascent: f32 = 0.;
|
let mut max_ascent: f32 = 0.;
|
||||||
|
|
@ -1534,5 +1705,8 @@ impl ShapeLine {
|
||||||
|
|
||||||
// Restore the buffer to the scratch set to prevent reallocations.
|
// Restore the buffer to the scratch set to prevent reallocations.
|
||||||
scratch.visual_lines = visual_lines;
|
scratch.visual_lines = visual_lines;
|
||||||
|
scratch.visual_lines.extend(cached_visual_lines.drain(..));
|
||||||
|
scratch.cached_visual_lines = cached_visual_lines;
|
||||||
|
scratch.glyph_sets = cached_glyph_sets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue