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