Java language bindings for the cairo graphics library using the new JEP-454 Foreign Function & Memory API. The bindings are based on cairo 1.18.0 and work with JDK 22 and newer.
I created these language bindings primarily as a companion to the GObject-Introspection-based Java language bindings for Gtk and GStreamer generated with Java-GI, but they can also be used independently.
In general, the Java bindings match the cairo C API, but with a Java "coding style". C structs like
cairo_t
, cairo_surface_t
and cairo_matrix_t
are modeled with Java proxy
classes like Context
, Surface
and Matrix
, and all flags and enumerations are
available as Java enums. The proxy classes inherit when applicable: RadialGradient
extends
Gradient
, which extends Pattern
, and ImageSurface
extends Surface
. Types,
functions and parameters follow Java (camel case) naming practices, so
cairo_move_to(*cr, x, y)
becomes cr.moveTo(x, y)
. Out-parameters in the C API
are mapped to return values in Java. Multiple out parameters (like coordinates) are mapped to a
Point
, Circle
, Rect
or RGBA
return type in Java.
Most resources are allocated and deallocated automatically, so there is no need to manually dispose
cairo resources in Java as would be the case in C with the various cairo_..._destroy()
functions.
However, please be aware that the disposal of these objects (like Context) is initiated by the Java
garbage collector, which does not know about the native resources, and might wait an indefinite
amount of time before the objects are effectively disposed. Therefore, manual calls to destroy()
are still possible in case the automatic cleanup during GC is not sufficient to prevent resource
exhaustion.
Some data types (like Matrix and TextExtents) must be allocated by the client; the create()
methods for these classes have an Arena
parameter. Consult the JDK documentation to choose the
optimal type of Arena for your use case.
The Surface
and Device
classes implement AutoCloseable
and are preferably used in
try-with-resources blocks. (The close()
method calls the C cairo_..._finish()
function.)
Cairo status codes are checked in the language binding, and throw exceptions
(IllegalStateException, IllegalArgumentException or IOException) with the detailed status
description (from cairo_status_to_string()
). The exceptions are documented in the
Javadoc, except for the CAIRO_STATUS_NO_MEMORY
status, which is not documented and will
throw a RuntimeException if it occurs. If your application consumes a lot of memory, add try-catch
blocks for this situation where applicable.
Some other features that the language bindings offer:
-
In the
Context
,Surface
andPattern
classes (likeMesh
), methods that returnvoid
in the C API, returnthis
in Java, to allow method chaining. -
The
Path
class is iterable, and path traversal is implemented withPathElement
objects. ThePathElement
type is a sealed interface implemented by a record type for every path operation. They can be iterated and processed with record patterns (JEP 440). See thePath
class javadoc for example code. -
I/O operations in cairo that are designed to work with streams accept Java
InputStream
andOutputStream
parameters. -
The cairo Script surface has been split into a
Script
class that inherits fromDevice
, and aScriptSurface
class that inherits fromSurface
. -
The functions for reading and comparing cairo version information are available in Java as static methods in the
Cairo
class. -
Basic functionality is included to load fonts with FreeType2 for use with cairo.
All API documentation is available as Javadoc, and has been reworked to use Javadoc syntax and cross-reference between Java classes and methods. You can lookup the Javadoc online, or download the javadoc or sources jar to use in your IDE.
The bindings are available to be redistributed and/or modified under the terms of the GNU Lesser General Public License (LGPL) version 2.1 (which is also one of the licenses of cairo itself.)
The library is available on Maven Central. Include it in your gradle.build
or pom.xml
file:
dependencies {
implementation 'io.github.jwharm.cairobindings:cairo:1.18.4.1'
}
Furthermore, you obviously need to have the cairo library version 1.18 installed on your system, or else the Java bindings have nothing to bind to. You also need to use JDK 22 or newer.
Now, you can start developing with cairo in Java. Have fun! This is a simple example to get started, ported from the first sample on this page:
import org.freedesktop.cairo.*;
import java.io.IOException;
public class CairoExample {
public static void main(String[] args) throws IOException {
// Create surface
try (var surface = ImageSurface.create(Format.ARGB32, 300, 300)) {
// Create drawing context
var cr = Context.create(surface);
double x = 128.0;
double y = 128.0;
double radius = 100.0;
double angle1 = 45.0 * (Math.PI / 180.0); // angles are specified
double angle2 = 180.0 * (Math.PI / 180.0); // in radians
// Draw shapes
cr.setLineWidth(10.0)
.arc(x, y, radius, angle1, angle2)
.stroke();
cr.setSourceRGBA(1.0, 0.2, 0.2, 0.6)
.setLineWidth(6.0)
.arc(x, y, 10.0, 0.0, 2 * Math.PI)
.fill();
cr.arc(x, y, radius, angle1, angle1)
.lineTo(x, y)
.arc(x, y, radius, angle2, angle2)
.lineTo(x, y)
.stroke();
// Write image to png file
surface.writeToPNG("example.png");
}
}
}
When compiling and running the application, suppress warnings about unsafe native
access with the command-line parameter --enable-native-access=org.freedesktop.cairo
.
Please contribute PRs or log issues on GitHub.