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