cosmic_text/
buffer_line.rs

1#[cfg(not(feature = "std"))]
2use alloc::{string::String, vec::Vec};
3use core::mem;
4
5use crate::{
6    Align, Attrs, AttrsList, Cached, FontSystem, LayoutLine, LineEnding, ShapeLine, Shaping, Wrap,
7};
8
9/// A line (or paragraph) of text that is shaped and laid out
10#[derive(Clone, Debug)]
11pub struct BufferLine {
12    text: String,
13    ending: LineEnding,
14    attrs_list: AttrsList,
15    align: Option<Align>,
16    shape_opt: Cached<ShapeLine>,
17    layout_opt: Cached<Vec<LayoutLine>>,
18    shaping: Shaping,
19    metadata: Option<usize>,
20}
21
22impl BufferLine {
23    /// Create a new line with the given text and attributes list
24    /// Cached shaping and layout can be done using the [`Self::shape`] and
25    /// [`Self::layout`] functions
26    pub fn new<T: Into<String>>(
27        text: T,
28        ending: LineEnding,
29        attrs_list: AttrsList,
30        shaping: Shaping,
31    ) -> Self {
32        Self {
33            text: text.into(),
34            ending,
35            attrs_list,
36            align: None,
37            shape_opt: Cached::Empty,
38            layout_opt: Cached::Empty,
39            shaping,
40            metadata: None,
41        }
42    }
43
44    /// Resets the current line with new internal values.
45    ///
46    /// Avoids deallocating internal caches so they can be reused.
47    pub fn reset_new<T: Into<String>>(
48        &mut self,
49        text: T,
50        ending: LineEnding,
51        attrs_list: AttrsList,
52        shaping: Shaping,
53    ) {
54        self.text = text.into();
55        self.ending = ending;
56        self.attrs_list = attrs_list;
57        self.align = None;
58        self.shape_opt.set_unused();
59        self.layout_opt.set_unused();
60        self.shaping = shaping;
61        self.metadata = None;
62    }
63
64    /// Get current text
65    pub fn text(&self) -> &str {
66        &self.text
67    }
68
69    /// Set text and attributes list
70    ///
71    /// Will reset shape and layout if it differs from current text and attributes list.
72    /// Returns true if the line was reset
73    pub fn set_text<T: AsRef<str>>(
74        &mut self,
75        text: T,
76        ending: LineEnding,
77        attrs_list: AttrsList,
78    ) -> bool {
79        let text = text.as_ref();
80        if text != self.text || ending != self.ending || attrs_list != self.attrs_list {
81            self.text.clear();
82            self.text.push_str(text);
83            self.ending = ending;
84            self.attrs_list = attrs_list;
85            self.reset();
86            true
87        } else {
88            false
89        }
90    }
91
92    /// Consume this line, returning only its text contents as a String.
93    pub fn into_text(self) -> String {
94        self.text
95    }
96
97    /// Get line ending
98    pub fn ending(&self) -> LineEnding {
99        self.ending
100    }
101
102    /// Set line ending
103    ///
104    /// Will reset shape and layout if it differs from current line ending.
105    /// Returns true if the line was reset
106    pub fn set_ending(&mut self, ending: LineEnding) -> bool {
107        if ending != self.ending {
108            self.ending = ending;
109            self.reset_shaping();
110            true
111        } else {
112            false
113        }
114    }
115
116    /// Get attributes list
117    pub fn attrs_list(&self) -> &AttrsList {
118        &self.attrs_list
119    }
120
121    /// Set attributes list
122    ///
123    /// Will reset shape and layout if it differs from current attributes list.
124    /// Returns true if the line was reset
125    pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool {
126        if attrs_list != self.attrs_list {
127            self.attrs_list = attrs_list;
128            self.reset_shaping();
129            true
130        } else {
131            false
132        }
133    }
134
135    /// Get the Text alignment
136    pub fn align(&self) -> Option<Align> {
137        self.align
138    }
139
140    /// Set the text alignment
141    ///
142    /// Will reset layout if it differs from current alignment.
143    /// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines.
144    /// Returns true if the line was reset
145    pub fn set_align(&mut self, align: Option<Align>) -> bool {
146        if align != self.align {
147            self.align = align;
148            self.reset_layout();
149            true
150        } else {
151            false
152        }
153    }
154
155    /// Append line at end of this line
156    ///
157    /// The wrap setting of the appended line will be lost
158    pub fn append(&mut self, other: Self) {
159        let len = self.text.len();
160        self.text.push_str(other.text());
161
162        if other.attrs_list.defaults() != self.attrs_list.defaults() {
163            // If default formatting does not match, make a new span for it
164            self.attrs_list
165                .add_span(len..len + other.text().len(), other.attrs_list.defaults());
166        }
167
168        for (other_range, attrs) in other.attrs_list.spans_iter() {
169            // Add previous attrs spans
170            let range = other_range.start + len..other_range.end + len;
171            self.attrs_list.add_span(range, attrs.as_attrs());
172        }
173
174        self.reset();
175    }
176
177    /// Split off new line at index
178    pub fn split_off(&mut self, index: usize) -> Self {
179        let text = self.text.split_off(index);
180        let attrs_list = self.attrs_list.split_off(index);
181        self.reset();
182
183        let mut new = Self::new(text, self.ending, attrs_list, self.shaping);
184        new.align = self.align;
185        new
186    }
187
188    /// Reset shaping, layout, and metadata caches
189    pub fn reset(&mut self) {
190        self.metadata = None;
191        self.reset_shaping();
192    }
193
194    /// Reset shaping and layout caches
195    pub fn reset_shaping(&mut self) {
196        self.shape_opt.set_unused();
197        self.reset_layout();
198    }
199
200    /// Reset only layout cache
201    pub fn reset_layout(&mut self) {
202        self.layout_opt.set_unused();
203    }
204
205    /// Shape line, will cache results
206    #[allow(clippy::missing_panics_doc)]
207    pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine {
208        if self.shape_opt.is_unused() {
209            let mut line = self
210                .shape_opt
211                .take_unused()
212                .unwrap_or_else(ShapeLine::empty);
213            line.build(
214                font_system,
215                &self.text,
216                &self.attrs_list,
217                self.shaping,
218                tab_width,
219            );
220            self.shape_opt.set_used(line);
221            self.layout_opt.set_unused();
222        }
223        self.shape_opt.get().expect("shape not found")
224    }
225
226    /// Get line shaping cache
227    pub fn shape_opt(&self) -> Option<&ShapeLine> {
228        self.shape_opt.get()
229    }
230
231    /// Layout line, will cache results
232    #[allow(clippy::missing_panics_doc)]
233    pub fn layout(
234        &mut self,
235        font_system: &mut FontSystem,
236        font_size: f32,
237        width_opt: Option<f32>,
238        wrap: Wrap,
239        match_mono_width: Option<f32>,
240        tab_width: u16,
241    ) -> &[LayoutLine] {
242        if self.layout_opt.is_unused() {
243            let align = self.align;
244            let mut layout = self
245                .layout_opt
246                .take_unused()
247                .unwrap_or_else(|| Vec::with_capacity(1));
248            let shape = self.shape(font_system, tab_width);
249            shape.layout_to_buffer(
250                &mut font_system.shape_buffer,
251                font_size,
252                width_opt,
253                wrap,
254                align,
255                &mut layout,
256                match_mono_width,
257            );
258            self.layout_opt.set_used(layout);
259        }
260        self.layout_opt.get().expect("layout not found")
261    }
262
263    /// Get line layout cache
264    pub fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
265        self.layout_opt.get()
266    }
267
268    /// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
269    /// after the last reset of shaping and layout caches
270    pub fn metadata(&self) -> Option<usize> {
271        self.metadata
272    }
273
274    /// Set line metadata. This is stored until the next line reset
275    pub fn set_metadata(&mut self, metadata: usize) {
276        self.metadata = Some(metadata);
277    }
278
279    /// Makes an empty buffer line.
280    ///
281    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
282    pub(crate) fn empty() -> Self {
283        Self {
284            text: String::default(),
285            ending: LineEnding::default(),
286            attrs_list: AttrsList::new(Attrs::new()),
287            align: None,
288            shape_opt: Cached::Empty,
289            layout_opt: Cached::Empty,
290            shaping: Shaping::Advanced,
291            metadata: None,
292        }
293    }
294
295    /// Reclaim attributes list memory that isn't needed any longer.
296    ///
297    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
298    pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
299        mem::replace(&mut self.attrs_list, AttrsList::new(Attrs::new()))
300    }
301
302    /// Reclaim text memory that isn't needed any longer.
303    ///
304    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
305    pub(crate) fn reclaim_text(&mut self) -> String {
306        let mut text = mem::take(&mut self.text);
307        text.clear();
308        text
309    }
310}