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#[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 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 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 pub fn text(&self) -> &str {
69 &self.text
70 }
71
72 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 pub fn into_text(self) -> String {
97 self.text
98 }
99
100 pub const fn ending(&self) -> LineEnding {
102 self.ending
103 }
104
105 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 pub const fn attrs_list(&self) -> &AttrsList {
121 &self.attrs_list
122 }
123
124 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 pub const fn align(&self) -> Option<Align> {
146 self.align
147 }
148
149 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 pub fn append(&mut self, other: &Self) {
168 let len = self.text.len();
169 self.text.push_str(other.text());
170
171 self.ending = other.ending();
173
174 if other.attrs_list.defaults() != self.attrs_list.defaults() {
175 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 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 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 self.ending = LineEnding::None;
198 new.align = self.align;
199 new
200 }
201
202 pub fn reset(&mut self) {
204 self.metadata = None;
205 self.reset_shaping();
206 }
207
208 pub fn reset_shaping(&mut self) {
210 self.shape_opt.set_unused();
211 self.reset_layout();
212 }
213
214 pub fn reset_layout(&mut self) {
216 self.layout_opt.set_unused();
217 }
218
219 #[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 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 #[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 pub const fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
297 self.layout_opt.get()
298 }
299
300 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 pub const fn metadata(&self) -> Option<usize> {
308 self.metadata
309 }
310
311 pub fn set_metadata(&mut self, metadata: usize) {
313 self.metadata = Some(metadata);
314 }
315
316 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 pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
336 mem::replace(&mut self.attrs_list, AttrsList::new(&Attrs::new()))
337 }
338
339 pub(crate) fn reclaim_text(&mut self) -> String {
343 let mut text = mem::take(&mut self.text);
344 text.clear();
345 text
346 }
347}