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),
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        // index represents a grapheme, not a glyph
354        // Let's find the first glyph for the given grapheme cluster
355        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", &paragraph.font)
403            .field("shaping", &paragraph.shaping)
404            .field("horizontal_alignment", &paragraph.align_x)
405            .field("vertical_alignment", &paragraph.align_y)
406            .field("bounds", &paragraph.bounds)
407            .field("min_bounds", &paragraph.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/// A weak reference to a [`Paragraph`].
444#[derive(Debug, Clone)]
445pub struct Weak {
446    raw: sync::Weak<Internal>,
447    /// The minimum bounds of the [`Paragraph`].
448    pub min_bounds: Size,
449    /// The horizontal alignment of the [`Paragraph`].
450    pub align_x: Alignment,
451    /// The vertical alignment of the [`Paragraph`].
452    pub align_y: alignment::Vertical,
453}
454
455impl Weak {
456    /// Tries to update the reference into a [`Paragraph`].
457    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}