#[cfg(not(feature = "std"))] use alloc::{string::String, vec::Vec}; use crate::{ Align, AttrsList, FontSystem, LayoutLine, LineEnding, ShapeBuffer, ShapeLine, Shaping, Wrap, }; /// A line (or paragraph) of text that is shaped and laid out #[derive(Clone, Debug)] pub struct BufferLine { text: String, ending: LineEnding, attrs_list: AttrsList, align: Option, shape_opt: Option, layout_opt: Option>, shaping: Shaping, metadata: Option, } impl BufferLine { /// Create a new line with the given text and attributes list /// Cached shaping and layout can be done using the [`Self::shape`] and /// [`Self::layout`] functions pub fn new>( text: T, ending: LineEnding, attrs_list: AttrsList, shaping: Shaping, ) -> Self { Self { text: text.into(), ending, attrs_list, align: None, shape_opt: None, layout_opt: None, shaping, metadata: None, } } /// Get current text pub fn text(&self) -> &str { &self.text } /// Set text and attributes list /// /// Will reset shape and layout if it differs from current text and attributes list. /// Returns true if the line was reset pub fn set_text>( &mut self, text: T, ending: LineEnding, attrs_list: AttrsList, ) -> bool { let text = text.as_ref(); if text != self.text || ending != self.ending || attrs_list != self.attrs_list { self.text.clear(); self.text.push_str(text); self.ending = ending; self.attrs_list = attrs_list; self.reset(); true } else { false } } /// Consume this line, returning only its text contents as a String. pub fn into_text(self) -> String { self.text } /// Get line ending pub fn ending(&self) -> LineEnding { self.ending } /// Set line ending /// /// Will reset shape and layout if it differs from current line ending. /// Returns true if the line was reset pub fn set_ending(&mut self, ending: LineEnding) -> bool { if ending != self.ending { self.ending = ending; self.reset_shaping(); true } else { false } } /// Get attributes list pub fn attrs_list(&self) -> &AttrsList { &self.attrs_list } /// Set attributes list /// /// Will reset shape and layout if it differs from current attributes list. /// Returns true if the line was reset pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool { if attrs_list != self.attrs_list { self.attrs_list = attrs_list; self.reset_shaping(); true } else { false } } /// Get the Text alignment pub fn align(&self) -> Option { self.align } /// Set the text alignment /// /// Will reset layout if it differs from current alignment. /// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines. /// Returns true if the line was reset pub fn set_align(&mut self, align: Option) -> bool { if align != self.align { self.align = align; self.reset_layout(); true } else { false } } /// Append line at end of this line /// /// The wrap setting of the appended line will be lost pub fn append(&mut self, other: Self) { let len = self.text.len(); self.text.push_str(other.text()); if other.attrs_list.defaults() != self.attrs_list.defaults() { // If default formatting does not match, make a new span for it self.attrs_list .add_span(len..len + other.text().len(), other.attrs_list.defaults()); } for (other_range, attrs) in other.attrs_list.spans() { // Add previous attrs spans let range = other_range.start + len..other_range.end + len; self.attrs_list.add_span(range, attrs.as_attrs()); } self.reset(); } /// Split off new line at index pub fn split_off(&mut self, index: usize) -> Self { let text = self.text.split_off(index); let attrs_list = self.attrs_list.split_off(index); self.reset(); let mut new = Self::new(text, self.ending, attrs_list, self.shaping); new.align = self.align; new } /// Reset shaping, layout, and metadata caches pub fn reset(&mut self) { self.metadata = None; self.reset_shaping(); } /// Reset shaping and layout caches pub fn reset_shaping(&mut self) { self.shape_opt = None; self.reset_layout(); } /// Reset only layout cache pub fn reset_layout(&mut self) { self.layout_opt = None; } /// Shape line, will cache results pub fn shape(&mut self, font_system: &mut FontSystem) -> &ShapeLine { self.shape_in_buffer(&mut ShapeBuffer::default(), font_system) } /// Shape a line using a pre-existing shape buffer, will cache results pub fn shape_in_buffer( &mut self, scratch: &mut ShapeBuffer, font_system: &mut FontSystem, ) -> &ShapeLine { if self.shape_opt.is_none() { self.shape_opt = Some(ShapeLine::new_in_buffer( scratch, font_system, &self.text, &self.attrs_list, self.shaping, )); self.layout_opt = None; } self.shape_opt.as_ref().expect("shape not found") } /// Get line shaping cache pub fn shape_opt(&self) -> &Option { &self.shape_opt } /// Layout line, will cache results pub fn layout( &mut self, font_system: &mut FontSystem, font_size: f32, width: f32, wrap: Wrap, match_mono_width: Option, ) -> &[LayoutLine] { self.layout_in_buffer( &mut ShapeBuffer::default(), font_system, font_size, width, wrap, match_mono_width, ) } /// Layout a line using a pre-existing shape buffer, will cache results pub fn layout_in_buffer( &mut self, scratch: &mut ShapeBuffer, font_system: &mut FontSystem, font_size: f32, width: f32, wrap: Wrap, match_mono_width: Option, ) -> &[LayoutLine] { if self.layout_opt.is_none() { let align = self.align; let shape = self.shape_in_buffer(scratch, font_system); let mut layout = Vec::with_capacity(1); shape.layout_to_buffer( scratch, font_size, width, wrap, align, &mut layout, match_mono_width, ); self.layout_opt = Some(layout); } self.layout_opt.as_ref().expect("layout not found") } /// Get line layout cache pub fn layout_opt(&self) -> &Option> { &self.layout_opt } /// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called /// after the last reset of shaping and layout caches pub fn metadata(&self) -> Option { self.metadata } /// Set line metadata. This is stored until the next line reset pub fn set_metadata(&mut self, metadata: usize) { self.metadata = Some(metadata); } }