1use crate::FontLibrary;
28
29use motion_core::{
30 Canvas, Color, DrawContext, Drawable, FillRule, Node, NodeModifiers, Paint, Rect, Time,
31 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 parts;
44#[cfg(test)]
45mod tests;
46
47pub use cosmic_text::Weight as FontWeight;
48pub use parts::{PartStyle, TextPart};
49
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub enum TextAlignment {
53 #[default]
55 Start,
56 End,
58 Center,
60 Justified,
62}
63
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
66pub enum TextSplit {
67 #[default]
69 Characters,
70 Words,
72 Lines,
74}
75
76#[derive(Clone, Debug)]
78pub struct TextSpan {
79 text: Arc<str>,
80 family: Option<Arc<str>>,
81 size: Option<f64>,
82 weight: Option<FontWeight>,
83 italic: bool,
84 fill: Option<Paint>,
85}
86
87impl TextSpan {
88 pub fn new(text: impl Into<Arc<str>>) -> Self {
90 Self {
91 text: text.into(),
92 family: None,
93 size: None,
94 weight: None,
95 italic: false,
96 fill: None,
97 }
98 }
99
100 pub fn family(mut self, family: impl Into<Arc<str>>) -> Self {
102 self.family = Some(family.into());
103 self
104 }
105
106 pub fn size(mut self, size: f64) -> Self {
108 self.size = Some(size);
109 self
110 }
111
112 pub fn weight(mut self, weight: FontWeight) -> Self {
114 self.weight = Some(weight);
115 self
116 }
117
118 pub fn italic(mut self, italic: bool) -> Self {
120 self.italic = italic;
121 self
122 }
123
124 pub fn fill(mut self, paint: impl Into<Paint>) -> Self {
126 self.fill = Some(paint.into());
127 self
128 }
129}
130
131#[derive(Clone, Debug)]
133pub struct TextGlyph {
134 pub source: Range<usize>,
136 pub line: usize,
138 pub family: Arc<str>,
140 pub bounds: Rect,
142 artwork: Arc<glyph::Artwork>,
143 transform: Transform,
144 span: usize,
145}
146
147#[derive(Clone, Debug)]
149pub struct ParagraphLayout {
150 glyphs: Arc<[TextGlyph]>,
151 bounds: Rect,
152 baseline: f64,
153 lines: Arc<[Rect]>,
154 line_sources: Arc<[Range<usize>]>,
155 text: Arc<str>,
156}
157
158impl ParagraphLayout {
159 pub fn bounds(&self) -> Rect {
161 self.bounds
162 }
163
164 pub fn baseline(&self) -> f64 {
166 self.baseline
167 }
168
169 pub fn glyphs(&self) -> &[TextGlyph] {
171 &self.glyphs
172 }
173
174 pub fn lines(&self) -> &[Rect] {
176 &self.lines
177 }
178}
179
180type LayoutCache = Arc<Mutex<BTreeMap<u64, Result<Arc<ParagraphLayout>, ValidationError>>>>;
181
182pub(crate) type SharedLayouts = Arc<Mutex<BTreeMap<LayoutKey, Arc<ParagraphLayout>>>>;
183
184type SpanLayoutKey = (Arc<str>, Option<Arc<str>>, Option<u64>, Option<u16>, bool);
185
186#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
187pub(crate) struct LayoutKey {
188 spans: Vec<SpanLayoutKey>,
189 family: Arc<str>,
190 size: u64,
191 weight: u16,
192 line_height: u64,
193 spacing: u64,
194 width: Option<u64>,
195 alignment: TextAlignment,
196}
197
198#[derive(Clone, Debug)]
201pub struct Text {
202 spans: Arc<[TextSpan]>,
203 family: Arc<str>,
204 size: f64,
205 weight: FontWeight,
206 fill: Paint,
207 stroke: Option<(f64, Paint)>,
208 line_height: f64,
209 spacing: f64,
210 width: Option<f64>,
211 alignment: TextAlignment,
212
213 write_progress: Option<f64>,
214 part_animation: Option<PartAnimation>,
215 split: TextSplit,
216 visible: Option<usize>,
217
218 fonts: Option<FontLibrary>,
219
220 layout_cache: LayoutCache,
221}
222
223#[derive(Clone)]
224struct PartAnimation {
225 delay: motion_core::Duration,
226 style: Arc<dyn Fn(&TextPart) -> PartStyle + Send + Sync>,
227}
228
229impl std::fmt::Debug for PartAnimation {
230 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 formatter
232 .debug_struct("PartAnimation")
233 .field("delay", &self.delay)
234 .finish_non_exhaustive()
235 }
236}
237
238impl Text {
239 pub fn from_font(
241 text: &str,
242 font: &crate::FontFace,
243 size: f64,
244 paint: impl Into<Paint>,
245 ) -> Result<Self, ValidationError> {
246 if !size.is_finite() || size <= 0.0 || size > 4096.0 {
247 return Err(invalid(
248 "font_size",
249 "Require a finite font size in (0, 4096]",
250 ));
251 }
252 let fonts = font.library()?;
253 let face = fonts
254 .database()
255 .faces()
256 .find(|face| face.index == font.index())
257 .ok_or_else(|| invalid("font", "Explicit font face is unavailable"))?;
258 let family = face
259 .families
260 .first()
261 .ok_or_else(|| invalid("font", "Explicit font face has no family"))?
262 .0
263 .clone();
264 let span = TextSpan::new(text)
265 .family(family)
266 .weight(FontWeight(face.weight.0))
267 .italic(face.style == fontdb::Style::Italic);
268 let value = Self::rich([span]).fonts(&fonts).size(size).fill(paint);
269 value.layout(&fonts)?;
270 Ok(value)
271 }
272
273 pub fn new(text: impl Into<Arc<str>>) -> Self {
275 Self::rich([TextSpan::new(text)])
276 }
277
278 pub fn rich(spans: impl IntoIterator<Item = TextSpan>) -> Self {
280 Self {
281 write_progress: None,
282 part_animation: None,
283 spans: spans.into_iter().take(257).collect::<Vec<_>>().into(),
284 family: "".into(),
285 size: 24.0,
286 weight: FontWeight::NORMAL,
287 fill: Color::WHITE.into(),
288 stroke: None,
289 line_height: 1.3,
290 spacing: 0.0,
291 width: None,
292 alignment: TextAlignment::Start,
293 fonts: None,
294 split: TextSplit::Characters,
295 visible: None,
296 layout_cache: Arc::new(Mutex::new(BTreeMap::new())),
297 }
298 }
299
300 fn invalidate_layout_cache(&mut self) {
301 self.layout_cache = Arc::new(Mutex::new(BTreeMap::new()));
302 }
303
304 pub fn family(mut self, family: impl Into<Arc<str>>) -> Self {
306 let family = family.into();
307 if self.family != family {
308 self.family = family;
309 self.invalidate_layout_cache();
310 }
311 self
312 }
313
314 pub fn size(mut self, size: f64) -> Self {
316 if self.size.to_bits() != size.to_bits() {
317 self.size = size;
318 self.invalidate_layout_cache();
319 }
320 self
321 }
322
323 pub fn weight(mut self, weight: FontWeight) -> Self {
325 if self.weight != weight {
326 self.weight = weight;
327 self.invalidate_layout_cache();
328 }
329 self
330 }
331
332 pub fn fill(mut self, paint: impl Into<Paint>) -> Self {
334 self.fill = paint.into();
335 self
336 }
337
338 pub fn stroke(mut self, width: f64, paint: impl Into<Paint>) -> Self {
340 self.stroke = Some((width, paint.into()));
341 self
342 }
343
344 pub fn line_height(mut self, height: f64) -> Self {
346 if self.line_height.to_bits() != height.to_bits() {
347 self.line_height = height;
348 self.invalidate_layout_cache();
349 }
350 self
351 }
352
353 pub fn letter_spacing(mut self, spacing: f64) -> Self {
355 if self.spacing.to_bits() != spacing.to_bits() {
356 self.spacing = spacing;
357 self.invalidate_layout_cache();
358 }
359 self
360 }
361
362 pub fn max_width(mut self, width: f64) -> Self {
364 if self.width.map(f64::to_bits) != Some(width.to_bits()) {
365 self.width = Some(width);
366 self.invalidate_layout_cache();
367 }
368 self
369 }
370
371 pub fn alignment(mut self, alignment: TextAlignment) -> Self {
373 if self.alignment != alignment {
374 self.alignment = alignment;
375 self.invalidate_layout_cache();
376 }
377 self
378 }
379
380 pub fn fonts(mut self, fonts: &FontLibrary) -> Self {
382 self.fonts = Some(fonts.clone());
383 self
384 }
385
386 pub fn split(mut self, split: TextSplit) -> Self {
388 self.split = split;
389 self
390 }
391
392 pub fn visible_parts(mut self, count: usize) -> Self {
394 self.visible = Some(count);
395 self
396 }
397
398 pub fn write(mut self, progress: f64) -> Self {
400 self.write_progress = Some(progress);
401 self
402 }
403
404 pub fn animate_parts(
406 mut self,
407 delay: motion_core::Duration,
408 style: impl Fn(&TextPart) -> PartStyle + Send + Sync + 'static,
409 ) -> Self {
410 self.part_animation = Some(PartAnimation {
411 delay,
412 style: Arc::new(style),
413 });
414 self
415 }
416
417 pub fn write_with_context(
420 &self,
421 progress: f64,
422 context: &DrawContext,
423 ) -> Result<Node, ValidationError> {
424 self.vector_tree(context)?
425 .write(progress, crate::vector::WriteOptions::default())
426 }
427
428 pub fn vector_tree(
431 &self,
432 context: &DrawContext,
433 ) -> Result<Arc<crate::vector::VectorTree>, ValidationError> {
434 use crate::vector::{VectorGroup, VectorImage, VectorItem, VectorPath, VectorTree};
435 let layout = self.resolve(context)?;
436 let parts = layout.parts(self.split);
437 let mut children = Vec::new();
438 for (index, glyph) in layout.glyphs.iter().enumerate() {
439 let owner = parts.iter().position(|part| part.owns(glyph)).unwrap_or(0);
440 if self.visible.is_some_and(|visible| owner >= visible) {
441 continue;
442 }
443 let fill = self.spans[glyph.span]
444 .fill
445 .as_ref()
446 .unwrap_or(&self.fill)
447 .clone();
448 let mut ink = Vec::new();
449 if !glyph.artwork.path.verbs().is_empty() {
450 ink.push(VectorItem::Path(VectorPath {
451 id: None,
452 path: glyph.artwork.path.clone(),
453 fill: Some((fill.clone(), FillRule::NonZero)),
454 stroke: None,
455 source: None,
456 }));
457 }
458 for (path, color) in &glyph.artwork.layers {
459 ink.push(VectorItem::Path(VectorPath {
460 id: None,
461 path: path.clone(),
462 fill: Some((
463 color.map_or_else(|| fill.clone(), Paint::from),
464 FillRule::NonZero,
465 )),
466 stroke: None,
467 source: None,
468 }));
469 }
470 let mut glyph_children = vec![VectorItem::Group(VectorGroup {
471 transform: glyph.transform,
472 children: ink,
473 ..Default::default()
474 })];
475 if let Some((image, local)) = &glyph.artwork.bitmap {
476 let pattern =
477 motion_core::ImagePattern::from_asset(image.clone()).transform(*local);
478 let rect = motion_core::display_list::mapped_bounds(
479 Rect::new(
480 0.0,
481 0.0,
482 f64::from(image.width()),
483 f64::from(image.height()),
484 ),
485 *local,
486 "text.bitmap",
487 )?;
488 let mut image = VectorImage::new(image.clone(), rect);
489 image.pixel_transform = *local;
490 image.sampling = pattern.sampling;
491 image.extend = pattern.extend;
492 image.decal = pattern.decal;
493 glyph_children.push(VectorItem::Group(VectorGroup {
494 transform: glyph.transform,
495 children: vec![VectorItem::Image(image)],
496 ..Default::default()
497 }));
498 }
499 if let Some((width, paint)) = &self.stroke {
500 for path in std::iter::once(&glyph.artwork.path)
501 .chain(glyph.artwork.layers.iter().map(|(path, _)| path))
502 .filter(|path| !path.verbs().is_empty())
503 {
504 let outline = motion_core::StrokeStyle {
505 width: *width,
506 ..Default::default()
507 }
508 .outline(&path.transformed(&glyph.transform)?, "text.vector.stroke")?;
509 glyph_children
510 .push(VectorItem::Path(VectorPath::filled(outline, paint.clone())));
511 }
512 }
513 children.push(VectorItem::Group(VectorGroup {
514 id: Some(format!("glyph-{index}").into()),
515 source: Some(glyph.source.clone()),
516 children: glyph_children,
517 ..Default::default()
518 }));
519 }
520 Ok(Arc::new(
521 VectorTree::new(
522 self.try_bounds(context)?,
523 VectorGroup {
524 children,
525 ..Default::default()
526 },
527 )?
528 .with_baseline(layout.baseline),
529 ))
530 }
531}
532
533pub(crate) fn invalid(field: &str, message: &str) -> ValidationError {
534 ValidationError::invalid("text", field, message)
535}
536
537pub(crate) fn limited(message: &str) -> ValidationError {
538 ValidationError::limit("text", "work", message)
539}