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_node(&self, glyph: &TextGlyph) -> Node {
7        let paint = self.spans[glyph.span]
8            .fill
9            .as_ref()
10            .unwrap_or(&self.fill)
11            .clone();
12        let mut node = Node::from(GlyphDrawable {
13            artwork: glyph.artwork.clone(),
14            transform: glyph.transform,
15            bounds: glyph.bounds,
16            fill: paint,
17            stroke: self.stroke.clone(),
18        });
19        if glyph.artwork.path.verbs().is_empty()
20            && glyph.artwork.layers.is_empty()
21            && glyph.artwork.bitmap.is_none()
22        {
23            node = Node::empty();
24        }
25        node
26    }
27}
28
29#[derive(Clone)]
30struct GlyphDrawable {
31    artwork: Arc<glyph::Artwork>,
32    transform: Transform,
33    bounds: Rect,
34    fill: Paint,
35    stroke: Option<(f64, Paint)>,
36}
37
38impl Drawable for GlyphDrawable {
39    fn bounds(&self, _: &DrawContext) -> Rect {
40        let margin = self.stroke.as_ref().map_or(0.0, |(width, _)| width * 2.0);
41        Rect::new(
42            self.bounds.left - margin,
43            self.bounds.top - margin,
44            self.bounds.right + margin,
45            self.bounds.bottom + margin,
46        )
47    }
48
49    fn draw(&self, context: &DrawContext, canvas: &mut Canvas<'_>) -> Result<(), ValidationError> {
50        context.font_usage().record(self.artwork.font.clone());
51        if !self.artwork.path.verbs().is_empty() {
52            canvas.fill_path(
53                self.artwork.path.clone(),
54                self.transform,
55                self.fill.clone(),
56                FillRule::NonZero,
57            )?;
58        }
59        for (path, color) in &self.artwork.layers {
60            canvas.fill_path(
61                path.clone(),
62                self.transform,
63                color.map_or_else(|| self.fill.clone(), Paint::from),
64                FillRule::NonZero,
65            )?;
66        }
67        if let Some((image, local)) = &self.artwork.bitmap {
68            let pattern = motion_core::ImagePattern::from_asset(image.clone()).transform(*local);
69            let bounds = motion_core::display_list::mapped_bounds(
70                Rect::new(
71                    0.0,
72                    0.0,
73                    f64::from(image.width()),
74                    f64::from(image.height()),
75                ),
76                *local,
77                "text.bitmap",
78            )?;
79            canvas.fill_path(
80                Arc::new(motion_core::Path2D::from_rect(bounds)?),
81                self.transform,
82                pattern,
83                FillRule::NonZero,
84            )?;
85        }
86        if let Some((width, paint)) = &self.stroke {
87            for path in std::iter::once(&self.artwork.path)
88                .chain(self.artwork.layers.iter().map(|(path, _)| path))
89                .filter(|path| !path.verbs().is_empty())
90            {
91                canvas.stroke(
92                    &path.transformed(&self.transform)?,
93                    &motion_core::StrokeStyle {
94                        width: *width,
95                        ..motion_core::StrokeStyle::default()
96                    },
97                    paint.clone(),
98                )?;
99            }
100        }
101        Ok(())
102    }
103}
104
105impl Drawable for Text {
106    fn resolve_node(&self, context: &DrawContext) -> Result<Option<Node>, ValidationError> {
107        if self.write_progress.is_none() && self.part_animation.is_none() {
108            return Ok(None);
109        }
110        let mut base = self.clone();
111        base.write_progress = None;
112        base.part_animation = None;
113        if let Some(animation) = &self.part_animation {
114            if self.write_progress.is_some() {
115                return Err(invalid(
116                    "effects",
117                    "Use separate Text nodes for simultaneous handwriting and part transforms",
118                ));
119            }
120            return base
121                .animate_parts_resolved(context, animation.delay, animation.style.as_ref())
122                .map(Some);
123        }
124        let Some(progress) = self.write_progress else {
125            return Ok(None);
126        };
127        if !progress.is_finite() {
128            return Err(invalid("progress", "Text write progress must be finite"));
129        }
130        base.write_with_context(progress, context).map(Some)
131    }
132
133    fn try_bounds(&self, context: &DrawContext) -> Result<Rect, ValidationError> {
134        if let Some(node) = self.resolve_node(context)? {
135            return node.measure_with(context, motion_core::ValidationLimits::default());
136        }
137        let layout = self.resolve(context)?;
138        let margin = self.stroke.as_ref().map_or(0.0, |(width, _)| width * 2.0);
139        Ok(Rect::new(
140            layout.bounds.left - margin,
141            layout.bounds.top - margin,
142            layout.bounds.right + margin,
143            layout.bounds.bottom + margin,
144        ))
145    }
146
147    fn bounds(&self, context: &DrawContext) -> Rect {
148        self.try_bounds(context)
149            .unwrap_or(Rect::new(f64::NAN, 0.0, 0.0, 0.0))
150    }
151
152    fn baseline(&self, context: &DrawContext) -> Option<f64> {
153        self.resolve(context).ok().map(|layout| layout.baseline)
154    }
155
156    fn draw(&self, context: &DrawContext, canvas: &mut Canvas<'_>) -> Result<(), ValidationError> {
157        let layout = self.resolve(context)?;
158        let parts = layout.parts(self.split);
159        for glyph in layout.glyphs.iter() {
160            let owner = parts.iter().position(|part| part.owns(glyph)).unwrap_or(0);
161            if self.visible.is_some_and(|visible| owner >= visible) {
162                continue;
163            }
164            GlyphDrawable {
165                artwork: glyph.artwork.clone(),
166                transform: glyph.transform,
167                bounds: glyph.bounds,
168                fill: self.spans[glyph.span]
169                    .fill
170                    .as_ref()
171                    .unwrap_or(&self.fill)
172                    .clone(),
173                stroke: self.stroke.clone(),
174            }
175            .draw(context, canvas)?;
176        }
177        Ok(())
178    }
179}