Include new cairo renderer (#48)

This commit is contained in:
Justin 2024-11-19 05:03:39 +01:00 committed by GitHub
parent c5a1c1a4ed
commit 330e56a858
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 566 additions and 3 deletions

View File

@ -6,5 +6,6 @@ add_subdirectory("examples/cpp-project-example")
# Don't try to compile C99 projects using MSVC
if(NOT MSVC)
add_subdirectory("examples/raylib-sidebar-scrolling-container")
add_subdirectory("examples/cairo-pdf-rendering")
add_subdirectory("examples/clay-official-website")
endif()

View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.28)
project(clay_examples_cairo_pdf_rendering C)
set(CMAKE_C_STANDARD 99)
add_executable(clay_examples_cairo_pdf_rendering main.c)
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC -DCLAY_DEBUG -DCLAY_OVERFLOW_TRAP -Wall -Werror -Wno-unknown-pragmas -g3 -O0)
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC .)
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC cairo)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_cairo_pdf_rendering POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -0,0 +1,161 @@
// Copyright (c) 2024 Justin Andreas Lacoste (@27justin)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software in a
// product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
// SPDX-License-Identifier: Zlib
#include <stdlib.h>
// The renderer includes clay.h while also providing the
// CLAY_IMPLEMENTATION
#include "../../renderers/cairo/clay_renderer_cairo.c"
// cairo-pdf, though this is optional and not required if you,
// e.g. render PNGs.
#include <cairo/cairo-pdf.h>
// Layout the first page.
void Layout() {
static Clay_Color PRIMARY = { 0xa8, 0x42, 0x1c, 255 };
static Clay_Color BACKGROUND = { 0xF4, 0xEB, 0xE6, 255 };
static Clay_Color ACCENT = { 0xFA, 0xE0, 0xD4, 255 };
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM }),
CLAY_RECTANGLE({ .color = BACKGROUND })) {
CLAY(CLAY_ID("PageMargins"),
CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() },
.padding = { 70., 50. }, // Some nice looking page margins
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 10})) {
// Section Title
CLAY(CLAY_TEXT(
CLAY_STRING("Features Overview"),
CLAY_TEXT_CONFIG({
.fontFamily = CLAY_STRING("Calistoga"),
.textColor = PRIMARY,
.fontSize = 24
})
));
// Feature Box
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT() }, .childGap = 10 })) {
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT() }}), CLAY_RECTANGLE({
.color = ACCENT,
.cornerRadius = CLAY_CORNER_RADIUS(12),
})) {
CLAY(CLAY_LAYOUT({.padding = { 20., 20. }, .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM })) {
CLAY_TEXT(CLAY_STRING("- High performance"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontFamily = CLAY_STRING("Quicksand SemiBold") }));
CLAY_TEXT(CLAY_STRING("- Declarative syntax"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontFamily = CLAY_STRING("Quicksand SemiBold") }));
CLAY_TEXT(CLAY_STRING("- Flexbox-style responsive layout"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontFamily = CLAY_STRING("Quicksand SemiBold") }));
CLAY_TEXT(CLAY_STRING("- Single .h file for C/C++"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontFamily = CLAY_STRING("Quicksand SemiBold") }));
CLAY_TEXT(CLAY_STRING("- And now with cairo!"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontFamily = CLAY_STRING("Quicksand SemiBold") }));
}
}
CLAY(CLAY_LAYOUT({
.sizing = {CLAY_SIZING_FIT(), CLAY_SIZING_GROW()},
.padding = { 10, 10 },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER },
.childGap = 4
}), CLAY_RECTANGLE({ .color = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(8) })) {
// Profile picture
CLAY(CLAY_LAYOUT({
.sizing = {CLAY_SIZING_FIT(), CLAY_SIZING_GROW()},
.padding = { 30, 0 },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }}), CLAY_BORDER_OUTSIDE_RADIUS(2, PRIMARY, 10)) {
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }), CLAY_IMAGE({ .sourceDimensions = { 32, 32 }, .path = CLAY_STRING("resources/check.png") }));
}
}
}
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(16) } }));
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childGap = 10, .layoutDirection = CLAY_TOP_TO_BOTTOM })) {
CLAY_TEXT(CLAY_STRING("Cairo"), CLAY_TEXT_CONFIG({ .fontFamily = CLAY_STRING("Calistoga"), .fontSize = 24, .textColor = PRIMARY }));
CLAY(CLAY_LAYOUT({ .padding = { 10, 10 } }), CLAY_RECTANGLE({ .color = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(10) })) {
CLAY_TEXT(CLAY_STRING("Officiis quia quia qui inventore ratione voluptas et. Quidem sunt unde similique. Qui est et exercitationem cumque harum illum. Numquam placeat aliquid quo voluptatem. "
"Deleniti saepe nihil exercitationem nemo illo. Consequatur beatae repellat provident similique. Provident qui exercitationem deserunt sapiente. Quam qui dolor corporis odit. "
"Assumenda corrupti sunt culpa pariatur. Vero sit ut minima. In est consequatur minus et cum sint illum aperiam. Qui ipsa quas nisi omnis aut quia nobis. "
"Corporis deserunt eum mollitia modi rerum voluptas. Expedita non ab esse. Sit voluptates eos voluptatem labore aspernatur quia eum. Modi cumque atque non. Sunt officiis corrupti neque ut inventore excepturi rem minima. Possimus sed soluta qui ea aut ipsum laborum fugit. "
"Voluptate eum consectetur non. Quo autem voluptate soluta atque dolorum maxime. Officiis inventore omnis eveniet beatae ipsa optio. Unde voluptatum ut autem quia sit sit et. Ut inventore qui quia totam consequatur. Sit ea consequatur omnis rerum nulla aspernatur deleniti."), CLAY_TEXT_CONFIG({ .fontFamily = CLAY_STRING("Quicksand SemiBold"), .fontSize = 16, .textColor = PRIMARY, .lineHeight = 16 }));
}
}
}
}
}
int main(void) {
// First we set up our cairo surface.
// In this example we will use the PDF backend,
// but you should be able to use any of them.
// Guaranteed to be working are: PDF, PNG
// Create a PDF surface that is the same size as a DIN A4 sheet
// When using the PDF backend, cairo calculates in points (1 point == 1/72.0 inch)
double width = (21.0 / 2.54) * 72, // cm in points
height = (29.7 / 2.54) * 72;
cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", width, height);
cairo_t *cr = cairo_create(surface);
cairo_surface_destroy(surface); // Drop reference
// Initialize internal global variable with `cr`.
// We require some kind of global reference to a valid
// cairo instance to properly measure text.
// Note that due to this, this interface is not thread-safe!
Clay_Cairo_Initialize(cr);
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = (Clay_Arena) { .label = CLAY_STRING("Clay Memory Arena"), .memory = malloc(totalMemorySize), .capacity = totalMemorySize };
Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText);
// We initialize Clay with the same size
Clay_Initialize(clayMemory, (Clay_Dimensions) { width, height });
Clay_BeginLayout();
// Here you can now create the declarative clay layout.
// Moved into a separate function for brevity.
Layout();
Clay_RenderCommandArray commands = Clay_EndLayout();
// Pass our layout to the cairo backend
Clay_Cairo_Render(commands);
// To keep this example short, we will not emit a second page in the PDF.
// But to do so, you have to
// 1. cairo_show_page(cr)
// 2. Clay_BeginLayout();
// 3. Create your layout
// 4. commands = Clay_EndLayout();
// 5. Clay_Cairo_Render(commands);
cairo_destroy(cr);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,383 @@
// Copyright (c) 2024 Justin Andreas Lacoste (@27justin)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software in a
// product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
// SPDX-License-Identifier: Zlib
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// TODO: Regarding image support, currently this renderer only
// supports PNG images, this is due to cairo having just PNG as it's
// main file format. We maybe should introduce stb_image to load them
// as bitmaps and feed cairo that way.
#define CLAY_EXTEND_CONFIG_IMAGE Clay_String path; // Filesystem path
// TODO: We should use the given `uint16_t fontId` instead of doing this.
#define CLAY_EXTEND_CONFIG_TEXT Clay_String fontFamily; // Font family
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include <cairo/cairo.h>
////////////////////////////////
//
// Public API
//
// Initialize the internal cairo pointer with the user provided instance.
// This is REQUIRED before calling Clay_Cairo_Render.
void Clay_Cairo_Initialize(cairo_t *cairo);
// Render the command queue to the `cairo_t*` instance you called
// `Clay_Cairo_Initialize` on.
void Clay_Cairo_Render(Clay_RenderCommandArray commands);
////////////////////////////////
////////////////////////////////
// Convencience macros
//
#define CLAY_TO_CAIRO(color) color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0
#define DEG2RAD(degrees) (degrees * ( M_PI / 180.0 ) )
////////////////////////////////
////////////////////////////////
// Implementation
//
// Cairo instance
static cairo_t *Clay__Cairo = NULL;
// Return a null-terminated copy of Clay_String `str`.
// Callee is required to free.
static inline char *Clay_Cairo__NullTerminate(Clay_String *str) {
char *copy = (char*) malloc(str->length + 1);
if (!copy) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
memcpy(copy, str->chars, str->length);
copy[str->length] = '\0';
return copy;
}
// Measure text using cairo's *toy* text API.
static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_String *str, Clay_TextElementConfig *config) {
// Edge case: Clay computes the width of a whitespace character
// once. Cairo does not factor in whitespaces when computing text
// extents, this edge-case serves as a short-circuit to introduce
// (somewhat) sensible values into Clay.
if(str->length == 1 && str->chars[0] == ' ') {
cairo_text_extents_t te;
cairo_text_extents(Clay__Cairo, " ", &te);
return (Clay_Dimensions) {
// The multiplication here follows no real logic, just
// brute-forcing it until the text boundaries look
// okay-ish. You should probably rather use a proper text
// shaping engine like HarfBuzz or Pango.
.width = ((float) te.x_advance) * 1.9f,
.height = (float) config->fontSize
};
}
// Ensure string is null-terminated for Cairo
char *text = Clay_Cairo__NullTerminate(str);
char *font_family = Clay_Cairo__NullTerminate(&config->fontFamily);
// Save and reset the Cairo context to avoid unwanted transformations
cairo_save(Clay__Cairo);
cairo_identity_matrix(Clay__Cairo);
// Set font properties
cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(Clay__Cairo, config->fontSize);
// Use glyph extents for better precision
cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(Clay__Cairo);
if (!scaled_font) {
fprintf(stderr, "Failed to get scaled font\n");
cairo_restore(Clay__Cairo);
free(text);
free(font_family);
return (Clay_Dimensions){0, 0};
}
cairo_glyph_t *glyphs = NULL;
int num_glyphs = 0;
cairo_status_t status = cairo_scaled_font_text_to_glyphs(
scaled_font, 0, 0, text, -1, &glyphs, &num_glyphs, NULL, NULL, NULL
);
if (status != CAIRO_STATUS_SUCCESS || !glyphs || num_glyphs == 0) {
fprintf(stderr, "Failed to generate glyphs: %s\n", cairo_status_to_string(status));
cairo_restore(Clay__Cairo);
free(text);
free(font_family);
return (Clay_Dimensions){0, 0};
}
// Measure the glyph extents
cairo_text_extents_t glyph_extents;
cairo_glyph_extents(Clay__Cairo, glyphs, num_glyphs, &glyph_extents);
// Clean up glyphs
cairo_glyph_free(glyphs);
// Restore the Cairo context
cairo_restore(Clay__Cairo);
// Free temporary strings
free(text);
free(font_family);
// Return dimensions
return (Clay_Dimensions){
.width = (float) glyph_extents.width,
.height = (float) glyph_extents.height
};
}
void Clay_Cairo_Initialize(cairo_t *cairo) {
Clay__Cairo = cairo;
}
// Internally used to copy images onto our document/active workspace.
void Clay_Cairo__Blit_Surface(cairo_surface_t *src_surface, cairo_surface_t *dest_surface,
double x, double y, double scale_x, double scale_y) {
// Create a cairo context for the destination surface
cairo_t *cr = cairo_create(dest_surface);
// Save the context's state
cairo_save(cr);
// Apply translation to position the source at (x, y)
cairo_translate(cr, x, y);
// Apply scaling to the context
cairo_scale(cr, scale_x, scale_y);
// Set the source surface at (0, 0) after applying transformations
cairo_set_source_surface(cr, src_surface, 0, 0);
// Paint the scaled source surface onto the destination surface
cairo_paint(cr);
// Restore the context's state to remove transformations
cairo_restore(cr);
// Clean up
cairo_destroy(cr);
}
void Clay_Cairo_Render(Clay_RenderCommandArray commands) {
cairo_t *cr = Clay__Cairo;
for(size_t i = 0; i < commands.length; i++) {
Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i);
switch(command->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleElementConfig *config = command->config.rectangleElementConfig;
Clay_Color color = config->color;
Clay_BoundingBox bb = command->boundingBox;
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(color));
cairo_new_sub_path(cr);
cairo_arc(cr, bb.x + config->cornerRadius.topLeft,
bb.y + config->cornerRadius.topLeft,
config->cornerRadius.topLeft,
M_PI, 3 * M_PI / 2); // 180° to 270°
cairo_arc(cr, bb.x + bb.width - config->cornerRadius.topRight,
bb.y + config->cornerRadius.topRight,
config->cornerRadius.topRight,
3 * M_PI / 2, 2 * M_PI); // 270° to 360°
cairo_arc(cr, bb.x + bb.width - config->cornerRadius.bottomRight,
bb.y + bb.height - config->cornerRadius.bottomRight,
config->cornerRadius.bottomRight,
0, M_PI / 2); // 0° to 90°
cairo_arc(cr, bb.x + config->cornerRadius.bottomLeft,
bb.y + bb.height - config->cornerRadius.bottomLeft,
config->cornerRadius.bottomLeft,
M_PI / 2, M_PI); // 90° to 180°
cairo_close_path(cr);
cairo_fill(cr);
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
// Cairo expects null terminated strings, we need to clone
// to temporarily introduce one.
char *text = Clay_Cairo__NullTerminate(&command->text);
char *font_family = Clay_Cairo__NullTerminate(&command->config.textElementConfig->fontFamily);
Clay_BoundingBox bb = command->boundingBox;
Clay_Color color = command->config.textElementConfig->textColor;
cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, command->config.textElementConfig->fontSize);
cairo_move_to(cr, bb.x, bb.y + bb.height);
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(color));
cairo_show_text(cr, text);
cairo_close_path(cr);
free(text);
free(font_family);
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderElementConfig *config = command->config.borderElementConfig;
Clay_BoundingBox bb = command->boundingBox;
double top_left_radius = config->cornerRadius.topLeft / 2.0;
double top_right_radius = config->cornerRadius.topRight / 2.0;
double bottom_right_radius = config->cornerRadius.bottomRight / 2.0;
double bottom_left_radius = config->cornerRadius.bottomLeft / 2.0;
// Draw the top border
if (config->top.width > 0) {
cairo_set_line_width(cr, config->top.width);
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->top.color));
cairo_new_sub_path(cr);
// Left half-arc for top-left corner
cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(225), DEG2RAD(270));
// Line to right half-arc
cairo_line_to(cr, bb.x + bb.width - top_right_radius, bb.y);
// Right half-arc for top-right corner
cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(270), DEG2RAD(305));
cairo_stroke(cr);
}
// Draw the right border
if (config->right.width > 0) {
cairo_set_line_width(cr, config->right.width);
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->right.color));
cairo_new_sub_path(cr);
// Top half-arc for top-right corner
cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(305), DEG2RAD(350));
// Line to bottom half-arc
cairo_line_to(cr, bb.x + bb.width, bb.y + bb.height - bottom_right_radius);
// Bottom half-arc for bottom-right corner
cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(0), DEG2RAD(45));
cairo_stroke(cr);
}
// Draw the bottom border
if (config->bottom.width > 0) {
cairo_set_line_width(cr, config->bottom.width);
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->bottom.color));
cairo_new_sub_path(cr);
// Right half-arc for bottom-right corner
cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(45), DEG2RAD(90));
// Line to left half-arc
cairo_line_to(cr, bb.x + bottom_left_radius, bb.y + bb.height);
// Left half-arc for bottom-left corner
cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(90), DEG2RAD(135));
cairo_stroke(cr);
}
// Draw the left border
if (config->left.width > 0) {
cairo_set_line_width(cr, config->left.width);
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->left.color));
cairo_new_sub_path(cr);
// Bottom half-arc for bottom-left corner
cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(135), DEG2RAD(180));
// Line to top half-arc
cairo_line_to(cr, bb.x, bb.y + top_left_radius);
// Top half-arc for top-left corner
cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(180), DEG2RAD(225));
cairo_stroke(cr);
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
Clay_ImageElementConfig *config = command->config.imageElementConfig;
Clay_BoundingBox bb = command->boundingBox;
char *path = Clay_Cairo__NullTerminate(&config->path);
cairo_surface_t *surf = cairo_image_surface_create_from_png(path),
*origin = cairo_get_target(cr);
// Calculate the original image dimensions
double image_w = cairo_image_surface_get_width(surf),
image_h = cairo_image_surface_get_height(surf);
// Calculate the scaling factor to fit within the bounding box while preserving aspect ratio
double scale_w = bb.width / image_w;
double scale_h = bb.height / image_h;
double scale = (scale_w < scale_h) ? scale_w : scale_h; // Use the smaller scaling factor
// Apply the same scale to both dimensions to preserve aspect ratio
double scale_x = scale;
double scale_y = scale;
// Calculate the scaled image dimensions
double scaled_w = image_w * scale_x;
double scaled_h = image_h * scale_y;
// Adjust the x and y coordinates to center the scaled image within the bounding box
double centered_x = bb.x + (bb.width - scaled_w) / 2.0;
double centered_y = bb.y + (bb.height - scaled_h) / 2.0;
// Blit the scaled and centered image
Clay_Cairo__Blit_Surface(surf, origin, centered_x, centered_y, scale_x, scale_y);
// Clean up the source surface
cairo_surface_destroy(surf);
free(path);
break;
}
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
// Slot your custom elements in here.
}
default: {
fprintf(stderr, "Unknown command type %d\n", (int) command->commandType);
}
}
}
}