///|
/// A typed element of an owned Cairo path snapshot.
///
/// Move and line elements store their endpoint. Curve elements store the
/// first control point, second control point, and endpoint in that order.
/// Coordinates use the user space in effect when the path was copied.
pub(all) enum PathSegment {
PathSegmentMoveTo(Double, Double)
PathSegmentLineTo(Double, Double)
PathSegmentCurveTo(Double, Double, Double, Double, Double, Double)
PathSegmentClosePath
} derive(Eq, Debug)
///|
/// Return the Cairo path-data discriminator for this segment.
pub fn PathSegment::data_type(self : PathSegment) -> PathDataType {
match self {
PathSegmentMoveTo(_, _) => PathMoveTo
PathSegmentLineTo(_, _) => PathLineTo
PathSegmentCurveTo(_, _, _, _, _, _) => PathCurveTo
PathSegmentClosePath => PathClosePath
}
}
///|
/// Return this segment's coordinates in Cairo order.
///
/// The new array contains two values for move/line, six for curve, and none
/// for close-path.
pub fn PathSegment::coordinates(self : PathSegment) -> Array[Double] {
match self {
PathSegmentMoveTo(x, y) | PathSegmentLineTo(x, y) => [x, y]
PathSegmentCurveTo(x1, y1, x2, y2, x3, y3) => [x1, y1, x2, y2, x3, y3]
PathSegmentClosePath => []
}
}
///|
/// Return the segment discriminator and a newly allocated coordinate array.
pub fn PathSegment::components(
self : PathSegment,
) -> (PathDataType, Array[Double]) {
(self.data_type(), self.coordinates())
}
///|
/// An owned, opaque snapshot of Cairo path data.
///
/// Paths come from `Context::copy_path`, `Context::copy_path_flat`, or another
/// Cairo producer and cannot be directly constructed. A path remains valid
/// independently of the context or pattern that produced it.
struct Path(@path_impl.RawPath)
///|
fn Path::from_raw(raw : @path_impl.RawPath) -> Path {
Path(raw)
}
///|
fn Path::to_raw(self : Path) -> @path_impl.RawPath {
self.0
}
///|
fn path_status_from_raw(raw : Int) -> Status {
status_from_raw(raw) catch {
_ => InvalidStatus
}
}
///|
fn check_path_status_raw(raw : Int) -> Unit raise CairoError {
check_status(status_from_raw(raw))
}
///|
fn path_data_type_from_raw(raw : Int) -> PathDataType raise CairoError {
match raw {
0 => PathMoveTo
1 => PathLineTo
2 => PathCurveTo
3 => PathClosePath
_ =>
raise CairoInvalidArgument(
InvalidStatus,
"unknown cairo path data type: \{raw}",
)
}
}
///|
/// Return the status stored in this path without raising it.
pub fn Path::status(self : Path) -> Status {
path_status_from_raw(@path_impl.status_raw(self.to_raw()))
}
///|
/// Test whether two wrappers own the same Cairo path allocation.
///
/// This is identity equality, not geometric or segment-content equality.
pub fn Path::equal(self : Path, other : Path) -> Bool {
@path_impl.equal_raw(self.to_raw(), other.to_raw())
}
///|
/// Return an identity hash for the underlying Cairo path allocation.
///
/// The value is process-local and must not be persisted.
pub fn Path::hash(self : Path) -> UInt64 {
@path_impl.hash_raw(self.to_raw())
}
///|
pub impl Eq for Path with fn equal(self, other) {
self.equal(other)
}
///|
pub impl Compare for Path with fn compare(self, other) {
self.hash().compare(other.hash())
}
///|
pub impl Hash for Path with fn hash(self) {
self.hash().hash()
}
///|
pub impl Hash for Path with fn hash_combine(self, hasher) {
hasher.combine_uint64(self.hash())
}
///|
/// Return the number of independent path segments.
///
/// Cairo may insert an explicit move segment after close-path; it counts as a
/// segment. Raises the path's checked `CairoError` status if it is invalid.
pub fn Path::length(self : Path) -> Int raise CairoError {
let status = Ref(0)
let count = @path_impl.num_segments_raw(self.to_raw(), status)
check_path_status_raw(status.val)
count
}
///|
/// Copy all path data into typed MoonBit segments.
///
/// Raises the path's checked `CairoError` status for invalid path data.
pub fn Path::segments(self : Path) -> Array[PathSegment] raise CairoError {
let status = Ref(0)
let data_count = @path_impl.num_data_raw(self.to_raw(), status)
check_path_status_raw(status.val)
let segment_count = @path_impl.num_segments_raw(self.to_raw(), status)
check_path_status_raw(status.val)
let segments : Array[PathSegment] = Array::new(capacity=segment_count)
for data_index = 0; data_index < data_count; {
let type_ = Ref(3)
let length = Ref(0)
let x1 = Ref(0.0)
let y1 = Ref(0.0)
let x2 = Ref(0.0)
let y2 = Ref(0.0)
let x3 = Ref(0.0)
let y3 = Ref(0.0)
check_path_status_raw(
@path_impl.get_data_raw(
self.to_raw(),
data_index,
type_,
length,
x1,
y1,
x2,
y2,
x3,
y3,
),
)
match path_data_type_from_raw(type_.val) {
PathMoveTo => segments.push(PathSegmentMoveTo(x1.val, y1.val))
PathLineTo => segments.push(PathSegmentLineTo(x1.val, y1.val))
PathCurveTo =>
segments.push(
PathSegmentCurveTo(x1.val, y1.val, x2.val, y2.val, x3.val, y3.val),
)
PathClosePath => segments.push(PathSegmentClosePath)
}
continue data_index + length.val
}
segments
}
///|
/// Return an iterator over a copied, validated segment snapshot.
///
/// Constructing the iterator materializes the complete `segments()` array and
/// can raise the same checked errors.
pub fn Path::iter(self : Path) -> Iter[PathSegment] raise CairoError {
self.segments().iter()
}
///|
/// Format segments in pycairo's newline-separated debug representation.
///
/// This representation is for diagnostics, not stable serialization. Raises
/// the path's checked `CairoError` status for invalid data or allocation errors.
pub fn Path::to_string(self : Path) -> String raise CairoError {
let status = Ref(0)
let bytes = @path_impl.to_string_raw(self.to_raw(), status)
check_path_status_raw(status.val)
@utf8.decode_lossy(bytes)
}