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#[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 #[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 pub const fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
277 self.layout_opt.get()
278 }
279
280 pub const fn metadata(&self) -> Option<usize> {
283 self.metadata
284 }
285
286 pub fn set_metadata(&mut self, metadata: usize) {
288 self.metadata = Some(metadata);
289 }
290
291 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 pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
311 mem::replace(&mut self.attrs_list, AttrsList::new(&Attrs::new()))
312 }
313
314 pub(crate) fn reclaim_text(&mut self) -> String {
318 let mut text = mem::take(&mut self.text);
319 text.clear();
320 text
321 }
322}