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