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, LayoutRunIter,
9 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 {
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 pub const fn align(&self) -> Option<Align> {
140 self.align
141 }
142
143 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 pub fn append(&mut self, other: &Self) {
162 let len = self.text.len();
163 self.text.push_str(other.text());
164
165 self.ending = other.ending();
167
168 if other.attrs_list.defaults() != self.attrs_list.defaults() {
169 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 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 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 self.ending = LineEnding::None;
192 new.align = self.align;
193 new
194 }
195
196 pub fn reset(&mut self) {
198 self.metadata = None;
199 self.reset_shaping();
200 }
201
202 pub fn reset_shaping(&mut self) {
204 self.shape_opt.set_unused();
205 self.reset_layout();
206 }
207
208 pub fn reset_layout(&mut self) {
210 self.layout_opt.set_unused();
211 }
212
213 #[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 pub const fn shape_opt(&self) -> Option<&ShapeLine> {
236 self.shape_opt.get()
237 }
238
239 pub const fn needs_reshaping(&self) -> bool {
240 self.shape_opt.is_invalidated() || self.layout_opt.is_invalidated()
241 }
242
243 #[allow(clippy::missing_panics_doc)]
245 pub fn layout(
246 &mut self,
247 font_system: &mut FontSystem,
248 font_size: f32,
249 width_opt: Option<f32>,
250 wrap: Wrap,
251 ellipsize: Ellipsize,
252 match_mono_width: Option<f32>,
253 tab_width: u16,
254 hinting: Hinting,
255 ) -> &[LayoutLine] {
256 if self.layout_opt.is_unused() {
257 let align = self.align;
258 let mut layout = self
259 .layout_opt
260 .take_unused()
261 .unwrap_or_else(|| Vec::with_capacity(1));
262 let shape = self.shape(font_system, tab_width);
263 shape.layout_to_buffer(
264 &mut font_system.shape_buffer,
265 font_size,
266 width_opt,
267 wrap,
268 ellipsize,
269 align,
270 &mut layout,
271 match_mono_width,
272 hinting,
273 );
274 self.layout_opt.set_used(layout);
275 }
276 self.layout_opt.get().expect("layout not found")
277 }
278
279 pub const fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
281 self.layout_opt.get()
282 }
283
284 pub fn layout_runs(&self, height_opt: Option<f32>, line_height: f32) -> LayoutRunIter<'_> {
286 LayoutRunIter::from_lines(core::slice::from_ref(self), height_opt, line_height, 0.0, 0)
287 }
288
289 pub const fn metadata(&self) -> Option<usize> {
292 self.metadata
293 }
294
295 pub fn set_metadata(&mut self, metadata: usize) {
297 self.metadata = Some(metadata);
298 }
299
300 pub(crate) fn empty() -> Self {
304 Self {
305 text: String::default(),
306 ending: LineEnding::None,
307 attrs_list: AttrsList::new(&Attrs::new()),
308 align: None,
309 shape_opt: Cached::Empty,
310 layout_opt: Cached::Empty,
311 shaping: Shaping::Advanced,
312 metadata: None,
313 }
314 }
315
316 pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
320 mem::replace(&mut self.attrs_list, AttrsList::new(&Attrs::new()))
321 }
322
323 pub(crate) fn reclaim_text(&mut self) -> String {
327 let mut text = mem::take(&mut self.text);
328 text.clear();
329 text
330 }
331}