motion_content/text/
drawable.rs1use 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}