diff --git a/README.md b/README.md
index bba7d6e..304f11a 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Cairo Java bindings
Java language bindings for the [cairo](https://www.cairographics.org) graphics library using the
-JEP-434 Panama FFI. The bindings are based on **cairo 1.18.0** and work with **JDK 20** (with preview
-features enabled).
+JEP-442 Panama FFI. The bindings are based on **cairo 1.18.0** and work with **JDK 21** (with preview
+features enabled).
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](https://github.com/jwharm/java-gi).
@@ -20,16 +20,24 @@ available as Java enums. The proxy classes inherit when applicable: `RadialGradi
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` or `Rectangle` return type in Java.
+`Point`, `Circle`, `Rect` or `RGBA` return type in Java.
### Resource allocation and disposal
-Resources are allocated and deallocated automatically, so there is no need to manually dispose
-cairo resources in Java. However, please be aware that the disposal of proxy objects (like Context,
-surfaces, matrices and patterns) 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
-normal cleanup during GC is not sufficient to prevent resource exhaustion.
+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.)
### Error handling
@@ -52,20 +60,9 @@ Some other features that the language bindings offer:
path operation. They can be iterated and processed with record patterns (JEP 440). See the
`Path` class javadoc for example code.
-* The `cairo_set_user_data()` and `cairo_get_user_data()` functions (to attach
- custom data to a cairo struct) are available in Java, with a twist. You can call
- `setUserData()` to attach any Java object instance, and `getUserData()` to get it
- back. Objects that can be marshaled to a native memory segment (primitive types, memory segments,
- and other `Proxy` objects) will be attached to the native cairo struct. Other types will only
- be attached to the Java object and will not be passed to cairo itself.
-
* I/O operations in cairo that are designed to work with streams accept Java `InputStream` and
`OutputStream` parameters.
-* The `Surface` and `Device` classes implement `AutoCloseable` and can be used in
- try-with-resources blocks. (The `close()` method calls the C `cairo_..._finish()`
- function.)
-
* The cairo Script surface has been split into a `Script` class that inherits from
`Device`, and a `ScriptSurface` class that inherits from `Surface`.
@@ -97,8 +94,8 @@ dependencies {
```
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 install JDK 20 (not JDK 19 or
-earlier, nor JDK 21 or later), because the Panama FFI is slightly different between JDK versions.
+or else the Java bindings have nothing to bind to. You also need to install JDK 21 (not JDK 20 or
+earlier, nor JDK 22 or later, because the Panama FFI is slightly different between JDK versions).
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](https://www.cairographics.org/samples/):
@@ -111,35 +108,36 @@ public class CairoExample {
public static void main(String[] args) throws IOException {
// Create surface
- 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");
+ 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");
+ }
}
}
```
@@ -150,4 +148,4 @@ access, you can optionally add `--enable-native-access=org.freedesktop.cairo`.
## Building and Contributing
-Please contribute PRs or log issues on [Github](https://github.com/jwharm/cairo-java-bindings).
+Please contribute PRs or log issues on [GitHub](https://github.com/jwharm/cairo-java-bindings).
diff --git a/build.gradle b/build.gradle
index 3d734f0..e06f0a8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ plugins {
}
group = "io.github.jwharm.cairobindings"
-version = "1.18.0"
+version = "1.18.1-SNAPSHOT"
repositories {
mavenCentral()
@@ -15,7 +15,7 @@ repositories {
java {
toolchain {
- languageVersion = JavaLanguageVersion.of(20)
+ languageVersion = JavaLanguageVersion.of(21)
}
withSourcesJar()
withJavadocJar()
@@ -37,7 +37,7 @@ tasks.named('compileJava') {
tasks.named('javadoc') {
options.addBooleanOption("-enable-preview", true)
- options.addStringOption("-release", "20")
+ options.addStringOption("-release", "21")
}
tasks.named('compileTestJava') {
diff --git a/settings.gradle b/settings.gradle
index 398eafe..201a6ce 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,5 @@
plugins {
- id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0'
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = 'cairo'
diff --git a/src/main/java/io/github/jwharm/cairobindings/ArenaCloseAction.java b/src/main/java/io/github/jwharm/cairobindings/ArenaCloseAction.java
new file mode 100644
index 0000000..34c38e5
--- /dev/null
+++ b/src/main/java/io/github/jwharm/cairobindings/ArenaCloseAction.java
@@ -0,0 +1,39 @@
+/* cairo-java-bindings - Java language bindings for cairo
+ * Copyright (C) 2023 Jan-Willem Harmannij
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
* The CTM is a two-dimensional affine transformation that maps all coordinates * and other drawing instruments from the user space into the surface's * canonical coordinate system, also known as the device space. * - * @return a matrix with the current transformation matrix + * @param matrix return value for the matrix * @since 1.0 */ - public Matrix getMatrix() { + public void getMatrix(Matrix matrix) { try { - Matrix matrix = Matrix.create(); cairo_get_matrix.invoke(handle(), matrix.handle()); - return matrix; } catch (Throwable e) { throw new RuntimeException(e); } @@ -2498,7 +2492,7 @@ public Point userToDevice(Point point) { return null; } try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment xPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); MemorySegment yPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); xPtr.set(ValueLayout.JAVA_DOUBLE, 0, point.x()); @@ -2528,7 +2522,7 @@ public Point userToDeviceDistance(Point point) { return null; } try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment xPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); MemorySegment yPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); xPtr.set(ValueLayout.JAVA_DOUBLE, 0, point.x()); @@ -2558,7 +2552,7 @@ public Point deviceToUser(Point point) { return null; } try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment xPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); MemorySegment yPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); xPtr.set(ValueLayout.JAVA_DOUBLE, 0, point.x()); @@ -2589,7 +2583,7 @@ public Point deviceToUserDistance(Point point) { return null; } try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment xPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); MemorySegment yPtr = arena.allocate(ValueLayout.JAVA_DOUBLE); xPtr.set(ValueLayout.JAVA_DOUBLE, 0, point.x()); @@ -2658,7 +2652,7 @@ public Point deviceToUserDistance(Point point) { */ public Context selectFontFace(String family, FontSlant slant, FontWeight weight) { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment utf8 = Interop.allocateNativeString(family, arena); cairo_select_font_face.invoke(handle(), utf8, slant.getValue(), weight.getValue()); return this; @@ -2725,16 +2719,15 @@ public Context setFontMatrix(Matrix matrix) { FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); /** - * Returns the current font matrix. See {@link #setFontMatrix(Matrix)} - * - * @return the matrix + * Stores the current font matrix into {@code matrix}. See + * {@link #setFontMatrix} + * + * @param matrix return value for the matrix * @since 1.0 */ - public Matrix getFontMatrix() { + public void getFontMatrix(Matrix matrix) { try { - Matrix fontMatrix = Matrix.create(); cairo_get_font_matrix.invoke(handle(), fontMatrix.handle()); - return fontMatrix; } catch (Throwable e) { throw new RuntimeException(e); } @@ -2838,7 +2831,7 @@ public FontFace getFontFace() { * Replaces the current font face, font matrix, and font options in the * {@link Context} with those of the {@link ScaledFont}. Except for some * translation, the current CTM of the Context should be the same as that of the - * ScaledFont, which can be accessed using {@link ScaledFont#getCTM()}. + * ScaledFont, which can be accessed using {@link ScaledFont#getCTM}. * * @param scaledFont a ScaledFont * @return the context @@ -2911,7 +2904,7 @@ public ScaledFont getScaledFont() { */ public Context showText(String string) { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment utf8 = Interop.allocateNativeString(string, arena); cairo_show_text.invoke(handle(), utf8); return this; @@ -2974,7 +2967,7 @@ public Context showGlyphs(Glyphs glyphs) { */ public Context showTextGlyphs(String string, Glyphs glyphs) { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment stringPtr = Interop.allocateNativeString(string, arena); cairo_show_text_glyphs.invoke(handle(), stringPtr, string == null ? 0 : string.length(), @@ -2996,14 +2989,12 @@ public Context showTextGlyphs(String string, Glyphs glyphs) { /** * Gets the font extents for the currently selected font. * - * @return the font extents + * @param extents a FontExtents object into which the results will be stored. * @since 1.0 */ - public FontExtents fontExtents() { + public void fontExtents(FontExtents extents) { try { - FontExtents extents = FontExtents.create(); cairo_font_extents.invoke(handle(), extents.handle()); - return extents; } catch (Throwable e) { throw new RuntimeException(e); } @@ -3026,17 +3017,15 @@ public FontExtents fontExtents() { * size of the rectangle, though they will affect the {@code xAdvance} and * {@code yAdvance} values. * - * @param string a string of text, or {@code null} - * @return the text extents + * @param string a string of text, or {@code null} + * @param extents a TextExtents object into which the results will be stored * @since 1.0 */ - public TextExtents textExtents(String string) { + public void textExtents(String string, TextExtents extents) { try { - try (Arena arena = Arena.openConfined()) { - TextExtents extents = TextExtents.create(); + try (Arena arena = Arena.ofConfined()) { MemorySegment utf8 = Interop.allocateNativeString(string, arena); cairo_text_extents.invoke(handle(), utf8, extents.handle()); - return extents; } } catch (Throwable e) { throw new RuntimeException(e); @@ -3057,17 +3046,15 @@ public TextExtents textExtents(String string) { * ({@code extents.width} and {@code extents.height}). * * @param glyphs an array of Glyph objects - * @return the glyph extents + * @param extents a TextExtents object into which the results will be stored * @since 1.0 */ - public TextExtents glyphExtents(Glyphs glyphs) { + public void glyphExtents(Glyphs glyphs, TextExtents extents) { if (glyphs == null) { - return null; + return; } try { - TextExtents extents = TextExtents.create(); cairo_glyph_extents.invoke(handle(), glyphs.getGlyphsPointer(), glyphs.getNumGlyphs(), extents.handle()); - return extents; } catch (Throwable e) { throw new RuntimeException(e); } @@ -3108,7 +3095,7 @@ public TextExtents glyphExtents(Glyphs glyphs) { */ public Context tagBegin(String tagName, String attributes) throws IllegalArgumentException { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment tagNamePtr = Interop.allocateNativeString(tagName, arena); MemorySegment attributesPtr = Interop.allocateNativeString(attributes, arena); cairo_tag_begin.invoke(handle(), tagNamePtr, attributesPtr); @@ -3137,7 +3124,7 @@ public Context tagBegin(String tagName, String attributes) throws IllegalArgumen */ public Context tagEnd(String tagName) throws IllegalArgumentException { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment tagNamePtr = Interop.allocateNativeString(tagName, arena); cairo_tag_end.invoke(handle(), tagNamePtr); } @@ -3153,44 +3140,21 @@ public Context tagEnd(String tagName) throws IllegalArgumentException { private static final MethodHandle cairo_tag_end = Interop.downcallHandle("cairo_tag_end", FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); - /** - * Attach user data to the context. This method will generate and return a - * {@link UserDataKey}. To update the user data for the same key, call - * {@link #setUserData(UserDataKey, Object)}. To remove user data from a - * context, call this function with {@code null} for {@code userData}. - * - * @param userData the user data to attach to the context. {@code userData} can - * be any Java object, but if it is a primitive type, a - * {@link MemorySegment} or a {@link Proxy} instance, it will be - * stored as cairo user data in native memory as well. - * @return the key that the user data is attached to - * @since 1.4 - */ - public UserDataKey setUserData(Object userData) { - UserDataKey key = UserDataKey.create(this); - return setUserData(key, userData); - } - /** * Attach user data to the context. To remove user data from a context, call * this function with the key that was used to set it and {@code null} for * {@code userData}. - * - * @param key the key to attach the user data to - * @param userData the user data to attach to the context. {@code userData} can - * be any Java object, but if it is a primitive type, a - * {@link MemorySegment} or a {@link Proxy} instance, it will be - * stored as cairo user data in native memory as well. + * + * @param key the key to attach the user data to + * @param userData the user data to attach to the context * @return the key * @throws NullPointerException if {@code key} is {@code null} * @since 1.4 */ - public UserDataKey setUserData(UserDataKey key, Object userData) { + public UserDataKey setUserData(UserDataKey key, MemorySegment userData) { Status status; - userDataStore.set(key, userData); try { - int result = (int) cairo_set_user_data.invoke(handle(), key.handle(), userDataStore.dataSegment(userData), - MemorySegment.NULL); + int result = (int) cairo_set_user_data.invoke(handle(), key.handle(), userData, MemorySegment.NULL); status = Status.of(result); } catch (Throwable e) { throw new RuntimeException(e); @@ -3210,14 +3174,25 @@ public UserDataKey setUserData(UserDataKey key, Object userData) { * If no user data has been attached with the given key this function returns * {@code null}. * - * @param key the UserDataKey the user data was attached to + * @param key the UserDataKey the user data was attached to * @return the user data previously attached or {@code null} * @since 1.4 */ - public Object getUserData(UserDataKey key) { - return key == null ? null : userDataStore.get(key); + public MemorySegment getUserData(UserDataKey key) { + if (key == null) { + return null; + } + try { + MemorySegment result = (MemorySegment) cairo_get_user_data.invoke(handle(), key.handle()); + return MemorySegment.NULL.equals(result) ? null : result; + } catch (Throwable e) { + throw new RuntimeException(e); + } } + private static final MethodHandle cairo_get_user_data = Interop.downcallHandle("cairo_get_user_data", + FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)); + /** * Get the CairoContext GType * @return the GType diff --git a/src/main/java/org/freedesktop/cairo/DestroyFunc.java b/src/main/java/org/freedesktop/cairo/DestroyFunc.java index 385d3d3..2d6000a 100644 --- a/src/main/java/org/freedesktop/cairo/DestroyFunc.java +++ b/src/main/java/org/freedesktop/cairo/DestroyFunc.java @@ -19,11 +19,7 @@ package org.freedesktop.cairo; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.Linker; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentScope; -import java.lang.foreign.ValueLayout; +import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -39,35 +35,36 @@ public interface DestroyFunc { /** * The function to implement as callback in a destroy operation. - * + * + * @param data the data element being destroyed. * @since 1.0 */ - void destroy(); + void destroy(MemorySegment data); /** * The callback that is executed by native code. This method marshals the - * parameters and calls {@link #destroy()}. + * parameters and calls {@link #destroy}. * - * @param data the buffer into which to read the data + * @param data the data element being destroyed. * @since 1.0 */ default void upcall(MemorySegment data) { - destroy(); + destroy(data); } /** * Generates an upcall stub, a C function pointer that will call - * {@link #upcall(MemorySegment)}. - * - * @param scope the scope in which the upcall stub will be allocated + * {@link #upcall}. + * + * @param arena the arena in which the upcall stub will be allocated * @return the function pointer of the upcall stub * @since 1.0 */ - default MemorySegment toCallback(SegmentScope scope) { + default MemorySegment toCallback(Arena arena) { try { FunctionDescriptor fdesc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS); MethodHandle handle = MethodHandles.lookup().findVirtual(DestroyFunc.class, "upcall", fdesc.toMethodType()); - return Linker.nativeLinker().upcallStub(handle.bindTo(this), fdesc, scope); + return Linker.nativeLinker().upcallStub(handle.bindTo(this), fdesc, arena); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/freedesktop/cairo/Device.java b/src/main/java/org/freedesktop/cairo/Device.java index 8f50dc2..5af90a2 100644 --- a/src/main/java/org/freedesktop/cairo/Device.java +++ b/src/main/java/org/freedesktop/cairo/Device.java @@ -63,9 +63,6 @@ public class Device extends Proxy implements AutoCloseable { Cairo.ensureInitialized(); } - // Keeps user data keys and values - private final UserDataStore userDataStore; - /** * Constructor used internally to instantiate a java Device object for a native * {@code cairo_device_t} instance @@ -76,7 +73,6 @@ public class Device extends Proxy implements AutoCloseable { public Device(MemorySegment address) { super(address); MemoryCleaner.setFreeFunc(handle(), "cairo_device_destroy"); - userDataStore = new UserDataStore(address.scope()); } /** @@ -325,9 +321,9 @@ public void observerPrint(OutputStream stream) throws IOException { } Status status; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { WriteFunc writeFunc = stream::write; - int result = (int) cairo_device_observer_print.invoke(handle(), writeFunc.toCallback(arena.scope()), + int result = (int) cairo_device_observer_print.invoke(handle(), writeFunc.toCallback(arena), MemorySegment.NULL); status = Status.of(result); } @@ -363,44 +359,21 @@ public double observerStrokeElapsed() { private static final MethodHandle cairo_device_observer_stroke_elapsed = Interop.downcallHandle( "cairo_device_observer_stroke_elapsed", FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.ADDRESS)); - /** - * Attach user data to the device. This method will generate and return a - * {@link UserDataKey}. To update the user data for the same key, call - * {@link #setUserData(UserDataKey, Object)}. To remove user data from a - * device, call this function with {@code null} for {@code userData}. - * - * @param userData the user data to attach to the device. {@code userData} can - * be any Java object, but if it is a primitive type, a - * {@link MemorySegment} or a {@link Proxy} instance, it will be - * stored as cairo user data in native memory as well. - * @return the key that the user data is attached to - * @since 1.10 - */ - public UserDataKey setUserData(Object userData) { - UserDataKey key = UserDataKey.create(this); - return setUserData(key, userData); - } - /** * Attach user data to the device. To remove user data from a device, call * this function with the key that was used to set it and {@code null} for * {@code userData}. - * - * @param key the key to attach the user data to - * @param userData the user data to attach to the device. {@code userData} can - * be any Java object, but if it is a primitive type, a - * {@link MemorySegment} or a {@link Proxy} instance, it will be - * stored as cairo user data in native memory as well. + * + * @param key the key to attach the user data to + * @param userData the user data to attach to the device * @return the key * @throws NullPointerException if {@code key} is {@code null} - * @since 1.10 + * @since 1.4 */ - public UserDataKey setUserData(UserDataKey key, Object userData) { + public UserDataKey setUserData(UserDataKey key, MemorySegment userData) { Status status; - userDataStore.set(key, userData); try { - int result = (int) cairo_device_set_user_data.invoke(handle(), key.handle(), - userDataStore.dataSegment(userData), MemorySegment.NULL); + int result = (int) cairo_device_set_user_data.invoke(handle(), key.handle(), userData, MemorySegment.NULL); status = Status.of(result); } catch (Throwable e) { throw new RuntimeException(e); @@ -419,15 +392,26 @@ public UserDataKey setUserData(UserDataKey key, Object userData) { * Return user data previously attached to the device using the specified key. * If no user data has been attached with the given key this function returns * {@code null}. - * - * @param key the UserDataKey the user data was attached to + * + * @param key the UserDataKey the user data was attached to * @return the user data previously attached or {@code null} - * @since 1.10 + * @since 1.4 */ - public Object getUserData(UserDataKey key) { - return key == null ? null : userDataStore.get(key); + public MemorySegment getUserData(UserDataKey key) { + if (key == null) { + return null; + } + try { + MemorySegment result = (MemorySegment) cairo_device_get_user_data.invoke(handle(), key.handle()); + return MemorySegment.NULL.equals(result) ? null : result; + } catch (Throwable e) { + throw new RuntimeException(e); + } } - + + private static final MethodHandle cairo_device_get_user_data = Interop.downcallHandle("cairo_device_get_user_data", + FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)); + /** * Closing a device will invoke {@link #finish()}, which will flush the * device and drop all references to external resources. A closed device diff --git a/src/main/java/org/freedesktop/cairo/FTScaledFont.java b/src/main/java/org/freedesktop/cairo/FTScaledFont.java index 9976eb7..7be3b14 100644 --- a/src/main/java/org/freedesktop/cairo/FTScaledFont.java +++ b/src/main/java/org/freedesktop/cairo/FTScaledFont.java @@ -19,7 +19,6 @@ package org.freedesktop.cairo; -import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; @@ -74,18 +73,16 @@ public FTScaledFont(MemorySegment address) { public static FTScaledFont create(FTFontFace fontFace, Matrix fontMatrix, Matrix ctm, FontOptions options) { FTScaledFont font; try { - try (Arena arena = Arena.openConfined()) { - MemorySegment result = (MemorySegment) cairo_scaled_font_create.invoke( - fontFace == null ? MemorySegment.NULL : fontFace.handle(), - fontMatrix == null ? MemorySegment.NULL : fontMatrix.handle(), - ctm == null ? MemorySegment.NULL : ctm.handle(), - options == null ? MemorySegment.NULL : options.handle()); - font = new FTScaledFont(result); - MemoryCleaner.takeOwnership(font.handle()); - font.fontFace = fontFace; - font.fontMatrix = fontMatrix; - font.ctm = ctm; - } + MemorySegment result = (MemorySegment) cairo_scaled_font_create.invoke( + fontFace == null ? MemorySegment.NULL : fontFace.handle(), + fontMatrix == null ? MemorySegment.NULL : fontMatrix.handle(), + ctm == null ? MemorySegment.NULL : ctm.handle(), + options == null ? MemorySegment.NULL : options.handle()); + font = new FTScaledFont(result); + MemoryCleaner.takeOwnership(font.handle()); + font.fontFace = fontFace; + font.fontMatrix = fontMatrix; + font.ctm = ctm; } catch (Throwable e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/freedesktop/cairo/FTSynthesize.java b/src/main/java/org/freedesktop/cairo/FTSynthesize.java index 0c806f6..7b31102 100644 --- a/src/main/java/org/freedesktop/cairo/FTSynthesize.java +++ b/src/main/java/org/freedesktop/cairo/FTSynthesize.java @@ -51,8 +51,8 @@ public record FTSynthesize(int value) { */ public FTSynthesize or(FTSynthesize... masks) { int value = this.value(); - for (FTSynthesize(int arg) : masks) { - value |= arg; + for (var arg : masks) { + value |= arg.value(); } return new FTSynthesize(value); } @@ -66,8 +66,8 @@ public FTSynthesize or(FTSynthesize... masks) { */ public static FTSynthesize combined(FTSynthesize mask, FTSynthesize... masks) { int value = mask.value(); - for (FTSynthesize(int arg) : masks) { - value |= arg; + for (var arg : masks) { + value |= arg.value(); } return new FTSynthesize(value); } diff --git a/src/main/java/org/freedesktop/cairo/FontExtents.java b/src/main/java/org/freedesktop/cairo/FontExtents.java index c33db34..1590c25 100644 --- a/src/main/java/org/freedesktop/cairo/FontExtents.java +++ b/src/main/java/org/freedesktop/cairo/FontExtents.java @@ -21,10 +21,9 @@ import io.github.jwharm.cairobindings.Proxy; +import java.lang.foreign.Arena; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.SegmentScope; import java.lang.foreign.ValueLayout; import java.lang.invoke.VarHandle; @@ -65,10 +64,13 @@ static MemoryLayout getMemoryLayout() { private static final VarHandle MAX_Y_ADVANCE = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("max_y_advance")); /** - * Allocate a new {@code cairo_font_extents_t} + * Allocate a new, uninitialized {@code cairo_font_extents_t} + * + * @param arena the arena in which the FontExtents will be allocated + * @return a newly allocated, uninitialized FontExtents */ - static FontExtents create() { - return new FontExtents(SegmentAllocator.nativeAllocator(SegmentScope.auto()).allocate(getMemoryLayout())); + public static FontExtents create(Arena arena) { + return new FontExtents(arena.allocate(getMemoryLayout())); } /** diff --git a/src/main/java/org/freedesktop/cairo/FontFace.java b/src/main/java/org/freedesktop/cairo/FontFace.java index c08a29e..89da0b0 100644 --- a/src/main/java/org/freedesktop/cairo/FontFace.java +++ b/src/main/java/org/freedesktop/cairo/FontFace.java @@ -58,9 +58,6 @@ public sealed class FontFace extends Proxy permits ToyFontFace, FTFontFace, User Cairo.ensureInitialized(); } - // Keeps user data keys and values - private final UserDataStore userDataStore; - /** * Constructor used internally to instantiate a java FontFace object for a * native {@code cairo_font_face_t} instance @@ -71,7 +68,6 @@ public sealed class FontFace extends Proxy permits ToyFontFace, FTFontFace, User public FontFace(MemorySegment address) { super(address); MemoryCleaner.setFreeFunc(handle(), "cairo_font_face_destroy"); - userDataStore = new UserDataStore(address.scope()); } /** @@ -139,44 +135,21 @@ void reference() { private static final MethodHandle cairo_font_face_reference = Interop.downcallHandle("cairo_font_face_reference", FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); - /** - * Attach user data to the font face. This method will generate and return a - * {@link UserDataKey}. To update the user data for the same key, call - * {@link #setUserData(UserDataKey, Object)}. To remove user data from a font - * face, call this function with {@code null} for {@code userData}. - * - * @param userData the user data to attach to the font face. {@code userData} - * can be any Java object, but if it is a primitive type, a - * {@link MemorySegment} or a {@link Proxy} instance, it will be - * stored as cairo user data in native memory as well. - * @return the key that the user data is attached to - * @since 1.4 - */ - public UserDataKey setUserData(Object userData) { - UserDataKey key = UserDataKey.create(this); - return setUserData(key, userData); - } - /** * Attach user data to the font face. To remove user data from a font face, call * this function with the key that was used to set it and {@code null} for * {@code userData}. - * - * @param key the key to attach the user data to - * @param userData the user data to attach to the font face. {@code userData} - * can be any Java object, but if it is a primitive type, a - * {@link MemorySegment} or a {@link Proxy} instance, it will be - * stored as cairo user data in native memory as well. + * + * @param key the key to attach the user data to + * @param userData the user data to attach to the font face * @return the key * @throws NullPointerException if {@code key} is {@code null} * @since 1.4 */ - public UserDataKey setUserData(UserDataKey key, Object userData) { + public UserDataKey setUserData(UserDataKey key, MemorySegment userData) { Status status; - userDataStore.set(key, userData); try { - int result = (int) cairo_font_face_set_user_data.invoke(handle(), key.handle(), - userDataStore.dataSegment(userData), MemorySegment.NULL); + int result = (int) cairo_font_face_set_user_data.invoke(handle(), key.handle(), userData, MemorySegment.NULL); status = Status.of(result); } catch (Throwable e) { throw new RuntimeException(e); @@ -195,15 +168,26 @@ public UserDataKey setUserData(UserDataKey key, Object userData) { * Return user data previously attached to the font face using the specified * key. If no user data has been attached with the given key this function * returns {@code null}. - * + * * @param key the UserDataKey the user data was attached to * @return the user data previously attached or {@code null} * @since 1.4 */ - public Object getUserData(UserDataKey key) { - return key == null ? null : userDataStore.get(key); + public MemorySegment getUserData(UserDataKey key) { + if (key == null) { + return null; + } + try { + MemorySegment result = (MemorySegment) cairo_font_face_get_user_data.invoke(handle(), key.handle()); + return MemorySegment.NULL.equals(result) ? null : result; + } catch (Throwable e) { + throw new RuntimeException(e); + } } + private static final MethodHandle cairo_font_face_get_user_data = Interop.downcallHandle("cairo_get_user_data", + FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)); + /** * Get the CairoFontFace GType * @return the GType diff --git a/src/main/java/org/freedesktop/cairo/FontOptions.java b/src/main/java/org/freedesktop/cairo/FontOptions.java index b6a92a4..b3111dd 100644 --- a/src/main/java/org/freedesktop/cairo/FontOptions.java +++ b/src/main/java/org/freedesktop/cairo/FontOptions.java @@ -391,7 +391,7 @@ public String getVariations() { if (MemorySegment.NULL.equals(result)) { return null; } - return result.getUtf8String(0); + return result.reinterpret(Integer.MAX_VALUE).getUtf8String(0); } catch (Throwable e) { throw new RuntimeException(e); } @@ -399,7 +399,7 @@ public String getVariations() { private static final MethodHandle cairo_font_options_get_variations = Interop.downcallHandle( "cairo_font_options_get_variations", - FunctionDescriptor.of(ValueLayout.ADDRESS.asUnbounded(), ValueLayout.ADDRESS)); + FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)); /** * Sets the OpenType font variations for the font options object. Font @@ -420,7 +420,7 @@ public String getVariations() { */ public void setVariations(String variations) { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment utf8 = Interop.allocateNativeString(variations, arena); cairo_font_options_set_variations.invoke(handle(), utf8); } @@ -549,7 +549,7 @@ public RGBA getCustomPaletteColor(int index) throws IndexOutOfBoundsException { Status status; RGBA rgba; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment x1Ptr = arena.allocate(ValueLayout.JAVA_DOUBLE); MemorySegment y1Ptr = arena.allocate(ValueLayout.JAVA_DOUBLE); MemorySegment x2Ptr = arena.allocate(ValueLayout.JAVA_DOUBLE); diff --git a/src/main/java/org/freedesktop/cairo/Glyph.java b/src/main/java/org/freedesktop/cairo/Glyph.java index 7da879a..5f2543d 100644 --- a/src/main/java/org/freedesktop/cairo/Glyph.java +++ b/src/main/java/org/freedesktop/cairo/Glyph.java @@ -100,6 +100,6 @@ public double y() { * instance */ public Glyph(MemorySegment address) { - super(Interop.reinterpret(address, getMemoryLayout())); + super(address.reinterpret(getMemoryLayout().byteSize())); } } diff --git a/src/main/java/org/freedesktop/cairo/Gradient.java b/src/main/java/org/freedesktop/cairo/Gradient.java index 71451b7..1dc2ace 100644 --- a/src/main/java/org/freedesktop/cairo/Gradient.java +++ b/src/main/java/org/freedesktop/cairo/Gradient.java @@ -122,7 +122,7 @@ public void addColorStopRGBA(double offset, double red, double green, double blu */ public int getColorStopCount() { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment countPtr = arena.allocate(ValueLayout.JAVA_INT); cairo_pattern_get_color_stop_count.invoke(handle(), countPtr); return countPtr.get(ValueLayout.JAVA_INT, 0); @@ -153,7 +153,7 @@ public int getColorStopCount() { public double[] getColorStopRGBA(int index) throws IndexOutOfBoundsException { double[] values; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment ptrs = arena.allocateArray(ValueLayout.JAVA_DOUBLE, 5); long size = ValueLayout.JAVA_DOUBLE.byteSize(); int result = (int) cairo_pattern_get_color_stop_rgba.invoke(handle(), index, ptrs, ptrs.asSlice(size), diff --git a/src/main/java/org/freedesktop/cairo/ImageSurface.java b/src/main/java/org/freedesktop/cairo/ImageSurface.java index e080efc..0d3b6e6 100644 --- a/src/main/java/org/freedesktop/cairo/ImageSurface.java +++ b/src/main/java/org/freedesktop/cairo/ImageSurface.java @@ -205,7 +205,7 @@ public static ImageSurface create(MemorySegment data, Format format, int width, public static ImageSurface createFromPNG(String filename) throws IOException { ImageSurface surface; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment filenamePtr = Interop.allocateNativeString(filename, arena); MemorySegment result = (MemorySegment) cairo_image_surface_create_from_png.invoke(filenamePtr); surface = new ImageSurface(result); @@ -244,10 +244,10 @@ public static ImageSurface createFromPNG(InputStream stream) throws IOException } ImageSurface surface; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { ReadFunc readFunc = stream::readNBytes; MemorySegment result = (MemorySegment) cairo_image_surface_create_from_png_stream - .invoke(readFunc.toCallback(arena.scope()), MemorySegment.NULL); + .invoke(readFunc.toCallback(arena), MemorySegment.NULL); surface = new ImageSurface(result); MemoryCleaner.takeOwnership(surface.handle()); } @@ -281,9 +281,9 @@ public static ImageSurface createFromPNG(InputStream stream) throws IOException * @since 1.0 */ public void writeToPNG(String filename) throws IOException { - Status status = null; + Status status; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment filenamePtr = Interop.allocateNativeString(filename, arena); int result = (int) cairo_surface_write_to_png.invoke(handle(), filenamePtr); status = Status.of(result); @@ -319,10 +319,10 @@ public void writeToPNG(OutputStream stream) throws IOException { } Status status; try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { WriteFunc writeFunc = stream::write; int result = (int) cairo_surface_write_to_png_stream.invoke(handle(), - writeFunc.toCallback(arena.scope()), MemorySegment.NULL); + writeFunc.toCallback(arena), MemorySegment.NULL); status = Status.of(result); } } catch (Throwable e) { diff --git a/src/main/java/org/freedesktop/cairo/LinearGradient.java b/src/main/java/org/freedesktop/cairo/LinearGradient.java index 0c3afd7..aaec6c4 100644 --- a/src/main/java/org/freedesktop/cairo/LinearGradient.java +++ b/src/main/java/org/freedesktop/cairo/LinearGradient.java @@ -90,7 +90,7 @@ public static LinearGradient create(double x0, double y0, double x1, double y1) */ public Point[] getLinearPoints() { try { - try (Arena arena = Arena.openConfined()) { + try (Arena arena = Arena.ofConfined()) { MemorySegment ptrs = arena.allocateArray(ValueLayout.JAVA_DOUBLE, 4); long size = ValueLayout.JAVA_DOUBLE.byteSize(); cairo_pattern_get_linear_points.invoke(handle(), ptrs, ptrs.asSlice(size), ptrs.asSlice(2 * size), diff --git a/src/main/java/org/freedesktop/cairo/Matrix.java b/src/main/java/org/freedesktop/cairo/Matrix.java index 7a21e4d..b7529e6 100644 --- a/src/main/java/org/freedesktop/cairo/Matrix.java +++ b/src/main/java/org/freedesktop/cairo/Matrix.java @@ -26,10 +26,9 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.SegmentScope; import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; /** * Generic matrix operations. @@ -46,7 +45,7 @@ * * The current transformation matrix of a {@link Context}, represented as a * Matrix, defines the transformation from user-space coordinates to - * device-space coordinates. See {@link Context#getMatrix()} and + * device-space coordinates. See {@link Context#getMatrix} and * {@link Context#setMatrix(Matrix)}. * * @since 1.0 @@ -68,41 +67,75 @@ static MemoryLayout getMemoryLayout() { .withName("cairo_matrix_t"); } + private static final VarHandle XX = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("xx")); + private static final VarHandle YX = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("yx")); + private static final VarHandle XY = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("xy")); + private static final VarHandle YY = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("yy")); + private static final VarHandle X0 = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("x0")); + private static final VarHandle Y0 = getMemoryLayout().varHandle(MemoryLayout.PathElement.groupElement("y0")); + /** - * Allocate a new {@code caio_matrix_t} + * Get the xx value of the Matrix + * + * @return the xx value */ - static Matrix create() { - return new Matrix(SegmentAllocator.nativeAllocator(SegmentScope.auto()).allocate(getMemoryLayout())); + public double xx() { + return (double) XX.get(handle()); } /** - * Sets the matrix to be the affine transformation given by {@code xx}, - * {@code yx}, {@code xy}, {@code yy}, {@code x0}, {@code y0}. The - * transformation is given by: - * - *
- * xNew = xx * x + xy * y + x0; - * yNew = yx * x + yy * y + y0; - *- * - * @param xx xx component of the affine transformation - * @param yx yx component of the affine transformation - * @param xy xy component of the affine transformation - * @param yy yy component of the affine transformation - * @param x0 X translation component of the affine transformation - * @param y0 Y translation component of the affine transformation - * @return the resulting Matrix - * @see Matrix#init(double, double, double, double, double, double) - * @since 1.0 + * Get the yx value of the Matrix + * + * @return the yx value */ - public static Matrix create(double xx, double yx, double xy, double yy, double x0, double y0) { - try { - Matrix matrix = create(); - cairo_matrix_init.invoke(matrix.handle(), xx, yx, xy, yy, x0, y0); - return matrix; - } catch (Throwable e) { - throw new RuntimeException(e); - } + public double yx() { + return (double) YX.get(handle()); + } + + /** + * Get the xy value of the Matrix + * + * @return the xy value + */ + public double xy() { + return (double) XY.get(handle()); + } + + /** + * Get the yy value of the Matrix + * + * @return the yy value + */ + public double yy() { + return (double) YY.get(handle()); + } + + /** + * Get the x0 value of the Matrix + * + * @return the x0 value + */ + public double x0() { + return (double) X0.get(handle()); + } + + /** + * Get the y0 value of the Matrix + * + * @return the y0 value + */ + public double y0() { + return (double) Y0.get(handle()); + } + + /** + * Allocate a new, uninitialized {@code caio_matrix_t} + * + * @param arena the arena in which the Matrix will be allocated + * @return the newly allocated, uninitialized Matrix object + */ + public static Matrix create(Arena arena) { + return new Matrix(arena.allocate(getMemoryLayout())); } /** @@ -139,15 +172,14 @@ public Matrix init(double xx, double yx, double xy, double yy, double x0, double /** * Modifies the matrix to be an identity transformation. - * - * @return the resulting Matrix + * + * @return the matrix * @since 1.0 */ - public static Matrix createIdentity() { + public Matrix initIdentity() { try { - Matrix matrix = create(); - cairo_matrix_init_identity.invoke(matrix.handle()); - return matrix; + cairo_matrix_init_identity.invoke(handle()); + return this; } catch (Throwable e) { throw new RuntimeException(e); } @@ -162,14 +194,13 @@ public static Matrix createIdentity() { * * @param tx amount to translate in the X direction * @param ty amount to translate in the Y direction - * @return the resulting Matrix + * @return the matrix * @since 1.0 */ - public static Matrix createTranslate(double tx, double ty) { + public Matrix initTranslate(double tx, double ty) { try { - Matrix matrix = create(); - cairo_matrix_init_translate.invoke(matrix.handle(), tx, ty); - return matrix; + cairo_matrix_init_translate.invoke(handle(), tx, ty); + return this; } catch (Throwable e) { throw new RuntimeException(e); } @@ -179,52 +210,6 @@ public static Matrix createTranslate(double tx, double ty) { "cairo_matrix_init_translate", FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)); - /** - * Initializes the matrix to a transformation that scales by {@code sx} and - * {@code sy} in the X and Y dimensions, respectively. - * - * @param sx scale factor in the X direction - * @param sy scale factor in the Y direction - * @return the resulting Matrix - * @since 1.0 - */ - public static Matrix createScale(double sx, double sy) { - try { - Matrix matrix = create(); - cairo_matrix_init_scale.invoke(matrix.handle(), sx, sy); - return matrix; - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - private static final MethodHandle cairo_matrix_init_scale = Interop.downcallHandle("cairo_matrix_init_scale", - FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)); - - /** - * Initializes the matrix to a transformation that rotates by {@code radians}. - * - * @param radians angle of rotation, in radians. The direction of rotation is - * defined such that positive angles rotate in the direction from - * the positive X axis toward the positive Y axis. With the - * default axis orientation of cairo, positive angles rotate in a - * clockwise direction. - * @return the resulting Matrix - * @since 1.0 - */ - public static Matrix createRotate(double radians) { - try { - Matrix matrix = create(); - cairo_matrix_init_rotate.invoke(matrix.handle(), radians); - return matrix; - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - private static final MethodHandle cairo_matrix_init_rotate = Interop.downcallHandle("cairo_matrix_init_rotate", - FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_DOUBLE)); - /** * Applies a translation by {@code tx, ty} to the transformation in the matrix. * The effect of the new transformation is to first translate the coordinates by @@ -333,22 +318,22 @@ public void invert() throws IllegalArgumentException { /** * Multiplies the affine transformations in this matrix and the provided matrix * together and stores the result in the resulting matrix. The effect of the - * resulting transformation is to first apply the transformation in this matrix - * to the coordinates and then apply the transformation in the provided matrix - * to the coordinates. + * resulting transformation is to first apply the transformation in {@code a} + * to the coordinates and then apply the transformation in {@code b} to the + * coordinates. *
- * It is allowable for the resulting matrix to be identical to either this
- * matrix or the provided matrix.
+ * It is allowable for the resulting matrix to be identical to either {@code a}
+ * or {@code b}.
*
- * @param other a matrix
- * @return the resulting matrix
+ * @param a a matrix
+ * @param b a matrix
* @since 1.0
*/
- public Matrix multiply(Matrix other) {
+ public void multiply(Matrix a, Matrix b) {
try {
- Matrix result = create();
- cairo_matrix_multiply.invoke(result.handle(), this.handle(), other == null ? MemorySegment.NULL : other.handle());
- return result;
+ cairo_matrix_multiply.invoke(this.handle(),
+ a == null ? MemorySegment.NULL : a.handle(),
+ b == null ? MemorySegment.NULL : b.handle());
} catch (Throwable e) {
throw new RuntimeException(e);
}
@@ -377,11 +362,9 @@ public Point transformDistance(Point distanceVector) {
return null;
}
try {
- try (Arena arena = Arena.openConfined()) {
- MemorySegment dxPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
- MemorySegment dyPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
- dxPtr.set(ValueLayout.JAVA_DOUBLE, 0, distanceVector.x());
- dyPtr.set(ValueLayout.JAVA_DOUBLE, 0, distanceVector.y());
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment dxPtr = arena.allocate(ValueLayout.JAVA_DOUBLE, distanceVector.x());
+ MemorySegment dyPtr = arena.allocate(ValueLayout.JAVA_DOUBLE, distanceVector.y());
cairo_matrix_transform_distance.invoke(handle(), dxPtr, dyPtr);
return new Point(dxPtr.get(ValueLayout.JAVA_DOUBLE, 0), dyPtr.get(ValueLayout.JAVA_DOUBLE, 0));
}
@@ -406,11 +389,9 @@ public Point transformPoint(Point point) {
return null;
}
try {
- try (Arena arena = Arena.openConfined()) {
- MemorySegment dxPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
- MemorySegment dyPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
- dxPtr.set(ValueLayout.JAVA_DOUBLE, 0, point.x());
- dyPtr.set(ValueLayout.JAVA_DOUBLE, 0, point.y());
+ try (Arena arena = Arena.ofConfined()) {
+ MemorySegment dxPtr = arena.allocate(ValueLayout.JAVA_DOUBLE, point.x());
+ MemorySegment dyPtr = arena.allocate(ValueLayout.JAVA_DOUBLE, point.y());
cairo_matrix_transform_point.invoke(handle(), dxPtr, dyPtr);
return new Point(dxPtr.get(ValueLayout.JAVA_DOUBLE, 0), dyPtr.get(ValueLayout.JAVA_DOUBLE, 0));
}
@@ -433,4 +414,15 @@ public Point transformPoint(Point point) {
public Matrix(MemorySegment address) {
super(address);
}
+
+ /**
+ * String representation of this Matrix
+ *
+ * @return a String representation of this Matrix
+ */
+ @Override
+ public String toString() {
+ return String.format("Matrix address=%d xx=%f yx=%f xy=%f yy=%f x0=%f y0=%f",
+ handle().address(), xx(), yx(), xy(), yy(), x0(), y0());
+ }
}
diff --git a/src/main/java/org/freedesktop/cairo/Mesh.java b/src/main/java/org/freedesktop/cairo/Mesh.java
index a4fdd34..2d4049e 100644
--- a/src/main/java/org/freedesktop/cairo/Mesh.java
+++ b/src/main/java/org/freedesktop/cairo/Mesh.java
@@ -461,7 +461,7 @@ public Mesh setCornerColorRGBA(int cornerNum, double red, double green, double b
*/
public int getPatchCount() {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment count = arena.allocate(ValueLayout.JAVA_INT);
cairo_mesh_pattern_get_patch_count.invoke(handle(), count);
return count.get(ValueLayout.JAVA_INT, 0);
@@ -505,7 +505,7 @@ public Path getPath(int patchNum) throws IndexOutOfBoundsException {
private static final MethodHandle cairo_mesh_pattern_get_path = Interop.downcallHandle(
"cairo_mesh_pattern_get_path",
- FunctionDescriptor.of(ValueLayout.ADDRESS.asUnbounded(), ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
/**
* Gets the control point {@code pointNum} of patch {@code patchNum} for a mesh
@@ -527,7 +527,7 @@ public Path getPath(int patchNum) throws IndexOutOfBoundsException {
*/
public Point getControlPoint(int patchNum, int pointNum) throws IndexOutOfBoundsException {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment xPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
MemorySegment yPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
int result = (int) cairo_mesh_pattern_get_control_point.invoke(handle(), patchNum, pointNum, xPtr,
@@ -566,7 +566,7 @@ public Point getControlPoint(int patchNum, int pointNum) throws IndexOutOfBounds
*/
public double[] getCornerColorRGBA(int patchNum, int cornerNum) throws IndexOutOfBoundsException {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment redPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
MemorySegment greenPtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
MemorySegment bluePtr = arena.allocate(ValueLayout.JAVA_DOUBLE);
diff --git a/src/main/java/org/freedesktop/cairo/PDFSurface.java b/src/main/java/org/freedesktop/cairo/PDFSurface.java
index 4b9ad6d..b2dfbe1 100644
--- a/src/main/java/org/freedesktop/cairo/PDFSurface.java
+++ b/src/main/java/org/freedesktop/cairo/PDFSurface.java
@@ -19,6 +19,7 @@
package org.freedesktop.cairo;
+import io.github.jwharm.cairobindings.ArenaCloseAction;
import io.github.jwharm.cairobindings.Interop;
import io.github.jwharm.cairobindings.MemoryCleaner;
@@ -26,9 +27,9 @@
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentScope;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
+import java.lang.ref.Cleaner;
/**
* The PDF surface is used to render cairo graphics to Adobe PDF files and is a
@@ -109,14 +110,6 @@ public final class PDFSurface extends Surface {
Cairo.ensureInitialized();
}
- /*
- * Initialized by {@link #create(OutputStream, int, int)} to keep a reference to
- * the memory segment for the upcall stub alive during the lifetime of the
- * PDFSurface instance.
- */
- @SuppressWarnings("unused")
- private MemorySegment callbackAllocation;
-
/**
* The root outline item in cairo_pdf_surface_add_outline().
*
@@ -153,7 +146,7 @@ public PDFSurface(MemorySegment address) {
public static PDFSurface create(String filename, int widthInPoints, int heightInPoints) {
PDFSurface surface;
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment filenamePtr = Interop.allocateNativeString(filename, arena);
MemorySegment result = (MemorySegment) cairo_pdf_surface_create.invoke(filenamePtr, widthInPoints,
heightInPoints);
@@ -189,11 +182,12 @@ public static PDFSurface create(String filename, int widthInPoints, int heightIn
*/
public static PDFSurface create(OutputStream stream, int widthInPoints, int heightInPoints) {
PDFSurface surface;
+ Arena arena = Arena.ofConfined();
try {
MemorySegment writeFuncPtr;
if (stream != null) {
WriteFunc writeFunc = stream::write;
- writeFuncPtr = writeFunc.toCallback(SegmentScope.auto());
+ writeFuncPtr = writeFunc.toCallback(arena);
} else {
writeFuncPtr = MemorySegment.NULL;
}
@@ -202,7 +196,7 @@ public static PDFSurface create(OutputStream stream, int widthInPoints, int heig
surface = new PDFSurface(result);
MemoryCleaner.takeOwnership(surface.handle());
if (stream != null) {
- surface.callbackAllocation = writeFuncPtr; // Keep the memory segment of the upcall stub alive
+ ArenaCloseAction.CLEANER.register(surface, new ArenaCloseAction(arena));
}
} catch (Throwable e) {
throw new RuntimeException(e);
@@ -289,7 +283,7 @@ public PDFSurface setSize(double widthInPoints, double heightInPoints) {
*/
public int addOutline(int parentId, String string, String linkAttribs, PDFOutlineFlags flags) {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment utf8 = Interop.allocateNativeString(string, arena);
MemorySegment linkAttribsPtr = linkAttribs == null ? MemorySegment.NULL
: arena.allocateUtf8String(linkAttribs);
@@ -326,7 +320,7 @@ public int addOutline(int parentId, String string, String linkAttribs, PDFOutlin
*/
public PDFSurface setMetadata(PDFMetadata metadata, String string) {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment utf8 = Interop.allocateNativeString(string, arena);
cairo_pdf_surface_set_metadata.invoke(handle(), metadata.getValue(), utf8);
return this;
@@ -357,7 +351,7 @@ public PDFSurface setMetadata(PDFMetadata metadata, String string) {
*/
public PDFSurface setCustomMetadata(String name, String value) {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment namePtr = Interop.allocateNativeString(name, arena);
MemorySegment valuePtr = Interop.allocateNativeString(value, arena);
cairo_pdf_surface_set_custom_metadata.invoke(handle(), namePtr, valuePtr);
@@ -381,7 +375,7 @@ public PDFSurface setCustomMetadata(String name, String value) {
*/
public PDFSurface setPageLabel(String string) {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment utf8 = Interop.allocateNativeString(string, arena);
cairo_pdf_surface_set_page_label.invoke(handle(), utf8);
return this;
diff --git a/src/main/java/org/freedesktop/cairo/PDFVersion.java b/src/main/java/org/freedesktop/cairo/PDFVersion.java
index a79a6a5..15659ed 100644
--- a/src/main/java/org/freedesktop/cairo/PDFVersion.java
+++ b/src/main/java/org/freedesktop/cairo/PDFVersion.java
@@ -95,13 +95,12 @@ public static PDFVersion of(int ordinal) {
*/
public static PDFVersion[] getVersions() {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment versionsPtr = arena.allocate(ValueLayout.ADDRESS);
MemorySegment numVersionsPtr = arena.allocate(ValueLayout.JAVA_INT);
cairo_pdf_get_versions.invoke(versionsPtr, numVersionsPtr);
int numVersions = numVersionsPtr.get(ValueLayout.JAVA_INT, 0);
- int[] versionInts = MemorySegment.ofAddress(versionsPtr.address(), numVersions, arena.scope())
- .toArray(ValueLayout.JAVA_INT);
+ int[] versionInts = versionsPtr.reinterpret(ValueLayout.JAVA_INT.byteSize() * numVersions).toArray(ValueLayout.JAVA_INT);
PDFVersion[] versions = new PDFVersion[numVersions];
for (int i = 0; i < versionInts.length; i++) {
versions[i] = PDFVersion.of(versionInts[i]);
@@ -131,7 +130,7 @@ public String toString() {
if (MemorySegment.NULL.equals(result)) {
return null;
}
- return result.getUtf8String(0);
+ return result.reinterpret(Integer.MAX_VALUE).getUtf8String(0);
} catch (Throwable e) {
throw new RuntimeException(e);
}
@@ -139,5 +138,5 @@ public String toString() {
private static final MethodHandle cairo_pdf_version_to_string = Interop.downcallHandle(
"cairo_pdf_version_to_string",
- FunctionDescriptor.of(ValueLayout.ADDRESS.asUnbounded(), ValueLayout.JAVA_INT));
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
}
diff --git a/src/main/java/org/freedesktop/cairo/PSLevel.java b/src/main/java/org/freedesktop/cairo/PSLevel.java
index c582a2f..94cbbaa 100644
--- a/src/main/java/org/freedesktop/cairo/PSLevel.java
+++ b/src/main/java/org/freedesktop/cairo/PSLevel.java
@@ -81,13 +81,12 @@ public static PSLevel of(int ordinal) {
*/
public static PSLevel[] getLevels() {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment levelsPtr = arena.allocate(ValueLayout.ADDRESS);
MemorySegment numLevelsPtr = arena.allocate(ValueLayout.JAVA_INT);
cairo_ps_get_levels.invoke(levelsPtr, numLevelsPtr);
int numLevels = numLevelsPtr.get(ValueLayout.JAVA_INT, 0);
- int[] levelInts = MemorySegment.ofAddress(levelsPtr.address(), numLevels, arena.scope())
- .toArray(ValueLayout.JAVA_INT);
+ int[] levelInts = levelsPtr.reinterpret(ValueLayout.JAVA_INT.byteSize() * numLevels).toArray(ValueLayout.JAVA_INT);
PSLevel[] levels = new PSLevel[numLevels];
for (int i = 0; i < levelInts.length; i++) {
levels[i] = PSLevel.of(levelInts[i]);
@@ -117,12 +116,12 @@ public String toString() {
if (MemorySegment.NULL.equals(result)) {
return null;
}
- return result.getUtf8String(0);
+ return result.reinterpret(Integer.MAX_VALUE).getUtf8String(0);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private static final MethodHandle cairo_ps_level_to_string = Interop.downcallHandle("cairo_ps_level_to_string",
- FunctionDescriptor.of(ValueLayout.ADDRESS.asUnbounded(), ValueLayout.JAVA_INT));
+ FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
}
diff --git a/src/main/java/org/freedesktop/cairo/PSSurface.java b/src/main/java/org/freedesktop/cairo/PSSurface.java
index 74f0238..947ac36 100644
--- a/src/main/java/org/freedesktop/cairo/PSSurface.java
+++ b/src/main/java/org/freedesktop/cairo/PSSurface.java
@@ -19,6 +19,7 @@
package org.freedesktop.cairo;
+import io.github.jwharm.cairobindings.ArenaCloseAction;
import io.github.jwharm.cairobindings.Interop;
import io.github.jwharm.cairobindings.MemoryCleaner;
@@ -26,9 +27,9 @@
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentScope;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
+import java.lang.ref.Cleaner;
/**
* The PostScript surface is used to render cairo graphics to Adobe PostScript
@@ -71,14 +72,6 @@ public final class PSSurface extends Surface {
Cairo.ensureInitialized();
}
- /*
- * Initialized by {@link #create(OutputStream, int, int)} to keep a reference to
- * the memory segment for the upcall stub alive during the lifetime of the
- * PSSurface instance.
- */
- @SuppressWarnings("unused")
- private MemorySegment callbackAllocation;
-
/**
* Constructor used internally to instantiate a java PSSurface object for a
* native {@code cairo_surface_t} instance
@@ -113,7 +106,7 @@ public PSSurface(MemorySegment address) {
public static PSSurface create(String filename, int widthInPoints, int heightInPoints) {
PSSurface surface;
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment filenamePtr = Interop.allocateNativeString(filename, arena);
MemorySegment result = (MemorySegment) cairo_ps_surface_create.invoke(filenamePtr, widthInPoints,
heightInPoints);
@@ -154,11 +147,12 @@ public static PSSurface create(String filename, int widthInPoints, int heightInP
*/
public static PSSurface create(OutputStream stream, int widthInPoints, int heightInPoints) {
PSSurface surface;
+ Arena arena = Arena.ofConfined();
try {
MemorySegment writeFuncPtr;
if (stream != null) {
WriteFunc writeFunc = stream::write;
- writeFuncPtr = writeFunc.toCallback(SegmentScope.auto());
+ writeFuncPtr = writeFunc.toCallback(arena);
} else {
writeFuncPtr = MemorySegment.NULL;
}
@@ -167,7 +161,7 @@ public static PSSurface create(OutputStream stream, int widthInPoints, int heigh
surface = new PSSurface(result);
MemoryCleaner.takeOwnership(surface.handle());
if (stream != null) {
- surface.callbackAllocation = writeFuncPtr; // Keep the memory segment of the upcall stub alive
+ ArenaCloseAction.CLEANER.register(surface, new ArenaCloseAction(arena));
}
} catch (Throwable e) {
throw new RuntimeException(e);
@@ -418,7 +412,7 @@ public PSSurface dscBeginPageSetup() {
*/
public PSSurface dscComment(String comment) throws IllegalArgumentException {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment commentPtr = Interop.allocateNativeString(comment, arena);
cairo_ps_surface_dsc_comment.invoke(handle(), commentPtr);
}
diff --git a/src/main/java/org/freedesktop/cairo/Path.java b/src/main/java/org/freedesktop/cairo/Path.java
index c3879ed..7d7ba0a 100644
--- a/src/main/java/org/freedesktop/cairo/Path.java
+++ b/src/main/java/org/freedesktop/cairo/Path.java
@@ -59,10 +59,10 @@ public class Path extends Proxy implements Iterable
+ * This is one of several helper classes in Java (see also {@link RGBA} and
+ * {@link Point}), that do not exist in the native cairo API. The difference between
+ * {@code Rect} and {@link Rectangle} is that the latter class is part of the native
+ * cairo API and stores its values in native memory, while {@code Rect} instances
+ * only exist in the JVM.
+ *
+ * @param x X coordinate of the left side of the rectangle
+ * @param y Y coordinate of the top side of the rectangle
+ * @param width width of the rectangle
+ * @param height height of the rectangle
+ * @since 1.18.1
+ */
+public record Rect(double x, double y, double width, double height) {
+}
diff --git a/src/main/java/org/freedesktop/cairo/Rectangle.java b/src/main/java/org/freedesktop/cairo/Rectangle.java
index 8c9fa5f..e27bba7 100644
--- a/src/main/java/org/freedesktop/cairo/Rectangle.java
+++ b/src/main/java/org/freedesktop/cairo/Rectangle.java
@@ -96,20 +96,21 @@ public double height() {
* instance
*/
public Rectangle(MemorySegment address) {
- super(Interop.reinterpret(address, getMemoryLayout()));
+ super(address.reinterpret(getMemoryLayout().byteSize()));
}
/**
* A data structure for holding a rectangle.
- *
+ *
+ * @param arena the arena in which memory for the Rectangle is allocated
* @param x X coordinate of the left side of the rectangle
* @param y Y coordinate of the top side of the rectangle
* @param width width of the rectangle
* @param height height of the rectangle
* @return the newly created rectangle
*/
- public static Rectangle create(double x, double y, double width, double height) {
- Rectangle rect = new Rectangle(SegmentAllocator.nativeAllocator(SegmentScope.auto()).allocate(getMemoryLayout()));
+ public static Rectangle create(Arena arena, double x, double y, double width, double height) {
+ Rectangle rect = new Rectangle(arena.allocate(getMemoryLayout()));
X.set(rect.handle(), x);
Y.set(rect.handle(), y);
WIDTH.set(rect.handle(), width);
@@ -124,7 +125,8 @@ public static Rectangle create(double x, double y, double width, double height)
*/
@Override
public String toString() {
- return String.format("Rectangle address=%d x=%f y=%f width=%f height=%f", handle().address(), x(), y(), width(), height());
+ return String.format("Rectangle address=%d x=%f y=%f width=%f height=%f",
+ handle().address(), x(), y(), width(), height());
}
/**
diff --git a/src/main/java/org/freedesktop/cairo/RectangleInt.java b/src/main/java/org/freedesktop/cairo/RectangleInt.java
index f494f0b..8311a42 100644
--- a/src/main/java/org/freedesktop/cairo/RectangleInt.java
+++ b/src/main/java/org/freedesktop/cairo/RectangleInt.java
@@ -101,15 +101,16 @@ public RectangleInt(MemorySegment address) {
/**
* Create a rectangle with integer coordinates.
- *
+ *
+ * @param arena the arena in which memory for the RectangleInt is allocated
* @param x X coordinate of the left side of the rectangle
* @param y Y coordinate of the the top side of the rectangle
* @param width width of the rectangle
* @param height height of the rectangle
* @return the newly created rectangle
*/
- public static RectangleInt create(int x, int y, int width, int height) {
- RectangleInt rect = new RectangleInt(SegmentAllocator.nativeAllocator(SegmentScope.auto()).allocate(getMemoryLayout()));
+ public static RectangleInt create(Arena arena, int x, int y, int width, int height) {
+ RectangleInt rect = new RectangleInt(arena.allocate(getMemoryLayout()));
X.set(rect.handle(), x);
Y.set(rect.handle(), y);
WIDTH.set(rect.handle(), width);
@@ -124,7 +125,8 @@ public static RectangleInt create(int x, int y, int width, int height) {
*/
@Override
public String toString() {
- return String.format("RectangleInt address=%d x=%d y=%d width=%d height=%d", handle().address(), x(), y(), width(), height());
+ return String.format("RectangleInt address=%d x=%d y=%d width=%d height=%d",
+ handle().address(), x(), y(), width(), height());
}
/**
diff --git a/src/main/java/org/freedesktop/cairo/RectangleList.java b/src/main/java/org/freedesktop/cairo/RectangleList.java
index 91f93a5..6223d07 100644
--- a/src/main/java/org/freedesktop/cairo/RectangleList.java
+++ b/src/main/java/org/freedesktop/cairo/RectangleList.java
@@ -22,11 +22,7 @@
import io.github.jwharm.cairobindings.Proxy;
import io.github.jwharm.cairobindings.MemoryCleaner;
-import java.lang.foreign.MemoryLayout;
-import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.foreign.SegmentScope;
-import java.lang.foreign.ValueLayout;
+import java.lang.foreign.*;
import java.lang.invoke.VarHandle;
import java.util.List;
@@ -49,10 +45,10 @@ public class RectangleList extends Proxy {
static MemoryLayout getMemoryLayout() {
return MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("status"),
- MemoryLayout.paddingLayout(32),
+ MemoryLayout.paddingLayout(4),
ValueLayout.ADDRESS.withName("rectangles"),
ValueLayout.JAVA_INT.withName("num_rectangles"),
- MemoryLayout.paddingLayout(32))
+ MemoryLayout.paddingLayout(4))
.withName("cairo_rectangle_list_t");
}
@@ -77,10 +73,9 @@ public Status status() {
* @return the list of rectangles
*/
public List
- * The lifetime of a UserDataKey is connected to the lifetime of the Proxy object
- * that is passed in the {@link #create(Proxy)} method.
- *
+ *
* @since 1.0
*/
public final class UserDataKey extends Proxy {
@@ -55,11 +49,10 @@ public UserDataKey(MemorySegment address) {
/**
* Create a new UserDataKey
*
- * @param proxy the ProxyInstace object whose memory scope (lifetime) will be
- * associated with the returned UserDataKey
+ * @param arena the arena in which the returned UserDataKey will be allocated
* @return the newly created UserDataKey
*/
- static UserDataKey create(Proxy proxy) {
- return new UserDataKey(SegmentAllocator.nativeAllocator(proxy.handle().scope()).allocate(getMemoryLayout()));
+ public static UserDataKey create(Arena arena) {
+ return new UserDataKey(arena.allocate(getMemoryLayout()));
}
}
diff --git a/src/main/java/org/freedesktop/cairo/UserDataStore.java b/src/main/java/org/freedesktop/cairo/UserDataStore.java
deleted file mode 100644
index acb5367..0000000
--- a/src/main/java/org/freedesktop/cairo/UserDataStore.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/* cairo-java-bindings - Java language bindings for cairo
- * Copyright (C) 2023 Jan-Willem Harmannij
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see
+ * The resulting Face instance must be cleaned up manually with a call to
+ * {@link #doneFace()}.
+ *
* @param library the library resource
* @param filepathname path to the font file
* @param faceIndex See FT_Open_Face
* for a detailed description of this parameter.
*/
- public Face(Library library, String filepathname, long faceIndex) {
- super(constructNew(library, filepathname, faceIndex));
- MemoryCleaner.takeOwnership(handle());
- }
-
- /*
- * Helper function for the Face(library, filepathname, faceIndex) constructor
- */
- private static MemorySegment constructNew(Library library, String filepathname, long faceIndex) {
+ public static Face newFace(Library library, String filepathname, long faceIndex) {
+ Arena allocator = Arena.ofConfined();
try {
- MemorySegment pointer = SegmentAllocator.nativeAllocator(SegmentScope.auto())
- .allocate(ValueLayout.ADDRESS.asUnbounded());
- try (Arena arena = Arena.openConfined()) {
+ MemorySegment pointer = allocator.allocate(ValueLayout.ADDRESS.withTargetLayout(ValueLayout.ADDRESS));
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment utf8 = Interop.allocateNativeString(filepathname, arena);
int result = (int) FT_New_Face.invoke(library.handle(), utf8, faceIndex, pointer);
if (result != 0) {
throw new UnsupportedOperationException(
"Error " + result + " occurred during FreeType FT_Face initialization");
}
+ Face face = new Face(pointer.get(ValueLayout.ADDRESS, 0));
+ face.allocator = allocator;
+ return face;
}
- return pointer.get(ValueLayout.ADDRESS, 0);
} catch (Throwable e) {
throw new RuntimeException(e);
}
@@ -99,4 +86,22 @@ private static MemorySegment constructNew(Library library, String filepathname,
private static final MethodHandle FT_New_Face = Interop.downcallHandle("FT_New_Face",
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS));
+
+ /**
+ * Discard a given face object, as well as all of its child slots and sizes.
+ */
+ public void doneFace() {
+ try {
+ int ignored = (int) FT_Done_Face.invoke(handle());
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ if (allocator != null) {
+ allocator.close();
+ allocator = null;
+ }
+ }
+
+ private static final MethodHandle FT_Done_Face = Interop.downcallHandle("FT_Done_Face",
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
}
diff --git a/src/main/java/org/freedesktop/freetype/Library.java b/src/main/java/org/freedesktop/freetype/Library.java
index 8e4906f..452a3af 100644
--- a/src/main/java/org/freedesktop/freetype/Library.java
+++ b/src/main/java/org/freedesktop/freetype/Library.java
@@ -21,13 +21,10 @@
import io.github.jwharm.cairobindings.Proxy;
import io.github.jwharm.cairobindings.Interop;
-import io.github.jwharm.cairobindings.MemoryCleaner;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.foreign.SegmentScope;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
@@ -43,6 +40,9 @@ public class Library extends Proxy {
FreeType2.ensureInitialized();
}
+ // The Arena in which the handle to the Library object was allocated.
+ private Arena allocator = null;
+
/**
* Constructor used internally to instantiate a java Library object for a native
* {@code FT_Library} instance
@@ -51,36 +51,29 @@ public class Library extends Proxy {
*/
public Library(MemorySegment address) {
super(address);
- MemoryCleaner.setFreeFunc(handle(), "FT_Done_FreeType");
- }
-
- /**
- * Invokes the cleanup action that is normally invoked during garbage collection.
- * If the instance is "owned" by the user, the {@code destroy()} function is run
- * to dispose the native instance.
- */
- public void destroy() {
- MemoryCleaner.free(handle());
}
/**
* Initialize a new FreeType library object.
- *
+ *
+ * The resulting Library instance must be cleaned up manually with a call to
+ * {@link #doneFreeType()}.
+ *
* @return a new library object
* @throws UnsupportedOperationException when {@code FT_Init_FreeType} returns a
* non-zero error code
*/
public static Library initFreeType() throws UnsupportedOperationException {
try {
- MemorySegment pointer = SegmentAllocator.nativeAllocator(SegmentScope.auto())
- .allocate(ValueLayout.ADDRESS.asUnbounded());
+ Arena allocator = Arena.ofConfined();
+ MemorySegment pointer = allocator.allocate(ValueLayout.ADDRESS.withTargetLayout(ValueLayout.ADDRESS));
int result = (int) FT_Init_FreeType.invoke(pointer);
if (result != 0) {
throw new UnsupportedOperationException(
"Error " + result + " occurred during FreeType library initialization");
}
Library library = new Library(pointer.get(ValueLayout.ADDRESS, 0));
- MemoryCleaner.takeOwnership(library.handle());
+ library.allocator = allocator;
return library;
} catch (Throwable e) {
throw new RuntimeException(e);
@@ -98,7 +91,7 @@ public static Library initFreeType() throws UnsupportedOperationException {
*/
public String version() {
try {
- try (Arena arena = Arena.openConfined()) {
+ try (Arena arena = Arena.ofConfined()) {
MemorySegment amajor = arena.allocate(ValueLayout.JAVA_INT);
MemorySegment aminor = arena.allocate(ValueLayout.JAVA_INT);
MemorySegment apatch = arena.allocate(ValueLayout.JAVA_INT);
@@ -116,4 +109,23 @@ public String version() {
private static final MethodHandle FT_Library_Version = Interop.downcallHandle(
"FT_Library_Version", FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS,
ValueLayout.ADDRESS, ValueLayout.ADDRESS));
+
+ /**
+ * Destroy a given FreeType library object and all of its children, including
+ * resources, drivers, faces, sizes, etc.
+ */
+ public void doneFreeType() {
+ try {
+ int ignored = (int) FT_Done_FreeType.invoke(handle());
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ if (allocator != null) {
+ allocator.close();
+ allocator = null;
+ }
+ }
+
+ private static final MethodHandle FT_Done_FreeType = Interop.downcallHandle("FT_Done_FreeType",
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
}
diff --git a/src/test/java/org/freedesktop/cairo/test/ContextTest.java b/src/test/java/org/freedesktop/cairo/test/ContextTest.java
index 6fcdef5..ecb1c4b 100644
--- a/src/test/java/org/freedesktop/cairo/test/ContextTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/ContextTest.java
@@ -1,6 +1,9 @@
package org.freedesktop.cairo.test;
import java.io.IOException;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
import org.freedesktop.cairo.*;
import org.junit.jupiter.api.Test;
@@ -355,7 +358,7 @@ void testStrokeExtents() {
Context cr = createContext();
cr.moveTo(20d, 10d);
cr.lineTo(30d, 40d);
- Rectangle r = cr.strokeExtents();
+ Rect r = cr.strokeExtents();
assertTrue(r.x() > 19d && r.x() < 20d);
assertTrue(r.y() > 9d && r.y() < 10d);
assertTrue(r.width() > 30d && r.width() < 31d);
@@ -514,18 +517,20 @@ void testRectangle() {
@Test
void testGlyphPath() {
- Context cr = createContext();
- ScaledFont font = ScaledFont.create(ToyFontFace.create("Arial", FontSlant.NORMAL, FontWeight.NORMAL),
- Matrix.createIdentity(), Matrix.createIdentity(), FontOptions.create());
- Glyphs glyphs = font.textToGlyphs(0, 0, "test");
- assertEquals(4, glyphs.getNumGlyphs());
- var path = cr.glyphPath(glyphs).copyPath();
- assertNotNull(path);
- // I'm not sure how many path elements there are in the glyphs; for me it reports 103,
- // but I'm not sure if that will be the same on all systems and platforms. So let's
- // just check if there are more than 10
- assertTrue(getPathLength(path) > 10);
- assertEquals(Status.SUCCESS, cr.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Context cr = createContext();
+ ScaledFont font = ScaledFont.create(ToyFontFace.create("Arial", FontSlant.NORMAL, FontWeight.NORMAL),
+ Matrix.create(arena).initIdentity(), Matrix.create(arena).initIdentity(), FontOptions.create());
+ Glyphs glyphs = font.textToGlyphs(0, 0, "test");
+ assertEquals(4, glyphs.getNumGlyphs());
+ var path = cr.glyphPath(glyphs).copyPath();
+ assertNotNull(path);
+ // I'm not sure how many path elements there are in the glyphs; for me it reports 103,
+ // but I'm not sure if that will be the same on all systems and platforms. So let's
+ // just check if there are more than 10
+ assertTrue(getPathLength(path) > 10);
+ assertEquals(Status.SUCCESS, cr.status());
+ }
}
@Test
@@ -563,7 +568,7 @@ void testRelMoveTo() {
void testPathExtents() {
Context cr = createContext();
cr.rectangle(10, 20, 30, 40);
- Rectangle rect = cr.pathExtents();
+ Rect rect = cr.pathExtents();
assertEquals(10, rect.x());
assertEquals(20, rect.y());
assertEquals(40, rect.width());
@@ -594,17 +599,29 @@ void testRotate() {
@Test
void testTransform() {
- Context cr = createContext();
- cr.transform(Matrix.createIdentity());
- assertEquals(Status.SUCCESS, cr.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Context cr = createContext();
+ cr.transform(Matrix.create(arena).initIdentity());
+ assertEquals(Status.SUCCESS, cr.status());
+ }
}
@Test
void testMatrix() {
- Context cr = createContext();
- cr.setMatrix(Matrix.createIdentity());
- cr.getMatrix();
- assertEquals(Status.SUCCESS, cr.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Context cr = createContext();
+ Matrix m1 = Matrix.create(arena).initTranslate(2.5, 3.0);
+ Matrix m2 = Matrix.create(arena);
+ cr.setMatrix(m1);
+ cr.getMatrix(m2);
+ assertEquals(m1.xx(), m2.xx());
+ assertEquals(m1.yx(), m2.yx());
+ assertEquals(m1.xy(), m2.xy());
+ assertEquals(m1.yy(), m2.yy());
+ assertEquals(m1.x0(), m2.x0());
+ assertEquals(m1.y0(), m2.y0());
+ assertEquals(Status.SUCCESS, cr.status());
+ }
}
@Test
@@ -645,11 +662,21 @@ void testDeviceToUserDistance() {
@Test
void testUserData() {
- Context cr = createContext();
- int input = 12345;
- UserDataKey key = cr.setUserData(input);
- int output = (int) cr.getUserData(key);
- assertEquals(input, output);
- assertEquals(cr.status(), Status.SUCCESS);
+ try (Arena arena = Arena.ofConfined()) {
+ var segmentIn = arena.allocate(ValueLayout.JAVA_INT);
+ Context cr = createContext();
+
+ int input = 12345;
+ segmentIn.set(ValueLayout.JAVA_INT, 0, input);
+
+ UserDataKey key = UserDataKey.create(arena);
+ cr.setUserData(key, segmentIn);
+
+ var segmentOut = cr.getUserData(key).reinterpret(ValueLayout.JAVA_INT.byteSize());
+ int output = segmentOut.get(ValueLayout.JAVA_INT, 0);
+
+ assertEquals(input, output);
+ assertEquals(cr.status(), Status.SUCCESS);
+ }
}
}
diff --git a/src/test/java/org/freedesktop/cairo/test/DeviceTest.java b/src/test/java/org/freedesktop/cairo/test/DeviceTest.java
index f07aa3a..3c3d848 100644
--- a/src/test/java/org/freedesktop/cairo/test/DeviceTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/DeviceTest.java
@@ -4,6 +4,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import java.lang.foreign.Arena;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
@@ -56,12 +57,14 @@ void acquireAndRelease() {
@Test
void testUserData() {
- try (Device d = Script.create(tempDir.resolve("test.script").toString())) {
- Rectangle input = Rectangle.create(0, 0, 10, 10);
- UserDataKey key = d.setUserData(input);
- Rectangle output = (Rectangle) d.getUserData(key);
+ try (Arena arena = Arena.ofConfined();
+ Device device = Script.create(tempDir.resolve("test.script").toString())) {
+ Rectangle input = Rectangle.create(arena, 0, 0, 10, 10);
+ UserDataKey key = UserDataKey.create(arena);
+ device.setUserData(key, input.handle());
+ Rectangle output = new Rectangle(device.getUserData(key));
assertEquals(input, output);
- assertEquals(d.status(), Status.SUCCESS);
+ assertEquals(device.status(), Status.SUCCESS);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/freedesktop/cairo/test/FTFontFaceTest.java b/src/test/java/org/freedesktop/cairo/test/FTFontFaceTest.java
index dd1510d..909cad0 100644
--- a/src/test/java/org/freedesktop/cairo/test/FTFontFaceTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/FTFontFaceTest.java
@@ -1,7 +1,5 @@
-package org.freedesktop.cairo.test;
+ package org.freedesktop.cairo.test;
-import static org.freedesktop.cairo.FTSynthesize.BOLD;
-import static org.freedesktop.cairo.FTSynthesize.OBLIQUE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.freedesktop.cairo.FTFontFace;
@@ -19,7 +17,7 @@
class FTFontFaceTest {
private static String TTF_FILE;
-
+
@BeforeAll
static void setup() {
// These files are going to be in different locations depending on your system.
@@ -36,23 +34,27 @@ static void setup() {
case "macos" -> TTF_FILE = "/Library/Fonts/Arial Unicode.ttf";
}
}
-
+
@Test
void testCreate() {
Library ftLib = Library.initFreeType();
- Face ftFace = new Face(ftLib, TTF_FILE, 0);
+ Face ftFace = Face.newFace(ftLib, TTF_FILE, 0);
FTFontFace face = FTFontFace.create(ftFace, 0);
assertEquals(Status.SUCCESS, face.status());
+ ftFace.doneFace();
+ ftLib.doneFreeType();
}
@Test
void testSynthesize() {
Library ftLib = Library.initFreeType();
- Face ftFace = new Face(ftLib, TTF_FILE, 0);
+ Face ftFace = Face.newFace(ftLib, TTF_FILE, 0);
FTFontFace face = FTFontFace.create(ftFace, 0);
face.setSynthesize(FTSynthesize.BOLD.or(FTSynthesize.OBLIQUE));
face.unsetSynthesize(FTSynthesize.BOLD);
assertEquals(FTSynthesize.OBLIQUE, face.getSynthesize());
assertEquals(Status.SUCCESS, face.status());
+ ftFace.doneFace();
+ ftLib.doneFreeType();
}
}
diff --git a/src/test/java/org/freedesktop/cairo/test/FTScaledFontTest.java b/src/test/java/org/freedesktop/cairo/test/FTScaledFontTest.java
index 909e9ac..d9c8deb 100644
--- a/src/test/java/org/freedesktop/cairo/test/FTScaledFontTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/FTScaledFontTest.java
@@ -1,7 +1,6 @@
package org.freedesktop.cairo.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import io.github.jwharm.cairobindings.Platform;
import org.freedesktop.cairo.FTFontFace;
@@ -12,10 +11,10 @@
import org.freedesktop.freetype.Face;
import org.freedesktop.freetype.Library;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.File;
+import java.lang.foreign.Arena;
class FTScaledFontTest {
@@ -40,21 +39,29 @@ static void setup() {
@Test
void testCreate() {
- Library ftLib = Library.initFreeType();
- Face ftFace = new Face(ftLib, TTF_FILE, 0);
- FTFontFace face = FTFontFace.create(ftFace, 0);
- FTScaledFont scaledFont = FTScaledFont.create(face, Matrix.createIdentity(), Matrix.createIdentity(), FontOptions.create());
- assertEquals(Status.SUCCESS, scaledFont.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Library ftLib = Library.initFreeType();
+ Face ftFace = Face.newFace(ftLib, TTF_FILE, 0);
+ FTFontFace face = FTFontFace.create(ftFace, 0);
+ FTScaledFont scaledFont = FTScaledFont.create(face, Matrix.create(arena).initIdentity(), Matrix.create(arena).initIdentity(), FontOptions.create());
+ assertEquals(Status.SUCCESS, scaledFont.status());
+ ftFace.doneFace();
+ ftLib.doneFreeType();
+ }
}
@Test
void testLockAndUnlockFace() {
- Library ftLib = Library.initFreeType();
- Face ftFace = new Face(ftLib, TTF_FILE, 0);
- FTFontFace face = FTFontFace.create(ftFace, 0);
- FTScaledFont scaledFont = FTScaledFont.create(face, Matrix.createIdentity(), Matrix.createIdentity(), FontOptions.create());
- scaledFont.lockFace();
- scaledFont.unlockFace();
- assertEquals(Status.SUCCESS, scaledFont.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Library ftLib = Library.initFreeType();
+ Face ftFace = Face.newFace(ftLib, TTF_FILE, 0);
+ FTFontFace face = FTFontFace.create(ftFace, 0);
+ FTScaledFont scaledFont = FTScaledFont.create(face, Matrix.create(arena).initIdentity(), Matrix.create(arena).initIdentity(), FontOptions.create());
+ scaledFont.lockFace();
+ scaledFont.unlockFace();
+ assertEquals(Status.SUCCESS, scaledFont.status());
+ ftFace.doneFace();
+ ftLib.doneFreeType();
+ }
}
}
diff --git a/src/test/java/org/freedesktop/cairo/test/ImageSurfaceTest.java b/src/test/java/org/freedesktop/cairo/test/ImageSurfaceTest.java
index 9c810eb..e56e8e8 100644
--- a/src/test/java/org/freedesktop/cairo/test/ImageSurfaceTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/ImageSurfaceTest.java
@@ -1,9 +1,8 @@
package org.freedesktop.cairo.test;
import java.io.*;
+import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
-import java.lang.foreign.SegmentAllocator;
-import java.lang.foreign.SegmentScope;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -53,10 +52,12 @@ void testCreateFormatIntInt() {
@Test
void testCreateMemorySegmentFormatIntIntInt() {
- int stride = ImageSurface.formatStrideForWidth(Format.ARGB32, 120);
- MemorySegment data = SegmentAllocator.nativeAllocator(SegmentScope.auto()).allocate(120L * 120 * stride);
- try (ImageSurface s = ImageSurface.create(data, Format.ARGB32, 120, 120, stride)) {
- assertEquals(Status.SUCCESS, s.status());
+ try (Arena arena = Arena.ofConfined()) {
+ int stride = ImageSurface.formatStrideForWidth(Format.ARGB32, 120);
+ MemorySegment data = arena.allocate(120L * 120 * stride);
+ try (ImageSurface s = ImageSurface.create(data, Format.ARGB32, 120, 120, stride)) {
+ assertEquals(Status.SUCCESS, s.status());
+ }
}
}
diff --git a/src/test/java/org/freedesktop/cairo/test/MatrixTest.java b/src/test/java/org/freedesktop/cairo/test/MatrixTest.java
index 834a76f..708ba7b 100644
--- a/src/test/java/org/freedesktop/cairo/test/MatrixTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/MatrixTest.java
@@ -7,88 +7,113 @@
import org.freedesktop.cairo.Point;
import org.junit.jupiter.api.Test;
-class MatrixTest {
+import java.lang.foreign.Arena;
- @Test
- void testCreateDoubleDoubleDoubleDoubleDoubleDouble() {
- Matrix matrix = Matrix.create(1, 0, 0, 1, 0, 0);
- assertNotNull(matrix);
- }
+class MatrixTest {
@Test
void testInit() {
- Matrix matrix = Matrix.create(1, 0, 0, 1, 0, 0);
- matrix.init(2, 0, 0, 2, 0, 0);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).init(1, 0, 0, 1, 0, 0);
+ matrix.init(2, 0, 0, 2, 0, 0);
+ }
}
@Test
void testCreateIdentity() {
- Matrix matrix = Matrix.createIdentity();
- assertNotNull(matrix);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initIdentity();
+ assertNotNull(matrix);
+ }
}
@Test
void testCreateTranslate() {
- Matrix matrix = Matrix.createTranslate(20, 20);
- assertNotNull(matrix);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initTranslate(20, 20);
+ assertNotNull(matrix);
+ }
}
@Test
void testCreateScale() {
- Matrix matrix = Matrix.createScale(2, 2);
- assertNotNull(matrix);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initIdentity();
+ matrix.scale(2, 2);
+ assertNotNull(matrix);
+ }
}
@Test
void testCreateRotate() {
- Matrix matrix = Matrix.createRotate(90);
- assertNotNull(matrix);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initIdentity();
+ matrix.rotate(90);
+ assertNotNull(matrix);
+ }
}
@Test
void testTranslate() {
- Matrix matrix = Matrix.createIdentity();
- matrix.translate(10, 10);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initIdentity();
+ matrix.translate(10, 10);
+ }
}
@Test
void testScale() {
- Matrix matrix = Matrix.createTranslate(20, 20);
- matrix.scale(0.5, 0.5);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initTranslate(20, 20);
+ matrix.scale(0.5, 0.5);
+ }
}
@Test
void testRotate() {
- Matrix matrix = Matrix.createTranslate(20, 20);
- matrix.rotate(180);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initTranslate(20, 20);
+ matrix.rotate(180);
+ }
}
@Test
void testInvert() {
- Matrix matrix = Matrix.createTranslate(20, 20);
- matrix.invert();
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initTranslate(20, 20);
+ matrix.invert();
+ }
}
@Test
void testMultiply() {
- Matrix matrix1 = Matrix.createRotate(90);
- Matrix matrix2 = Matrix.createTranslate(20, 20);
- matrix1.multiply(matrix2);
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix1 = Matrix.create(arena).initIdentity();
+ matrix1.rotate(90);
+ Matrix matrix2 = Matrix.create(arena).initTranslate(20, 20);
+ Matrix matrix3 = Matrix.create(arena).initIdentity();
+ matrix3.multiply(matrix1, matrix2);
+ }
}
@Test
void testTransformDistance() {
- Matrix matrix = Matrix.createScale(3, 2);
- Point point = matrix.transformDistance(new Point(10, 10));
- assertEquals(30, point.x());
- assertEquals(20, point.y());
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initIdentity();
+ matrix.scale(3, 2);
+ Point point = matrix.transformDistance(new Point(10, 10));
+ assertEquals(30, point.x());
+ assertEquals(20, point.y());
+ }
}
@Test
void testTransformPoint() {
- Matrix matrix = Matrix.createTranslate(20, 30);
- Point point = matrix.transformPoint(new Point(30, 40));
- assertEquals(50, point.x());
- assertEquals(70, point.y());
+ try (Arena arena = Arena.ofConfined()) {
+ Matrix matrix = Matrix.create(arena).initTranslate(20, 30);
+ Point point = matrix.transformPoint(new Point(30, 40));
+ assertEquals(50, point.x());
+ assertEquals(70, point.y());
+ }
}
}
diff --git a/src/test/java/org/freedesktop/cairo/test/PatternTest.java b/src/test/java/org/freedesktop/cairo/test/PatternTest.java
index 2c32a95..52c1c56 100644
--- a/src/test/java/org/freedesktop/cairo/test/PatternTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/PatternTest.java
@@ -6,6 +6,8 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import java.lang.foreign.Arena;
+
class PatternTest {
@Test
@@ -34,11 +36,15 @@ void testFilter() {
@Test
void testMatrix() {
- Gradient pattern = LinearGradient.create(0, 0, 10, 10);
- pattern.setMatrix(Matrix.createScale(2, 2));
- Matrix m = Matrix.create(0, 0, 0, 0, 0, 0);
- pattern.getMatrix(m);
- assertEquals(Status.SUCCESS, pattern.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Gradient pattern = LinearGradient.create(0, 0, 10, 10);
+ Matrix scale = Matrix.create(arena).initIdentity();
+ scale.scale(2, 2);
+ pattern.setMatrix(scale);
+ Matrix m = Matrix.create(arena).init(0, 0, 0, 0, 0, 0);
+ pattern.getMatrix(m);
+ assertEquals(Status.SUCCESS, pattern.status());
+ }
}
@Test
diff --git a/src/test/java/org/freedesktop/cairo/test/RecordingSurfaceTest.java b/src/test/java/org/freedesktop/cairo/test/RecordingSurfaceTest.java
index cb9d5e9..08f8313 100644
--- a/src/test/java/org/freedesktop/cairo/test/RecordingSurfaceTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/RecordingSurfaceTest.java
@@ -4,6 +4,7 @@
import org.junit.jupiter.api.Test;
import java.io.IOException;
+import java.lang.foreign.Arena;
import static org.junit.jupiter.api.Assertions.*;
@@ -11,8 +12,9 @@ class RecordingSurfaceTest {
@Test
void testCreate() throws IOException {
- try (Surface s = ImageSurface.create(Format.ARGB32, 120, 120);
- RecordingSurface r = RecordingSurface.create(Content.COLOR_ALPHA, Rectangle.create(20, 20, 50, 50))) {
+ try (Arena arena = Arena.ofConfined();
+ Surface s = ImageSurface.create(Format.ARGB32, 120, 120);
+ RecordingSurface r = RecordingSurface.create(Content.COLOR_ALPHA, Rectangle.create(arena, 20, 20, 50, 50))) {
assertEquals(Status.SUCCESS, s.status());
assertEquals(Status.SUCCESS, r.status());
}
@@ -25,7 +27,7 @@ void testInkExtents() throws IOException {
Context.create(r)
.rectangle(12, 14, 16, 18)
.fill();
- Rectangle rect = r.inkExtents();
+ Rect rect = r.inkExtents();
assertEquals(12, rect.x());
assertEquals(14, rect.y());
assertEquals(16, rect.width());
@@ -37,9 +39,11 @@ void testInkExtents() throws IOException {
@Test
void testGetExtents() {
- try (Surface s = ImageSurface.create(Format.ARGB32, 120, 120);
- RecordingSurface r = RecordingSurface.create(Content.COLOR_ALPHA, Rectangle.create(20, 30, 50, 60))) {
- Rectangle rect = r.getExtents();
+ try (Arena arena = Arena.ofConfined();
+ Surface s = ImageSurface.create(Format.ARGB32, 120, 120);
+ RecordingSurface r = RecordingSurface.create(Content.COLOR_ALPHA, Rectangle.create(arena, 20, 30, 50, 60))) {
+ Rectangle rect = Rectangle.create(arena, 0, 0, 0, 0);
+ r.getExtents(rect);
assertEquals(20, rect.x());
assertEquals(30, rect.y());
assertEquals(50, rect.width());
diff --git a/src/test/java/org/freedesktop/cairo/test/RegionTest.java b/src/test/java/org/freedesktop/cairo/test/RegionTest.java
index 7db5510..dc1ebf5 100644
--- a/src/test/java/org/freedesktop/cairo/test/RegionTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/RegionTest.java
@@ -10,6 +10,8 @@
import org.freedesktop.cairo.Status;
import org.junit.jupiter.api.Test;
+import java.lang.foreign.Arena;
+
class RegionTest {
@Test
@@ -20,173 +22,249 @@ void testCreate() {
@Test
void testCreateRectangleInt() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testCreateRectangleIntArray() {
- Region region = Region.create(
- new RectangleInt[] { RectangleInt.create(10, 20, 100, 200), RectangleInt.create(20, 30, 100, 200) });
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(
+ new RectangleInt[]{
+ RectangleInt.create(arena, 10, 20, 100, 200),
+ RectangleInt.create(arena, 20, 30, 100, 200)
+ });
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testCopy() {
- Region region1 = Region.create(RectangleInt.create(10, 20, 100, 200));
- Region region2 = region1.copy();
- assertTrue(region1.equal(region2));
- assertEquals(Status.SUCCESS, region1.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ Region region2 = region1.copy();
+ assertTrue(region1.equal(region2));
+ assertEquals(Status.SUCCESS, region1.status());
+ }
}
@Test
void testGetExtents() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- RectangleInt r = region.getExtents();
- assertTrue(r.toString().endsWith("x=10 y=20 width=100 height=200"));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ RectangleInt r = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getExtents(r);
+ assertTrue(r.toString().endsWith("x=10 y=20 width=100 height=200"));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testNumRectangles() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- int num = region.numRectangles();
- assertEquals(1, num);
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ int num = region.numRectangles();
+ assertEquals(1, num);
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testGetRectangle() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- RectangleInt r = region.getRectangle(0);
- assertTrue(r.toString().endsWith("x=10 y=20 width=100 height=200"));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ RectangleInt r = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getRectangle(0, r);
+ assertTrue(r.toString().endsWith("x=10 y=20 width=100 height=200"));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testIsEmpty() {
- Region region1 = Region.create();
- Region region2 = Region.create(RectangleInt.create(10, 20, 100, 200));
- assertTrue(region1.isEmpty());
- assertFalse(region2.isEmpty());
- assertEquals(Status.SUCCESS, region1.status());
- assertEquals(Status.SUCCESS, region2.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create();
+ Region region2 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ assertTrue(region1.isEmpty());
+ assertFalse(region2.isEmpty());
+ assertEquals(Status.SUCCESS, region1.status());
+ assertEquals(Status.SUCCESS, region2.status());
+ }
}
@Test
void testContainsPoint() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- assertTrue(region.containsPoint(10, 20));
- assertFalse(region.containsPoint(9, 20));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ assertTrue(region.containsPoint(10, 20));
+ assertFalse(region.containsPoint(9, 20));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testContainsRectangle() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- assertEquals(RegionOverlap.IN, region.containsRectangle(RectangleInt.create(10, 20, 100, 200)));
- assertEquals(RegionOverlap.PART, region.containsRectangle(RectangleInt.create(11, 21, 100, 200)));
- assertEquals(RegionOverlap.OUT, region.containsRectangle(RectangleInt.create(111, 221, 100, 200)));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ assertEquals(RegionOverlap.IN, region.containsRectangle(RectangleInt.create(arena, 10, 20, 100, 200)));
+ assertEquals(RegionOverlap.PART, region.containsRectangle(RectangleInt.create(arena, 11, 21, 100, 200)));
+ assertEquals(RegionOverlap.OUT, region.containsRectangle(RectangleInt.create(arena, 111, 221, 100, 200)));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testEqual() {
- Region region1 = Region.create(RectangleInt.create(10, 20, 100, 200));
- Region region2 = Region.create(RectangleInt.create(10, 20, 100, 200));
- Region region3 = Region.create(RectangleInt.create(11, 20, 100, 200));
- assertTrue(region1.equal(region2));
- assertFalse(region1.equal(region3));
- assertEquals(Status.SUCCESS, region1.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ Region region2 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ Region region3 = Region.create(RectangleInt.create(arena, 11, 20, 100, 200));
+ assertTrue(region1.equal(region2));
+ assertFalse(region1.equal(region3));
+ assertEquals(Status.SUCCESS, region1.status());
+ }
}
@Test
void testTranslate() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- region.translate(2, -2);
- assertEquals(12, region.getExtents().x());
- assertEquals(18, region.getExtents().y());
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ region.translate(2, -2);
+ var extents = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getExtents(extents);
+ assertEquals(12, extents.x());
+ assertEquals(18, extents.y());
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testIntersectRegion() {
- Region region1 = Region.create(RectangleInt.create(10, 20, 100, 200));
- Region region2 = Region.create(RectangleInt.create(50, 80, 100, 200));
- region1.intersect(region2);
- assertTrue(region1.getExtents().toString().endsWith("x=50 y=80 width=60 height=140"));
- assertTrue(region2.getExtents().toString().endsWith("x=50 y=80 width=100 height=200"));
- assertEquals(Status.SUCCESS, region1.status());
- assertEquals(Status.SUCCESS, region2.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ Region region2 = Region.create(RectangleInt.create(arena, 50, 80, 100, 200));
+ region1.intersect(region2);
+
+ var extents1 = RectangleInt.create(arena, 0, 0, 0, 0);
+ var extents2 = RectangleInt.create(arena, 0, 0, 0, 0);
+ region1.getExtents(extents1);
+ region2.getExtents(extents2);
+
+ assertTrue(extents1.toString().endsWith("x=50 y=80 width=60 height=140"));
+ assertTrue(extents2.toString().endsWith("x=50 y=80 width=100 height=200"));
+ assertEquals(Status.SUCCESS, region1.status());
+ assertEquals(Status.SUCCESS, region2.status());
+ }
}
@Test
void testIntersectRectangleInt() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- RectangleInt rect = RectangleInt.create(50, 80, 100, 200);
- region.intersect(rect);
- assertTrue(region.getExtents().toString().endsWith("x=50 y=80 width=60 height=140"));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ RectangleInt rect = RectangleInt.create(arena, 50, 80, 100, 200);
+ region.intersect(rect);
+ RectangleInt extents = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getExtents(extents);
+ assertTrue(extents.toString().endsWith("x=50 y=80 width=60 height=140"));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testSubtractRegion() {
- Region region1 = Region.create(RectangleInt.create(10, 20, 100, 200));
- Region region2 = Region.create(RectangleInt.create(20, 20, 100, 200));
- region1.subtract(region2);
- assertTrue(region1.getExtents().toString().endsWith("x=10 y=20 width=10 height=200"));
- assertTrue(region2.getExtents().toString().endsWith("x=20 y=20 width=100 height=200"));
- assertEquals(Status.SUCCESS, region1.status());
- assertEquals(Status.SUCCESS, region2.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ Region region2 = Region.create(RectangleInt.create(arena, 20, 20, 100, 200));
+ region1.subtract(region2);
+
+ var extents1 = RectangleInt.create(arena, 0, 0, 0, 0);
+ var extents2 = RectangleInt.create(arena, 0, 0, 0, 0);
+ region1.getExtents(extents1);
+ region2.getExtents(extents2);
+
+ assertTrue(extents1.toString().endsWith("x=10 y=20 width=10 height=200"));
+ assertTrue(extents2.toString().endsWith("x=20 y=20 width=100 height=200"));
+ assertEquals(Status.SUCCESS, region1.status());
+ assertEquals(Status.SUCCESS, region2.status());
+ }
}
@Test
void testSubtractRectangleInt() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- RectangleInt rect = RectangleInt.create(20, 20, 100, 200);
- region.subtract(rect);
- assertTrue(region.getExtents().toString().endsWith("x=10 y=20 width=10 height=200"));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ RectangleInt rect = RectangleInt.create(arena, 20, 20, 100, 200);
+ region.subtract(rect);
+ RectangleInt extents = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getExtents(extents);
+ assertTrue(extents.toString().endsWith("x=10 y=20 width=10 height=200"));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testUnionRegion() {
- Region region1 = Region.create(RectangleInt.create(10, 20, 100, 200));
- Region region2 = Region.create(RectangleInt.create(20, 20, 100, 200));
- region1.union(region2);
- assertTrue(region1.getExtents().toString().endsWith("x=10 y=20 width=110 height=200"));
- assertTrue(region2.getExtents().toString().endsWith("x=20 y=20 width=100 height=200"));
- assertEquals(Status.SUCCESS, region1.status());
- assertEquals(Status.SUCCESS, region2.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ Region region2 = Region.create(RectangleInt.create(arena, 20, 20, 100, 200));
+ region1.union(region2);
+
+ var extents1 = RectangleInt.create(arena, 0, 0, 0, 0);
+ var extents2 = RectangleInt.create(arena, 0, 0, 0, 0);
+ region1.getExtents(extents1);
+ region2.getExtents(extents2);
+
+ assertTrue(extents1.toString().endsWith("x=10 y=20 width=110 height=200"));
+ assertTrue(extents2.toString().endsWith("x=20 y=20 width=100 height=200"));
+ assertEquals(Status.SUCCESS, region1.status());
+ assertEquals(Status.SUCCESS, region2.status());
+ }
}
@Test
void testUnionRectangleInt() {
- Region region = Region.create(RectangleInt.create(10, 20, 100, 200));
- RectangleInt rect = RectangleInt.create(20, 20, 100, 200);
- region.union(rect);
- assertTrue(region.getExtents().toString().endsWith("x=10 y=20 width=110 height=200"));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 10, 20, 100, 200));
+ RectangleInt rect = RectangleInt.create(arena, 20, 20, 100, 200);
+ region.union(rect);
+ RectangleInt extents = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getExtents(extents);
+ assertTrue(extents.toString().endsWith("x=10 y=20 width=110 height=200"));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
@Test
void testXorRegion() {
- Region region1 = Region.create(RectangleInt.create(0, 0, 50, 50));
- Region region2 = Region.create(RectangleInt.create(0, 0, 50, 20));
- region1.xor(region2);
- assertTrue(region1.getExtents().toString().endsWith("x=0 y=20 width=50 height=30"));
- assertTrue(region2.getExtents().toString().endsWith("x=0 y=0 width=50 height=20"));
- assertEquals(Status.SUCCESS, region1.status());
- assertEquals(Status.SUCCESS, region2.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region1 = Region.create(RectangleInt.create(arena, 0, 0, 50, 50));
+ Region region2 = Region.create(RectangleInt.create(arena, 0, 0, 50, 20));
+ region1.xor(region2);
+
+ var extents1 = RectangleInt.create(arena, 0, 0, 0, 0);
+ var extents2 = RectangleInt.create(arena, 0, 0, 0, 0);
+ region1.getExtents(extents1);
+ region2.getExtents(extents2);
+
+ assertTrue(extents1.toString().endsWith("x=0 y=20 width=50 height=30"));
+ assertTrue(extents2.toString().endsWith("x=0 y=0 width=50 height=20"));
+ assertEquals(Status.SUCCESS, region1.status());
+ assertEquals(Status.SUCCESS, region2.status());
+ }
}
@Test
void testXorRectangleInt() {
- Region region = Region.create(RectangleInt.create(0, 0, 50, 50));
- RectangleInt rect = RectangleInt.create(0, 0, 50, 20);
- region.xor(rect);
- assertTrue(region.getExtents().toString().endsWith("x=0 y=20 width=50 height=30"));
- assertEquals(Status.SUCCESS, region.status());
+ try (Arena arena = Arena.ofConfined()) {
+ Region region = Region.create(RectangleInt.create(arena, 0, 0, 50, 50));
+ RectangleInt rect = RectangleInt.create(arena, 0, 0, 50, 20);
+ region.xor(rect);
+ RectangleInt extents = RectangleInt.create(arena, 0, 0, 0, 0);
+ region.getExtents(extents);
+ assertTrue(extents.toString().endsWith("x=0 y=20 width=50 height=30"));
+ assertEquals(Status.SUCCESS, region.status());
+ }
}
-
}
diff --git a/src/test/java/org/freedesktop/cairo/test/ScaledFontTest.java b/src/test/java/org/freedesktop/cairo/test/ScaledFontTest.java
index b6ca767..227cf65 100644
--- a/src/test/java/org/freedesktop/cairo/test/ScaledFontTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/ScaledFontTest.java
@@ -7,42 +7,40 @@
import org.freedesktop.cairo.*;
import org.junit.jupiter.api.Test;
+import java.lang.foreign.Arena;
+
class ScaledFontTest {
- private ScaledFont create() {
+ private ScaledFont create(Arena arena) {
return ScaledFont.create(ToyFontFace.create("Arial", FontSlant.NORMAL, FontWeight.NORMAL),
- Matrix.createIdentity(), Matrix.createIdentity(), FontOptions.create());
+ Matrix.create(arena).initIdentity(), Matrix.create(arena).initIdentity(), FontOptions.create());
}
@Test
void testCreate() {
- ScaledFont sf = create();
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testExtents() {
- ScaledFont sf = create();
- FontExtents e = sf.extents();
- assertNotNull(e);
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ FontExtents e = FontExtents.create(arena);
+ sf.extents(e);
+ assertNotNull(e);
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testTextExtents() {
- ScaledFont sf = create();
- TextExtents extents = sf.textExtents("test");
- // I'm not sure if I can hard-code the expected height and width, so
- // we will only test if the extents are wider than they are high.
- assertTrue(extents.width() > extents.height());
- assertEquals(Status.SUCCESS, sf.status());
- }
-
- @Test
- void testGlyphExtents() {
- ScaledFont sf = create();
- try (Glyphs glyphs = sf.textToGlyphs(0, 0, "test")) {
- TextExtents extents = sf.glyphExtents(glyphs);
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ TextExtents extents = TextExtents.create(arena);
+ sf.textExtents("test", extents);
// I'm not sure if I can hard-code the expected height and width, so
// we will only test if the extents are wider than they are high.
assertTrue(extents.width() > extents.height());
@@ -50,61 +48,94 @@ void testGlyphExtents() {
}
}
+ @Test
+ void testGlyphExtents() {
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ try (Glyphs glyphs = sf.textToGlyphs(0, 0, "test")) {
+ TextExtents extents = TextExtents.create(arena);
+ sf.glyphExtents(glyphs, extents);
+ // I'm not sure if I can hard-code the expected height and width, so
+ // we will only test if the extents are wider than they are high.
+ assertTrue(extents.width() > extents.height());
+ assertEquals(Status.SUCCESS, sf.status());
+ }
+ }
+ }
+
@Test
void testTextToGlyphs() {
- ScaledFont sf = create();
- try (Glyphs glyphs = sf.textToGlyphs(0, 0, "test")) {
- assertEquals(4, glyphs.getNumGlyphs());
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ try (Glyphs glyphs = sf.textToGlyphs(0, 0, "test")) {
+ assertEquals(4, glyphs.getNumGlyphs());
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
}
@Test
void testGetFontFace() {
- ScaledFont sf = create();
- FontFace f = sf.getFontFace();
- assertEquals(FontType.TOY, f.getFontType());
- assertEquals(Status.SUCCESS, f.status());
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ FontFace f = sf.getFontFace();
+ assertEquals(FontType.TOY, f.getFontType());
+ assertEquals(Status.SUCCESS, f.status());
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testGetFontOptions() {
- ScaledFont sf = create();
- FontOptions options = FontOptions.create();
- sf.getFontOptions(options);
- assertEquals(Status.SUCCESS, options.status());
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ FontOptions options = FontOptions.create();
+ sf.getFontOptions(options);
+ assertEquals(Status.SUCCESS, options.status());
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testGetFontMatrix() {
- ScaledFont sf = create();
- Matrix m = sf.getFontMatrix();
- assertNotNull(m);
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ Matrix m = Matrix.create(arena);
+ sf.getFontMatrix(m);
+ assertNotNull(m);
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testGetCTM() {
- ScaledFont sf = create();
- Matrix ctm = sf.getCTM();
- assertNotNull(ctm);
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ Matrix ctm = Matrix.create(arena);
+ sf.getCTM(ctm);
+ assertNotNull(ctm);
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testGetScaleMatrix() {
- ScaledFont sf = create();
- Matrix m = sf.getScaleMatrix();
- assertNotNull(m);
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ Matrix m = Matrix.create(arena);
+ sf.getScaleMatrix(m);
+ assertNotNull(m);
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
@Test
void testGetFontType() {
- ScaledFont sf = create();
- FontType t = sf.getFontType();
- assertEquals(Status.SUCCESS, sf.status());
+ try (Arena arena = Arena.ofConfined()) {
+ ScaledFont sf = create(arena);
+ FontType t = sf.getFontType();
+ assertNotNull(t);
+ assertEquals(Status.SUCCESS, sf.status());
+ }
}
}
diff --git a/src/test/java/org/freedesktop/cairo/test/SurfaceTest.java b/src/test/java/org/freedesktop/cairo/test/SurfaceTest.java
index f1963ec..cc9f0be 100644
--- a/src/test/java/org/freedesktop/cairo/test/SurfaceTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/SurfaceTest.java
@@ -2,7 +2,9 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.lang.foreign.Arena;
+import io.github.jwharm.cairobindings.Interop;
import org.freedesktop.cairo.*;
import org.junit.jupiter.api.Test;
@@ -194,8 +196,9 @@ void testSupportsMimeType() {
@Test
void testMapToImage() {
- try (Surface s1 = ImageSurface.create(Format.ARGB32, 120, 120);
- ImageSurface s2 = s1.mapToImage(RectangleInt.create(0, 0, 120, 120))) {
+ try (Arena arena = Arena.ofConfined();
+ Surface s1 = ImageSurface.create(Format.ARGB32, 120, 120);
+ ImageSurface s2 = s1.mapToImage(RectangleInt.create(arena, 0, 0, 120, 120))) {
s1.unmapImage(s2);
assertEquals(Status.SUCCESS, s1.status());
assertEquals(Status.SUCCESS, s2.status());
@@ -204,10 +207,17 @@ void testMapToImage() {
@Test
void testUserData() {
- try (Surface s = ImageSurface.create(Format.ARGB32, 120, 120)) {
+ try (Arena arena = Arena.ofConfined();
+ Surface s = ImageSurface.create(Format.ARGB32, 120, 120)) {
String input = "test";
- UserDataKey key = s.setUserData(input);
- String output = (String) s.getUserData(key);
+ var segmentIn = Interop.allocateNativeString(input, arena);
+
+ UserDataKey key = UserDataKey.create(arena);
+ s.setUserData(key, segmentIn);
+
+ var segmentOut = s.getUserData(key).reinterpret(Integer.MAX_VALUE);
+ String output = segmentOut.getUtf8String(0);
+
assertEquals(input, output);
assertEquals(Status.SUCCESS, s.status());
}
diff --git a/src/test/java/org/freedesktop/cairo/test/ToyFontFaceTest.java b/src/test/java/org/freedesktop/cairo/test/ToyFontFaceTest.java
index 50c91b4..d70cd90 100644
--- a/src/test/java/org/freedesktop/cairo/test/ToyFontFaceTest.java
+++ b/src/test/java/org/freedesktop/cairo/test/ToyFontFaceTest.java
@@ -6,7 +6,6 @@
import org.freedesktop.cairo.FontWeight;
import org.freedesktop.cairo.Status;
import org.freedesktop.cairo.ToyFontFace;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class ToyFontFaceTest {