iced_graphics/text/
paragraph.rs

1//! Draw paragraphs.
2use 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/// A bunch of text.
14#[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    /// Creates a new empty [`Paragraph`].
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Returns the buffer of the [`Paragraph`].
37    pub fn buffer(&self) -> &cosmic_text::Buffer {
38        &self.internal().buffer
39    }
40
41    /// Creates a [`Weak`] reference to the [`Paragraph`].
42    ///
43    /// This is useful to avoid cloning the [`Paragraph`] when
44    /// referential guarantees are unnecessary. For instance,
45    /// when creating a rendering tree.
46    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        // index represents a grapheme, not a glyph
355        // Let's find the first glyph for the given grapheme cluster
356        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", &paragraph.font)
404            .field("shaping", &paragraph.shaping)
405            .field("horizontal_alignment", &paragraph.align_x)
406            .field("vertical_alignment", &paragraph.align_y)
407            .field("bounds", &paragraph.bounds)
408            .field("min_bounds", &paragraph.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/// A weak reference to a [`Paragraph`].
445#[derive(Debug, Clone)]
446pub struct Weak {
447    raw: sync::Weak<Internal>,
448    /// The minimum bounds of the [`Paragraph`].
449    pub min_bounds: Size,
450    /// The horizontal alignment of the [`Paragraph`].
451    pub align_x: Alignment,
452    /// The vertical alignment of the [`Paragraph`].
453    pub align_y: alignment::Vertical,
454}
455
456impl Weak {
457    /// Tries to update the reference into a [`Paragraph`].
458    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}