iced_graphics/text/
paragraph.rs

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