# 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/.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/.rs` file must expose EXACTLY these two items and nothing else that `mod.rs` depends on: ```ignore // 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.: ```ignore use super::{cairo_dlsym, cairo_t}; struct Syms { create: unsafe extern "C" fn(*mut cairo_surface_t) -> *mut cairo_t } static SYMS: OnceLock = 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 - [`Peta::FFI::Cairo::Context`](Cairo/Context.md) — `Cairo::Context` package plug-in. - [`Peta::FFI::Cairo::Font`](Cairo/Font.md) — `Cairo::Font` family plug-in: FontFace, ToyFontFace, ScaledFont, - [`Peta::FFI::Cairo::Matrix`](Cairo/Matrix.md) — `Cairo::Matrix` package plug-in. - [`Peta::FFI::Cairo::Path`](Cairo/Path.md) — `Cairo::Path` package plug-in - the tied-array live view onto `cairo_path_t*`. - [`Peta::FFI::Cairo::Pattern`](Cairo/Pattern.md) — `Cairo::Pattern` family plug-in (CairoPattern.xs phenotype). - [`Peta::FFI::Cairo::Region`](Cairo/Region.md) — `Cairo::Region` package plug-in. - [`Peta::FFI::Cairo::Surface`](Cairo/Surface.md) — `Cairo::Surface` family package plug-in. ## 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.