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