1use crate::core;
3use crate::core::alignment;
4use crate::core::text::{Hit, Shaping, Span, Text, Wrapping};
5use crate::core::{Font, Point, Rectangle, Size};
6use crate::text;
7
8use std::fmt;
9use std::sync::{self, Arc};
10
11#[derive(Clone, PartialEq)]
13pub struct Paragraph(Arc<Internal>);
14
15#[derive(Clone)]
16struct Internal {
17 buffer: cosmic_text::Buffer,
18 font: Font,
19 shaping: Shaping,
20 wrapping: Wrapping,
21 horizontal_alignment: alignment::Horizontal,
22 vertical_alignment: alignment::Vertical,
23 bounds: Size,
24 min_bounds: Size,
25 version: text::Version,
26}
27
28impl Paragraph {
29 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn buffer(&self) -> &cosmic_text::Buffer {
36 &self.internal().buffer
37 }
38
39 pub fn downgrade(&self) -> Weak {
45 let paragraph = self.internal();
46
47 Weak {
48 raw: Arc::downgrade(paragraph),
49 min_bounds: paragraph.min_bounds,
50 horizontal_alignment: paragraph.horizontal_alignment,
51 vertical_alignment: paragraph.vertical_alignment,
52 }
53 }
54
55 fn internal(&self) -> &Arc<Internal> {
56 &self.0
57 }
58}
59
60impl core::text::Paragraph for Paragraph {
61 type Font = Font;
62
63 fn with_text(text: Text<&str>) -> Self {
64 log::trace!("Allocating plain paragraph: {}", text.content);
65
66 let mut font_system =
67 text::font_system().write().expect("Write font system");
68
69 let mut buffer = cosmic_text::Buffer::new(
70 font_system.raw(),
71 cosmic_text::Metrics::new(
72 text.size.into(),
73 text.line_height.to_absolute(text.size).into(),
74 ),
75 );
76
77 buffer.set_size(
78 font_system.raw(),
79 Some(text.bounds.width),
80 Some(text.bounds.height),
81 );
82
83 buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
84
85 buffer.set_text(
86 font_system.raw(),
87 text.content,
88 &text::to_attributes(text.font),
89 text::to_shaping(text.shaping),
90 );
91
92 let min_bounds = text::measure(&buffer);
93
94 Self(Arc::new(Internal {
95 buffer,
96 font: text.font,
97 horizontal_alignment: text.horizontal_alignment,
98 vertical_alignment: text.vertical_alignment,
99 shaping: text.shaping,
100 wrapping: text.wrapping,
101 bounds: text.bounds,
102 min_bounds,
103 version: font_system.version(),
104 }))
105 }
106
107 fn with_spans<Link>(text: Text<&[Span<'_, Link>]>) -> Self {
108 log::trace!("Allocating rich paragraph: {} spans", text.content.len());
109
110 let mut font_system =
111 text::font_system().write().expect("Write font system");
112
113 let mut buffer = cosmic_text::Buffer::new(
114 font_system.raw(),
115 cosmic_text::Metrics::new(
116 text.size.into(),
117 text.line_height.to_absolute(text.size).into(),
118 ),
119 );
120
121 buffer.set_size(
122 font_system.raw(),
123 Some(text.bounds.width),
124 Some(text.bounds.height),
125 );
126
127 buffer.set_rich_text(
128 font_system.raw(),
129 text.content.iter().enumerate().map(|(i, span)| {
130 let attrs = text::to_attributes(span.font.unwrap_or(text.font));
131
132 let attrs = match (span.size, span.line_height) {
133 (None, None) => attrs,
134 _ => {
135 let size = span.size.unwrap_or(text.size);
136
137 attrs.metrics(cosmic_text::Metrics::new(
138 size.into(),
139 span.line_height
140 .unwrap_or(text.line_height)
141 .to_absolute(size)
142 .into(),
143 ))
144 }
145 };
146
147 let attrs = if let Some(color) = span.color {
148 attrs.color(text::to_color(color))
149 } else {
150 attrs
151 };
152
153 (span.text.as_ref(), attrs.metadata(i))
154 }),
155 &text::to_attributes(text.font),
156 text::to_shaping(text.shaping),
157 Some(match text.horizontal_alignment {
158 alignment::Horizontal::Left => cosmic_text::Align::Left,
159 alignment::Horizontal::Center => cosmic_text::Align::Center,
160 alignment::Horizontal::Right => cosmic_text::Align::Right,
161 }),
162 );
163
164 let min_bounds = text::measure(&buffer);
165
166 Self(Arc::new(Internal {
167 buffer,
168 font: text.font,
169 horizontal_alignment: text.horizontal_alignment,
170 vertical_alignment: text.vertical_alignment,
171 shaping: text.shaping,
172 wrapping: text.wrapping,
173 bounds: text.bounds,
174 min_bounds,
175 version: font_system.version(),
176 }))
177 }
178
179 fn resize(&mut self, new_bounds: Size) {
180 let paragraph = Arc::make_mut(&mut self.0);
181
182 let mut font_system =
183 text::font_system().write().expect("Write font system");
184
185 paragraph.buffer.set_size(
186 font_system.raw(),
187 Some(new_bounds.width),
188 Some(new_bounds.height),
189 );
190
191 paragraph.bounds = new_bounds;
192 paragraph.min_bounds = text::measure(¶graph.buffer);
193 }
194
195 fn compare(&self, text: Text<()>) -> core::text::Difference {
196 let font_system = text::font_system().read().expect("Read font system");
197 let paragraph = self.internal();
198 let metrics = paragraph.buffer.metrics();
199
200 if paragraph.version != font_system.version
201 || metrics.font_size != text.size.0
202 || metrics.line_height != text.line_height.to_absolute(text.size).0
203 || paragraph.font != text.font
204 || paragraph.shaping != text.shaping
205 || paragraph.wrapping != text.wrapping
206 || paragraph.horizontal_alignment != text.horizontal_alignment
207 || paragraph.vertical_alignment != text.vertical_alignment
208 {
209 core::text::Difference::Shape
210 } else if paragraph.bounds != text.bounds {
211 core::text::Difference::Bounds
212 } else {
213 core::text::Difference::None
214 }
215 }
216
217 fn horizontal_alignment(&self) -> alignment::Horizontal {
218 self.internal().horizontal_alignment
219 }
220
221 fn vertical_alignment(&self) -> alignment::Vertical {
222 self.internal().vertical_alignment
223 }
224
225 fn min_bounds(&self) -> Size {
226 self.internal().min_bounds
227 }
228
229 fn hit_test(&self, point: Point) -> Option<Hit> {
230 let cursor = self.internal().buffer.hit(point.x, point.y)?;
231
232 Some(Hit::CharOffset(cursor.index))
233 }
234
235 fn hit_span(&self, point: Point) -> Option<usize> {
236 let internal = self.internal();
237
238 let cursor = internal.buffer.hit(point.x, point.y)?;
239 let line = internal.buffer.lines.get(cursor.line)?;
240
241 let mut last_glyph = None;
242 let mut glyphs = line
243 .layout_opt()
244 .as_ref()?
245 .iter()
246 .flat_map(|line| line.glyphs.iter())
247 .peekable();
248
249 while let Some(glyph) = glyphs.peek() {
250 if glyph.start <= cursor.index && cursor.index < glyph.end {
251 break;
252 }
253
254 last_glyph = glyphs.next();
255 }
256
257 let glyph = match cursor.affinity {
258 cosmic_text::Affinity::Before => last_glyph,
259 cosmic_text::Affinity::After => glyphs.next(),
260 }?;
261
262 Some(glyph.metadata)
263 }
264
265 fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
266 let internal = self.internal();
267
268 let mut bounds = Vec::new();
269 let mut current_bounds = None;
270
271 let glyphs = internal
272 .buffer
273 .layout_runs()
274 .flat_map(|run| {
275 let line_top = run.line_top;
276 let line_height = run.line_height;
277
278 run.glyphs
279 .iter()
280 .map(move |glyph| (line_top, line_height, glyph))
281 })
282 .skip_while(|(_, _, glyph)| glyph.metadata != index)
283 .take_while(|(_, _, glyph)| glyph.metadata == index);
284
285 for (line_top, line_height, glyph) in glyphs {
286 let y = line_top + glyph.y;
287
288 let new_bounds = || {
289 Rectangle::new(
290 Point::new(glyph.x, y),
291 Size::new(
292 glyph.w,
293 glyph.line_height_opt.unwrap_or(line_height),
294 ),
295 )
296 };
297
298 match current_bounds.as_mut() {
299 None => {
300 current_bounds = Some(new_bounds());
301 }
302 Some(current_bounds) if y != current_bounds.y => {
303 bounds.push(*current_bounds);
304 *current_bounds = new_bounds();
305 }
306 Some(current_bounds) => {
307 current_bounds.width += glyph.w;
308 }
309 }
310 }
311
312 bounds.extend(current_bounds);
313 bounds
314 }
315
316 fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
317 use unicode_segmentation::UnicodeSegmentation;
318
319 let run = self.internal().buffer.layout_runs().nth(line)?;
320
321 let mut last_start = None;
324 let mut last_grapheme_count = 0;
325 let mut graphemes_seen = 0;
326
327 let glyph = run
328 .glyphs
329 .iter()
330 .find(|glyph| {
331 if Some(glyph.start) != last_start {
332 last_grapheme_count = run.text[glyph.start..glyph.end]
333 .graphemes(false)
334 .count();
335 last_start = Some(glyph.start);
336 graphemes_seen += last_grapheme_count;
337 }
338
339 graphemes_seen >= index
340 })
341 .or_else(|| run.glyphs.last())?;
342
343 let advance = if index == 0 {
344 0.0
345 } else {
346 glyph.w
347 * (1.0
348 - graphemes_seen.saturating_sub(index) as f32
349 / last_grapheme_count.max(1) as f32)
350 };
351
352 Some(Point::new(
353 glyph.x + glyph.x_offset * glyph.font_size + advance,
354 glyph.y - glyph.y_offset * glyph.font_size,
355 ))
356 }
357}
358
359impl Default for Paragraph {
360 fn default() -> Self {
361 Self(Arc::new(Internal::default()))
362 }
363}
364
365impl fmt::Debug for Paragraph {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 let paragraph = self.internal();
368
369 f.debug_struct("Paragraph")
370 .field("font", ¶graph.font)
371 .field("shaping", ¶graph.shaping)
372 .field("horizontal_alignment", ¶graph.horizontal_alignment)
373 .field("vertical_alignment", ¶graph.vertical_alignment)
374 .field("bounds", ¶graph.bounds)
375 .field("min_bounds", ¶graph.min_bounds)
376 .finish()
377 }
378}
379
380impl PartialEq for Internal {
381 fn eq(&self, other: &Self) -> bool {
382 self.font == other.font
383 && self.shaping == other.shaping
384 && self.horizontal_alignment == other.horizontal_alignment
385 && self.vertical_alignment == other.vertical_alignment
386 && self.bounds == other.bounds
387 && self.min_bounds == other.min_bounds
388 && self.buffer.metrics() == other.buffer.metrics()
389 }
390}
391
392impl Default for Internal {
393 fn default() -> Self {
394 Self {
395 buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
396 font_size: 1.0,
397 line_height: 1.0,
398 }),
399 font: Font::default(),
400 shaping: Shaping::default(),
401 wrapping: Wrapping::default(),
402 horizontal_alignment: alignment::Horizontal::Left,
403 vertical_alignment: alignment::Vertical::Top,
404 bounds: Size::ZERO,
405 min_bounds: Size::ZERO,
406 version: text::Version::default(),
407 }
408 }
409}
410
411#[derive(Debug, Clone)]
413pub struct Weak {
414 raw: sync::Weak<Internal>,
415 pub min_bounds: Size,
417 pub horizontal_alignment: alignment::Horizontal,
419 pub vertical_alignment: alignment::Vertical,
421}
422
423impl Weak {
424 pub fn upgrade(&self) -> Option<Paragraph> {
426 self.raw.upgrade().map(Paragraph)
427 }
428}
429
430impl PartialEq for Weak {
431 fn eq(&self, other: &Self) -> bool {
432 match (self.raw.upgrade(), other.raw.upgrade()) {
433 (Some(p1), Some(p2)) => p1 == p2,
434 _ => false,
435 }
436 }
437}