Skip to main content

vello_common/
transforms.rs

1// Copyright 2026 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Shared structures for holding different transforms.
5
6use crate::kurbo::Affine;
7use smallvec::{SmallVec, smallvec};
8
9/// Context for holding transforms for paths and paints.
10#[derive(Debug, Clone)]
11pub struct Transforms {
12    transform: Affine,
13    paint_transform: Affine,
14}
15
16impl Default for Transforms {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22impl Transforms {
23    /// Create a new transforms context.
24    pub fn new() -> Self {
25        Self {
26            transform: Affine::IDENTITY,
27            paint_transform: Affine::IDENTITY,
28        }
29    }
30
31    /// Return the current scene transform.
32    pub fn transform(&self) -> &Affine {
33        &self.transform
34    }
35
36    /// Return the transform used for rendering scene geometry.
37    pub fn scene_transform(&self) -> Affine {
38        self.transform
39    }
40
41    /// Set the current scene transform.
42    pub fn set_transform(&mut self, transform: Affine) {
43        self.transform = transform;
44    }
45
46    /// Reset the current scene transform.
47    pub fn reset_transform(&mut self) {
48        self.transform = Affine::IDENTITY;
49    }
50
51    /// Return the current paint transform.
52    pub fn paint_transform(&self) -> &Affine {
53        &self.paint_transform
54    }
55
56    /// Return the transform used for rendering scene paints.
57    pub fn scene_paint_transform(&self) -> Affine {
58        self.scene_transform() * self.paint_transform
59    }
60
61    /// Set the current paint transform.
62    pub fn set_paint_transform(&mut self, paint_transform: Affine) {
63        self.paint_transform = paint_transform;
64    }
65
66    /// Reset the current paint transform.
67    pub fn reset_paint_transform(&mut self) {
68        self.paint_transform = Affine::IDENTITY;
69    }
70
71    // Unlike [`Self::effective_path_transform`], this intentionally does not apply
72    // the root transform because clipping handles root viewport shifts separately.
73    /// Return the transform used for non-isolated clip paths.
74    pub fn clip_path_transform(&self) -> Affine {
75        self.transform
76    }
77}
78
79/// Stack of root transforms.
80#[derive(Debug)]
81pub struct RootTransforms {
82    transforms: SmallVec<[Affine; 3]>,
83}
84
85impl Default for RootTransforms {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl RootTransforms {
92    /// Create a new root transform stack.
93    pub fn new() -> Self {
94        Self {
95            transforms: smallvec![Affine::IDENTITY],
96        }
97    }
98
99    /// Return the root transform of the currently active root viewport, including
100    /// shifts inherited from nested filters.
101    pub fn root_transform(&self) -> Affine {
102        *self
103            .transforms
104            .last()
105            .expect("root transform stack should never be empty")
106    }
107
108    /// Return the transform used for rendering paths.
109    pub fn effective_path_transform(&self, transforms: &Transforms) -> Affine {
110        self.root_transform() * transforms.transform
111    }
112
113    /// Return the transform used for rendering paints.
114    pub fn effective_paint_transform(&self, transforms: &Transforms) -> Affine {
115        self.effective_path_transform(transforms) * transforms.paint_transform
116    }
117
118    /// Push a new root transform relative to the currently active root transform.
119    pub fn push_root(&mut self, relative_transform: Affine) {
120        self.transforms
121            .push(relative_transform * self.root_transform());
122    }
123
124    /// Pop the last root layer.
125    pub fn pop_root(&mut self) {
126        self.transforms.pop();
127    }
128
129    /// Reset the root transform stack.
130    pub fn reset(&mut self) {
131        self.transforms.clear();
132        self.transforms.push(Affine::IDENTITY);
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn root_transforms_accumulate_relative_transforms() {
142        let mut roots = RootTransforms::new();
143        let parent = Affine::translate((10.0, 20.0));
144        let child = Affine::scale(2.0);
145
146        roots.push_root(parent);
147        roots.push_root(child);
148
149        assert_eq!(roots.root_transform(), child * parent);
150    }
151
152    #[test]
153    fn pop_root_restores_parent_transform() {
154        let mut roots = RootTransforms::new();
155        let parent = Affine::translate((10.0, 20.0));
156
157        roots.push_root(parent);
158        roots.push_root(Affine::scale(2.0));
159        roots.pop_root();
160
161        assert_eq!(roots.root_transform(), parent);
162    }
163
164    #[test]
165    fn effective_transforms_include_root_scene_and_paint_transforms() {
166        let mut roots = RootTransforms::new();
167        let root = Affine::translate((10.0, 20.0));
168        let scene = Affine::scale(2.0);
169        let paint = Affine::translate((3.0, 4.0));
170        let mut transforms = Transforms::new();
171        transforms.set_transform(scene);
172        transforms.set_paint_transform(paint);
173        roots.push_root(root);
174
175        assert_eq!(roots.effective_path_transform(&transforms), root * scene);
176        assert_eq!(
177            roots.effective_paint_transform(&transforms),
178            root * scene * paint
179        );
180    }
181}