blob: a5d4b2a437fcd48acad1e2a5f41416ffef2a9fbe [file]
// 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
);
}
}