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