| // Copyright 2026 the Vello Authors |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| //! Shared structures for holding different transforms. |
| |
| use crate::kurbo::Affine; |
| use smallvec::{SmallVec, smallvec}; |
| |
| /// Context for holding transforms for paths and paints. |
| #[derive(Debug, Clone)] |
| pub struct Transforms { |
| transform: Affine, |
| paint_transform: Affine, |
| } |
| |
| impl Default for Transforms { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| impl Transforms { |
| /// Create a new transforms context. |
| pub fn new() -> Self { |
| Self { |
| transform: Affine::IDENTITY, |
| paint_transform: Affine::IDENTITY, |
| } |
| } |
| |
| /// Return the current scene transform. |
| pub fn transform(&self) -> &Affine { |
| &self.transform |
| } |
| |
| /// Return the transform used for rendering scene geometry. |
| pub fn scene_transform(&self) -> Affine { |
| self.transform |
| } |
| |
| /// Set the current scene transform. |
| pub fn set_transform(&mut self, transform: Affine) { |
| self.transform = transform; |
| } |
| |
| /// Reset the current scene transform. |
| pub fn reset_transform(&mut self) { |
| self.transform = Affine::IDENTITY; |
| } |
| |
| /// Return the current paint transform. |
| pub fn paint_transform(&self) -> &Affine { |
| &self.paint_transform |
| } |
| |
| /// Return the transform used for rendering scene paints. |
| pub fn scene_paint_transform(&self) -> Affine { |
| self.scene_transform() * self.paint_transform |
| } |
| |
| /// Set the current paint transform. |
| pub fn set_paint_transform(&mut self, paint_transform: Affine) { |
| self.paint_transform = paint_transform; |
| } |
| |
| /// Reset the current paint transform. |
| pub fn reset_paint_transform(&mut self) { |
| self.paint_transform = Affine::IDENTITY; |
| } |
| |
| // Unlike [`Self::effective_path_transform`], this intentionally does not apply |
| // the root transform because clipping handles root viewport shifts separately. |
| /// Return the transform used for non-isolated clip paths. |
| pub fn clip_path_transform(&self) -> Affine { |
| self.transform |
| } |
| } |
| |
| /// Stack of root transforms. |
| #[derive(Debug)] |
| pub struct RootTransforms { |
| transforms: SmallVec<[Affine; 3]>, |
| } |
| |
| impl Default for RootTransforms { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| impl RootTransforms { |
| /// Create a new root transform stack. |
| pub fn new() -> Self { |
| Self { |
| transforms: smallvec![Affine::IDENTITY], |
| } |
| } |
| |
| /// Return the root transform of the currently active root viewport, including |
| /// shifts inherited from nested filters. |
| pub fn root_transform(&self) -> Affine { |
| *self |
| .transforms |
| .last() |
| .expect("root transform stack should never be empty") |
| } |
| |
| /// Return the transform used for rendering paths. |
| pub fn effective_path_transform(&self, transforms: &Transforms) -> Affine { |
| self.root_transform() * transforms.transform |
| } |
| |
| /// Return the transform used for rendering paints. |
| pub fn effective_paint_transform(&self, transforms: &Transforms) -> Affine { |
| self.effective_path_transform(transforms) * transforms.paint_transform |
| } |
| |
| /// Push a new root transform relative to the currently active root transform. |
| pub fn push_root(&mut self, relative_transform: Affine) { |
| self.transforms |
| .push(relative_transform * self.root_transform()); |
| } |
| |
| /// Pop the last root layer. |
| pub fn pop_root(&mut self) { |
| self.transforms.pop(); |
| } |
| |
| /// Reset the root transform stack. |
| pub fn reset(&mut self) { |
| self.transforms.clear(); |
| self.transforms.push(Affine::IDENTITY); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn root_transforms_accumulate_relative_transforms() { |
| let mut roots = RootTransforms::new(); |
| let parent = Affine::translate((10.0, 20.0)); |
| let child = Affine::scale(2.0); |
| |
| roots.push_root(parent); |
| roots.push_root(child); |
| |
| assert_eq!(roots.root_transform(), child * parent); |
| } |
| |
| #[test] |
| fn pop_root_restores_parent_transform() { |
| let mut roots = RootTransforms::new(); |
| let parent = Affine::translate((10.0, 20.0)); |
| |
| roots.push_root(parent); |
| roots.push_root(Affine::scale(2.0)); |
| roots.pop_root(); |
| |
| assert_eq!(roots.root_transform(), parent); |
| } |
| |
| #[test] |
| fn effective_transforms_include_root_scene_and_paint_transforms() { |
| let mut roots = RootTransforms::new(); |
| let root = Affine::translate((10.0, 20.0)); |
| let scene = Affine::scale(2.0); |
| let paint = Affine::translate((3.0, 4.0)); |
| let mut transforms = Transforms::new(); |
| transforms.set_transform(scene); |
| transforms.set_paint_transform(paint); |
| roots.push_root(root); |
| |
| assert_eq!(roots.effective_path_transform(&transforms), root * scene); |
| assert_eq!( |
| roots.effective_paint_transform(&transforms), |
| root * scene * paint |
| ); |
| } |
| } |