1use crate::core;
3use crate::core::alignment;
4use crate::core::text::{Alignment, 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 align_x: Alignment,
22 align_y: 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 align_x: paragraph.align_x,
51 align_y: paragraph.align_y,
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 = align(&mut buffer, &mut font_system, text.align_x);
93
94 Self(Arc::new(Internal {
95 buffer,
96 font: text.font,
97 align_x: text.align_x,
98 align_y: text.align_y,
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_wrap(font_system.raw(), text::to_wrap(text.wrapping));
128
129 buffer.set_rich_text(
130 font_system.raw(),
131 text.content.iter().enumerate().map(|(i, span)| {
132 let attrs = text::to_attributes(span.font.unwrap_or(text.font));
133
134 let attrs = match (span.size, span.line_height) {
135 (None, None) => attrs,
136 _ => {
137 let size = span.size.unwrap_or(text.size);
138
139 attrs.metrics(cosmic_text::Metrics::new(
140 size.into(),
141 span.line_height
142 .unwrap_or(text.line_height)
143 .to_absolute(size)
144 .into(),
145 ))
146 }
147 };
148
149 let attrs = if let Some(color) = span.color {
150 attrs.color(text::to_color(color))
151 } else {
152 attrs
153 };
154
155 (span.text.as_ref(), attrs.metadata(i))
156 }),
157 &text::to_attributes(text.font),
158 text::to_shaping(text.shaping),
159 None,
160 );
161
162 let min_bounds = align(&mut buffer, &mut font_system, text.align_x);
163
164 Self(Arc::new(Internal {
165 buffer,
166 font: text.font,
167 align_x: text.align_x,
168 align_y: text.align_y,
169 shaping: text.shaping,
170 wrapping: text.wrapping,
171 bounds: text.bounds,
172 min_bounds,
173 version: font_system.version(),
174 }))
175 }
176
177 fn resize(&mut self, new_bounds: Size) {
178 let paragraph = Arc::make_mut(&mut self.0);
179
180 let mut font_system =
181 text::font_system().write().expect("Write font system");
182
183 paragraph.buffer.set_size(
184 font_system.raw(),
185 Some(new_bounds.width),
186 Some(new_bounds.height),
187 );
188
189 let min_bounds =
190 align(&mut paragraph.buffer, &mut font_system, paragraph.align_x);
191
192 paragraph.bounds = new_bounds;
193 paragraph.min_bounds = min_bounds;
194 }
195
196 fn compare(&self, text: Text<()>) -> core::text::Difference {
197 let font_system = text::font_system().read().expect("Read font system");
198 let paragraph = self.internal();
199 let metrics = paragraph.buffer.metrics();
200
201 if paragraph.version != font_system.version
202 || metrics.font_size != text.size.0
203 || metrics.line_height != text.line_height.to_absolute(text.size).0
204 || paragraph.font != text.font
205 || paragraph.shaping != text.shaping
206 || paragraph.wrapping != text.wrapping
207 || paragraph.align_x != text.align_x
208 || paragraph.align_y != text.align_y
209 {
210 core::text::Difference::Shape
211 } else if paragraph.bounds != text.bounds {
212 core::text::Difference::Bounds
213 } else {
214 core::text::Difference::None
215 }
216 }
217
218 fn align_x(&self) -> Alignment {
219 self.internal().align_x
220 }
221
222 fn align_y(&self) -> alignment::Vertical {
223 self.internal().align_y
224 }
225
226 fn min_bounds(&self) -> Size {
227 self.internal().min_bounds
228 }
229
230 fn hit_test(&self, point: Point) -> Option<Hit> {
231 let cursor = self.internal().buffer.hit(point.x, point.y)?;
232
233 Some(Hit::CharOffset(cursor.index))
234 }
235
236 fn hit_span(&self, point: Point) -> Option<usize> {
237 let internal = self.internal();
238
239 let cursor = internal.buffer.hit(point.x, point.y)?;
240 let line = internal.buffer.lines.get(cursor.line)?;
241
242 let mut last_glyph = None;
243 let mut glyphs = line
244 .layout_opt()
245 .as_ref()?
246 .iter()
247 .flat_map(|line| line.glyphs.iter())
248 .peekable();
249
250 while let Some(glyph) = glyphs.peek() {
251 if glyph.start <= cursor.index && cursor.index < glyph.end {
252 break;
253 }
254
255 last_glyph = glyphs.next();
256 }
257
258 let glyph = match cursor.affinity {
259 cosmic_text::Affinity::Before => last_glyph,
260 cosmic_text::Affinity::After => glyphs.next(),
261 }?;
262
263 Some(glyph.metadata)
264 }
265
266 fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
267 let internal = self.internal();
268
269 let mut bounds = Vec::new();
270 let mut current_bounds = None;
271
272 let glyphs = internal
273 .buffer
274 .layout_runs()
275 .flat_map(|run| {
276 let line_top = run.line_top;
277 let line_height = run.line_height;
278
279 run.glyphs
280 .iter()
281 .map(move |glyph| (line_top, line_height, glyph))
282 })
283 .skip_while(|(_, _, glyph)| glyph.metadata != index)
284 .take_while(|(_, _, glyph)| glyph.metadata == index);
285
286 for (line_top, line_height, glyph) in glyphs {
287 let y = line_top + glyph.y;
288
289 let new_bounds = || {
290 Rectangle::new(
291 Point::new(glyph.x, y),
292 Size::new(
293 glyph.w,
294 glyph.line_height_opt.unwrap_or(line_height),
295 ),
296 )
297 };
298
299 match current_bounds.as_mut() {
300 None => {
301 current_bounds = Some(new_bounds());
302 }
303 Some(current_bounds) if y != current_bounds.y => {
304 bounds.push(*current_bounds);
305 *current_bounds = new_bounds();
306 }
307 Some(current_bounds) => {
308 current_bounds.width += glyph.w;
309 }
310 }
311 }
312
313 bounds.extend(current_bounds);
314 bounds
315 }
316
317 fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
318 use unicode_segmentation::UnicodeSegmentation;
319
320 let run = self.internal().buffer.layout_runs().nth(line)?;
321
322 let mut last_start = None;
325 let mut last_grapheme_count = 0;
326 let mut graphemes_seen = 0;
327
328 let glyph = run
329 .glyphs
330 .iter()
331 .find(|glyph| {
332 if Some(glyph.start) != last_start {
333 last_grapheme_count = run.text[glyph.start..glyph.end]
334 .graphemes(false)
335 .count();
336 last_start = Some(glyph.start);
337 graphemes_seen += last_grapheme_count;
338 }
339
340 graphemes_seen >= index
341 })
342 .or_else(|| run.glyphs.last())?;
343
344 let advance = if index == 0 {
345 0.0
346 } else {
347 glyph.w
348 * (1.0
349 - graphemes_seen.saturating_sub(index) as f32
350 / last_grapheme_count.max(1) as f32)
351 };
352
353 Some(Point::new(
354 glyph.x + glyph.x_offset * glyph.font_size + advance,
355 glyph.y - glyph.y_offset * glyph.font_size,
356 ))
357 }
358}
359
360fn align(
361 buffer: &mut cosmic_text::Buffer,
362 font_system: &mut text::FontSystem,
363 alignment: Alignment,
364) -> Size {
365 let (min_bounds, has_rtl) = text::measure(buffer);
366 let mut needs_relayout = has_rtl;
367
368 if let Some(align) = text::to_align(alignment) {
369 let has_multiple_lines = buffer.lines.len() > 1
370 || buffer.lines.first().is_some_and(|line| {
371 line.layout_opt().is_some_and(|layout| layout.len() > 1)
372 });
373
374 if has_multiple_lines {
375 for line in &mut buffer.lines {
376 let _ = line.set_align(Some(align));
377 }
378
379 needs_relayout = true;
380 } else if let Some(line) = buffer.lines.first_mut() {
381 needs_relayout = line.set_align(None);
382 }
383 }
384
385 if needs_relayout {
387 log::trace!("Relayouting paragraph...");
388
389 buffer.set_size(
390 font_system.raw(),
391 Some(min_bounds.width),
392 Some(min_bounds.height),
393 );
394 }
395
396 min_bounds
397}
398
399impl Default for Paragraph {
400 fn default() -> Self {
401 Self(Arc::new(Internal::default()))
402 }
403}
404
405impl fmt::Debug for Paragraph {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 let paragraph = self.internal();
408
409 f.debug_struct("Paragraph")
410 .field("font", ¶graph.font)
411 .field("shaping", ¶graph.shaping)
412 .field("horizontal_alignment", ¶graph.align_x)
413 .field("vertical_alignment", ¶graph.align_y)
414 .field("bounds", ¶graph.bounds)
415 .field("min_bounds", ¶graph.min_bounds)
416 .finish()
417 }
418}
419
420impl PartialEq for Internal {
421 fn eq(&self, other: &Self) -> bool {
422 self.font == other.font
423 && self.shaping == other.shaping
424 && self.align_x == other.align_x
425 && self.align_y == other.align_y
426 && self.bounds == other.bounds
427 && self.min_bounds == other.min_bounds
428 && self.buffer.metrics() == other.buffer.metrics()
429 }
430}
431
432impl Default for Internal {
433 fn default() -> Self {
434 Self {
435 buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
436 font_size: 1.0,
437 line_height: 1.0,
438 }),
439 font: Font::default(),
440 shaping: Shaping::default(),
441 wrapping: Wrapping::default(),
442 align_x: Alignment::Default,
443 align_y: alignment::Vertical::Top,
444 bounds: Size::ZERO,
445 min_bounds: Size::ZERO,
446 version: text::Version::default(),
447 }
448 }
449}
450
451#[derive(Debug, Clone)]
453pub struct Weak {
454 raw: sync::Weak<Internal>,
455 pub min_bounds: Size,
457 pub align_x: Alignment,
459 pub align_y: alignment::Vertical,
461}
462
463impl Weak {
464 pub fn upgrade(&self) -> Option<Paragraph> {
466 self.raw.upgrade().map(Paragraph)
467 }
468}
469
470impl PartialEq for Weak {
471 fn eq(&self, other: &Self) -> bool {
472 match (self.raw.upgrade(), other.raw.upgrade()) {
473 (Some(p1), Some(p2)) => p1 == p2,
474 _ => false,
475 }
476 }
477}