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}