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