Skip to main content

cosmic_text/
buffer_line.rs

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