1#[cfg(not(feature = "std"))]
2use alloc::{string::String, vec::Vec};
3use core::mem;
4
5use crate::{
6 Align, Attrs, AttrsList, Cached, FontSystem, Hinting, LayoutLine, LineEnding, ShapeLine,
7 Shaping, Wrap,
8};
9
10#[derive(Clone, Debug)]
12pub struct BufferLine {
13 text: String,
14 ending: LineEnding,
15 attrs_list: AttrsList,
16 align: Option<Align>,
17 shape_opt: Cached<ShapeLine>,
18 layout_opt: Cached<Vec<LayoutLine>>,
19 shaping: Shaping,
20 metadata: Option<usize>,
21}
22
23impl BufferLine {
24 pub fn new<T: Into<String>>(
28 text: T,
29 ending: LineEnding,
30 attrs_list: AttrsList,
31 shaping: Shaping,
32 ) -> Self {
33 Self {
34 text: text.into(),
35 ending,
36 attrs_list,
37 align: None,
38 shape_opt: Cached::Empty,
39 layout_opt: Cached::Empty,
40 shaping,
41 metadata: None,
42 }
43 }
44
45 pub fn reset_new<T: Into<String>>(
49 &mut self,
50 text: T,
51 ending: LineEnding,
52 attrs_list: AttrsList,
53 shaping: Shaping,
54 ) {
55 self.text = text.into();
56 self.ending = ending;
57 self.attrs_list = attrs_list;
58 self.align = None;
59 self.shape_opt.set_unused();
60 self.layout_opt.set_unused();
61 self.shaping = shaping;
62 self.metadata = None;
63 }
64
65 pub fn text(&self) -> &str {
67 &self.text
68 }
69
70 pub fn set_text<T: AsRef<str>>(
75 &mut self,
76 text: T,
77 ending: LineEnding,
78 attrs_list: AttrsList,
79 ) -> bool {
80 let text = text.as_ref();
81 if text != self.text || ending != self.ending || attrs_list != self.attrs_list {
82 self.text.clear();
83 self.text.push_str(text);
84 self.ending = ending;
85 self.attrs_list = attrs_list;
86 self.reset();
87 true
88 } else {
89 false
90 }
91 }
92
93 pub fn into_text(self) -> String {
95 self.text
96 }
97
98 pub const fn ending(&self) -> LineEnding {
100 self.ending
101 }
102
103 pub fn set_ending(&mut self, ending: LineEnding) -> bool {
108 if ending != self.ending {
109 self.ending = ending;
110 self.reset_shaping();
111 true
112 } else {
113 false
114 }
115 }
116
117 pub const fn attrs_list(&self) -> &AttrsList {
119 &self.attrs_list
120 }
121
122 pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool {
127 if attrs_list != self.attrs_list {
128 self.attrs_list = attrs_list;
129 self.reset_shaping();
130 true
131 } else {
132 false
133 }
134 }
135
136 pub const fn align(&self) -> Option<Align> {
138 self.align
139 }
140
141 pub fn set_align(&mut self, align: Option<Align>) -> bool {
147 if align != self.align {
148 self.align = align;
149 self.reset_layout();
150 true
151 } else {
152 false
153 }
154 }
155
156 pub fn append(&mut self, other: &Self) {
160 let len = self.text.len();
161 self.text.push_str(other.text());
162
163 self.ending = other.ending();
165
166 if other.attrs_list.defaults() != self.attrs_list.defaults() {
167 self.attrs_list
169 .add_span(len..len + other.text().len(), &other.attrs_list.defaults());
170 }
171
172 for (other_range, attrs) in other.attrs_list.spans_iter() {
173 let range = other_range.start + len..other_range.end + len;
175 self.attrs_list.add_span(range, &attrs.as_attrs());
176 }
177
178 self.reset();
179 }
180
181 pub fn split_off(&mut self, index: usize) -> Self {
183 let text = self.text.split_off(index);
184 let attrs_list = self.attrs_list.split_off(index);
185 self.reset();
186
187 let mut new = Self::new(text, self.ending, attrs_list, self.shaping);
188 self.ending = LineEnding::None;
190 new.align = self.align;
191 new
192 }
193
194 pub fn reset(&mut self) {
196 self.metadata = None;
197 self.reset_shaping();
198 }
199
200 pub fn reset_shaping(&mut self) {
202 self.shape_opt.set_unused();
203 self.reset_layout();
204 }
205
206 pub fn reset_layout(&mut self) {
208 self.layout_opt.set_unused();
209 }
210
211 #[allow(clippy::missing_panics_doc)]
213 pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine {
214 if self.shape_opt.is_unused() {
215 let mut line = self
216 .shape_opt
217 .take_unused()
218 .unwrap_or_else(ShapeLine::empty);
219 line.build(
220 font_system,
221 &self.text,
222 &self.attrs_list,
223 self.shaping,
224 tab_width,
225 );
226 self.shape_opt.set_used(line);
227 self.layout_opt.set_unused();
228 }
229 self.shape_opt.get().expect("shape not found")
230 }
231
232 pub const fn shape_opt(&self) -> Option<&ShapeLine> {
234 self.shape_opt.get()
235 }
236
237 #[allow(clippy::missing_panics_doc)]
239 pub fn layout(
240 &mut self,
241 font_system: &mut FontSystem,
242 font_size: f32,
243 width_opt: Option<f32>,
244 wrap: Wrap,
245 match_mono_width: Option<f32>,
246 tab_width: u16,
247 hinting: Hinting,
248 ) -> &[LayoutLine] {
249 if self.layout_opt.is_unused() {
250 let align = self.align;
251 let mut layout = self
252 .layout_opt
253 .take_unused()
254 .unwrap_or_else(|| Vec::with_capacity(1));
255 let shape = self.shape(font_system, tab_width);
256 shape.layout_to_buffer(
257 &mut font_system.shape_buffer,
258 font_size,
259 width_opt,
260 wrap,
261 align,
262 &mut layout,
263 match_mono_width,
264 hinting,
265 );
266 self.layout_opt.set_used(layout);
267 }
268 self.layout_opt.get().expect("layout not found")
269 }
270
271 pub const fn layout_opt(&self) -> Option<&Vec<LayoutLine>> {
273 self.layout_opt.get()
274 }
275
276 pub const fn metadata(&self) -> Option<usize> {
279 self.metadata
280 }
281
282 pub fn set_metadata(&mut self, metadata: usize) {
284 self.metadata = Some(metadata);
285 }
286
287 pub(crate) fn empty() -> Self {
291 Self {
292 text: String::default(),
293 ending: LineEnding::None,
294 attrs_list: AttrsList::new(&Attrs::new()),
295 align: None,
296 shape_opt: Cached::Empty,
297 layout_opt: Cached::Empty,
298 shaping: Shaping::Advanced,
299 metadata: None,
300 }
301 }
302
303 pub(crate) fn reclaim_attrs(&mut self) -> AttrsList {
307 mem::replace(&mut self.attrs_list, AttrsList::new(&Attrs::new()))
308 }
309
310 pub(crate) fn reclaim_text(&mut self) -> String {
314 let mut text = mem::take(&mut self.text);
315 text.clear();
316 text
317 }
318}