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, Ellipsize, FontSystem, Hinting, LayoutLine, LineEnding,
9    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    /// Will reset shape and layout if it differs from current attributes list.
127    /// Returns true if the line was reset
128    pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool {
129        if attrs_list != self.attrs_list {
130            self.attrs_list = attrs_list;
131            self.reset_shaping();
132            true
133        } else {
134            false
135        }
136    }
137
138    /// Get the Text alignment
139    pub const fn align(&self) -> Option<Align> {
140        self.align
141    }
142
143    /// Set the text alignment
144    ///
145    /// Will reset layout if it differs from current alignment.
146    /// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines.
147    /// Returns true if the line was reset
148    pub fn set_align(&mut self, align: Option<Align>) -> bool {
149        if align != self.align {
150            self.align = align;
151            self.reset_layout();
152            true
153        } else {
154            false
155        }
156    }
157
158    /// Append line at end of this line
159    ///
160    /// The wrap setting of the appended line will be lost
161    pub fn append(&mut self, other: &Self) {
162        let len = self.text.len();
163        self.text.push_str(other.text());
164
165        // To preserve line endings, we use the one from the other line
166        self.ending = other.ending();
167
168        if other.attrs_list.defaults() != self.attrs_list.defaults() {
169            // If default formatting does not match, make a new span for it
170            self.attrs_list
171                .add_span(len..len + other.text().len(), &other.attrs_list.defaults());
172        }
173
174        for (other_range, attrs) in other.attrs_list.spans_iter() {
175            // Add previous attrs spans
176            let range = other_range.start + len..other_range.end + len;
177            self.attrs_list.add_span(range, &attrs.as_attrs());
178        }
179
180        self.reset();
181    }
182
183    /// Split off new line at index
184    pub fn split_off(&mut self, index: usize) -> Self {
185        let text = self.text.split_off(index);
186        let attrs_list = self.attrs_list.split_off(index);
187        self.reset();
188
189        let mut new = Self::new(text, self.ending, attrs_list, self.shaping);
190        // To preserve line endings, it moves to the new line
191        self.ending = LineEnding::None;
192        new.align = self.align;
193        new
194    }
195
196    /// Reset shaping, layout, and metadata caches
197    pub fn reset(&mut self) {
198        self.metadata = None;
199        self.reset_shaping();
200    }
201
202    /// Reset shaping and layout caches
203    pub fn reset_shaping(&mut self) {
204        self.shape_opt.set_unused();
205        self.reset_layout();
206    }
207
208    /// Reset only layout cache
209    pub fn reset_layout(&mut self) {
210        self.layout_opt.set_unused();
211    }
212
213    /// Shape line, will cache results
214    #[allow(clippy::missing_panics_doc)]
215    pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine {
216        if self.shape_opt.is_unused() {
217            let mut line = self
218                .shape_opt
219                .take_unused()
220                .unwrap_or_else(ShapeLine::empty);
221            line.build(
222                font_system,
223                &self.text,
224                &self.attrs_list,
225                self.shaping,
226                tab_width,
227            );
228            self.shape_opt.set_used(line);
229            self.layout_opt.set_unused();
230        }
231        self.shape_opt.get().expect("shape not found")
232    }
233
234    /// Get line shaping cache
235    pub const fn shape_opt(&self) -> Option<&ShapeLine> {
236        self.shape_opt.get()
237    }
238
239    /// Layout line, will cache results
240    #[allow(clippy::missing_panics_doc)]
241    pub fn layout(
242        &mut self,
243        font_system: &mut FontSystem,
244        font_size: f32,
245        width_opt: Option<f32>,
246        wrap: Wrap,
247        ellipsize: Ellipsize,
248        match_mono_width: Option<f32>,
249        tab_width: u16,
250        hinting: Hinting,
251    ) -> &[LayoutLine] {
252        if self.layout_opt.is_unused() {
253            let align = self.align;
254            let mut layout = self
255                .layout_opt
256                .take_unused()
257                .unwrap_or_else(|| Vec::with_capacity(1));
258            let shape = self.shape(font_system, tab_width);
259            shape.layout_to_buffer(
260                &mut font_system.shape_buffer,
261                font_size,
262                width_opt,
263                wrap,
264                ellipsize,
265                align,
266                &mut layout,
267                match_mono_width,
268                hinting,
269            );
270            self.layout_opt.set_used(layout);
271        }
272        self.layout_opt.get().expect("layout not found")
273    }
274
275    /// Get line layout cache
276    pub const fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
277        self.layout_opt.get()
278    }
279
280    /// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
281    /// after the last reset of shaping and layout caches
282    pub const fn metadata(&self) -> Option<usize> {
283        self.metadata
284    }
285
286    /// Set line metadata. This is stored until the next line reset
287    pub fn set_metadata(&mut self, metadata: usize) {
288        self.metadata = Some(metadata);
289    }
290
291    /// Makes an empty buffer line.
292    ///
293    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
294    pub(crate) fn empty() -> Self {
295        Self {
296            text: String::default(),
297            ending: LineEnding::None,
298            attrs_list: AttrsList::new(&Attrs::new()),
299            align: None,
300            shape_opt: Cached::Empty,
301            layout_opt: Cached::Empty,
302            shaping: Shaping::Advanced,
303            metadata: None,
304        }
305    }
306
307    /// Reclaim attributes list memory that isn't needed any longer.
308    ///
309    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
310    pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
311        mem::replace(&mut self.attrs_list, AttrsList::new(&Attrs::new()))
312    }
313
314    /// Reclaim text memory that isn't needed any longer.
315    ///
316    /// The buffer line is in an invalid state after this is called. See [`Self::reset_new`].
317    pub(crate) fn reclaim_text(&mut self) -> String {
318        let mut text = mem::take(&mut self.text);
319        text.clear();
320        text
321    }
322}