From d341c0d148ef949821cc97dafdf189e394b92795 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Fri, 25 Sep 2026 04:13:39 +0100 Subject: [PATCH 1/4] plutovg: add 2D vector graphics library Software rasteriser for paths, strokes, gradients and transforms onto a 32-bit premultiplied ARGB surface, which it can create over memory the caller already owns. Needed to draw a boot splash procedurally rather than from a baked frame sequence. Links against nothing but libc, and comes to 172 KiB on aarch64. The font face cache is disabled along with the examples and tests, since nothing here renders text. Signed-off-by: Daniel Golle --- libs/plutovg/Makefile | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 libs/plutovg/Makefile diff --git a/libs/plutovg/Makefile b/libs/plutovg/Makefile new file mode 100644 index 0000000..4d6cf33 --- /dev/null +++ b/libs/plutovg/Makefile @@ -0,0 +1,55 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=plutovg +PKG_VERSION:=1.3.3 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/sammycage/plutovg/tar.gz/v$(PKG_VERSION)? +PKG_HASH:=2b0d17a6e016f47b86f9c00e2cb82600041b1ea1f7d2a00c2d46ae542cbfed3c + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE + +PKG_INSTALL:=1 +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/meson.mk + +MESON_ARGS += \ + --wrap-mode=nofallback \ + -Dexamples=disabled \ + -Dtests=disabled \ + -Dfont-face-cache-load=disabled + +define Package/plutovg + SECTION:=libs + CATEGORY:=Libraries + TITLE:=2D vector graphics library + URL:=https://github.com/sammycage/plutovg +endef + +define Package/plutovg/description + Software rasteriser for 2D vector graphics: path filling, stroking and + dashing, solid, gradient and texture paints, clipping, compositing and + transformations, onto a 32-bit premultiplied ARGB surface that can wrap + memory the caller already owns. Depends on nothing but libc. +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(CP) $(PKG_INSTALL_DIR)/usr/include/plutovg $(1)/usr/include/ + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libplutovg.so* $(1)/usr/lib/ + $(INSTALL_DIR) $(1)/usr/lib/pkgconfig + $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/plutovg.pc $(1)/usr/lib/pkgconfig/ +endef + +define Package/plutovg/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libplutovg.so.* $(1)/usr/lib/ +endef + +$(eval $(call BuildPackage,plutovg)) From 21997828f7d74075aca6bf143eda66ba9f708c76 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Fri, 25 Sep 2026 04:13:44 +0100 Subject: [PATCH 2/4] plutosvg: add SVG rendering library Parses SVG and draws it through plutovg, which lets a theme ship its artwork as text rather than as rendered bitmaps. 33 KiB on aarch64, and it needs nothing beyond plutovg and libc. freetype is disabled: it only serves OpenType SVG glyphs, and pulling it in for that would outweigh the library several times over. The bundled plutovg wrap is refused so a failed dependency check cannot stage a second copy over the packaged one. Signed-off-by: Daniel Golle --- libs/plutosvg/Makefile | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 libs/plutosvg/Makefile diff --git a/libs/plutosvg/Makefile b/libs/plutosvg/Makefile new file mode 100644 index 0000000..7066bc1 --- /dev/null +++ b/libs/plutosvg/Makefile @@ -0,0 +1,57 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=plutosvg +PKG_VERSION:=0.0.8 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/sammycage/plutosvg/tar.gz/v$(PKG_VERSION)? +PKG_HASH:=49d5cfe772d3aa10cd4879f2f6e189f5083c08e4c8ea01bf3d5b87c97dfca7d2 + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE + +PKG_INSTALL:=1 +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/meson.mk + +# plutosvg ships subprojects/plutovg.wrap and declares it as a fallback, +# so a failed dependency check would quietly build and stage a second +# copy of plutovg over the packaged one. +MESON_ARGS += \ + --wrap-mode=nofallback \ + -Dexamples=disabled \ + -Dtests=disabled \ + -Dfreetype=disabled + +define Package/plutosvg + SECTION:=libs + CATEGORY:=Libraries + TITLE:=SVG rendering library + URL:=https://github.com/sammycage/plutosvg + DEPENDS:=+plutovg +endef + +define Package/plutosvg/description + Parses SVG documents and renders them through plutovg. Built without + the optional freetype support, which only serves OpenType SVG glyphs. +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(CP) $(PKG_INSTALL_DIR)/usr/include/plutosvg $(1)/usr/include/ + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libplutosvg.so* $(1)/usr/lib/ + $(INSTALL_DIR) $(1)/usr/lib/pkgconfig + $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/plutosvg.pc $(1)/usr/lib/pkgconfig/ +endef + +define Package/plutosvg/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/libplutosvg.so.* $(1)/usr/lib/ +endef + +$(eval $(call BuildPackage,plutosvg)) From 2c2ece45ce112d5dca425b86a61e5c7ff537e7e6 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Fri, 25 Sep 2026 08:55:55 +0100 Subject: [PATCH 3/4] ucode-mod-plutovg: add ucode binding for plutovg Make the plutovg drawing API usable from ucode. Surfaces, canvases, paths, paints and font faces become ucode resource types with method tables, while colours, points, rectangles, matrices and gradient stops cross the boundary as plain ucode objects or arrays, so a whole scene can be built and mutated from script. Enumerations are spelled as strings rather than opaque integers. Each resource holds one plutovg reference and drops it from the type destructor; getters that return a borrowed pointer take a reference before wrapping it, so a canvas keeps its surface alive and nothing is released twice at collection time. The value conversion helpers live in a separate header installed into the staging directory because the plutosvg binding needs the same colour and rectangle marshalling. Signed-off-by: Daniel Golle --- libs/ucode-mod-plutovg/Makefile | 48 + libs/ucode-mod-plutovg/src/LICENSE | 13 + libs/ucode-mod-plutovg/src/plutovg.c | 3311 +++++++++++++++++++++++ libs/ucode-mod-plutovg/src/uc-plutovg.h | 177 ++ 4 files changed, 3549 insertions(+) create mode 100644 libs/ucode-mod-plutovg/Makefile create mode 100644 libs/ucode-mod-plutovg/src/LICENSE create mode 100644 libs/ucode-mod-plutovg/src/plutovg.c create mode 100644 libs/ucode-mod-plutovg/src/uc-plutovg.h diff --git a/libs/ucode-mod-plutovg/Makefile b/libs/ucode-mod-plutovg/Makefile new file mode 100644 index 0000000..536c113 --- /dev/null +++ b/libs/ucode-mod-plutovg/Makefile @@ -0,0 +1,48 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ucode-mod-plutovg +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=ISC +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/ucode-mod-plutovg + SECTION:=libs + CATEGORY:=Libraries + SUBMENU:=ucode + TITLE:=ucode binding for the plutovg 2D vector graphics library + DEPENDS:=+libucode +plutovg +endef + +define Package/ucode-mod-plutovg/description + Exposes the plutovg drawing API to ucode scripts: surfaces, canvases, + paths, matrices, solid, gradient and texture paints, stroking with dash + patterns, clipping, compositing and text rendering. Colours, points, + rectangles, matrices and gradient stops are passed as plain ucode + objects or arrays. +endef + +define Build/Configure +endef + +define Build/Compile + $(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_CPPFLAGS) $(TARGET_LDFLAGS) \ + $(FPIC) -Wall -ffunction-sections -Wl,--gc-sections -shared \ + -o $(PKG_BUILD_DIR)/plutovg.so $(PKG_BUILD_DIR)/plutovg.c \ + -lplutovg +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/include + $(CP) $(PKG_BUILD_DIR)/uc-plutovg.h $(1)/usr/include/ +endef + +define Package/ucode-mod-plutovg/install + $(INSTALL_DIR) $(1)/usr/lib/ucode + $(CP) $(PKG_BUILD_DIR)/plutovg.so $(1)/usr/lib/ucode/ +endef + +$(eval $(call BuildPackage,ucode-mod-plutovg)) diff --git a/libs/ucode-mod-plutovg/src/LICENSE b/libs/ucode-mod-plutovg/src/LICENSE new file mode 100644 index 0000000..8fbfabd --- /dev/null +++ b/libs/ucode-mod-plutovg/src/LICENSE @@ -0,0 +1,13 @@ +Copyright (C) 2026 Daniel Golle + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/libs/ucode-mod-plutovg/src/plutovg.c b/libs/ucode-mod-plutovg/src/plutovg.c new file mode 100644 index 0000000..b28e12e --- /dev/null +++ b/libs/ucode-mod-plutovg/src/plutovg.c @@ -0,0 +1,3311 @@ +/* + * Copyright (C) 2026 Daniel Golle + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "uc-plutovg.h" + +static const char * const uc_pvg_spread_names[] = { + "pad", "reflect", "repeat", NULL +}; + +static const char * const uc_pvg_texture_names[] = { + "plain", "tiled", NULL +}; + +static const char * const uc_pvg_fill_rule_names[] = { + "nonzero", "evenodd", NULL +}; + +static const char * const uc_pvg_operator_names[] = { + "clear", "src", "dst", "src-over", "dst-over", "src-in", "dst-in", + "src-out", "dst-out", "src-atop", "dst-atop", "xor", NULL +}; + +static const char * const uc_pvg_line_cap_names[] = { + "butt", "round", "square", NULL +}; + +static const char * const uc_pvg_line_join_names[] = { + "miter", "round", "bevel", NULL +}; + +static const char * const uc_pvg_command_names[] = { + "move_to", "line_to", "cubic_to", "close", NULL +}; + +static bool +uc_pvg_args_num(uc_vm_t *vm, size_t nargs, size_t first, size_t count, + float *out) +{ + uc_value_t *arg; + size_t i; + + for (i = 0; i < count; i++) { + arg = _uc_fn_arg(vm, nargs, first + i); + + if (ucv_type(arg) != UC_INTEGER && ucv_type(arg) != UC_DOUBLE) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Argument %zu is not a number", first + i + 1); + + return false; + } + + out[i] = (float)ucv_to_double(arg); + } + + return true; +} + +static bool +uc_pvg_enum_get(uc_vm_t *vm, uc_value_t *uv, const char * const *names, + const char *what, int *out) +{ + const char *str; + int i; + + if (ucv_type(uv) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting %s name string", what); + + return false; + } + + str = ucv_string_get(uv); + + for (i = 0; names[i]; i++) { + if (strcmp(names[i], str)) + continue; + + *out = i; + + return true; + } + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Unknown %s '%s'", what, str); + + return false; +} + +static bool +uc_pvg_enum_opt(uc_vm_t *vm, uc_value_t *uv, const char * const *names, + const char *what, int *out) +{ + if (uv == NULL || ucv_type(uv) == UC_NULL) + return true; + + return uc_pvg_enum_get(vm, uv, names, what, out); +} + +static bool +uc_pvg_matrix_opt(uc_vm_t *vm, uc_value_t *uv, plutovg_matrix_t *storage, + const plutovg_matrix_t **out) +{ + *out = NULL; + + if (uv == NULL || ucv_type(uv) == UC_NULL) + return true; + + if (!uc_pvg_matrix_get(uv, storage)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Invalid matrix argument"); + + return false; + } + + *out = storage; + + return true; +} + +static bool +uc_pvg_matrix_arg(uc_vm_t *vm, uc_value_t *uv, plutovg_matrix_t *matrix) +{ + if (uc_pvg_matrix_get(uv, matrix)) + return true; + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid matrix argument"); + + return false; +} + +static plutovg_gradient_stop_t * +uc_pvg_stops_get(uc_vm_t *vm, uc_value_t *uv, int *nstops) +{ + plutovg_gradient_stop_t *stops; + uc_value_t *entry; + size_t count, i; + + *nstops = 0; + + if (ucv_type(uv) != UC_ARRAY) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting array of gradient stops"); + + return NULL; + } + + count = ucv_array_length(uv); + + if (count == 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Gradient stop array is empty"); + + return NULL; + } + + stops = calloc(count, sizeof(*stops)); + + if (!stops) + return NULL; + + for (i = 0; i < count; i++) { + entry = ucv_array_get(uv, i); + + if (!uc_pvg_component(entry, "offset", 0, &stops[i].offset) || + !uc_pvg_color_get(ucv_type(entry) == UC_OBJECT + ? ucv_object_get(entry, "color", NULL) + : ucv_array_get(entry, 1), + &stops[i].color)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Invalid gradient stop at index %zu", i); + free(stops); + + return NULL; + } + } + + *nstops = (int)count; + + return stops; +} + +static float * +uc_pvg_dashes_get(uc_vm_t *vm, uc_value_t *uv, int *ndashes) +{ + uc_value_t *entry; + size_t count, i; + float *dashes; + + *ndashes = 0; + + if (uv == NULL || ucv_type(uv) == UC_NULL) + return NULL; + + if (ucv_type(uv) != UC_ARRAY) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting array of dash lengths"); + + return NULL; + } + + count = ucv_array_length(uv); + + if (count == 0) + return NULL; + + dashes = calloc(count, sizeof(*dashes)); + + if (!dashes) + return NULL; + + for (i = 0; i < count; i++) { + entry = ucv_array_get(uv, i); + + if (ucv_type(entry) != UC_INTEGER && + ucv_type(entry) != UC_DOUBLE) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Invalid dash length at index %zu", i); + free(dashes); + + return NULL; + } + + dashes[i] = (float)ucv_to_double(entry); + } + + *ndashes = (int)count; + + return dashes; +} + +static void * +uc_pvg_arg_res(uc_vm_t *vm, uc_value_t *uv, const char *type, const char *what) +{ + void *ptr = ucv_resource_data(uv, type); + + if (!ptr) + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting %s object", what); + + return ptr; +} + +static void +uc_pvg_surface_free(void *ptr) +{ + plutovg_surface_destroy(ptr); +} + +static void +uc_pvg_canvas_free(void *ptr) +{ + plutovg_canvas_destroy(ptr); +} + +static void +uc_pvg_path_free(void *ptr) +{ + plutovg_path_destroy(ptr); +} + +static void +uc_pvg_paint_free(void *ptr) +{ + plutovg_paint_destroy(ptr); +} + +static void +uc_pvg_font_face_free(void *ptr) +{ + plutovg_font_face_destroy(ptr); +} + +static uc_value_t * +uc_pvg_surface_wrap(uc_vm_t *vm, plutovg_surface_t *surface) +{ + uc_value_t *rv; + + if (!surface) + return NULL; + + rv = ucv_resource_create(vm, UC_PVG_SURFACE, surface); + + if (!rv) + plutovg_surface_destroy(surface); + + return rv; +} + +static uc_value_t * +uc_pvg_path_wrap(uc_vm_t *vm, plutovg_path_t *path) +{ + uc_value_t *rv; + + if (!path) + return NULL; + + rv = ucv_resource_create(vm, UC_PVG_PATH, path); + + if (!rv) + plutovg_path_destroy(path); + + return rv; +} + +static uc_value_t * +uc_pvg_paint_wrap(uc_vm_t *vm, plutovg_paint_t *paint) +{ + uc_value_t *rv; + + if (!paint) + return NULL; + + rv = ucv_resource_create(vm, UC_PVG_PAINT, paint); + + if (!rv) + plutovg_paint_destroy(paint); + + return rv; +} + +static uc_value_t * +uc_pvg_font_face_wrap(uc_vm_t *vm, plutovg_font_face_t *face) +{ + uc_value_t *rv; + + if (!face) + return NULL; + + rv = ucv_resource_create(vm, UC_PVG_FONT_FACE, face); + + if (!rv) + plutovg_font_face_destroy(face); + + return rv; +} + +static uc_value_t * +uc_pvg_version(uc_vm_t *vm, size_t nargs) +{ + return ucv_int64_new(plutovg_version()); +} + +static uc_value_t * +uc_pvg_version_string(uc_vm_t *vm, size_t nargs) +{ + return ucv_string_new(plutovg_version_string()); +} + +static uc_value_t * +uc_pvg_deg2rad(uc_vm_t *vm, size_t nargs) +{ + float v[1]; + + if (!uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + return ucv_double_new(PLUTOVG_DEG2RAD(v[0])); +} + +static uc_value_t * +uc_pvg_rad2deg(uc_vm_t *vm, size_t nargs) +{ + float v[1]; + + if (!uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + return ucv_double_new(PLUTOVG_RAD2DEG(v[0])); +} + +static uc_value_t * +uc_pvg_color_parse(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + plutovg_color_t color; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting color string"); + + return NULL; + } + + if (plutovg_color_parse(&color, ucv_string_get(arg), + ucv_string_length(arg)) == 0) + return NULL; + + return uc_pvg_color_new(vm, &color); +} + +static uc_value_t * +uc_pvg_color_hsl(uc_vm_t *vm, size_t nargs) +{ + plutovg_color_t color; + float v[3]; + + if (!uc_pvg_args_num(vm, nargs, 0, 3, v)) + return NULL; + + plutovg_color_init_hsla(&color, v[0], v[1], v[2], + uc_pvg_number(uc_fn_arg(3), 1.0f)); + + return uc_pvg_color_new(vm, &color); +} + +static uc_value_t * +uc_pvg_color_rgba32(uc_vm_t *vm, size_t nargs) +{ + plutovg_color_t color; + + if (!uc_pvg_color_get(uc_fn_arg(0), &color)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid color value"); + + return NULL; + } + + return ucv_uint64_new(plutovg_color_to_rgba32(&color)); +} + +static uc_value_t * +uc_pvg_color_argb32(uc_vm_t *vm, size_t nargs) +{ + plutovg_color_t color; + + if (!uc_pvg_color_get(uc_fn_arg(0), &color)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid color value"); + + return NULL; + } + + return ucv_uint64_new(plutovg_color_to_argb32(&color)); +} + +static uc_value_t * +uc_pvg_matrix_identity(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + + plutovg_matrix_init_identity(&matrix); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_init_translate(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[2]; + + if (!uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_matrix_init_translate(&matrix, v[0], v[1]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_init_scale(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[2]; + + if (!uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_matrix_init_scale(&matrix, v[0], v[1]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_init_rotate(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[1]; + + if (!uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_matrix_init_rotate(&matrix, v[0]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_init_shear(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[2]; + + if (!uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_matrix_init_shear(&matrix, v[0], v[1]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_translate(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[2]; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 1, 2, v)) + return NULL; + + plutovg_matrix_translate(&matrix, v[0], v[1]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_scale(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[2]; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 1, 2, v)) + return NULL; + + plutovg_matrix_scale(&matrix, v[0], v[1]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_rotate(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[1]; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 1, 1, v)) + return NULL; + + plutovg_matrix_rotate(&matrix, v[0]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_shear(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + float v[2]; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 1, 2, v)) + return NULL; + + plutovg_matrix_shear(&matrix, v[0], v[1]); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_matrix_multiply(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t left, right, result; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &left)) + return NULL; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(1), &right)) + return NULL; + + plutovg_matrix_multiply(&result, &left, &right); + + return uc_pvg_matrix_new(vm, &result); +} + +static uc_value_t * +uc_pvg_matrix_invert(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix, inverse; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!plutovg_matrix_invert(&matrix, &inverse)) + return NULL; + + return uc_pvg_matrix_new(vm, &inverse); +} + +static uc_value_t * +uc_pvg_matrix_map(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + plutovg_point_t point; + float v[2]; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 1, 2, v)) + return NULL; + + plutovg_matrix_map(&matrix, v[0], v[1], &point.x, &point.y); + + return uc_pvg_point_new(vm, &point); +} + +static uc_value_t * +uc_pvg_matrix_map_point(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + plutovg_point_t point; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_point_get(uc_fn_arg(1), &point)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid point value"); + + return NULL; + } + + plutovg_matrix_map_point(&matrix, &point, &point); + + return uc_pvg_point_new(vm, &point); +} + +static uc_value_t * +uc_pvg_matrix_map_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_matrix_t matrix; + plutovg_rect_t rect; + + if (!uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + if (!uc_pvg_rect_get(uc_fn_arg(1), &rect)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Invalid rectangle value"); + + return NULL; + } + + plutovg_matrix_map_rect(&matrix, &rect, &rect); + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_pvg_matrix_parse(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + plutovg_matrix_t matrix; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting transform string"); + + return NULL; + } + + if (!plutovg_matrix_parse(&matrix, ucv_string_get(arg), + ucv_string_length(arg))) + return NULL; + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_surface_create(uc_vm_t *vm, size_t nargs) +{ + float v[2]; + + if (!uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + if (v[0] <= 0 || v[1] <= 0) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "Surface dimensions must be positive"); + + return NULL; + } + + return uc_pvg_surface_wrap(vm, plutovg_surface_create((int)v[0], + (int)v[1])); +} + +static uc_value_t * +uc_pvg_surface_from_file(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting file name"); + + return NULL; + } + + return uc_pvg_surface_wrap(vm, + plutovg_surface_load_from_image_file(ucv_string_get(arg))); +} + +static uc_value_t * +uc_pvg_surface_from_data(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting image data"); + + return NULL; + } + + return uc_pvg_surface_wrap(vm, + plutovg_surface_load_from_image_data(ucv_string_get(arg), + (int)ucv_string_length(arg))); +} + +static uc_value_t * +uc_pvg_surface_from_base64(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting base64 image data"); + + return NULL; + } + + return uc_pvg_surface_wrap(vm, + plutovg_surface_load_from_image_base64(ucv_string_get(arg), + (int)ucv_string_length(arg))); +} + +static uc_value_t * +uc_pvg_surface_width(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + + if (!surface) + return NULL; + + return ucv_int64_new(plutovg_surface_get_width(surface)); +} + +static uc_value_t * +uc_pvg_surface_height(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + + if (!surface) + return NULL; + + return ucv_int64_new(plutovg_surface_get_height(surface)); +} + +static uc_value_t * +uc_pvg_surface_stride(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + + if (!surface) + return NULL; + + return ucv_int64_new(plutovg_surface_get_stride(surface)); +} + +static uc_value_t * +uc_pvg_surface_clear(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + plutovg_color_t color = PLUTOVG_MAKE_COLOR(0, 0, 0, 0); + uc_value_t *arg = uc_fn_arg(0); + + if (!surface) + return NULL; + + if (arg && ucv_type(arg) != UC_NULL && !uc_pvg_color_get(arg, &color)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid color value"); + + return NULL; + } + + plutovg_surface_clear(surface, &color); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_surface_data(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + size_t len; + + if (!surface) + return NULL; + + len = (size_t)plutovg_surface_get_stride(surface) * + (size_t)plutovg_surface_get_height(surface); + + return ucv_string_new_length((char *)plutovg_surface_get_data(surface), + len); +} + +static uc_value_t * +uc_pvg_surface_rgba(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + unsigned char *buf; + uc_value_t *rv; + size_t len; + int stride; + + if (!surface) + return NULL; + + stride = plutovg_surface_get_stride(surface); + len = (size_t)stride * (size_t)plutovg_surface_get_height(surface); + buf = malloc(len); + + if (!buf) + return NULL; + + plutovg_convert_argb_to_rgba(buf, plutovg_surface_get_data(surface), + plutovg_surface_get_width(surface), + plutovg_surface_get_height(surface), stride); + + rv = ucv_string_new_length((char *)buf, len); + free(buf); + + return rv; +} + +static uc_value_t * +uc_pvg_surface_write_png(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + uc_value_t *arg = uc_fn_arg(0); + + if (!surface) + return NULL; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting file name"); + + return NULL; + } + + return ucv_boolean_new(plutovg_surface_write_to_png(surface, + ucv_string_get(arg))); +} + +static uc_value_t * +uc_pvg_surface_write_jpg(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + uc_value_t *arg = uc_fn_arg(0); + + if (!surface) + return NULL; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting file name"); + + return NULL; + } + + return ucv_boolean_new(plutovg_surface_write_to_jpg(surface, + ucv_string_get(arg), + (int)uc_pvg_number(uc_fn_arg(1), 90.0f))); +} + +static void +uc_pvg_stream_write(void *closure, void *data, int size) +{ + ucv_stringbuf_addstr((uc_stringbuf_t *)closure, (const char *)data, + (size_t)size); +} + +static uc_value_t * +uc_pvg_surface_png(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + uc_stringbuf_t *buf; + + if (!surface) + return NULL; + + buf = ucv_stringbuf_new(); + + if (!plutovg_surface_write_to_png_stream(surface, uc_pvg_stream_write, + buf)) { + printbuf_free(buf); + + return NULL; + } + + return ucv_stringbuf_finish(buf); +} + +static uc_value_t * +uc_pvg_surface_jpg(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface = uc_fn_thisval(UC_PVG_SURFACE); + uc_stringbuf_t *buf; + int quality; + + if (!surface) + return NULL; + + quality = (int)uc_pvg_number(uc_fn_arg(0), 90.0f); + buf = ucv_stringbuf_new(); + + if (!plutovg_surface_write_to_jpg_stream(surface, uc_pvg_stream_write, + buf, quality)) { + printbuf_free(buf); + + return NULL; + } + + return ucv_stringbuf_finish(buf); +} + +static uc_value_t * +uc_pvg_path_create(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + plutovg_path_t *path; + + path = plutovg_path_create(); + + if (!path) + return NULL; + + if (ucv_type(arg) == UC_STRING && + !plutovg_path_parse(path, ucv_string_get(arg), + ucv_string_length(arg))) { + plutovg_path_destroy(path); + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "Unable to parse path data"); + + return NULL; + } + + return uc_pvg_path_wrap(vm, path); +} + +static uc_value_t * +uc_pvg_path_move_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[2]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_path_move_to(path, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_line_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[2]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_path_line_to(path, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_quad_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[4]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_path_quad_to(path, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_cubic_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[6]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 6, v)) + return NULL; + + plutovg_path_cubic_to(path, v[0], v[1], v[2], v[3], v[4], v[5]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_arc_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[3], e[2]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 3, v)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 5, 2, e)) + return NULL; + + plutovg_path_arc_to(path, v[0], v[1], v[2], + ucv_is_truish(uc_fn_arg(3)), ucv_is_truish(uc_fn_arg(4)), + e[0], e[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_close(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + + if (!path) + return NULL; + + plutovg_path_close(path); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_reset(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + + if (!path) + return NULL; + + plutovg_path_reset(path); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_reserve(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[1]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_path_reserve(path, (int)v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_current_point(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + plutovg_point_t point; + + if (!path) + return NULL; + + plutovg_path_get_current_point(path, &point.x, &point.y); + + return uc_pvg_point_new(vm, &point); +} + +static uc_value_t * +uc_pvg_path_add_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[4]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_path_add_rect(path, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_add_round_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[6]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 6, v)) + return NULL; + + plutovg_path_add_round_rect(path, v[0], v[1], v[2], v[3], v[4], v[5]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_add_ellipse(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[4]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_path_add_ellipse(path, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_add_circle(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[3]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 3, v)) + return NULL; + + plutovg_path_add_circle(path, v[0], v[1], v[2]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_add_arc(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + float v[5]; + + if (!path || !uc_pvg_args_num(vm, nargs, 0, 5, v)) + return NULL; + + plutovg_path_add_arc(path, v[0], v[1], v[2], v[3], v[4], + ucv_is_truish(uc_fn_arg(5))); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_add_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + const plutovg_matrix_t *mptr; + plutovg_matrix_t matrix; + plutovg_path_t *source; + + if (!path) + return NULL; + + source = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_PATH, "path"); + + if (!source) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(1), &matrix, &mptr)) + return NULL; + + plutovg_path_add_path(path, source, mptr); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_transform(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + plutovg_matrix_t matrix; + + if (!path || !uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + plutovg_path_transform(path, &matrix); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_path_clone(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + + if (!path) + return NULL; + + return uc_pvg_path_wrap(vm, plutovg_path_clone(path)); +} + +static uc_value_t * +uc_pvg_path_clone_flatten(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + + if (!path) + return NULL; + + return uc_pvg_path_wrap(vm, plutovg_path_clone_flatten(path)); +} + +static uc_value_t * +uc_pvg_path_clone_dashed(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + uc_value_t *rv; + float *dashes; + int ndashes; + + if (!path) + return NULL; + + dashes = uc_pvg_dashes_get(vm, uc_fn_arg(1), &ndashes); + + if (vm->exception.type != EXCEPTION_NONE) + return NULL; + + rv = uc_pvg_path_wrap(vm, plutovg_path_clone_dashed(path, + uc_pvg_number(uc_fn_arg(0), 0.0f), dashes, ndashes)); + free(dashes); + + return rv; +} + +static uc_value_t * +uc_pvg_path_extents(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + plutovg_rect_t rect; + + if (!path) + return NULL; + + plutovg_path_extents(path, &rect, ucv_is_truish(uc_fn_arg(0))); + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_pvg_path_length(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + + if (!path) + return NULL; + + return ucv_double_new(plutovg_path_length(path)); +} + +static uc_value_t * +uc_pvg_path_parse(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + uc_value_t *arg = uc_fn_arg(0); + + if (!path) + return NULL; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting path data"); + + return NULL; + } + + return ucv_boolean_new(plutovg_path_parse(path, ucv_string_get(arg), + ucv_string_length(arg))); +} + +static uc_value_t * +uc_pvg_path_elements(uc_vm_t *vm, size_t nargs) +{ + plutovg_path_t *path = uc_fn_thisval(UC_PVG_PATH); + plutovg_path_iterator_t it; + plutovg_point_t points[3]; + uc_value_t *rv, *entry; + int command, npoints; + uc_value_t *coords; + int i; + + if (!path) + return NULL; + + plutovg_path_iterator_init(&it, path); + + rv = ucv_array_new(vm); + + while (plutovg_path_iterator_has_next(&it)) { + command = plutovg_path_iterator_next(&it, points); + npoints = (command == PLUTOVG_PATH_COMMAND_CUBIC_TO) ? 3 : 1; + coords = ucv_array_new(vm); + + for (i = 0; i < npoints; i++) + ucv_array_push(coords, uc_pvg_point_new(vm, &points[i])); + + entry = ucv_object_new(vm); + ucv_object_add(entry, "command", + ucv_string_new(uc_pvg_command_names[command])); + ucv_object_add(entry, "points", coords); + ucv_array_push(rv, entry); + } + + return rv; +} + +static uc_value_t * +uc_pvg_paint_color(uc_vm_t *vm, size_t nargs) +{ + plutovg_color_t color; + + if (!uc_pvg_color_get(uc_fn_arg(0), &color)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid color value"); + + return NULL; + } + + return uc_pvg_paint_wrap(vm, plutovg_paint_create_color(&color)); +} + +static uc_value_t * +uc_pvg_paint_linear_gradient(uc_vm_t *vm, size_t nargs) +{ + plutovg_gradient_stop_t *stops; + const plutovg_matrix_t *mptr; + plutovg_matrix_t matrix; + int spread, nstops; + uc_value_t *rv; + float v[4]; + + spread = PLUTOVG_SPREAD_METHOD_PAD; + + if (!uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + if (!uc_pvg_enum_opt(vm, uc_fn_arg(4), uc_pvg_spread_names, + "spread method", &spread)) + return NULL; + + stops = uc_pvg_stops_get(vm, uc_fn_arg(5), &nstops); + + if (!stops) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(6), &matrix, &mptr)) { + free(stops); + + return NULL; + } + + rv = uc_pvg_paint_wrap(vm, plutovg_paint_create_linear_gradient(v[0], + v[1], v[2], v[3], spread, stops, nstops, mptr)); + free(stops); + + return rv; +} + +static uc_value_t * +uc_pvg_paint_radial_gradient(uc_vm_t *vm, size_t nargs) +{ + plutovg_gradient_stop_t *stops; + const plutovg_matrix_t *mptr; + plutovg_matrix_t matrix; + int spread, nstops; + uc_value_t *rv; + float v[6]; + + spread = PLUTOVG_SPREAD_METHOD_PAD; + + if (!uc_pvg_args_num(vm, nargs, 0, 6, v)) + return NULL; + + if (!uc_pvg_enum_opt(vm, uc_fn_arg(6), uc_pvg_spread_names, + "spread method", &spread)) + return NULL; + + stops = uc_pvg_stops_get(vm, uc_fn_arg(7), &nstops); + + if (!stops) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(8), &matrix, &mptr)) { + free(stops); + + return NULL; + } + + rv = uc_pvg_paint_wrap(vm, plutovg_paint_create_radial_gradient(v[0], + v[1], v[2], v[3], v[4], v[5], spread, stops, nstops, mptr)); + free(stops); + + return rv; +} + +static uc_value_t * +uc_pvg_paint_texture(uc_vm_t *vm, size_t nargs) +{ + const plutovg_matrix_t *mptr; + plutovg_surface_t *surface; + plutovg_matrix_t matrix; + int type; + + type = PLUTOVG_TEXTURE_TYPE_TILED; + surface = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_SURFACE, "surface"); + + if (!surface) + return NULL; + + if (!uc_pvg_enum_opt(vm, uc_fn_arg(1), uc_pvg_texture_names, + "texture type", &type)) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(3), &matrix, &mptr)) + return NULL; + + return uc_pvg_paint_wrap(vm, plutovg_paint_create_texture(surface, type, + uc_pvg_number(uc_fn_arg(2), 1.0f), mptr)); +} + +static uc_value_t * +uc_pvg_font_face_from_file(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting file name"); + + return NULL; + } + + return uc_pvg_font_face_wrap(vm, + plutovg_font_face_load_from_file(ucv_string_get(arg), + (int)uc_pvg_number(uc_fn_arg(1), 0.0f))); +} + +static uc_value_t * +uc_pvg_font_face_from_data(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + plutovg_font_face_t *face; + size_t len; + char *copy; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting font data"); + + return NULL; + } + + len = ucv_string_length(arg); + copy = malloc(len); + + if (!copy) + return NULL; + + memcpy(copy, ucv_string_get(arg), len); + + face = plutovg_font_face_load_from_data(copy, (unsigned int)len, + (int)uc_pvg_number(uc_fn_arg(1), 0.0f), free, copy); + + if (!face) { + free(copy); + + return NULL; + } + + return uc_pvg_font_face_wrap(vm, face); +} + +static uc_value_t * +uc_pvg_font_face_metrics(uc_vm_t *vm, size_t nargs) +{ + plutovg_font_face_t *face = uc_fn_thisval(UC_PVG_FONT_FACE); + float ascent, descent, line_gap; + plutovg_rect_t extents; + uc_value_t *rv; + float v[1]; + + if (!face || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_font_face_get_metrics(face, v[0], &ascent, &descent, &line_gap, + &extents); + + rv = ucv_object_new(vm); + ucv_object_add(rv, "ascent", ucv_double_new(ascent)); + ucv_object_add(rv, "descent", ucv_double_new(descent)); + ucv_object_add(rv, "line_gap", ucv_double_new(line_gap)); + ucv_object_add(rv, "extents", uc_pvg_rect_new(vm, &extents)); + + return rv; +} + +static uc_value_t * +uc_pvg_font_face_glyph_metrics(uc_vm_t *vm, size_t nargs) +{ + plutovg_font_face_t *face = uc_fn_thisval(UC_PVG_FONT_FACE); + float advance_width, left_side_bearing; + plutovg_rect_t extents; + uc_value_t *rv; + float v[2]; + + if (!face || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_font_face_get_glyph_metrics(face, v[0], + (plutovg_codepoint_t)v[1], &advance_width, &left_side_bearing, + &extents); + + rv = ucv_object_new(vm); + ucv_object_add(rv, "advance_width", ucv_double_new(advance_width)); + ucv_object_add(rv, "left_side_bearing", + ucv_double_new(left_side_bearing)); + ucv_object_add(rv, "extents", uc_pvg_rect_new(vm, &extents)); + + return rv; +} + +static uc_value_t * +uc_pvg_font_face_glyph_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_font_face_t *face = uc_fn_thisval(UC_PVG_FONT_FACE); + plutovg_path_t *path; + float v[4]; + + if (!face || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + path = uc_pvg_arg_res(vm, uc_fn_arg(4), UC_PVG_PATH, "path"); + + if (!path) + return NULL; + + return ucv_double_new(plutovg_font_face_get_glyph_path(face, v[0], v[1], + v[2], (plutovg_codepoint_t)v[3], path)); +} + +static uc_value_t * +uc_pvg_font_face_text_extents(uc_vm_t *vm, size_t nargs) +{ + plutovg_font_face_t *face = uc_fn_thisval(UC_PVG_FONT_FACE); + uc_value_t *text = uc_fn_arg(1); + plutovg_rect_t extents; + uc_value_t *rv; + float advance; + float v[1]; + + if (!face || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + if (ucv_type(text) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting text string"); + + return NULL; + } + + advance = plutovg_font_face_text_extents(face, v[0], + ucv_string_get(text), (int)ucv_string_length(text), + PLUTOVG_TEXT_ENCODING_UTF8, &extents); + + rv = ucv_object_new(vm); + ucv_object_add(rv, "advance_width", ucv_double_new(advance)); + ucv_object_add(rv, "extents", uc_pvg_rect_new(vm, &extents)); + + return rv; +} + +static uc_value_t * +uc_pvg_canvas_create(uc_vm_t *vm, size_t nargs) +{ + plutovg_surface_t *surface; + plutovg_canvas_t *canvas; + uc_value_t *rv; + + surface = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_SURFACE, "surface"); + + if (!surface) + return NULL; + + canvas = plutovg_canvas_create(surface); + + if (!canvas) + return NULL; + + rv = ucv_resource_create(vm, UC_PVG_CANVAS, canvas); + + if (!rv) + plutovg_canvas_destroy(canvas); + + return rv; +} + +static uc_value_t * +uc_pvg_canvas_surface(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return uc_pvg_surface_wrap(vm, plutovg_surface_reference( + plutovg_canvas_get_surface(canvas))); +} + +static uc_value_t * +uc_pvg_canvas_save(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_save(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_restore(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_restore(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_color(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_color_t color; + + if (!canvas) + return NULL; + + if (!uc_pvg_color_get(uc_fn_arg(0), &color)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid color value"); + + return NULL; + } + + plutovg_canvas_set_color(canvas, &color); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_color(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_color_t color; + + if (!canvas) + return NULL; + + plutovg_canvas_get_paint(canvas, &color); + + return uc_pvg_color_new(vm, &color); +} + +static uc_value_t * +uc_pvg_canvas_get_paint(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_paint_t *paint; + + if (!canvas) + return NULL; + + paint = plutovg_canvas_get_paint(canvas, NULL); + + if (!paint) + return NULL; + + return uc_pvg_paint_wrap(vm, plutovg_paint_reference(paint)); +} + +static uc_value_t * +uc_pvg_canvas_set_paint(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_paint_t *paint; + + if (!canvas) + return NULL; + + paint = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_PAINT, "paint"); + + if (!paint) + return NULL; + + plutovg_canvas_set_paint(canvas, paint); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_linear_gradient(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_gradient_stop_t *stops; + const plutovg_matrix_t *mptr; + plutovg_matrix_t matrix; + int spread, nstops; + float v[4]; + + spread = PLUTOVG_SPREAD_METHOD_PAD; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + if (!uc_pvg_enum_opt(vm, uc_fn_arg(4), uc_pvg_spread_names, + "spread method", &spread)) + return NULL; + + stops = uc_pvg_stops_get(vm, uc_fn_arg(5), &nstops); + + if (!stops) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(6), &matrix, &mptr)) { + free(stops); + + return NULL; + } + + plutovg_canvas_set_linear_gradient(canvas, v[0], v[1], v[2], v[3], + spread, stops, nstops, mptr); + free(stops); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_radial_gradient(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_gradient_stop_t *stops; + const plutovg_matrix_t *mptr; + plutovg_matrix_t matrix; + int spread, nstops; + float v[6]; + + spread = PLUTOVG_SPREAD_METHOD_PAD; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 6, v)) + return NULL; + + if (!uc_pvg_enum_opt(vm, uc_fn_arg(6), uc_pvg_spread_names, + "spread method", &spread)) + return NULL; + + stops = uc_pvg_stops_get(vm, uc_fn_arg(7), &nstops); + + if (!stops) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(8), &matrix, &mptr)) { + free(stops); + + return NULL; + } + + plutovg_canvas_set_radial_gradient(canvas, v[0], v[1], v[2], v[3], v[4], + v[5], spread, stops, nstops, mptr); + free(stops); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_texture(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + const plutovg_matrix_t *mptr; + plutovg_surface_t *surface; + plutovg_matrix_t matrix; + int type; + + type = PLUTOVG_TEXTURE_TYPE_TILED; + + if (!canvas) + return NULL; + + surface = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_SURFACE, "surface"); + + if (!surface) + return NULL; + + if (!uc_pvg_enum_opt(vm, uc_fn_arg(1), uc_pvg_texture_names, + "texture type", &type)) + return NULL; + + if (!uc_pvg_matrix_opt(vm, uc_fn_arg(3), &matrix, &mptr)) + return NULL; + + plutovg_canvas_set_texture(canvas, surface, type, + uc_pvg_number(uc_fn_arg(2), 1.0f), mptr); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_font(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_font_face_t *face; + float v[1]; + + if (!canvas) + return NULL; + + face = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_FONT_FACE, "font face"); + + if (!face || !uc_pvg_args_num(vm, nargs, 1, 1, v)) + return NULL; + + plutovg_canvas_set_font(canvas, face, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_font_face(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_font_face_t *face; + + if (!canvas) + return NULL; + + face = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_FONT_FACE, "font face"); + + if (!face) + return NULL; + + plutovg_canvas_set_font_face(canvas, face); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_font_face(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_font_face_t *face; + + if (!canvas) + return NULL; + + face = plutovg_canvas_get_font_face(canvas); + + if (!face) + return NULL; + + return uc_pvg_font_face_wrap(vm, plutovg_font_face_reference(face)); +} + +static uc_value_t * +uc_pvg_canvas_set_font_size(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_set_font_size(canvas, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_font_size(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_double_new(plutovg_canvas_get_font_size(canvas)); +} + +static uc_value_t * +uc_pvg_canvas_add_font_face(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + uc_value_t *family = uc_fn_arg(0); + plutovg_font_face_t *face; + + if (!canvas) + return NULL; + + if (ucv_type(family) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting font family name"); + + return NULL; + } + + face = uc_pvg_arg_res(vm, uc_fn_arg(3), UC_PVG_FONT_FACE, "font face"); + + if (!face) + return NULL; + + plutovg_canvas_add_font_face(canvas, ucv_string_get(family), + ucv_is_truish(uc_fn_arg(1)), ucv_is_truish(uc_fn_arg(2)), face); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_add_font_file(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + uc_value_t *family = uc_fn_arg(0); + uc_value_t *file = uc_fn_arg(3); + + if (!canvas) + return NULL; + + if (ucv_type(family) != UC_STRING || ucv_type(file) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting font family name and file name"); + + return NULL; + } + + return ucv_boolean_new(plutovg_canvas_add_font_file(canvas, + ucv_string_get(family), ucv_is_truish(uc_fn_arg(1)), + ucv_is_truish(uc_fn_arg(2)), ucv_string_get(file), + (int)uc_pvg_number(uc_fn_arg(4), 0.0f))); +} + +static uc_value_t * +uc_pvg_canvas_select_font_face(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + uc_value_t *family = uc_fn_arg(0); + + if (!canvas) + return NULL; + + if (ucv_type(family) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting font family name"); + + return NULL; + } + + return ucv_boolean_new(plutovg_canvas_select_font_face(canvas, + ucv_string_get(family), ucv_is_truish(uc_fn_arg(1)), + ucv_is_truish(uc_fn_arg(2)))); +} + +static uc_value_t * +uc_pvg_canvas_set_fill_rule(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + int rule; + + if (!canvas) + return NULL; + + if (!uc_pvg_enum_get(vm, uc_fn_arg(0), uc_pvg_fill_rule_names, + "fill rule", &rule)) + return NULL; + + plutovg_canvas_set_fill_rule(canvas, rule); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_fill_rule(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_string_new( + uc_pvg_fill_rule_names[plutovg_canvas_get_fill_rule(canvas)]); +} + +static uc_value_t * +uc_pvg_canvas_set_operator(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + int op; + + if (!canvas) + return NULL; + + if (!uc_pvg_enum_get(vm, uc_fn_arg(0), uc_pvg_operator_names, + "operator", &op)) + return NULL; + + plutovg_canvas_set_operator(canvas, op); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_operator(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_string_new( + uc_pvg_operator_names[plutovg_canvas_get_operator(canvas)]); +} + +static uc_value_t * +uc_pvg_canvas_set_opacity(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_set_opacity(canvas, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_opacity(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_double_new(plutovg_canvas_get_opacity(canvas)); +} + +static uc_value_t * +uc_pvg_canvas_set_line_width(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_set_line_width(canvas, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_line_width(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_double_new(plutovg_canvas_get_line_width(canvas)); +} + +static uc_value_t * +uc_pvg_canvas_set_line_cap(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + int cap; + + if (!canvas) + return NULL; + + if (!uc_pvg_enum_get(vm, uc_fn_arg(0), uc_pvg_line_cap_names, + "line cap", &cap)) + return NULL; + + plutovg_canvas_set_line_cap(canvas, cap); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_line_cap(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_string_new( + uc_pvg_line_cap_names[plutovg_canvas_get_line_cap(canvas)]); +} + +static uc_value_t * +uc_pvg_canvas_set_line_join(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + int join; + + if (!canvas) + return NULL; + + if (!uc_pvg_enum_get(vm, uc_fn_arg(0), uc_pvg_line_join_names, + "line join", &join)) + return NULL; + + plutovg_canvas_set_line_join(canvas, join); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_line_join(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_string_new( + uc_pvg_line_join_names[plutovg_canvas_get_line_join(canvas)]); +} + +static uc_value_t * +uc_pvg_canvas_set_miter_limit(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_set_miter_limit(canvas, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_miter_limit(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_double_new(plutovg_canvas_get_miter_limit(canvas)); +} + +static uc_value_t * +uc_pvg_canvas_set_dash(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float *dashes; + int ndashes; + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + dashes = uc_pvg_dashes_get(vm, uc_fn_arg(1), &ndashes); + + if (vm->exception.type != EXCEPTION_NONE) + return NULL; + + plutovg_canvas_set_dash(canvas, v[0], dashes, ndashes); + free(dashes); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_dash_offset(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_set_dash_offset(canvas, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_dash_offset(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + return ucv_double_new(plutovg_canvas_get_dash_offset(canvas)); +} + +static uc_value_t * +uc_pvg_canvas_set_dash_array(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float *dashes; + int ndashes; + + if (!canvas) + return NULL; + + dashes = uc_pvg_dashes_get(vm, uc_fn_arg(0), &ndashes); + + if (vm->exception.type != EXCEPTION_NONE) + return NULL; + + plutovg_canvas_set_dash_array(canvas, dashes, ndashes); + free(dashes); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_dash_array(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + const float *dashes; + uc_value_t *rv; + int count, i; + + if (!canvas) + return NULL; + + count = plutovg_canvas_get_dash_array(canvas, &dashes); + rv = ucv_array_new(vm); + + for (i = 0; i < count; i++) + ucv_array_push(rv, ucv_double_new(dashes[i])); + + return rv; +} + +static uc_value_t * +uc_pvg_canvas_translate(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_canvas_translate(canvas, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_scale(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_canvas_scale(canvas, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_shear(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_canvas_shear(canvas, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_rotate(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_rotate(canvas, v[0]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_transform(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_matrix_t matrix; + + if (!canvas || !uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + plutovg_canvas_transform(canvas, &matrix); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_reset_matrix(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_reset_matrix(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_set_matrix(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_matrix_t matrix; + + if (!canvas || !uc_pvg_matrix_arg(vm, uc_fn_arg(0), &matrix)) + return NULL; + + plutovg_canvas_set_matrix(canvas, &matrix); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_matrix(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_matrix_t matrix; + + if (!canvas) + return NULL; + + plutovg_canvas_get_matrix(canvas, &matrix); + + return uc_pvg_matrix_new(vm, &matrix); +} + +static uc_value_t * +uc_pvg_canvas_map(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_point_t point; + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_canvas_map(canvas, v[0], v[1], &point.x, &point.y); + + return uc_pvg_point_new(vm, &point); +} + +static uc_value_t * +uc_pvg_canvas_map_point(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_point_t point; + + if (!canvas) + return NULL; + + if (!uc_pvg_point_get(uc_fn_arg(0), &point)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid point value"); + + return NULL; + } + + plutovg_canvas_map_point(canvas, &point, &point); + + return uc_pvg_point_new(vm, &point); +} + +static uc_value_t * +uc_pvg_canvas_map_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_rect_t rect; + + if (!canvas) + return NULL; + + if (!uc_pvg_rect_get(uc_fn_arg(0), &rect)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Invalid rectangle value"); + + return NULL; + } + + plutovg_canvas_map_rect(canvas, &rect, &rect); + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_pvg_canvas_move_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_canvas_move_to(canvas, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_line_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + plutovg_canvas_line_to(canvas, v[0], v[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_quad_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[4]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_canvas_quad_to(canvas, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_cubic_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[6]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 6, v)) + return NULL; + + plutovg_canvas_cubic_to(canvas, v[0], v[1], v[2], v[3], v[4], v[5]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_arc_to(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[3], e[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 3, v)) + return NULL; + + if (!uc_pvg_args_num(vm, nargs, 5, 2, e)) + return NULL; + + plutovg_canvas_arc_to(canvas, v[0], v[1], v[2], + ucv_is_truish(uc_fn_arg(3)), ucv_is_truish(uc_fn_arg(4)), + e[0], e[1]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[4]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_canvas_rect(canvas, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_round_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[6]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 6, v)) + return NULL; + + plutovg_canvas_round_rect(canvas, v[0], v[1], v[2], v[3], v[4], v[5]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_ellipse(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[4]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_canvas_ellipse(canvas, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_circle(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[3]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 3, v)) + return NULL; + + plutovg_canvas_circle(canvas, v[0], v[1], v[2]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_arc(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[5]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 5, v)) + return NULL; + + plutovg_canvas_arc(canvas, v[0], v[1], v[2], v[3], v[4], + ucv_is_truish(uc_fn_arg(5))); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_add_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_path_t *path; + + if (!canvas) + return NULL; + + path = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_PATH, "path"); + + if (!path) + return NULL; + + plutovg_canvas_add_path(canvas, path); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_new_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_new_path(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_close_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_close_path(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_current_point(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_point_t point; + + if (!canvas) + return NULL; + + plutovg_canvas_get_current_point(canvas, &point.x, &point.y); + + return uc_pvg_point_new(vm, &point); +} + +static uc_value_t * +uc_pvg_canvas_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_path_t *path; + + if (!canvas) + return NULL; + + path = plutovg_canvas_get_path(canvas); + + if (!path) + return NULL; + + return uc_pvg_path_wrap(vm, plutovg_path_reference(path)); +} + +static uc_value_t * +uc_pvg_canvas_fill_contains(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + return ucv_boolean_new(plutovg_canvas_fill_contains(canvas, v[0], v[1])); +} + +static uc_value_t * +uc_pvg_canvas_stroke_contains(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + return ucv_boolean_new(plutovg_canvas_stroke_contains(canvas, v[0], + v[1])); +} + +static uc_value_t * +uc_pvg_canvas_clip_contains(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[2]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 2, v)) + return NULL; + + return ucv_boolean_new(plutovg_canvas_clip_contains(canvas, v[0], v[1])); +} + +static uc_value_t * +uc_pvg_canvas_fill_extents(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_rect_t rect; + + if (!canvas) + return NULL; + + plutovg_canvas_fill_extents(canvas, &rect); + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_pvg_canvas_stroke_extents(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_rect_t rect; + + if (!canvas) + return NULL; + + plutovg_canvas_stroke_extents(canvas, &rect); + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_pvg_canvas_clip_extents(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_rect_t rect; + + if (!canvas) + return NULL; + + plutovg_canvas_clip_extents(canvas, &rect); + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_pvg_canvas_fill(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_fill(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_stroke(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_stroke(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_clip(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_clip(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_paint(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_paint(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_fill_preserve(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_fill_preserve(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_stroke_preserve(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_stroke_preserve(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_clip_preserve(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + + if (!canvas) + return NULL; + + plutovg_canvas_clip_preserve(canvas); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_fill_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[4]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_canvas_fill_rect(canvas, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_stroke_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[4]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_canvas_stroke_rect(canvas, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_clip_rect(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[4]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 4, v)) + return NULL; + + plutovg_canvas_clip_rect(canvas, v[0], v[1], v[2], v[3]); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_fill_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_path_t *path; + + if (!canvas) + return NULL; + + path = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_PATH, "path"); + + if (!path) + return NULL; + + plutovg_canvas_fill_path(canvas, path); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_stroke_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_path_t *path; + + if (!canvas) + return NULL; + + path = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_PATH, "path"); + + if (!path) + return NULL; + + plutovg_canvas_stroke_path(canvas, path); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_clip_path(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + plutovg_path_t *path; + + if (!canvas) + return NULL; + + path = uc_pvg_arg_res(vm, uc_fn_arg(0), UC_PVG_PATH, "path"); + + if (!path) + return NULL; + + plutovg_canvas_clip_path(canvas, path); + + return ucv_boolean_new(true); +} + +static uc_value_t * +uc_pvg_canvas_add_glyph(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float v[3]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 3, v)) + return NULL; + + return ucv_double_new(plutovg_canvas_add_glyph(canvas, + (plutovg_codepoint_t)v[0], v[1], v[2])); +} + +static float +uc_pvg_canvas_text_op(uc_vm_t *vm, size_t nargs, plutovg_canvas_t *canvas, + int op, bool *ok) +{ + uc_value_t *text = _uc_fn_arg(vm, nargs, 0); + const char *str; + float v[2]; + int len; + + *ok = false; + + if (ucv_type(text) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting text string"); + + return 0; + } + + if (!uc_pvg_args_num(vm, nargs, 1, 2, v)) + return 0; + + str = ucv_string_get(text); + len = (int)ucv_string_length(text); + *ok = true; + + if (op == 0) + return plutovg_canvas_add_text(canvas, str, len, + PLUTOVG_TEXT_ENCODING_UTF8, v[0], v[1]); + + if (op == 1) + return plutovg_canvas_fill_text(canvas, str, len, + PLUTOVG_TEXT_ENCODING_UTF8, v[0], v[1]); + + if (op == 2) + return plutovg_canvas_stroke_text(canvas, str, len, + PLUTOVG_TEXT_ENCODING_UTF8, v[0], v[1]); + + return plutovg_canvas_clip_text(canvas, str, len, + PLUTOVG_TEXT_ENCODING_UTF8, v[0], v[1]); +} + +static uc_value_t * +uc_pvg_canvas_add_text(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float advance; + bool ok; + + if (!canvas) + return NULL; + + advance = uc_pvg_canvas_text_op(vm, nargs, canvas, 0, &ok); + + return ok ? ucv_double_new(advance) : NULL; +} + +static uc_value_t * +uc_pvg_canvas_fill_text(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float advance; + bool ok; + + if (!canvas) + return NULL; + + advance = uc_pvg_canvas_text_op(vm, nargs, canvas, 1, &ok); + + return ok ? ucv_double_new(advance) : NULL; +} + +static uc_value_t * +uc_pvg_canvas_stroke_text(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float advance; + bool ok; + + if (!canvas) + return NULL; + + advance = uc_pvg_canvas_text_op(vm, nargs, canvas, 2, &ok); + + return ok ? ucv_double_new(advance) : NULL; +} + +static uc_value_t * +uc_pvg_canvas_clip_text(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float advance; + bool ok; + + if (!canvas) + return NULL; + + advance = uc_pvg_canvas_text_op(vm, nargs, canvas, 3, &ok); + + return ok ? ucv_double_new(advance) : NULL; +} + +static uc_value_t * +uc_pvg_canvas_font_metrics(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float ascent, descent, line_gap; + plutovg_rect_t extents; + uc_value_t *rv; + + if (!canvas) + return NULL; + + plutovg_canvas_font_metrics(canvas, &ascent, &descent, &line_gap, + &extents); + + rv = ucv_object_new(vm); + ucv_object_add(rv, "ascent", ucv_double_new(ascent)); + ucv_object_add(rv, "descent", ucv_double_new(descent)); + ucv_object_add(rv, "line_gap", ucv_double_new(line_gap)); + ucv_object_add(rv, "extents", uc_pvg_rect_new(vm, &extents)); + + return rv; +} + +static uc_value_t * +uc_pvg_canvas_glyph_metrics(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + float advance_width, left_side_bearing; + plutovg_rect_t extents; + uc_value_t *rv; + float v[1]; + + if (!canvas || !uc_pvg_args_num(vm, nargs, 0, 1, v)) + return NULL; + + plutovg_canvas_glyph_metrics(canvas, (plutovg_codepoint_t)v[0], + &advance_width, &left_side_bearing, &extents); + + rv = ucv_object_new(vm); + ucv_object_add(rv, "advance_width", ucv_double_new(advance_width)); + ucv_object_add(rv, "left_side_bearing", + ucv_double_new(left_side_bearing)); + ucv_object_add(rv, "extents", uc_pvg_rect_new(vm, &extents)); + + return rv; +} + +static uc_value_t * +uc_pvg_canvas_text_extents(uc_vm_t *vm, size_t nargs) +{ + plutovg_canvas_t *canvas = uc_fn_thisval(UC_PVG_CANVAS); + uc_value_t *text = uc_fn_arg(0); + plutovg_rect_t extents; + uc_value_t *rv; + float advance; + + if (!canvas) + return NULL; + + if (ucv_type(text) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting text string"); + + return NULL; + } + + advance = plutovg_canvas_text_extents(canvas, ucv_string_get(text), + (int)ucv_string_length(text), PLUTOVG_TEXT_ENCODING_UTF8, + &extents); + + rv = ucv_object_new(vm); + ucv_object_add(rv, "advance_width", ucv_double_new(advance)); + ucv_object_add(rv, "extents", uc_pvg_rect_new(vm, &extents)); + + return rv; +} + +static const uc_function_list_t surface_fns[] = { + { "width", uc_pvg_surface_width }, + { "height", uc_pvg_surface_height }, + { "stride", uc_pvg_surface_stride }, + { "clear", uc_pvg_surface_clear }, + { "data", uc_pvg_surface_data }, + { "rgba", uc_pvg_surface_rgba }, + { "write_png", uc_pvg_surface_write_png }, + { "write_jpg", uc_pvg_surface_write_jpg }, + { "png", uc_pvg_surface_png }, + { "jpg", uc_pvg_surface_jpg }, +}; + +static const uc_function_list_t path_fns[] = { + { "move_to", uc_pvg_path_move_to }, + { "line_to", uc_pvg_path_line_to }, + { "quad_to", uc_pvg_path_quad_to }, + { "cubic_to", uc_pvg_path_cubic_to }, + { "arc_to", uc_pvg_path_arc_to }, + { "close", uc_pvg_path_close }, + { "reset", uc_pvg_path_reset }, + { "reserve", uc_pvg_path_reserve }, + { "current_point", uc_pvg_path_current_point }, + { "add_rect", uc_pvg_path_add_rect }, + { "add_round_rect", uc_pvg_path_add_round_rect }, + { "add_ellipse", uc_pvg_path_add_ellipse }, + { "add_circle", uc_pvg_path_add_circle }, + { "add_arc", uc_pvg_path_add_arc }, + { "add_path", uc_pvg_path_add_path }, + { "transform", uc_pvg_path_transform }, + { "clone", uc_pvg_path_clone }, + { "clone_flatten", uc_pvg_path_clone_flatten }, + { "clone_dashed", uc_pvg_path_clone_dashed }, + { "extents", uc_pvg_path_extents }, + { "length", uc_pvg_path_length }, + { "parse", uc_pvg_path_parse }, + { "elements", uc_pvg_path_elements }, +}; + +static const uc_function_list_t font_face_fns[] = { + { "metrics", uc_pvg_font_face_metrics }, + { "glyph_metrics", uc_pvg_font_face_glyph_metrics }, + { "glyph_path", uc_pvg_font_face_glyph_path }, + { "text_extents", uc_pvg_font_face_text_extents }, +}; + +static const uc_function_list_t canvas_fns[] = { + { "surface", uc_pvg_canvas_surface }, + { "save", uc_pvg_canvas_save }, + { "restore", uc_pvg_canvas_restore }, + { "set_color", uc_pvg_canvas_set_color }, + { "color", uc_pvg_canvas_color }, + { "set_paint", uc_pvg_canvas_set_paint }, + { "get_paint", uc_pvg_canvas_get_paint }, + { "set_linear_gradient", uc_pvg_canvas_set_linear_gradient }, + { "set_radial_gradient", uc_pvg_canvas_set_radial_gradient }, + { "set_texture", uc_pvg_canvas_set_texture }, + { "set_font", uc_pvg_canvas_set_font }, + { "set_font_face", uc_pvg_canvas_set_font_face }, + { "font_face", uc_pvg_canvas_font_face }, + { "set_font_size", uc_pvg_canvas_set_font_size }, + { "font_size", uc_pvg_canvas_font_size }, + { "add_font_face", uc_pvg_canvas_add_font_face }, + { "add_font_file", uc_pvg_canvas_add_font_file }, + { "select_font_face", uc_pvg_canvas_select_font_face }, + { "set_fill_rule", uc_pvg_canvas_set_fill_rule }, + { "fill_rule", uc_pvg_canvas_fill_rule }, + { "set_operator", uc_pvg_canvas_set_operator }, + { "operator", uc_pvg_canvas_operator }, + { "set_opacity", uc_pvg_canvas_set_opacity }, + { "opacity", uc_pvg_canvas_opacity }, + { "set_line_width", uc_pvg_canvas_set_line_width }, + { "line_width", uc_pvg_canvas_line_width }, + { "set_line_cap", uc_pvg_canvas_set_line_cap }, + { "line_cap", uc_pvg_canvas_line_cap }, + { "set_line_join", uc_pvg_canvas_set_line_join }, + { "line_join", uc_pvg_canvas_line_join }, + { "set_miter_limit", uc_pvg_canvas_set_miter_limit }, + { "miter_limit", uc_pvg_canvas_miter_limit }, + { "set_dash", uc_pvg_canvas_set_dash }, + { "set_dash_offset", uc_pvg_canvas_set_dash_offset }, + { "dash_offset", uc_pvg_canvas_dash_offset }, + { "set_dash_array", uc_pvg_canvas_set_dash_array }, + { "dash_array", uc_pvg_canvas_dash_array }, + { "translate", uc_pvg_canvas_translate }, + { "scale", uc_pvg_canvas_scale }, + { "shear", uc_pvg_canvas_shear }, + { "rotate", uc_pvg_canvas_rotate }, + { "transform", uc_pvg_canvas_transform }, + { "reset_matrix", uc_pvg_canvas_reset_matrix }, + { "set_matrix", uc_pvg_canvas_set_matrix }, + { "matrix", uc_pvg_canvas_matrix }, + { "map", uc_pvg_canvas_map }, + { "map_point", uc_pvg_canvas_map_point }, + { "map_rect", uc_pvg_canvas_map_rect }, + { "move_to", uc_pvg_canvas_move_to }, + { "line_to", uc_pvg_canvas_line_to }, + { "quad_to", uc_pvg_canvas_quad_to }, + { "cubic_to", uc_pvg_canvas_cubic_to }, + { "arc_to", uc_pvg_canvas_arc_to }, + { "rect", uc_pvg_canvas_rect }, + { "round_rect", uc_pvg_canvas_round_rect }, + { "ellipse", uc_pvg_canvas_ellipse }, + { "circle", uc_pvg_canvas_circle }, + { "arc", uc_pvg_canvas_arc }, + { "add_path", uc_pvg_canvas_add_path }, + { "new_path", uc_pvg_canvas_new_path }, + { "close_path", uc_pvg_canvas_close_path }, + { "current_point", uc_pvg_canvas_current_point }, + { "path", uc_pvg_canvas_path }, + { "fill_contains", uc_pvg_canvas_fill_contains }, + { "stroke_contains", uc_pvg_canvas_stroke_contains }, + { "clip_contains", uc_pvg_canvas_clip_contains }, + { "fill_extents", uc_pvg_canvas_fill_extents }, + { "stroke_extents", uc_pvg_canvas_stroke_extents }, + { "clip_extents", uc_pvg_canvas_clip_extents }, + { "fill", uc_pvg_canvas_fill }, + { "stroke", uc_pvg_canvas_stroke }, + { "clip", uc_pvg_canvas_clip }, + { "paint", uc_pvg_canvas_paint }, + { "fill_preserve", uc_pvg_canvas_fill_preserve }, + { "stroke_preserve", uc_pvg_canvas_stroke_preserve }, + { "clip_preserve", uc_pvg_canvas_clip_preserve }, + { "fill_rect", uc_pvg_canvas_fill_rect }, + { "stroke_rect", uc_pvg_canvas_stroke_rect }, + { "clip_rect", uc_pvg_canvas_clip_rect }, + { "fill_path", uc_pvg_canvas_fill_path }, + { "stroke_path", uc_pvg_canvas_stroke_path }, + { "clip_path", uc_pvg_canvas_clip_path }, + { "add_glyph", uc_pvg_canvas_add_glyph }, + { "add_text", uc_pvg_canvas_add_text }, + { "fill_text", uc_pvg_canvas_fill_text }, + { "stroke_text", uc_pvg_canvas_stroke_text }, + { "clip_text", uc_pvg_canvas_clip_text }, + { "font_metrics", uc_pvg_canvas_font_metrics }, + { "glyph_metrics", uc_pvg_canvas_glyph_metrics }, + { "text_extents", uc_pvg_canvas_text_extents }, +}; + +static const uc_function_list_t module_fns[] = { + { "version", uc_pvg_version }, + { "version_string", uc_pvg_version_string }, + { "deg2rad", uc_pvg_deg2rad }, + { "rad2deg", uc_pvg_rad2deg }, + { "color_parse", uc_pvg_color_parse }, + { "color_hsl", uc_pvg_color_hsl }, + { "color_rgba32", uc_pvg_color_rgba32 }, + { "color_argb32", uc_pvg_color_argb32 }, + { "matrix_identity", uc_pvg_matrix_identity }, + { "matrix_init_translate", uc_pvg_matrix_init_translate }, + { "matrix_init_scale", uc_pvg_matrix_init_scale }, + { "matrix_init_rotate", uc_pvg_matrix_init_rotate }, + { "matrix_init_shear", uc_pvg_matrix_init_shear }, + { "matrix_translate", uc_pvg_matrix_translate }, + { "matrix_scale", uc_pvg_matrix_scale }, + { "matrix_rotate", uc_pvg_matrix_rotate }, + { "matrix_shear", uc_pvg_matrix_shear }, + { "matrix_multiply", uc_pvg_matrix_multiply }, + { "matrix_invert", uc_pvg_matrix_invert }, + { "matrix_map", uc_pvg_matrix_map }, + { "matrix_map_point", uc_pvg_matrix_map_point }, + { "matrix_map_rect", uc_pvg_matrix_map_rect }, + { "matrix_parse", uc_pvg_matrix_parse }, + { "surface", uc_pvg_surface_create }, + { "surface_from_file", uc_pvg_surface_from_file }, + { "surface_from_data", uc_pvg_surface_from_data }, + { "surface_from_base64", uc_pvg_surface_from_base64 }, + { "canvas", uc_pvg_canvas_create }, + { "path", uc_pvg_path_create }, + { "paint_color", uc_pvg_paint_color }, + { "paint_linear_gradient", uc_pvg_paint_linear_gradient }, + { "paint_radial_gradient", uc_pvg_paint_radial_gradient }, + { "paint_texture", uc_pvg_paint_texture }, + { "font_face_from_file", uc_pvg_font_face_from_file }, + { "font_face_from_data", uc_pvg_font_face_from_data }, +}; + +void +uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, module_fns); + + uc_type_declare(vm, UC_PVG_SURFACE, surface_fns, uc_pvg_surface_free); + uc_type_declare(vm, UC_PVG_CANVAS, canvas_fns, uc_pvg_canvas_free); + uc_type_declare(vm, UC_PVG_PATH, path_fns, uc_pvg_path_free); + uc_type_declare(vm, UC_PVG_FONT_FACE, font_face_fns, + uc_pvg_font_face_free); + + ucv_resource_type_add(vm, UC_PVG_PAINT, ucv_object_new(NULL), + uc_pvg_paint_free); +} diff --git a/libs/ucode-mod-plutovg/src/uc-plutovg.h b/libs/ucode-mod-plutovg/src/uc-plutovg.h new file mode 100644 index 0000000..d4efc2d --- /dev/null +++ b/libs/ucode-mod-plutovg/src/uc-plutovg.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2026 Daniel Golle + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef UC_PLUTOVG_H +#define UC_PLUTOVG_H + +#include +#include + +#define UC_PVG_SURFACE "plutovg.surface" +#define UC_PVG_CANVAS "plutovg.canvas" +#define UC_PVG_PATH "plutovg.path" +#define UC_PVG_PAINT "plutovg.paint" +#define UC_PVG_FONT_FACE "plutovg.font_face" + +static inline float +uc_pvg_number(uc_value_t *uv, float fallback) +{ + if (ucv_type(uv) != UC_INTEGER && ucv_type(uv) != UC_DOUBLE) + return fallback; + + return (float)ucv_to_double(uv); +} + +static inline bool +uc_pvg_component(uc_value_t *uv, const char *key, size_t idx, float *out) +{ + uc_value_t *val; + + if (ucv_type(uv) == UC_OBJECT) + val = ucv_object_get(uv, key, NULL); + else + val = ucv_array_get(uv, idx); + + if (ucv_type(val) != UC_INTEGER && ucv_type(val) != UC_DOUBLE) + return false; + + *out = (float)ucv_to_double(val); + + return true; +} + +static inline bool +uc_pvg_color_get(uc_value_t *uv, plutovg_color_t *color) +{ + uc_value_t *alpha; + + if (ucv_type(uv) == UC_STRING) + return plutovg_color_parse(color, ucv_string_get(uv), + ucv_string_length(uv)) > 0; + + if (ucv_type(uv) != UC_OBJECT && ucv_type(uv) != UC_ARRAY) + return false; + + if (!uc_pvg_component(uv, "r", 0, &color->r)) + return false; + if (!uc_pvg_component(uv, "g", 1, &color->g)) + return false; + if (!uc_pvg_component(uv, "b", 2, &color->b)) + return false; + + if (ucv_type(uv) == UC_OBJECT) + alpha = ucv_object_get(uv, "a", NULL); + else + alpha = ucv_array_get(uv, 3); + + color->a = uc_pvg_number(alpha, 1.0f); + + return true; +} + +static inline uc_value_t * +uc_pvg_color_new(uc_vm_t *vm, const plutovg_color_t *color) +{ + uc_value_t *rv = ucv_object_new(vm); + + ucv_object_add(rv, "r", ucv_double_new(color->r)); + ucv_object_add(rv, "g", ucv_double_new(color->g)); + ucv_object_add(rv, "b", ucv_double_new(color->b)); + ucv_object_add(rv, "a", ucv_double_new(color->a)); + + return rv; +} + +static inline bool +uc_pvg_rect_get(uc_value_t *uv, plutovg_rect_t *rect) +{ + if (ucv_type(uv) != UC_OBJECT && ucv_type(uv) != UC_ARRAY) + return false; + + return uc_pvg_component(uv, "x", 0, &rect->x) && + uc_pvg_component(uv, "y", 1, &rect->y) && + uc_pvg_component(uv, "w", 2, &rect->w) && + uc_pvg_component(uv, "h", 3, &rect->h); +} + +static inline uc_value_t * +uc_pvg_rect_new(uc_vm_t *vm, const plutovg_rect_t *rect) +{ + uc_value_t *rv = ucv_object_new(vm); + + ucv_object_add(rv, "x", ucv_double_new(rect->x)); + ucv_object_add(rv, "y", ucv_double_new(rect->y)); + ucv_object_add(rv, "w", ucv_double_new(rect->w)); + ucv_object_add(rv, "h", ucv_double_new(rect->h)); + + return rv; +} + +static inline bool +uc_pvg_point_get(uc_value_t *uv, plutovg_point_t *point) +{ + if (ucv_type(uv) != UC_OBJECT && ucv_type(uv) != UC_ARRAY) + return false; + + return uc_pvg_component(uv, "x", 0, &point->x) && + uc_pvg_component(uv, "y", 1, &point->y); +} + +static inline uc_value_t * +uc_pvg_point_new(uc_vm_t *vm, const plutovg_point_t *point) +{ + uc_value_t *rv = ucv_object_new(vm); + + ucv_object_add(rv, "x", ucv_double_new(point->x)); + ucv_object_add(rv, "y", ucv_double_new(point->y)); + + return rv; +} + +static inline bool +uc_pvg_matrix_get(uc_value_t *uv, plutovg_matrix_t *matrix) +{ + if (ucv_type(uv) == UC_STRING) + return plutovg_matrix_parse(matrix, ucv_string_get(uv), + ucv_string_length(uv)); + + if (ucv_type(uv) != UC_OBJECT && ucv_type(uv) != UC_ARRAY) + return false; + + return uc_pvg_component(uv, "a", 0, &matrix->a) && + uc_pvg_component(uv, "b", 1, &matrix->b) && + uc_pvg_component(uv, "c", 2, &matrix->c) && + uc_pvg_component(uv, "d", 3, &matrix->d) && + uc_pvg_component(uv, "e", 4, &matrix->e) && + uc_pvg_component(uv, "f", 5, &matrix->f); +} + +static inline uc_value_t * +uc_pvg_matrix_new(uc_vm_t *vm, const plutovg_matrix_t *matrix) +{ + uc_value_t *rv = ucv_object_new(vm); + + ucv_object_add(rv, "a", ucv_double_new(matrix->a)); + ucv_object_add(rv, "b", ucv_double_new(matrix->b)); + ucv_object_add(rv, "c", ucv_double_new(matrix->c)); + ucv_object_add(rv, "d", ucv_double_new(matrix->d)); + ucv_object_add(rv, "e", ucv_double_new(matrix->e)); + ucv_object_add(rv, "f", ucv_double_new(matrix->f)); + + return rv; +} + +#endif From 26615d4358d9ecddb34d7570cc39b9453484f648 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Fri, 25 Sep 2026 08:56:03 +0100 Subject: [PATCH 4/4] ucode-mod-plutosvg: add ucode binding for plutosvg Make SVG documents loadable and renderable from ucode. A document is a resource type that reports its intrinsic size, computes the extents of the whole drawing or of one element by id, and renders either onto a plutovg canvas or into a fresh surface. CSS currentColor is passed as an ordinary colour value and colour variables resolve through a ucode object or callback invoked during rendering. Script data handed to the loader is copied because the parser keeps pointers into it for the lifetime of the document; the copy is released through the destroy callback. An element id is dereferenced in the frame that owns the argument, as a short ucode string lives inside the variable holding the value. Surfaces returned by the renderer are plutovg resources, so the plutovg module is loaded on demand when it is not already present. Signed-off-by: Daniel Golle --- libs/ucode-mod-plutosvg/Makefile | 44 +++ libs/ucode-mod-plutosvg/src/LICENSE | 13 + libs/ucode-mod-plutosvg/src/plutosvg.c | 370 +++++++++++++++++++++++++ 3 files changed, 427 insertions(+) create mode 100644 libs/ucode-mod-plutosvg/Makefile create mode 100644 libs/ucode-mod-plutosvg/src/LICENSE create mode 100644 libs/ucode-mod-plutosvg/src/plutosvg.c diff --git a/libs/ucode-mod-plutosvg/Makefile b/libs/ucode-mod-plutosvg/Makefile new file mode 100644 index 0000000..14819f5 --- /dev/null +++ b/libs/ucode-mod-plutosvg/Makefile @@ -0,0 +1,44 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ucode-mod-plutosvg +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Daniel Golle +PKG_LICENSE:=ISC +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/ucode-mod-plutosvg + SECTION:=libs + CATEGORY:=Libraries + SUBMENU:=ucode + TITLE:=ucode binding for the plutosvg SVG rendering library + DEPENDS:=+libucode +plutovg +plutosvg +ucode-mod-plutovg +endef + +define Package/ucode-mod-plutosvg/description + Exposes the plutosvg document API to ucode scripts: loading SVG + documents from memory or from a file, querying their intrinsic size and + element extents, and rendering a document or a single element onto a + plutovg canvas or into a fresh surface. CSS currentColor and colour + variables can be resolved from a ucode object or callback. +endef + +define Build/Configure +endef + +define Build/Compile + $(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_CPPFLAGS) $(TARGET_LDFLAGS) \ + $(FPIC) -Wall -ffunction-sections -Wl,--gc-sections -shared \ + -I$(STAGING_DIR)/usr/include/plutovg \ + -o $(PKG_BUILD_DIR)/plutosvg.so $(PKG_BUILD_DIR)/plutosvg.c \ + -lplutosvg -lplutovg +endef + +define Package/ucode-mod-plutosvg/install + $(INSTALL_DIR) $(1)/usr/lib/ucode + $(CP) $(PKG_BUILD_DIR)/plutosvg.so $(1)/usr/lib/ucode/ +endef + +$(eval $(call BuildPackage,ucode-mod-plutosvg)) diff --git a/libs/ucode-mod-plutosvg/src/LICENSE b/libs/ucode-mod-plutosvg/src/LICENSE new file mode 100644 index 0000000..8fbfabd --- /dev/null +++ b/libs/ucode-mod-plutosvg/src/LICENSE @@ -0,0 +1,13 @@ +Copyright (C) 2026 Daniel Golle + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/libs/ucode-mod-plutosvg/src/plutosvg.c b/libs/ucode-mod-plutosvg/src/plutosvg.c new file mode 100644 index 0000000..3e56fe5 --- /dev/null +++ b/libs/ucode-mod-plutosvg/src/plutosvg.c @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2026 Daniel Golle + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include + +#define UC_PSVG_DOCUMENT "plutosvg.document" + +typedef struct { + uc_value_t *palette; + uc_vm_t *vm; +} uc_psvg_palette_t; + +static bool +uc_psvg_palette_lookup(uc_psvg_palette_t *ctx, const char *name, int length, + plutovg_color_t *color) +{ + uc_value_t *val; + char *key; + bool ok; + + key = strndup(name, (size_t)length); + + if (!key) + return false; + + val = ucv_object_get(ctx->palette, key, NULL); + ok = uc_pvg_color_get(val, color); + free(key); + + return ok; +} + +static bool +uc_psvg_palette_call(uc_psvg_palette_t *ctx, const char *name, int length, + plutovg_color_t *color) +{ + uc_value_t *rv; + bool ok; + + uc_vm_stack_push(ctx->vm, ucv_get(ctx->palette)); + uc_vm_stack_push(ctx->vm, ucv_string_new_length(name, (size_t)length)); + + if (uc_vm_call(ctx->vm, false, 1) != EXCEPTION_NONE) + return false; + + rv = uc_vm_stack_pop(ctx->vm); + ok = uc_pvg_color_get(rv, color); + ucv_put(rv); + + return ok; +} + +static bool +uc_psvg_palette_cb(void *closure, const char *name, int length, + plutovg_color_t *color) +{ + uc_psvg_palette_t *ctx = closure; + + if (ucv_type(ctx->palette) == UC_OBJECT) + return uc_psvg_palette_lookup(ctx, name, length, color); + + if (ucv_is_callable(ctx->palette)) + return uc_psvg_palette_call(ctx, name, length, color); + + return false; +} + +static void +uc_psvg_document_free(void *ptr) +{ + plutosvg_document_destroy(ptr); +} + +/* a short string lives inside the variable holding the value */ +static bool +uc_psvg_id_check(uc_vm_t *vm, uc_value_t *uv) +{ + if (uv == NULL || ucv_type(uv) == UC_NULL || ucv_type(uv) == UC_STRING) + return true; + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting element id string"); + + return false; +} + +static const plutovg_color_t * +uc_psvg_color_opt(uc_vm_t *vm, uc_value_t *uv, plutovg_color_t *storage, + bool *ok) +{ + *ok = true; + + if (uv == NULL || ucv_type(uv) == UC_NULL) + return NULL; + + if (uc_pvg_color_get(uv, storage)) + return storage; + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Invalid color value"); + *ok = false; + + return NULL; +} + +static plutosvg_palette_func_t +uc_psvg_palette_opt(uc_value_t *uv, uc_psvg_palette_t *ctx, uc_vm_t *vm) +{ + if (uv == NULL || ucv_type(uv) == UC_NULL) + return NULL; + + if (ucv_type(uv) != UC_OBJECT && !ucv_is_callable(uv)) + return NULL; + + ctx->palette = uv; + ctx->vm = vm; + + return uc_psvg_palette_cb; +} + +static uc_value_t * +uc_psvg_version(uc_vm_t *vm, size_t nargs) +{ + return ucv_int64_new(plutosvg_version()); +} + +static uc_value_t * +uc_psvg_version_string(uc_vm_t *vm, size_t nargs) +{ + return ucv_string_new(plutosvg_version_string()); +} + +static uc_value_t * +uc_psvg_document_wrap(uc_vm_t *vm, plutosvg_document_t *doc) +{ + uc_value_t *rv; + + if (!doc) + return NULL; + + rv = ucv_resource_create(vm, UC_PSVG_DOCUMENT, doc); + + if (!rv) + plutosvg_document_destroy(doc); + + return rv; +} + +static uc_value_t * +uc_psvg_document_from_data(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + plutosvg_document_t *doc; + size_t len; + char *copy; + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting SVG data"); + + return NULL; + } + + len = ucv_string_length(arg); + copy = malloc(len); + + if (!copy) + return NULL; + + memcpy(copy, ucv_string_get(arg), len); + + doc = plutosvg_document_load_from_data(copy, (int)len, + uc_pvg_number(uc_fn_arg(1), -1.0f), + uc_pvg_number(uc_fn_arg(2), -1.0f), free, copy); + + if (!doc) { + free(copy); + + return NULL; + } + + return uc_psvg_document_wrap(vm, doc); +} + +static uc_value_t * +uc_psvg_document_from_file(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + + if (ucv_type(arg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expecting file name"); + + return NULL; + } + + return uc_psvg_document_wrap(vm, + plutosvg_document_load_from_file(ucv_string_get(arg), + uc_pvg_number(uc_fn_arg(1), -1.0f), + uc_pvg_number(uc_fn_arg(2), -1.0f))); +} + +static uc_value_t * +uc_psvg_document_width(uc_vm_t *vm, size_t nargs) +{ + plutosvg_document_t *doc = uc_fn_thisval(UC_PSVG_DOCUMENT); + + if (!doc) + return NULL; + + return ucv_double_new(plutosvg_document_get_width(doc)); +} + +static uc_value_t * +uc_psvg_document_height(uc_vm_t *vm, size_t nargs) +{ + plutosvg_document_t *doc = uc_fn_thisval(UC_PSVG_DOCUMENT); + + if (!doc) + return NULL; + + return ucv_double_new(plutosvg_document_get_height(doc)); +} + +static uc_value_t * +uc_psvg_document_extents(uc_vm_t *vm, size_t nargs) +{ + plutosvg_document_t *doc = uc_fn_thisval(UC_PSVG_DOCUMENT); + uc_value_t *idval = uc_fn_arg(0); + plutovg_rect_t rect; + const char *id; + + if (!doc || !uc_psvg_id_check(vm, idval)) + return NULL; + + id = (ucv_type(idval) == UC_STRING) ? ucv_string_get(idval) : NULL; + + if (!plutosvg_document_extents(doc, id, &rect)) + return NULL; + + return uc_pvg_rect_new(vm, &rect); +} + +static uc_value_t * +uc_psvg_document_render(uc_vm_t *vm, size_t nargs) +{ + plutosvg_document_t *doc = uc_fn_thisval(UC_PSVG_DOCUMENT); + plutosvg_palette_func_t palette_func; + uc_value_t *idval = uc_fn_arg(1); + const plutovg_color_t *current; + plutovg_canvas_t *canvas; + uc_psvg_palette_t ctx; + plutovg_color_t color; + const char *id; + bool ok; + + if (!doc || !uc_psvg_id_check(vm, idval)) + return NULL; + + canvas = ucv_resource_data(uc_fn_arg(0), UC_PVG_CANVAS); + + if (!canvas) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting plutovg canvas object"); + + return NULL; + } + + id = (ucv_type(idval) == UC_STRING) ? ucv_string_get(idval) : NULL; + current = uc_psvg_color_opt(vm, uc_fn_arg(2), &color, &ok); + + if (!ok) + return NULL; + + palette_func = uc_psvg_palette_opt(uc_fn_arg(3), &ctx, vm); + + return ucv_boolean_new(plutosvg_document_render(doc, id, canvas, + current, palette_func, &ctx)); +} + +static uc_value_t * +uc_psvg_document_render_to_surface(uc_vm_t *vm, size_t nargs) +{ + plutosvg_document_t *doc = uc_fn_thisval(UC_PSVG_DOCUMENT); + plutosvg_palette_func_t palette_func; + uc_value_t *idval = uc_fn_arg(0); + const plutovg_color_t *current; + plutovg_surface_t *surface; + uc_psvg_palette_t ctx; + plutovg_color_t color; + uc_value_t *rv, *name; + const char *id; + bool ok; + + if (!doc || !uc_psvg_id_check(vm, idval)) + return NULL; + + if (!ucv_resource_type_lookup(vm, UC_PVG_SURFACE)) { + name = ucv_string_new("plutovg"); + ucv_put(uc_vm_invoke(vm, "require", 1, name)); + ucv_put(name); + } + + if (!ucv_resource_type_lookup(vm, UC_PVG_SURFACE)) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "The plutovg module is required to create surfaces"); + + return NULL; + } + + id = (ucv_type(idval) == UC_STRING) ? ucv_string_get(idval) : NULL; + current = uc_psvg_color_opt(vm, uc_fn_arg(3), &color, &ok); + + if (!ok) + return NULL; + + palette_func = uc_psvg_palette_opt(uc_fn_arg(4), &ctx, vm); + + surface = plutosvg_document_render_to_surface(doc, id, + (int)uc_pvg_number(uc_fn_arg(1), -1.0f), + (int)uc_pvg_number(uc_fn_arg(2), -1.0f), current, + palette_func, &ctx); + + if (!surface) + return NULL; + + rv = ucv_resource_create(vm, UC_PVG_SURFACE, surface); + + if (!rv) + plutovg_surface_destroy(surface); + + return rv; +} + +static const uc_function_list_t document_fns[] = { + { "width", uc_psvg_document_width }, + { "height", uc_psvg_document_height }, + { "extents", uc_psvg_document_extents }, + { "render", uc_psvg_document_render }, + { "render_to_surface", uc_psvg_document_render_to_surface }, +}; + +static const uc_function_list_t module_fns[] = { + { "version", uc_psvg_version }, + { "version_string", uc_psvg_version_string }, + { "document", uc_psvg_document_from_data }, + { "document_from_file", uc_psvg_document_from_file }, +}; + +void +uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, module_fns); + + uc_type_declare(vm, UC_PSVG_DOCUMENT, document_fns, + uc_psvg_document_free); +}