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