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