1use motion_core::{
39 DrawContext, Drawable, FillRule, Node, NodeModifiers, Paint, Path2D, Rect, StrokeStyle,
40 Transform, ValidationError, ValidationLimits,
41};
42use std::{collections::BTreeMap, ops::Range, sync::Arc};
43
44mod deferred;
45mod effects;
46#[cfg(feature = "ink")]
47pub mod ink;
48mod morph;
49mod surface;
50#[cfg(test)]
51mod tests;
52
53pub use deferred::{CompileCache, VectorSelection};
54pub use effects::WriteOptions;
55pub use morph::Morph;
56pub use surface::{SurfaceTriangle, VectorSurface};
57
58fn invalid(field: &str, message: &str) -> ValidationError {
59 ValidationError::invalid("vector_tree", field, message)
60}
61
62fn limited(message: &str) -> ValidationError {
63 ValidationError::limit("vector_tree", "structure", message)
64}
65
66#[derive(Clone, Debug)]
68pub struct VectorPath {
69 pub id: Option<Arc<str>>,
71 pub path: Arc<Path2D>,
73 pub fill: Option<(Paint, FillRule)>,
75 pub stroke: Option<(StrokeStyle, Paint)>,
77 pub source: Option<Range<usize>>,
79}
80
81impl VectorPath {
82 pub fn filled(path: Path2D, paint: impl Into<Paint>) -> Self {
84 Self {
85 id: None,
86 path: Arc::new(path),
87 fill: Some((paint.into(), FillRule::NonZero)),
88 stroke: None,
89 source: None,
90 }
91 }
92}
93
94impl Drawable for VectorPath {
95 fn bounds(&self, _: &DrawContext) -> Result<Rect, ValidationError> {
96 let mut bounds = self.path.bounding_box();
97 if let Some((style, _)) = &self.stroke {
98 bounds = bounds.union(
99 style
100 .outline(&self.path, "vector_tree.stroke")?
101 .bounding_box(),
102 );
103 }
104 Ok(bounds)
105 }
106
107 fn draw(
108 &self,
109 _: &DrawContext,
110 canvas: &mut motion_core::Canvas<'_>,
111 ) -> Result<(), ValidationError> {
112 if let Some((paint, rule)) = &self.fill {
113 canvas.fill_path(self.path.clone(), Transform::IDENTITY, paint.clone(), *rule)?;
114 }
115 if let Some((style, paint)) = &self.stroke {
116 canvas.stroke(self.path.as_ref(), style, paint.clone())?;
117 }
118 Ok(())
119 }
120}
121
122#[derive(Clone, Debug)]
124pub struct VectorImage {
125 pub id: Option<Arc<str>>,
127 pub image: Arc<crate::ImageAsset>,
129 pub rect: Rect,
131 pub pixel_transform: Transform,
133 pub sampling: crate::Sampling,
135 pub extend: motion_core::GradientExtend,
137 pub decal: bool,
139 pub source: Option<Range<usize>>,
141}
142
143impl VectorImage {
144 pub fn new(image: Arc<crate::ImageAsset>, rect: Rect) -> Self {
147 let pixel_transform = Transform::scale_non_uniform(
148 rect.width() / f64::from(image.width()),
149 rect.height() / f64::from(image.height()),
150 )
151 .then(Transform::translate(rect.left, rect.top));
152 Self {
153 id: None,
154 image,
155 rect,
156 pixel_transform,
157 sampling: crate::Sampling::Trilinear,
158 extend: motion_core::GradientExtend::Pad,
159 decal: true,
160 source: None,
161 }
162 }
163
164 pub(crate) fn pattern(&self) -> motion_core::ImagePattern {
165 let mut pattern = motion_core::ImagePattern::from_asset(self.image.clone())
166 .transform(self.pixel_transform)
167 .sampling(self.sampling)
168 .extend(self.extend);
169 pattern.decal = self.decal;
170 pattern
171 }
172}
173
174impl Drawable for VectorImage {
175 fn bounds(&self, _: &DrawContext) -> Result<Rect, ValidationError> {
176 Ok(self.rect)
177 }
178
179 fn draw(
180 &self,
181 _: &DrawContext,
182 canvas: &mut motion_core::Canvas<'_>,
183 ) -> Result<(), ValidationError> {
184 if self.rect.width() == 0.0 || self.rect.height() == 0.0 {
185 return Ok(());
186 }
187 canvas.fill_path(
188 Arc::new(Path2D::from_rect(self.rect)?),
189 Transform::IDENTITY,
190 self.pattern(),
191 FillRule::NonZero,
192 )
193 }
194}
195
196#[derive(Clone, Debug)]
198pub enum VectorItem {
199 Group(VectorGroup),
201 Path(VectorPath),
203 Image(VectorImage),
205}
206
207#[derive(Clone, Debug)]
209pub struct VectorMask {
210 pub tree: Arc<VectorTree>,
212 pub mode: motion_core::MaskMode,
214 pub rect: Rect,
216}
217
218#[derive(Clone, Debug)]
220pub struct VectorGroup {
221 pub id: Option<Arc<str>>,
223 pub transform: Transform,
225 pub opacity: f64,
227 pub clip: Option<motion_core::PathClip>,
229 pub mask: Option<VectorMask>,
231 pub blend_mode: motion_core::BlendMode,
233 pub isolate: bool,
235 pub filters: Vec<motion_core::Filter>,
237 pub source: Option<Range<usize>>,
239 pub children: Vec<VectorItem>,
241}
242
243impl Default for VectorGroup {
244 fn default() -> Self {
245 Self {
246 id: None,
247 transform: Transform::IDENTITY,
248 opacity: 1.0,
249 clip: None,
250 mask: None,
251 blend_mode: motion_core::BlendMode::SourceOver,
252 isolate: false,
253 filters: Vec::new(),
254 source: None,
255 children: Vec::new(),
256 }
257 }
258}
259
260impl VectorGroup {
261 fn node(&self) -> Node {
262 self.decorate(self.children.iter().map(item_node))
263 }
264
265 pub(crate) fn decorate(&self, children: impl IntoIterator<Item = Node>) -> Node {
266 let content = Node::group(children);
267 let content = if self.isolate {
268 Node::accumulate([content]).expect("one isolated content sample")
269 } else {
270 content
271 };
272 let mut node = content.opacity(self.opacity).blend_mode(self.blend_mode);
273 for filter in &self.filters {
274 node = node.filter(filter.clone());
275 }
276 if let Some(mask) = &self.mask {
277 node = node.mask(
278 mask.tree
279 .node()
280 .clip_with(mask.rect, motion_core::ClipMode::Geometric),
281 mask.mode,
282 );
283 }
284 if let Some(clip) = &self.clip {
285 node = node.clip(clip.clone());
286 }
287 node = node.transformed(self.transform);
288 if let Some(id) = &self.id {
289 node = node.named(id);
290 }
291 node
292 }
293}
294
295pub(crate) fn item_node(item: &VectorItem) -> Node {
296 match item {
297 VectorItem::Group(group) => group.node(),
298 VectorItem::Path(path) => Node::from(path.clone()),
299 VectorItem::Image(image) => Node::from(image.clone()),
300 }
301}
302
303#[derive(Clone, Debug)]
305pub struct VectorLeaf<'a> {
306 pub item: &'a VectorItem,
308 pub transform: Transform,
310 pub opacity: f64,
312 pub source: Option<Range<usize>>,
314 pub address: Vec<usize>,
316 pub(crate) clips: Vec<motion_core::PathClip>,
317}
318
319#[derive(Clone, Debug)]
323pub struct VectorTree {
324 data: Arc<TreeData>,
325}
326
327#[derive(Clone, Debug)]
328struct TreeData {
329 view_box: Rect,
330 root: VectorGroup,
331 ids: BTreeMap<Arc<str>, Vec<usize>>,
332 baseline: Option<f64>,
333 surface: std::sync::OnceLock<Result<Arc<VectorSurface>, ValidationError>>,
334}
335
336impl VectorTree {
337 pub fn new(view_box: Rect, root: VectorGroup) -> Result<Self, ValidationError> {
339 motion_core::drawing::validate_rect(view_box, "vector_tree", "view_box")?;
340 let mut ids = BTreeMap::new();
341 let mut count = 0;
342 let mut verbs = 0;
343 validate_group(&root, &mut Vec::new(), &mut ids, &mut count, &mut verbs)?;
344 let tree = Self {
345 data: Arc::new(TreeData {
346 view_box,
347 root,
348 ids,
349 baseline: None,
350 surface: std::sync::OnceLock::new(),
351 }),
352 };
353 tree.node().measure(ValidationLimits::default())?;
354 Ok(tree)
355 }
356
357 pub fn view_box(&self) -> Rect {
359 self.data.view_box
360 }
361
362 pub fn root(&self) -> &VectorGroup {
364 &self.data.root
365 }
366
367 pub fn ids(&self) -> &BTreeMap<Arc<str>, Vec<usize>> {
369 &self.data.ids
370 }
371
372 pub fn get(&self, id: &str) -> Option<&VectorItem> {
374 let address = self.data.ids.get(id)?;
375 let (last, parents) = address.split_last()?;
376 let mut group = &self.data.root;
377 for index in parents {
378 let VectorItem::Group(child) = &group.children[*index] else {
379 return None;
380 };
381 group = child;
382 }
383 group.children.get(*last)
384 }
385
386 pub fn node(&self) -> Node {
388 self.node_mapped(&|_, node| node)
389 }
390
391 pub fn node_mapped(&self, map: &impl Fn(&[usize], Node) -> Node) -> Node {
394 fn build(
395 group: &VectorGroup,
396 address: &mut Vec<usize>,
397 map: &impl Fn(&[usize], Node) -> Node,
398 ) -> Node {
399 let children = group
400 .children
401 .iter()
402 .enumerate()
403 .map(|(index, item)| {
404 address.push(index);
405 let node = match item {
406 VectorItem::Group(group) => build(group, address, map),
407 _ => map(address, item_node(item)),
408 };
409 address.pop();
410 node
411 })
412 .collect::<Vec<_>>();
413 map(address, group.decorate(children))
414 }
415 let node = build(&self.data.root, &mut Vec::new(), map);
416 if let Some(baseline) = self.data.baseline {
417 Node::group([node, Node::from(TextBounds(self.data.view_box))])
418 .with_text_metrics(baseline, self.data.view_box)
419 } else {
420 node
421 }
422 }
423
424 pub fn with_baseline(mut self, baseline: f64) -> Self {
426 Arc::make_mut(&mut self.data).baseline = Some(baseline);
427 self
428 }
429
430 pub fn paint_leaves(&self) -> Vec<VectorLeaf<'_>> {
432 let mut leaves = Vec::new();
433 gather(
434 &self.data.root,
435 Transform::IDENTITY,
436 1.0,
437 None,
438 &[],
439 &mut Vec::new(),
440 &mut leaves,
441 );
442 leaves
443 }
444
445 pub fn leaves(&self) -> Vec<VectorLeaf<'_>> {
448 let mut leaves = self.paint_leaves();
449 leaves.sort_by_key(|leaf| {
450 leaf.source
451 .as_ref()
452 .map(|range| range.start)
453 .unwrap_or(usize::MAX)
454 });
455 leaves
456 }
457}
458
459#[derive(Clone)]
460struct TextBounds(Rect);
461
462impl Drawable for TextBounds {
463 fn bounds(&self, _: &DrawContext) -> Result<Rect, ValidationError> {
464 Ok(self.0)
465 }
466
467 fn draw(
468 &self,
469 _: &DrawContext,
470 _: &mut motion_core::Canvas<'_>,
471 ) -> Result<(), ValidationError> {
472 Ok(())
473 }
474}
475
476fn metadata(
477 id: &Option<Arc<str>>,
478 source: &Option<Range<usize>>,
479 address: &[usize],
480 ids: &mut BTreeMap<Arc<str>, Vec<usize>>,
481) -> Result<(), ValidationError> {
482 if source.as_ref().is_some_and(|range| range.start > range.end) {
483 return Err(invalid("source", "Source ranges must not be inverted"));
484 }
485 if let Some(id) = id
486 && (id.is_empty()
487 || id.len() > 1024
488 || id.chars().any(char::is_control)
489 || ids.insert(id.clone(), address.to_vec()).is_some())
490 {
491 return Err(invalid(
492 "id",
493 "IDs must be unique nonempty strings of at most 1024 bytes without controls",
494 ));
495 }
496 Ok(())
497}
498
499fn validate_group(
500 group: &VectorGroup,
501 address: &mut Vec<usize>,
502 ids: &mut BTreeMap<Arc<str>, Vec<usize>>,
503 count: &mut usize,
504 verbs: &mut usize,
505) -> Result<(), ValidationError> {
506 *count += 1;
507 if address.len() > 48 || *count > 8192 {
508 return Err(limited("Vector tree depth/item allowance exhausted"));
509 }
510 metadata(&group.id, &group.source, address, ids)?;
511 if group.filters.len() > 32 {
512 return Err(limited("Vector group exceeds 32 filters"));
513 }
514 for filter in &group.filters {
515 filter.validate("vector_tree.filters")?;
516 }
517 if let Some(mask) = &group.mask {
518 motion_core::drawing::validate_rect(mask.rect, "vector_tree.mask", "rect")?;
519 address.push(usize::MAX);
520 validate_group(
521 mask.tree.root(),
522 address,
523 &mut BTreeMap::new(),
524 count,
525 verbs,
526 )?;
527 address.pop();
528 }
529 if !group.transform.is_finite()
530 || !group.opacity.is_finite()
531 || !(0.0..=1.0).contains(&group.opacity)
532 {
533 return Err(invalid(
534 "group",
535 "Require finite matrices and opacity in 0..=1",
536 ));
537 }
538 if let Some(clip) = &group.clip {
539 *verbs += clip.path.verbs().len();
540 }
541 for (index, item) in group.children.iter().enumerate() {
542 address.push(index);
543 match item {
544 VectorItem::Group(child) => validate_group(child, address, ids, count, verbs)?,
545 VectorItem::Path(path) => {
546 *count += 1;
547 *verbs += path.path.verbs().len();
548 metadata(&path.id, &path.source, address, ids)?;
549 if let Some((paint, _)) = &path.fill {
550 paint.validate()?;
551 }
552 if let Some((style, paint)) = &path.stroke {
553 style.validate("vector_tree.stroke")?;
554 paint.validate()?;
555 }
556 }
557 VectorItem::Image(image) => {
558 *count += 1;
559 metadata(&image.id, &image.source, address, ids)?;
560 motion_core::drawing::validate_rect(image.rect, "vector_tree.image", "rect")?;
561 if !image.pixel_transform.is_finite() {
562 return Err(invalid("transform", "Image pixel mapping must be finite"));
563 }
564 if image.rect.width() > 0.0 && image.rect.height() > 0.0 {
565 Paint::from(image.pattern()).validate()?;
566 }
567 }
568 }
569 address.pop();
570 if *count > 8192 || *verbs > 262144 {
571 return Err(limited("Vector tree item/verb allowance exhausted"));
572 }
573 }
574 Ok(())
575}
576
577fn gather<'a>(
578 group: &'a VectorGroup,
579 parent: Transform,
580 opacity: f64,
581 source: Option<Range<usize>>,
582 clips: &[motion_core::PathClip],
583 address: &mut Vec<usize>,
584 leaves: &mut Vec<VectorLeaf<'a>>,
585) {
586 let transform = group.transform.then(parent);
587 let opacity = opacity * group.opacity;
588 let source = group.source.clone().or(source);
589 let mut clips = clips.to_vec();
590 if let Some(mut clip) = group.clip.clone() {
591 clip.transform = clip.transform.then(transform);
592 clips.push(clip);
593 }
594 for (index, item) in group.children.iter().enumerate() {
595 address.push(index);
596 match item {
597 VectorItem::Group(child) => gather(
598 child,
599 transform,
600 opacity,
601 source.clone(),
602 &clips,
603 address,
604 leaves,
605 ),
606 _ => {
607 let own = match item {
608 VectorItem::Path(path) => path.source.clone(),
609 VectorItem::Image(image) => image.source.clone(),
610 _ => None,
611 };
612 leaves.push(VectorLeaf {
613 item,
614 transform,
615 opacity,
616 source: own.or(source.clone()),
617 address: address.clone(),
618 clips: clips.clone(),
619 });
620 }
621 }
622 address.pop();
623 }
624}