# 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.