Peta::FFI::Cairo#

📦 std

Native reimplementation of the Perl Cairo module against libcairo.so.2.

The upstream Cairo CPAN distribution (v1.109) is a thin XS shim over the cairo 2d graphics C library. This module reproduces its observable Perl behavior (the ”phenotype“) by calling into libcairo.so.2 directly through dlopen/dlsym, without a build-time C binding.

Although the Rust source lives under Peta::FFI::Cairo, the Perl-visible packages are the real Cairo, Cairo::Context, Cairo::Surface, Cairo::ImageSurface, etc. - so ported cairo tests and existing Perl code see the same API they would from the XS module.

Architecture: per-package plug-in modules#

mod.rs is the FOUNDATION: the shared dlopen handle, the string<->int enum conversion tables, the blessing/refcount contract, the struct-as-hashref marshallers, the status-to-$@ croak helper, and the base Cairo:: version/capability subs. Each Perl-visible object package (Cairo::Context, Cairo::Surface, Cairo::Pattern, …) lives in its OWN sibling file Cairo/<Pkg>.rs and plugs into boot() through a NARROW, STABLE contract so that the seven package files can be authored in PARALLEL without touching shared state.

Crucially, each package resolves its OWN cairo symbols locally (via cairo_dlsym over the shared cairo_handle), so a package agent never edits a central symbol table. The foundation resolves only the two base-namespace symbols (cairo_version, cairo_version_string).

THE PER-PACKAGE PLUG-IN CONTRACT (read this before authoring a package)#

Every Cairo/<Pkg>.rs file must expose EXACTLY these two items and nothing else that mod.rs depends on:

// The fully-qualified Perl sub names this package installs, paired
// with their XS thunks. `boot()` newXS-registers every entry. Names
// carry the REAL Cairo namespace (e.g. "Cairo::Context::create").
pub(crate) const XS_FUNCTIONS:
    &[(&str, unsafe extern "C" fn(*mut CV))] = &[ /* ... */ ];

// @ISA wiring for this package's classes (set_isa calls), or empty.
// Called once by boot() after all XS subs are registered.
pub(crate) unsafe fn install_isa() { /* ... */ }

A package resolves the cairo C symbols it needs itself, e.g.:

use super::{cairo_dlsym, cairo_t};
struct Syms { create: unsafe extern "C" fn(*mut cairo_surface_t) -> *mut cairo_t }
static SYMS: OnceLock<Syms> = OnceLock::new();
unsafe fn syms() -> &'static Syms {
    SYMS.get_or_init(|| Syms {
        create: cairo_dlsym(b"cairo_create\0").expect("cairo_create"),
    })
}

It draws every foundation helper it needs from super:: (all the pub(crate) items below): the opaque cairo_*_t types, bless_ptr, extract_ptr, bless_surface_noinc, nv_hashref, check_status, set_isa, read_doubles, cairo_handle, cairo_dlsym, and every EnumTable static (FORMAT, STATUS, OPERATOR, …).

Refcount contract (census section 4)#

Every cairo object is a blessed scalar ref whose referent’s IV holds the raw cairo_*_t*. Constructors (create*) return an object with a reference already owned (+1), so we bless the pointer directly. Borrowed getters (get_source, get_target, pop_group) must take an extra reference (cairo_*_reference) before blessing. Every DESTROY drops exactly one reference via cairo_*_destroy.

Modules#

Functions#

Other Functions#

lib_version#

Cairo::LIB_VERSION / Cairo::version / Cairo::lib_version -> the compile-time-encoded cairo version integer of the loaded library. Note: the Perl-level Cairo::VERSION dispatcher (2-arg -> version check, else -> LIB_VERSION) lives in the .pm; here we provide the underlying LIB_VERSION that it and the tests call.

version_string#

Cairo::version_string / Cairo::lib_version_string -> the cairo version as a string (e.g. ”1.18.4“).

version_encode#

Cairo::VERSION_ENCODE / Cairo::LIB_VERSION_ENCODE (major,minor,micro) -> encoded int, mirroring CAIRO_VERSION_ENCODE. Called as either a function Cairo::VERSION_ENCODE(1,2,0) or class method Cairo->VERSION_ENCODE(1,2,0); the class-method form prepends the invocant, so we read the LAST three numeric args.

has_png#

Cairo::HAS_PNG_FUNCTIONS -> whether the loaded libcairo exposes PNG output. Probed by dlsym presence (census runtime-env note), not a compile-time ifdef. The symbol itself lives in the Surface package, so we probe it directly rather than through a central table field.