Skip to main content

motion_content/text/
paragraph.rs

1//! Paragraph description: text, style, width, alignment and fonts, keyed for shaping.
2
3use super::*;
4use crate::vector::CompileCache;
5
6pub(crate) type SharedLayouts = Arc<Mutex<BTreeMap<ParagraphSpec, Arc<ParagraphLayout>>>>;
7
8/// Shaping-relevant part of one span; paints are not part of the key.
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub(crate) struct SpanSpec {
11    pub(super) text: Arc<str>,
12    pub(super) family: Option<Arc<str>>,
13    pub(super) size: Option<u64>,
14    pub(super) weight: Option<u16>,
15    pub(super) italic: bool,
16}
17
18impl From<&TextSpan> for SpanSpec {
19    fn from(span: &TextSpan) -> Self {
20        Self {
21            text: span.text.clone(),
22            family: span.family.clone(),
23            size: span.size.map(f64::to_bits),
24            weight: span.weight.map(|weight| weight.0),
25            italic: span.italic,
26        }
27    }
28}
29
30/// The immutable compile key of a paragraph: everything that determines glyph geometry.
31#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub(crate) struct ParagraphSpec {
33    pub(super) spans: Vec<SpanSpec>,
34    pub(super) family: Arc<str>,
35    pub(super) size: u64,
36    pub(super) weight: u16,
37    pub(super) line_height: u64,
38    pub(super) spacing: u64,
39    pub(super) width: Option<u64>,
40    pub(super) alignment: TextAlignment,
41}
42
43/// Paragraph description: rich text, base style, width, alignment and optional fonts.
44///
45/// Shaping it yields a [`ParagraphLayout`], the single intermediate
46/// representation behind static drawing, reveal effects and captions. Changing a
47/// shaping parameter gives a new cache; unchanged ones keep sharing the layout.
48#[derive(Clone, Debug)]
49pub struct ParagraphStyle {
50    pub(super) cache: CompileCache<ParagraphSpec, Result<Arc<ParagraphLayout>, ValidationError>>,
51    pub(super) fills: Arc<[Option<Paint>]>,
52    pub(super) fonts: Option<FontLibrary>,
53}
54
55impl ParagraphStyle {
56    /// Creates a paragraph of one inheriting span with size 24 and line height 1.3.
57    pub fn new(text: impl Into<Arc<str>>) -> Self {
58        Self::rich([TextSpan::new(text)])
59    }
60
61    /// Creates rich spans, whose unset styles inherit paragraph defaults.
62    pub fn rich(spans: impl IntoIterator<Item = TextSpan>) -> Self {
63        let spans = spans.into_iter().take(257).collect::<Vec<_>>();
64        Self {
65            fills: spans.iter().map(|span| span.fill.clone()).collect(),
66            cache: CompileCache::new(ParagraphSpec {
67                spans: spans.iter().map(SpanSpec::from).collect(),
68                family: "".into(),
69                size: 24.0f64.to_bits(),
70                weight: FontWeight::NORMAL.0,
71                line_height: 1.3f64.to_bits(),
72                spacing: 0.0f64.to_bits(),
73                width: None,
74                alignment: TextAlignment::Start,
75            }),
76            fonts: None,
77        }
78    }
79
80    pub(super) fn spec(&self) -> &ParagraphSpec {
81        self.cache.key()
82    }
83
84    pub(super) fn with_spans(&self, spans: Vec<SpanSpec>, fills: Vec<Option<Paint>>) -> Self {
85        Self {
86            cache: self.cache.map_key(|spec| spec.spans = spans),
87            fills: fills.into(),
88            fonts: self.fonts.clone(),
89        }
90    }
91
92    fn rekey(mut self, change: impl FnOnce(&mut ParagraphSpec)) -> Self {
93        self.cache = self.cache.map_key(change);
94        self
95    }
96
97    /// Selects an explicit family; empty uses the library's generic default.
98    pub fn family(self, family: impl Into<Arc<str>>) -> Self {
99        let family = family.into();
100        self.rekey(|spec| spec.family = family)
101    }
102
103    /// Sets finite logical size in (0,4096].
104    pub fn size(self, size: f64) -> Self {
105        self.rekey(|spec| spec.size = size.to_bits())
106    }
107
108    /// Selects OpenType weight; variable weight participates in glyph identity.
109    pub fn weight(self, weight: FontWeight) -> Self {
110        self.rekey(|spec| spec.weight = weight.0)
111    }
112
113    /// Sets a positive relative line-height multiplier.
114    pub fn line_height(self, height: f64) -> Self {
115        self.rekey(|spec| spec.line_height = height.to_bits())
116    }
117
118    /// Sets logical tracking, converted to em internally for shaping.
119    pub fn letter_spacing(self, spacing: f64) -> Self {
120        self.rekey(|spec| spec.spacing = spacing.to_bits())
121    }
122
123    /// Wraps to finite positive logical width.
124    pub fn max_width(self, width: f64) -> Self {
125        self.rekey(|spec| spec.width = Some(width.to_bits()))
126    }
127
128    /// Selects bidirectional-aware paragraph alignment.
129    pub fn alignment(self, alignment: TextAlignment) -> Self {
130        self.rekey(|spec| spec.alignment = alignment)
131    }
132
133    /// Binds an explicit font snapshot, overriding a DrawContext environment.
134    pub fn fonts(mut self, fonts: &FontLibrary) -> Self {
135        self.fonts = Some(fonts.clone());
136        self
137    }
138}