Skip to main content

thorvg/
paint.rs

1//! Paints, transforms, and the shared [`Paint`] trait.
2//!
3//! Wraps the [`ThorVG` C API](https://www.thorvg.org/c-native).
4
5use crate::error::{Error, Result};
6use crate::shape::Shape;
7use thorvg_sys as sys;
8
9/// A 3×3 affine transformation matrix.
10///
11/// The layout is row-major and matches thorvg's `Tvg_Matrix`:
12///
13/// ```text
14/// | e11 e12 e13 |
15/// | e21 e22 e23 |
16/// | e31 e32 e33 |
17/// ```
18///
19/// It transforms a column vector `[x y 1]ᵀ`, so:
20/// - `e11`, `e12`, `e21`, `e22` form the rotation/scale block.
21/// - `e13`, `e23` hold the x and y **translation** (third column,
22///   not the bottom row).
23/// - `e31`, `e32` are `0` and `e33` is `1` for a well-formed affine
24///   transform; see [`IDENTITY`](Self::IDENTITY).
25///
26/// Note: `PartialEq` uses exact floating-point comparison.
27/// Use approximate comparison for transformed values.
28#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct Matrix {
30    pub e11: f32,
31    pub e12: f32,
32    pub e13: f32,
33    pub e21: f32,
34    pub e22: f32,
35    pub e23: f32,
36    pub e31: f32,
37    pub e32: f32,
38    pub e33: f32,
39}
40
41impl Matrix {
42    /// The identity matrix.
43    pub const IDENTITY: Self = Self {
44        e11: 1.0,
45        e12: 0.0,
46        e13: 0.0,
47        e21: 0.0,
48        e22: 1.0,
49        e23: 0.0,
50        e31: 0.0,
51        e32: 0.0,
52        e33: 1.0,
53    };
54
55    /// Row-major 3×3 multiply: `self × rhs`, matching thorvg's
56    /// `operator*` (tvgMath.cpp). The combinators build the new
57    /// operation as the left operand so it applies *after* `self`.
58    #[must_use]
59    fn multiply(self, rhs: Matrix) -> Matrix {
60        Matrix {
61            e11: self.e11 * rhs.e11 + self.e12 * rhs.e21 + self.e13 * rhs.e31,
62            e12: self.e11 * rhs.e12 + self.e12 * rhs.e22 + self.e13 * rhs.e32,
63            e13: self.e11 * rhs.e13 + self.e12 * rhs.e23 + self.e13 * rhs.e33,
64            e21: self.e21 * rhs.e11 + self.e22 * rhs.e21 + self.e23 * rhs.e31,
65            e22: self.e21 * rhs.e12 + self.e22 * rhs.e22 + self.e23 * rhs.e32,
66            e23: self.e21 * rhs.e13 + self.e22 * rhs.e23 + self.e23 * rhs.e33,
67            e31: self.e31 * rhs.e11 + self.e32 * rhs.e21 + self.e33 * rhs.e31,
68            e32: self.e31 * rhs.e12 + self.e32 * rhs.e22 + self.e33 * rhs.e32,
69            e33: self.e31 * rhs.e13 + self.e32 * rhs.e23 + self.e33 * rhs.e33,
70        }
71    }
72
73    /// Returns `self` with a translation applied after it.
74    ///
75    /// Combinators chain in reading order: `m.translate(..).rotate(..)`
76    /// translates first, then rotates. Note this is *true* matrix
77    /// composition — unlike thorvg's incremental [`Paint::translate`],
78    /// which always keeps the translation outermost. Build the matrix
79    /// here and hand it to `set_transform` for full control.
80    #[must_use]
81    pub fn translate(self, x: f32, y: f32) -> Matrix {
82        Matrix {
83            e13: x,
84            e23: y,
85            ..Matrix::IDENTITY
86        }
87        .multiply(self)
88    }
89
90    /// Returns `self` with a (possibly non-uniform) scale applied after
91    /// it. `Paint::scale` only does uniform scaling; this allows `sx != sy`.
92    #[must_use]
93    pub fn scale(self, sx: f32, sy: f32) -> Matrix {
94        Matrix {
95            e11: sx,
96            e22: sy,
97            ..Matrix::IDENTITY
98        }
99        .multiply(self)
100    }
101
102    /// Returns `self` with a rotation applied after it. `degrees`
103    /// matches the unit of [`Paint::rotate`].
104    #[must_use]
105    pub fn rotate(self, degrees: f32) -> Matrix {
106        let r = degrees.to_radians();
107        let (s, c) = (libm::sinf(r), libm::cosf(r));
108        Matrix {
109            e11: c,
110            e12: -s,
111            e21: s,
112            e22: c,
113            ..Matrix::IDENTITY
114        }
115        .multiply(self)
116    }
117
118    fn to_raw(self) -> sys::Tvg_Matrix {
119        sys::Tvg_Matrix {
120            e11: self.e11,
121            e12: self.e12,
122            e13: self.e13,
123            e21: self.e21,
124            e22: self.e22,
125            e23: self.e23,
126            e31: self.e31,
127            e32: self.e32,
128            e33: self.e33,
129        }
130    }
131
132    fn from_raw(m: sys::Tvg_Matrix) -> Self {
133        Self {
134            e11: m.e11,
135            e12: m.e12,
136            e13: m.e13,
137            e21: m.e21,
138            e22: m.e22,
139            e23: m.e23,
140            e31: m.e31,
141            e32: m.e32,
142            e33: m.e33,
143        }
144    }
145}
146
147impl Default for Matrix {
148    /// The identity matrix; see [`Matrix::IDENTITY`].
149    fn default() -> Self {
150        Self::IDENTITY
151    }
152}
153
154/// A point in 2D space.
155#[derive(Debug, Clone, Copy, PartialEq)]
156pub struct Point {
157    /// X coordinate.
158    pub x: f32,
159    /// Y coordinate.
160    pub y: f32,
161}
162
163impl Point {
164    /// Builds a [`Point`] from its coordinates.
165    #[must_use]
166    pub const fn new(x: f32, y: f32) -> Self {
167        Self { x, y }
168    }
169}
170
171/// Blending method for compositing paint objects.
172///
173/// Covers thorvg's user-facing blend modes (`Tvg_Blend_Method`
174/// `NORMAL`..=`ADD`). The C enum's `TVG_BLEND_METHOD_COMPOSITION`
175/// (= 255) is deliberately omitted: it is an internal value the engine
176/// uses for intermediate composition, not a mode callers select. Blend
177/// is write-only in the C API (there is no `tvg_paint_get_blend_method`),
178/// so the engine never hands a value back that would need mapping here —
179/// hence this enum has only `to_raw` and no `from_raw`.
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
181#[repr(u8)]
182#[non_exhaustive]
183pub enum BlendMethod {
184    /// Source is drawn over the destination (the default, no blending).
185    Normal = 0,
186    /// Multiplies source and destination — always darkens.
187    Multiply,
188    /// Inverse-multiplies — always lightens.
189    Screen,
190    /// `Multiply` in dark areas, `Screen` in light areas.
191    Overlay,
192    /// Keeps the darker of source and destination per channel.
193    Darken,
194    /// Keeps the lighter of source and destination per channel.
195    Lighten,
196    /// Brightens the destination to reflect the source.
197    ColorDodge,
198    /// Darkens the destination to reflect the source.
199    ColorBurn,
200    /// `Overlay` with source and destination swapped.
201    HardLight,
202    /// A softer variant of `HardLight`.
203    SoftLight,
204    /// Absolute difference of source and destination per channel.
205    Difference,
206    /// Like `Difference` but with lower contrast.
207    Exclusion,
208    /// Hue of the source with destination saturation and luminosity.
209    Hue,
210    /// Saturation of the source with destination hue and luminosity.
211    Saturation,
212    /// Hue and saturation of the source with destination luminosity.
213    Color,
214    /// Luminosity of the source with destination hue and saturation.
215    Luminosity,
216    /// Additive blending — sums source and destination per channel.
217    Add,
218}
219
220impl BlendMethod {
221    fn to_raw(self) -> sys::Tvg_Blend_Method {
222        match self {
223            Self::Normal => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_NORMAL,
224            Self::Multiply => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_MULTIPLY,
225            Self::Screen => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_SCREEN,
226            Self::Overlay => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_OVERLAY,
227            Self::Darken => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_DARKEN,
228            Self::Lighten => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_LIGHTEN,
229            Self::ColorDodge => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_COLORDODGE,
230            Self::ColorBurn => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_COLORBURN,
231            Self::HardLight => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_HARDLIGHT,
232            Self::SoftLight => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_SOFTLIGHT,
233            Self::Difference => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_DIFFERENCE,
234            Self::Exclusion => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_EXCLUSION,
235            Self::Hue => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_HUE,
236            Self::Saturation => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_SATURATION,
237            Self::Color => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_COLOR,
238            Self::Luminosity => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_LUMINOSITY,
239            Self::Add => sys::Tvg_Blend_Method::TVG_BLEND_METHOD_ADD,
240        }
241    }
242}
243
244/// Masking method for combining two paint objects.
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
246#[repr(u8)]
247#[non_exhaustive]
248pub enum MaskMethod {
249    /// No masking applied.
250    None = 0,
251    /// Masks by the target's alpha channel.
252    Alpha,
253    /// Masks by the inverse of the target's alpha channel.
254    InvAlpha,
255    /// Masks by the target's luminance.
256    Luma,
257    /// Masks by the inverse of the target's luminance.
258    InvLuma,
259    /// Adds the target's coverage to the source.
260    Add,
261    /// Subtracts the target's coverage from the source.
262    Subtract,
263    /// Keeps only where source and target overlap.
264    Intersect,
265    /// Keeps the non-overlapping coverage of source and target.
266    Difference,
267    /// Keeps the lighter coverage of source and target.
268    Lighten,
269    /// Keeps the darker coverage of source and target.
270    Darken,
271}
272
273impl MaskMethod {
274    fn to_raw(self) -> sys::Tvg_Mask_Method {
275        match self {
276            Self::None => sys::Tvg_Mask_Method::TVG_MASK_METHOD_NONE,
277            Self::Alpha => sys::Tvg_Mask_Method::TVG_MASK_METHOD_ALPHA,
278            Self::InvAlpha => sys::Tvg_Mask_Method::TVG_MASK_METHOD_INVERSE_ALPHA,
279            Self::Luma => sys::Tvg_Mask_Method::TVG_MASK_METHOD_LUMA,
280            Self::InvLuma => sys::Tvg_Mask_Method::TVG_MASK_METHOD_INVERSE_LUMA,
281            Self::Add => sys::Tvg_Mask_Method::TVG_MASK_METHOD_ADD,
282            Self::Subtract => sys::Tvg_Mask_Method::TVG_MASK_METHOD_SUBTRACT,
283            Self::Intersect => sys::Tvg_Mask_Method::TVG_MASK_METHOD_INTERSECT,
284            Self::Difference => sys::Tvg_Mask_Method::TVG_MASK_METHOD_DIFFERENCE,
285            Self::Lighten => sys::Tvg_Mask_Method::TVG_MASK_METHOD_LIGHTEN,
286            Self::Darken => sys::Tvg_Mask_Method::TVG_MASK_METHOD_DARKEN,
287        }
288    }
289
290    pub(crate) fn from_raw(m: sys::Tvg_Mask_Method) -> Self {
291        match m {
292            sys::Tvg_Mask_Method::TVG_MASK_METHOD_ALPHA => Self::Alpha,
293            sys::Tvg_Mask_Method::TVG_MASK_METHOD_INVERSE_ALPHA => Self::InvAlpha,
294            sys::Tvg_Mask_Method::TVG_MASK_METHOD_LUMA => Self::Luma,
295            sys::Tvg_Mask_Method::TVG_MASK_METHOD_INVERSE_LUMA => Self::InvLuma,
296            sys::Tvg_Mask_Method::TVG_MASK_METHOD_ADD => Self::Add,
297            sys::Tvg_Mask_Method::TVG_MASK_METHOD_SUBTRACT => Self::Subtract,
298            sys::Tvg_Mask_Method::TVG_MASK_METHOD_INTERSECT => Self::Intersect,
299            sys::Tvg_Mask_Method::TVG_MASK_METHOD_DIFFERENCE => Self::Difference,
300            sys::Tvg_Mask_Method::TVG_MASK_METHOD_LIGHTEN => Self::Lighten,
301            sys::Tvg_Mask_Method::TVG_MASK_METHOD_DARKEN => Self::Darken,
302            sys::Tvg_Mask_Method::TVG_MASK_METHOD_NONE => Self::None,
303        }
304    }
305}
306
307/// A read-only borrow of a paint owned by another object.
308///
309/// Returned by getters that expose an internal paint handle whose
310/// lifetime is governed by the containing object (e.g. the mask
311/// target stored inside a `Paint::mask` relationship).  The `'a`
312/// lifetime ties the borrow to the source: dropping the source
313/// invalidates the borrow.
314///
315/// Read-only surface only — mutating the borrowed paint via raw
316/// access would race with the owner.
317pub struct BorrowedPaint<'a> {
318    raw: sys::Tvg_Paint,
319    _life: core::marker::PhantomData<&'a ()>,
320}
321
322impl BorrowedPaint<'_> {
323    /// Wraps a raw handle as a read-only borrow.
324    ///
325    /// # Safety
326    ///
327    /// `raw` must be a valid paint handle whose owner outlives `'a`.
328    pub(crate) unsafe fn from_raw(raw: sys::Tvg_Paint) -> Self {
329        Self {
330            raw,
331            _life: core::marker::PhantomData,
332        }
333    }
334
335    /// Returns the underlying raw handle.
336    ///
337    /// Intended for use with `thorvg-sys` C APIs not yet wrapped here; the
338    /// `'a` lifetime keeps the borrow honest. Do not use it to mutate the
339    /// paint — that would race with the owner.
340    pub fn raw(&self) -> sys::Tvg_Paint {
341        self.raw
342    }
343
344    /// Returns the runtime [`PaintType`] of the borrowed handle.
345    ///
346    /// # Errors
347    ///
348    /// Returns [`Error::InvalidArguments`] if the underlying handle is
349    /// invalid.
350    pub fn paint_type(&self) -> Result<PaintType> {
351        let mut t = sys::Tvg_Type::TVG_TYPE_UNDEF;
352        Error::from_raw(unsafe { sys::tvg_paint_get_type(self.raw, &raw mut t) })?;
353        Ok(PaintType::from_raw(t))
354    }
355
356    /// Returns the user-assigned id, or `0` if none was set.
357    pub fn id(&self) -> u32 {
358        unsafe { sys::tvg_paint_get_id(self.raw) }
359    }
360
361    /// Returns the opacity, in the range `0..=255`.
362    ///
363    /// # Errors
364    ///
365    /// Returns [`Error::InvalidArguments`] if the underlying handle is
366    /// invalid.
367    pub fn opacity(&self) -> Result<u8> {
368        let mut o: u8 = 0;
369        Error::from_raw(unsafe { sys::tvg_paint_get_opacity(self.raw, &raw mut o) })?;
370        Ok(o)
371    }
372
373    /// Returns the axis-aligned bounding box `(x, y, w, h)` in canvas space.
374    ///
375    /// # Errors
376    ///
377    /// Returns [`Error::InsufficientCondition`] if the bounds cannot be
378    /// computed (usually invalid path data), or [`Error::InvalidArguments`]
379    /// if the underlying handle is invalid.
380    pub fn bounds(&self) -> Result<(f32, f32, f32, f32)> {
381        let (mut x, mut y, mut w, mut h) = (0f32, 0f32, 0f32, 0f32);
382        Error::from_raw(unsafe {
383            sys::tvg_paint_get_aabb(self.raw, &raw mut x, &raw mut y, &raw mut w, &raw mut h)
384        })?;
385        Ok((x, y, w, h))
386    }
387}
388
389impl core::fmt::Debug for BorrowedPaint<'_> {
390    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
391        f.debug_struct("BorrowedPaint").finish_non_exhaustive()
392    }
393}
394
395/// The concrete type of a paint object.
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
397#[non_exhaustive]
398pub enum PaintType {
399    /// Unknown or unset type.
400    Undefined,
401    /// A [`Shape`](crate::Shape).
402    Shape,
403    /// A [`Scene`](crate::Scene).
404    Scene,
405    /// A [`Picture`](crate::Picture).
406    Picture,
407    /// A [`Text`](crate::Text).
408    Text,
409    /// A [`LinearGradient`](crate::LinearGradient).
410    LinearGradient,
411    /// A [`RadialGradient`](crate::RadialGradient).
412    RadialGradient,
413}
414
415impl PaintType {
416    pub(crate) fn from_raw(t: sys::Tvg_Type) -> Self {
417        match t {
418            sys::Tvg_Type::TVG_TYPE_SHAPE => Self::Shape,
419            sys::Tvg_Type::TVG_TYPE_SCENE => Self::Scene,
420            sys::Tvg_Type::TVG_TYPE_PICTURE => Self::Picture,
421            sys::Tvg_Type::TVG_TYPE_TEXT => Self::Text,
422            sys::Tvg_Type::TVG_TYPE_LINEAR_GRAD => Self::LinearGradient,
423            sys::Tvg_Type::TVG_TYPE_RADIAL_GRAD => Self::RadialGradient,
424            sys::Tvg_Type::TVG_TYPE_UNDEF => Self::Undefined,
425        }
426    }
427}
428
429/// Sealed-trait marker.
430///
431/// `Paint::raw`/`into_raw`/`from_raw_paint` move opaque `ThorVG`
432/// pointers across the FFI boundary without any further validation;
433/// allowing downstream crates to implement [`Paint`] would let safe
434/// code hand bogus pointers to `tvg_canvas_add` etc.  The supertrait
435/// bound on [`sealed::Sealed`] makes the trait closed.
436pub(crate) mod sealed {
437    pub trait Sealed {}
438}
439
440/// Common trait for all paint objects (Shape, Scene, Picture, Text).
441///
442/// Paint represents a graphical element that can be added to a canvas
443/// with transformations, opacity, masking, and clipping.
444///
445/// This trait is **sealed**: only types defined in this crate may
446/// implement it.
447pub trait Paint: sealed::Sealed {
448    /// Returns the underlying raw `Tvg_Paint` handle without transferring
449    /// ownership.
450    fn raw(&self) -> sys::Tvg_Paint;
451
452    /// Consumes `self` and returns the raw handle, transferring ownership.
453    ///
454    /// The caller becomes responsible for the handle; the Rust `Drop` that
455    /// would otherwise free it is suppressed.
456    fn into_raw(self) -> sys::Tvg_Paint;
457
458    /// Sets the opacity, where `0` is fully transparent and `255` is fully
459    /// opaque.
460    ///
461    /// Changing the opacity can force the engine into a multi-pass
462    /// composition, so prefer leaving it unchanged on hot paths.
463    ///
464    /// # Errors
465    ///
466    /// Returns [`Error::InvalidArguments`] if the underlying handle is
467    /// invalid.
468    fn set_opacity(&mut self, opacity: u8) -> Result<()> {
469        Error::from_raw(unsafe { sys::tvg_paint_set_opacity(self.raw(), opacity) })
470    }
471
472    /// Returns the opacity, in the range `0..=255`.
473    ///
474    /// # Errors
475    ///
476    /// Returns [`Error::InvalidArguments`] if the underlying handle is
477    /// invalid.
478    fn opacity(&self) -> Result<u8> {
479        let mut opacity: u8 = 0;
480        Error::from_raw(unsafe { sys::tvg_paint_get_opacity(self.raw(), &raw mut opacity) })?;
481        Ok(opacity)
482    }
483
484    /// Sets whether the paint is rendered.
485    ///
486    /// A hidden paint is excluded from the rendered output but is not
487    /// deactivated: it may still be processed during updates. To remove it
488    /// entirely, take it off the canvas instead.
489    ///
490    /// # Errors
491    ///
492    /// Returns [`Error::InvalidArguments`] if the underlying handle is
493    /// invalid.
494    fn set_visible(&mut self, visible: bool) -> Result<()> {
495        Error::from_raw(unsafe { sys::tvg_paint_set_visible(self.raw(), visible) })
496    }
497
498    /// Returns `true` if the paint is rendered.
499    fn visible(&self) -> bool {
500        unsafe { sys::tvg_paint_get_visible(self.raw()) }
501    }
502
503    /// Scales the paint uniformly by `factor`.
504    ///
505    /// This is an incremental helper layered on the paint's transform; for
506    /// non-uniform scaling build a [`Matrix`] and pass it to
507    /// [`set_transform`](Paint::set_transform).
508    ///
509    /// # Errors
510    ///
511    /// Returns [`Error::InsufficientCondition`] if a custom transform has
512    /// already been set via [`set_transform`](Paint::set_transform), or
513    /// [`Error::InvalidArguments`] if the underlying handle is invalid.
514    fn scale(&mut self, factor: f32) -> Result<()> {
515        Error::from_raw(unsafe { sys::tvg_paint_scale(self.raw(), factor) })
516    }
517
518    /// Rotates the paint by `degree` degrees, clockwise about the origin.
519    ///
520    /// The rotation axis passes through the object's local origin `(0, 0)`.
521    ///
522    /// # Errors
523    ///
524    /// Returns [`Error::InsufficientCondition`] if a custom transform has
525    /// already been set via [`set_transform`](Paint::set_transform), or
526    /// [`Error::InvalidArguments`] if the underlying handle is invalid.
527    fn rotate(&mut self, degree: f32) -> Result<()> {
528        Error::from_raw(unsafe { sys::tvg_paint_rotate(self.raw(), degree) })
529    }
530
531    /// Translates the paint by `(x, y)`.
532    ///
533    /// The coordinate origin is the upper-left corner of the canvas, with
534    /// `x` increasing right and `y` increasing down.
535    ///
536    /// # Errors
537    ///
538    /// Returns [`Error::InsufficientCondition`] if a custom transform has
539    /// already been set via [`set_transform`](Paint::set_transform), or
540    /// [`Error::InvalidArguments`] if the underlying handle is invalid.
541    fn translate(&mut self, x: f32, y: f32) -> Result<()> {
542        Error::from_raw(unsafe { sys::tvg_paint_translate(self.raw(), x, y) })
543    }
544
545    /// Sets the affine transformation matrix, replacing any prior transform.
546    ///
547    /// Unlike [`scale`](Paint::scale), [`rotate`](Paint::rotate), and
548    /// [`translate`](Paint::translate) — which the engine rejects once a
549    /// custom transform exists — this overwrites the transform outright.
550    ///
551    /// # Errors
552    ///
553    /// Returns [`Error::InvalidArguments`] if the underlying handle is
554    /// invalid.
555    fn set_transform(&mut self, m: &Matrix) -> Result<()> {
556        let raw_m = m.to_raw();
557        Error::from_raw(unsafe { sys::tvg_paint_set_transform(self.raw(), &raw const raw_m) })
558    }
559
560    /// Returns the affine transformation matrix.
561    ///
562    /// If no transform was set, this is [`Matrix::IDENTITY`].
563    ///
564    /// # Errors
565    ///
566    /// Returns [`Error::InvalidArguments`] if the underlying handle is
567    /// invalid.
568    fn transform(&self) -> Result<Matrix> {
569        let mut m = sys::Tvg_Matrix {
570            e11: 0.0,
571            e12: 0.0,
572            e13: 0.0,
573            e21: 0.0,
574            e22: 0.0,
575            e23: 0.0,
576            e31: 0.0,
577            e32: 0.0,
578            e33: 0.0,
579        };
580        Error::from_raw(unsafe { sys::tvg_paint_get_transform(self.raw(), &raw mut m) })?;
581        Ok(Matrix::from_raw(m))
582    }
583
584    /// Returns the axis-aligned bounding box `(x, y, width, height)` in
585    /// canvas space, with all transforms applied.
586    ///
587    /// The paint must have been updated on a canvas first (typically after
588    /// a draw/sync), otherwise the bounds reflect stale geometry.
589    ///
590    /// # Errors
591    ///
592    /// Returns [`Error::InsufficientCondition`] if the bounds cannot be
593    /// computed (usually invalid path data), or [`Error::InvalidArguments`]
594    /// if the underlying handle is invalid.
595    fn bounds(&self) -> Result<(f32, f32, f32, f32)> {
596        let mut x: f32 = 0.0;
597        let mut y: f32 = 0.0;
598        let mut w: f32 = 0.0;
599        let mut h: f32 = 0.0;
600        Error::from_raw(unsafe {
601            sys::tvg_paint_get_aabb(self.raw(), &raw mut x, &raw mut y, &raw mut w, &raw mut h)
602        })?;
603        Ok((x, y, w, h))
604    }
605
606    /// Returns the oriented bounding box (OBB) as its four corner points in
607    /// canvas space.
608    ///
609    /// Unlike [`bounds`](Paint::bounds), the OBB preserves the object's
610    /// rotation: it is the local AABB with the object's transform applied.
611    ///
612    /// # Errors
613    ///
614    /// Returns [`Error::InsufficientCondition`] if the bounds cannot be
615    /// computed (usually invalid path data), or [`Error::InvalidArguments`]
616    /// if the underlying handle is invalid.
617    fn bounds_obb(&self) -> Result<[Point; 4]> {
618        let mut pts = [sys::Tvg_Point { x: 0.0, y: 0.0 }; 4];
619        Error::from_raw(unsafe { sys::tvg_paint_get_obb(self.raw(), pts.as_mut_ptr()) })?;
620        Ok([
621            Point {
622                x: pts[0].x,
623                y: pts[0].y,
624            },
625            Point {
626                x: pts[1].x,
627                y: pts[1].y,
628            },
629            Point {
630                x: pts[2].x,
631                y: pts[2].y,
632            },
633            Point {
634                x: pts[3].x,
635                y: pts[3].y,
636            },
637        ])
638    }
639
640    /// Clips this paint's drawing region to the `clipper` shape's paths.
641    ///
642    /// On success the `clipper` is consumed: the engine takes a reference
643    /// and ties its lifetime to this paint (released when the owning canvas
644    /// is destroyed), and the Rust `Drop` is suppressed to avoid a
645    /// double-free. On error the engine never touches the `clipper`, so it
646    /// is dropped normally and its allocation is released rather than
647    /// leaked.
648    ///
649    /// # Errors
650    ///
651    /// Returns [`Error::InsufficientCondition`] if `clipper` already belongs
652    /// to another paint. (The C API also reports `NOT_SUPPORTED` for a
653    /// non-shape clipper, but the [`Shape`] argument type makes that
654    /// unreachable here.)
655    fn set_clip(&mut self, clipper: Shape<'_>) -> Result<()> {
656        let raw = clipper.raw();
657        let r = Error::from_raw(unsafe { sys::tvg_paint_set_clip(self.raw(), raw) });
658        if r.is_ok() {
659            let _ = clipper.into_raw();
660        }
661        r
662    }
663
664    /// Returns the clip shape set via [`set_clip`](Paint::set_clip), or
665    /// `None` if none is set.
666    ///
667    /// The returned [`Shape`] is a non-owning view: it does not free the
668    /// underlying clipper on drop.
669    fn clip(&self) -> Option<Shape<'_>> {
670        let raw = unsafe { sys::tvg_paint_get_clip(self.raw()) };
671        if raw.is_null() {
672            None
673        } else {
674            Some(unsafe { Shape::from_raw(raw, false) })
675        }
676    }
677
678    /// Masks this paint with `mask` using the given [`MaskMethod`].
679    ///
680    /// On success the `mask` is consumed: the engine takes a reference and
681    /// ties its lifetime to this paint (released when the owning canvas is
682    /// destroyed), and the Rust `Drop` is suppressed to avoid a
683    /// double-free. On error the engine does not take ownership, so `mask`
684    /// is dropped normally and its allocation is released rather than
685    /// leaked.
686    ///
687    /// # Errors
688    ///
689    /// Returns [`Error::InsufficientCondition`] if `mask` already belongs to
690    /// another paint, or [`Error::InvalidArguments`] if `method` is
691    /// [`MaskMethod::None`] (passing a target with no masking method is
692    /// rejected — use a real method, or clear the mask through the engine).
693    fn set_mask<P: Paint>(&mut self, mask: P, method: MaskMethod) -> Result<()> {
694        let raw = mask.raw();
695        let r = Error::from_raw(unsafe {
696            sys::tvg_paint_set_mask_method(self.raw(), raw, method.to_raw())
697        });
698        if r.is_ok() {
699            let _ = mask.into_raw();
700        }
701        r
702    }
703
704    /// Returns the mask target and [`MaskMethod`] set via
705    /// [`set_mask`](Paint::set_mask), or `None` if no mask is attached.
706    ///
707    /// The returned [`BorrowedPaint`] is read-only and tied to this paint's
708    /// lifetime — dropping the source invalidates the borrow.
709    ///
710    /// # Why a borrow rather than `Shape<'_>`
711    ///
712    /// `set_mask` accepts any `Paint` (Shape, Scene, Picture, Text),
713    /// so the returned mask cannot be type-pinned to `Shape` without
714    /// risking a silent type-pun.  `BorrowedPaint::paint_type` lets
715    /// callers dispatch on the runtime type.
716    fn mask(&self) -> Option<(BorrowedPaint<'_>, MaskMethod)> {
717        let mut target: sys::Tvg_Paint = core::ptr::null_mut();
718        let mut method = sys::Tvg_Mask_Method::TVG_MASK_METHOD_NONE;
719
720        // The C signature `tvg_paint_get_mask_method(paint, target,
721        // method)` types `target` as `Tvg_Paint` (= *mut c_void) but
722        // treats it internally as a `Paint**` out-param — passing a
723        // real paint handle here would corrupt that paint's vtable.
724        // We pass the address of a local `Tvg_Paint` slot cast to
725        // `Tvg_Paint`, which is what the C side actually wants.
726        let r = unsafe {
727            sys::tvg_paint_get_mask_method(
728                self.raw(),
729                (&raw mut target).cast::<sys::_Tvg_Paint>(),
730                &raw mut method,
731            )
732        };
733
734        if Error::from_raw(r).is_err() || target.is_null() {
735            return None;
736        }
737        // SAFETY: target is non-null and refers to a paint owned by
738        // `self` (the masking source); the BorrowedPaint's lifetime
739        // is bounded by `&self`.
740        Some((
741            unsafe { BorrowedPaint::from_raw(target) },
742            MaskMethod::from_raw(method),
743        ))
744    }
745
746    /// Sets the [`BlendMethod`] used to composite this paint over the layer
747    /// beneath it.
748    ///
749    /// # Errors
750    ///
751    /// Returns [`Error::InvalidArguments`] if the underlying handle is
752    /// invalid.
753    fn set_blend(&mut self, method: BlendMethod) -> Result<()> {
754        Error::from_raw(unsafe { sys::tvg_paint_set_blend_method(self.raw(), method.to_raw()) })
755    }
756
757    /// Sets the paint's id, used to identify a paint instance within a scene.
758    ///
759    /// *Experimental in `ThorVG`; the API may change.*
760    ///
761    /// # Errors
762    ///
763    /// Returns [`Error::InvalidArguments`] if the underlying handle is
764    /// invalid.
765    fn set_id(&mut self, id: u32) -> Result<()> {
766        Error::from_raw(unsafe { sys::tvg_paint_set_id(self.raw(), id) })
767    }
768
769    /// Returns the user-assigned id, or `0` if none was set.
770    ///
771    /// *Experimental in `ThorVG`; the API may change.*
772    fn id(&self) -> u32 {
773        unsafe { sys::tvg_paint_get_id(self.raw()) }
774    }
775
776    /// Returns the concrete [`PaintType`] of this paint.
777    ///
778    /// # Errors
779    ///
780    /// Returns [`Error::InvalidArguments`] if the underlying handle is
781    /// invalid.
782    fn paint_type(&self) -> Result<PaintType> {
783        let mut t = sys::Tvg_Type::TVG_TYPE_UNDEF;
784        Error::from_raw(unsafe { sys::tvg_paint_get_type(self.raw(), &raw mut t) })?;
785        Ok(PaintType::from_raw(t))
786    }
787
788    /// Returns `true` if the rectangle `(x, y, w, h)` intersects this paint's
789    /// filled area (hit-testing).
790    ///
791    /// To test a single point, use `w = 1, h = 1`; non-positive `w` or `h`
792    /// always returns `false`. The paint must have been updated on a canvas
793    /// first. Blending and masking are ignored, but hidden paints still
794    /// count.
795    fn intersects(&self, x: i32, y: i32, w: i32, h: i32) -> bool {
796        unsafe { sys::tvg_paint_intersects(self.raw(), x, y, w, h) }
797    }
798
799    /// Returns a deep copy of this paint with all properties preserved, or
800    /// `None` if duplication fails.
801    fn duplicate(&self) -> Option<Self>
802    where
803        Self: Sized,
804    {
805        let raw = unsafe { sys::tvg_paint_duplicate(self.raw()) };
806        if raw.is_null() {
807            None
808        } else {
809            // Safety: duplicate returns a new owned paint
810            Some(unsafe { Self::from_raw_paint(raw) })
811        }
812    }
813
814    // Note: paint_ref/paint_unref/ref_cnt/parent_raw are intentionally
815    // not exposed. They manipulate ThorVG's internal reference counting
816    // which conflicts with Rust's ownership model. Misuse (e.g.
817    // paint_unref(true)) causes double-free when Drop runs. Use
818    // thorvg-sys directly if you need low-level refcount control.
819
820    /// Constructs a new owned instance from a raw `Tvg_Paint` handle.
821    ///
822    /// # Safety
823    ///
824    /// `raw` must be a valid `Tvg_Paint` owned by the caller, and its
825    /// runtime [`PaintType`] must match `Self`. The returned value takes
826    /// ownership and frees the handle on drop.
827    unsafe fn from_raw_paint(raw: sys::Tvg_Paint) -> Self
828    where
829        Self: Sized;
830}