Skip to main content

motion_content/text/
drawable.rs

1//! Glyph drawables and `Drawable` rendering for shaped text.
2
3use super::*;
4
5impl Text {
6    pub(super) fn glyph_fill(&self, glyph: &TextGlyph) -> Paint {
7        self.paragraph
8            .fills
9            .get(glyph.span)
10            .and_then(Option::as_ref)
11            .unwrap_or(&self.fill)
12            .clone()
13    }
14
15    /// Glyphs whose owning part is within the visible count, with source indices.
16    pub(super) fn visible_glyphs<'a>(
17        &self,
18        layout: &'a ParagraphLayout,
19    ) -> Vec<(usize, &'a TextGlyph)> {
20        let parts = layout.parts(self.split);
21        layout
22            .glyphs
23            .iter()
24            .enumerate()
25            .filter(|(_, glyph)| {
26                let owner = parts.iter().position(|part| part.owns(glyph)).unwrap_or(0);
27                self.visible.is_none_or(|visible| owner < visible)
28            })
29            .collect()
30    }
31
32    pub(super) fn ink_bounds(&self, layout: &ParagraphLayout) -> Rect {
33        let margin = self.stroke.as_ref().map_or(0.0, |(width, _)| width * 2.0);
34        Rect::new(
35            layout.bounds.left - margin,
36            layout.bounds.top - margin,
37            layout.bounds.right + margin,
38            layout.bounds.bottom + margin,
39        )
40    }
41
42    fn glyph_drawable(&self, glyph: &TextGlyph) -> GlyphDrawable {
43        GlyphDrawable {
44            artwork: glyph.artwork.clone(),
45            transform: glyph.transform,
46            bounds: glyph.bounds,
47            fill: self.glyph_fill(glyph),
48            stroke: self.stroke.clone(),
49        }
50    }
51
52    pub(super) fn glyph_node(&self, glyph: &TextGlyph) -> Node {
53        if glyph.artwork.path.verbs().is_empty()
54            && glyph.artwork.layers.is_empty()
55            && glyph.artwork.bitmap.is_none()
56        {
57            return Node::empty();
58        }
59        Node::from(self.glyph_drawable(glyph))
60    }
61}
62
63#[derive(Clone)]
64struct GlyphDrawable {
65    artwork: Arc<glyph::Artwork>,
66    transform: Transform,
67    bounds: Rect,
68    fill: Paint,
69    stroke: Option<(f64, Paint)>,
70}
71
72impl Drawable for GlyphDrawable {
73    fn bounds(&self, _: &DrawContext) -> Result<Rect, ValidationError> {
74        let margin = self.stroke.as_ref().map_or(0.0, |(width, _)| width * 2.0);
75        Ok(Rect::new(
76            self.bounds.left - margin,
77            self.bounds.top - margin,
78            self.bounds.right + margin,
79            self.bounds.bottom + margin,
80        ))
81    }
82
83    fn draw(&self, context: &DrawContext, canvas: &mut Canvas<'_>) -> Result<(), ValidationError> {
84        context.font_usage().record(self.artwork.font.clone());
85        if !self.artwork.path.verbs().is_empty() {
86            canvas.fill_path(
87                self.artwork.path.clone(),
88                self.transform,
89                self.fill.clone(),
90                FillRule::NonZero,
91            )?;
92        }
93        for (path, color) in &self.artwork.layers {
94            canvas.fill_path(
95                path.clone(),
96                self.transform,
97                color.map_or_else(|| self.fill.clone(), Paint::from),
98                FillRule::NonZero,
99            )?;
100        }
101        if let Some((image, local)) = &self.artwork.bitmap {
102            let pattern = motion_core::ImagePattern::from_asset(image.clone()).transform(*local);
103            let bounds = motion_core::display_list::mapped_bounds(
104                Rect::new(
105                    0.0,
106                    0.0,
107                    f64::from(image.width()),
108                    f64::from(image.height()),
109                ),
110                *local,
111                "text.bitmap",
112            )?;
113            canvas.fill_path(
114                Arc::new(motion_core::Path2D::from_rect(bounds)?),
115                self.transform,
116                pattern,
117                FillRule::NonZero,
118            )?;
119        }
120        if let Some((width, paint)) = &self.stroke {
121            for path in std::iter::once(&self.artwork.path)
122                .chain(self.artwork.layers.iter().map(|(path, _)| path))
123                .filter(|path| !path.verbs().is_empty())
124            {
125                canvas.stroke(
126                    &path.transformed(&self.transform)?,
127                    &motion_core::StrokeStyle {
128                        width: *width,
129                        ..motion_core::StrokeStyle::default()
130                    },
131                    paint.clone(),
132                )?;
133            }
134        }
135        Ok(())
136    }
137}
138
139impl Text {
140    pub(crate) fn is_deferred(&self) -> bool {
141        !matches!(self.reveal, Reveal::None)
142    }
143}
144
145impl DeferredContent for Text {
146    fn resolve(&self, context: &DrawContext) -> Result<Node, ValidationError> {
147        match &self.reveal {
148            Reveal::None => Ok(Node::from(TextRun(self.clone()))),
149            Reveal::Write(progress) => {
150                if !progress.is_finite() {
151                    return Err(invalid("progress", "Text write progress must be finite"));
152                }
153                self.write_with_context(*progress, context)
154            }
155            Reveal::Parts(animation) => {
156                self.animate_parts_resolved(context, animation.delay, animation.style.as_ref())
157            }
158        }
159    }
160
161    fn baseline(&self, context: &DrawContext) -> Option<f64> {
162        self.resolve(context).ok().map(|layout| layout.baseline)
163    }
164}
165
166impl From<Text> for Node {
167    fn from(text: Text) -> Node {
168        if text.is_deferred() {
169            Node::deferred(text)
170        } else {
171            Node::from(TextRun(text))
172        }
173    }
174}
175
176/// Static text laid out and drawn as a leaf.
177struct TextRun(Text);
178
179impl Drawable for TextRun {
180    fn bounds(&self, context: &DrawContext) -> Result<Rect, ValidationError> {
181        let layout = self.0.resolve(context)?;
182        Ok(self.0.ink_bounds(&layout))
183    }
184
185    fn baseline(&self, context: &DrawContext) -> Option<f64> {
186        self.0.resolve(context).ok().map(|layout| layout.baseline)
187    }
188
189    fn draw(&self, context: &DrawContext, canvas: &mut Canvas<'_>) -> Result<(), ValidationError> {
190        let layout = self.0.resolve(context)?;
191        for (_, glyph) in self.0.visible_glyphs(&layout) {
192            self.0.glyph_drawable(glyph).draw(context, canvas)?;
193        }
194        Ok(())
195    }
196}