1use crate::FontLibrary;
28
29use motion_core::{
30 Canvas, Color, DeferredContent, DrawContext, Drawable, FillRule, Node, NodeModifiers, Paint,
31 Rect, Time, Transform, ValidationError,
32};
33use std::{
34 collections::BTreeMap,
35 ops::Range,
36 sync::{Arc, Mutex},
37};
38use unicode_segmentation::UnicodeSegmentation;
39
40mod drawable;
41pub(crate) mod glyph;
42mod layout;
43mod paragraph;
44mod parts;
45#[cfg(test)]
46mod tests;
47
48pub use crate::{TextAlignment, TextSplit};
49pub use cosmic_text::Weight as FontWeight;
50pub use paragraph::ParagraphStyle;
51pub(crate) use paragraph::{ParagraphSpec, SharedLayouts, SpanSpec};
52pub use parts::{PartStyle, TextPart};
53
54#[derive(Clone, Debug)]
56pub struct TextSpan {
57 text: Arc<str>,
58 family: Option<Arc<str>>,
59 size: Option<f64>,
60 weight: Option<FontWeight>,
61 italic: bool,
62 fill: Option<Paint>,
63}
64
65impl TextSpan {
66 pub fn new(text: impl Into<Arc<str>>) -> Self {
68 Self {
69 text: text.into(),
70 family: None,
71 size: None,
72 weight: None,
73 italic: false,
74 fill: None,
75 }
76 }
77
78 pub fn family(mut self, family: impl Into<Arc<str>>) -> Self {
80 self.family = Some(family.into());
81 self
82 }
83
84 pub fn size(mut self, size: f64) -> Self {
86 self.size = Some(size);
87 self
88 }
89
90 pub fn weight(mut self, weight: FontWeight) -> Self {
92 self.weight = Some(weight);
93 self
94 }
95
96 pub fn italic(mut self, italic: bool) -> Self {
98 self.italic = italic;
99 self
100 }
101
102 pub fn fill(mut self, paint: impl Into<Paint>) -> Self {
104 self.fill = Some(paint.into());
105 self
106 }
107}
108
109#[derive(Clone, Debug)]
111pub struct TextGlyph {
112 pub source: Range<usize>,
114 pub line: usize,
116 pub family: Arc<str>,
118 pub bounds: Rect,
120 artwork: Arc<glyph::Artwork>,
121 transform: Transform,
122 span: usize,
123}
124
125#[derive(Clone, Debug)]
127pub struct ParagraphLayout {
128 glyphs: Arc<[TextGlyph]>,
129 bounds: Rect,
130 baseline: f64,
131 lines: Arc<[Rect]>,
132 line_sources: Arc<[Range<usize>]>,
133 text: Arc<str>,
134}
135
136impl ParagraphLayout {
137 pub fn bounds(&self) -> Rect {
139 self.bounds
140 }
141
142 pub fn baseline(&self) -> f64 {
144 self.baseline
145 }
146
147 pub fn glyphs(&self) -> &[TextGlyph] {
149 &self.glyphs
150 }
151
152 pub fn lines(&self) -> &[Rect] {
154 &self.lines
155 }
156}
157
158#[derive(Clone, Debug)]
163pub struct Text {
164 paragraph: ParagraphStyle,
165 fill: Paint,
166 stroke: Option<(f64, Paint)>,
167 reveal: Reveal,
168 split: TextSplit,
169 visible: Option<usize>,
170}
171
172#[derive(Clone, Debug, Default)]
174enum Reveal {
175 #[default]
176 None,
177 Write(f64),
178 Parts(PartAnimation),
179}
180
181#[derive(Clone)]
182struct PartAnimation {
183 delay: motion_core::Duration,
184 style: Arc<dyn Fn(&TextPart) -> PartStyle + Send + Sync>,
185}
186
187impl std::fmt::Debug for PartAnimation {
188 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 formatter
190 .debug_struct("PartAnimation")
191 .field("delay", &self.delay)
192 .finish_non_exhaustive()
193 }
194}
195
196impl Text {
197 pub fn from_font(
199 text: &str,
200 font: &crate::FontFace,
201 size: f64,
202 paint: impl Into<Paint>,
203 ) -> Result<Self, ValidationError> {
204 if !size.is_finite() || size <= 0.0 || size > 4096.0 {
205 return Err(invalid(
206 "font_size",
207 "Require a finite font size in (0, 4096]",
208 ));
209 }
210 let fonts = font.library()?;
211 let face = fonts
212 .database()
213 .faces()
214 .find(|face| face.index == font.index())
215 .ok_or_else(|| invalid("font", "Explicit font face is unavailable"))?;
216 let family = face
217 .families
218 .first()
219 .ok_or_else(|| invalid("font", "Explicit font face has no family"))?
220 .0
221 .clone();
222 let span = TextSpan::new(text)
223 .family(family)
224 .weight(FontWeight(face.weight.0))
225 .italic(face.style == fontdb::Style::Italic);
226 let value = Self::rich([span]).fonts(&fonts).size(size).fill(paint);
227 value.layout(&fonts)?;
228 Ok(value)
229 }
230
231 pub fn new(text: impl Into<Arc<str>>) -> Self {
233 Self::from_paragraph(ParagraphStyle::new(text))
234 }
235
236 pub fn rich(spans: impl IntoIterator<Item = TextSpan>) -> Self {
238 Self::from_paragraph(ParagraphStyle::rich(spans))
239 }
240
241 pub fn from_paragraph(paragraph: ParagraphStyle) -> Self {
243 Self {
244 paragraph,
245 fill: Color::WHITE.into(),
246 stroke: None,
247 reveal: Reveal::None,
248 split: TextSplit::Characters,
249 visible: None,
250 }
251 }
252
253 pub fn paragraph(&self) -> &ParagraphStyle {
255 &self.paragraph
256 }
257
258 pub fn family(mut self, family: impl Into<Arc<str>>) -> Self {
260 self.paragraph = self.paragraph.family(family);
261 self
262 }
263
264 pub fn size(mut self, size: f64) -> Self {
266 self.paragraph = self.paragraph.size(size);
267 self
268 }
269
270 pub fn weight(mut self, weight: FontWeight) -> Self {
272 self.paragraph = self.paragraph.weight(weight);
273 self
274 }
275
276 pub fn fill(mut self, paint: impl Into<Paint>) -> Self {
278 self.fill = paint.into();
279 self
280 }
281
282 pub fn stroke(mut self, width: f64, paint: impl Into<Paint>) -> Self {
284 self.stroke = Some((width, paint.into()));
285 self
286 }
287
288 pub fn line_height(mut self, height: f64) -> Self {
290 self.paragraph = self.paragraph.line_height(height);
291 self
292 }
293
294 pub fn letter_spacing(mut self, spacing: f64) -> Self {
296 self.paragraph = self.paragraph.letter_spacing(spacing);
297 self
298 }
299
300 pub fn max_width(mut self, width: f64) -> Self {
302 self.paragraph = self.paragraph.max_width(width);
303 self
304 }
305
306 pub fn alignment(mut self, alignment: TextAlignment) -> Self {
308 self.paragraph = self.paragraph.alignment(alignment);
309 self
310 }
311
312 pub fn fonts(mut self, fonts: &FontLibrary) -> Self {
314 self.paragraph = self.paragraph.fonts(fonts);
315 self
316 }
317
318 pub fn split(mut self, split: TextSplit) -> Self {
320 self.split = split;
321 self
322 }
323
324 pub fn visible_parts(mut self, count: usize) -> Self {
326 self.visible = Some(count);
327 self
328 }
329
330 pub fn write(mut self, progress: f64) -> Self {
333 self.reveal = Reveal::Write(progress);
334 self
335 }
336
337 pub fn animate_parts(
340 mut self,
341 delay: motion_core::Duration,
342 style: impl Fn(&TextPart) -> PartStyle + Send + Sync + 'static,
343 ) -> Self {
344 self.reveal = Reveal::Parts(PartAnimation {
345 delay,
346 style: Arc::new(style),
347 });
348 self
349 }
350
351 pub fn fit_lines(
354 &self,
355 max_lines: usize,
356 fonts: &FontLibrary,
357 ) -> Result<(Self, bool), ValidationError> {
358 self.validate_ink()?;
359 let (paragraph, truncated) = self.paragraph.fit_lines(max_lines, fonts)?;
360 Ok((
361 Self {
362 paragraph,
363 ..self.clone()
364 },
365 truncated,
366 ))
367 }
368
369 pub fn layout(&self, fonts: &FontLibrary) -> Result<Arc<ParagraphLayout>, ValidationError> {
371 self.validate_ink()?;
372 self.paragraph.layout(fonts)
373 }
374
375 pub(super) fn resolve(
376 &self,
377 context: &DrawContext,
378 ) -> Result<Arc<ParagraphLayout>, ValidationError> {
379 self.validate_ink()?;
380 self.paragraph.resolve(context)
381 }
382
383 fn validate_ink(&self) -> Result<(), ValidationError> {
384 self.fill.validate()?;
385 if let Some((width, paint)) = &self.stroke {
386 if !width.is_finite() || *width < 0.0 {
387 return Err(invalid(
388 "stroke",
389 "Text stroke requires finite nonnegative width",
390 ));
391 }
392 paint.validate()?;
393 }
394 Ok(())
395 }
396
397 pub fn write_with_context(
400 &self,
401 progress: f64,
402 context: &DrawContext,
403 ) -> Result<Node, ValidationError> {
404 self.vector_tree(context)?.reveal(&[], Some(progress))
405 }
406
407 pub fn vector_tree(
410 &self,
411 context: &DrawContext,
412 ) -> Result<Arc<crate::vector::VectorTree>, ValidationError> {
413 let layout = self.resolve(context)?;
414 self.vector_tree_of(&layout)
415 }
416
417 pub fn vector_tree_of(
420 &self,
421 layout: &ParagraphLayout,
422 ) -> Result<Arc<crate::vector::VectorTree>, ValidationError> {
423 use crate::vector::{VectorGroup, VectorImage, VectorItem, VectorPath, VectorTree};
424 let mut children = Vec::new();
425 for (index, glyph) in self.visible_glyphs(layout) {
426 let fill = self.glyph_fill(glyph);
427 let mut ink = Vec::new();
428 if !glyph.artwork.path.verbs().is_empty() {
429 ink.push(VectorItem::Path(VectorPath {
430 id: None,
431 path: glyph.artwork.path.clone(),
432 fill: Some((fill.clone(), FillRule::NonZero)),
433 stroke: None,
434 source: None,
435 }));
436 }
437 for (path, color) in &glyph.artwork.layers {
438 ink.push(VectorItem::Path(VectorPath {
439 id: None,
440 path: path.clone(),
441 fill: Some((
442 color.map_or_else(|| fill.clone(), Paint::from),
443 FillRule::NonZero,
444 )),
445 stroke: None,
446 source: None,
447 }));
448 }
449 let mut glyph_children = vec![VectorItem::Group(VectorGroup {
450 transform: glyph.transform,
451 children: ink,
452 ..Default::default()
453 })];
454 if let Some((image, local)) = &glyph.artwork.bitmap {
455 let pattern =
456 motion_core::ImagePattern::from_asset(image.clone()).transform(*local);
457 let rect = motion_core::display_list::mapped_bounds(
458 Rect::new(
459 0.0,
460 0.0,
461 f64::from(image.width()),
462 f64::from(image.height()),
463 ),
464 *local,
465 "text.bitmap",
466 )?;
467 let mut image = VectorImage::new(image.clone(), rect);
468 image.pixel_transform = *local;
469 image.sampling = pattern.sampling;
470 image.extend = pattern.extend;
471 image.decal = pattern.decal;
472 glyph_children.push(VectorItem::Group(VectorGroup {
473 transform: glyph.transform,
474 children: vec![VectorItem::Image(image)],
475 ..Default::default()
476 }));
477 }
478 if let Some((width, paint)) = &self.stroke {
479 for path in std::iter::once(&glyph.artwork.path)
480 .chain(glyph.artwork.layers.iter().map(|(path, _)| path))
481 .filter(|path| !path.verbs().is_empty())
482 {
483 let outline = motion_core::StrokeStyle {
484 width: *width,
485 ..Default::default()
486 }
487 .outline(&path.transformed(&glyph.transform)?, "text.vector.stroke")?;
488 glyph_children
489 .push(VectorItem::Path(VectorPath::filled(outline, paint.clone())));
490 }
491 }
492 children.push(VectorItem::Group(VectorGroup {
493 id: Some(format!("glyph-{index}").into()),
494 source: Some(glyph.source.clone()),
495 children: glyph_children,
496 ..Default::default()
497 }));
498 }
499 Ok(Arc::new(
500 VectorTree::new(
501 self.ink_bounds(layout),
502 VectorGroup {
503 children,
504 ..Default::default()
505 },
506 )?
507 .with_baseline(layout.baseline),
508 ))
509 }
510}
511
512pub(crate) fn invalid(field: &str, message: &str) -> ValidationError {
513 ValidationError::invalid("text", field, message)
514}
515
516pub(crate) fn limited(message: &str) -> ValidationError {
517 ValidationError::limit("text", "work", message)
518}