PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
-PointerAlignment: Right
+PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
<a href="https://github.com/jpbruyere/vkvg/actions/workflows/cmake.yml">
<img src="https://github.com/jpbruyere/vkvg/actions/workflows/cmake.yml/badge.svg">
</a>
- <a href="https://travis-ci.org/jpbruyere/vkvg">
- <img src="https://img.shields.io/travis/jpbruyere/vkvg.svg?label=Linux&logo=travis&logoColor=white&message=build">
- </a>
- <a href="https://ci.appveyor.com/project/jpbruyere/vkvg">
- <img src="https://img.shields.io/appveyor/ci/jpbruyere/vkvg?label=Win64&logo=appveyor&logoColor=lightgrey">
- </a>
<img src="https://img.shields.io/github/license/jpbruyere/vkvg.svg?style=flat-square">
<a href="https://www.paypal.me/GrandTetraSoftware">
<img src="https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square">
EXPECT_EQ(1, vkvg_surface_get_reference_count(surf));
EXPECT_EQ(2, vkvg_device_get_reference_count(dev));
}
+TEST_F(ContextTest, CtxDrawBasicNullContext) {
+ //test method with context in error
+ VkvgContext ctx = vkvg_create(NULL);
+ EXPECT_NO_FATAL_FAILURE(vkvg_new_path(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_close_path(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_new_sub_path(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_path_extents(ctx, NULL, NULL, NULL, NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_get_current_point(ctx, NULL, NULL));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_line_to(ctx,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rel_line_to(ctx,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_move_to(ctx,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rel_move_to(ctx,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_arc(ctx,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_arc_negative(ctx,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_curve_to(ctx,0,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rel_curve_to(ctx,0,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_quadratic_to(ctx,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rel_quadratic_to(ctx,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rectangle(ctx,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rounded_rectangle(ctx,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rounded_rectangle2(ctx,0,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_ellipse(ctx,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_elliptic_arc_to(ctx,0,0,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rel_elliptic_arc_to(ctx,0,0,0,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_stroke(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_stroke_preserve(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_fill(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_fill_preserve(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_paint(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_clear(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_reset_clip(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_clip(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_clip_preserve(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_opacity(ctx,0));
+
+ EXPECT_EQ(0, vkvg_get_opacity(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_source_color(ctx,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_source_rgb(ctx,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_source_rgba(ctx,0,0,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_source_surface(ctx,NULL,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_source(ctx,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_line_width(ctx,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_miter_limit(ctx,0));
+
+ EXPECT_EQ(0, vkvg_get_miter_limit(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_line_cap(ctx, VKVG_LINE_CAP_ROUND));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_line_join(ctx, VKVG_LINE_JOIN_MITER));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_operator(ctx, VKVG_OPERATOR_OVER));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_fill_rule(ctx, VKVG_FILL_RULE_EVEN_ODD));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_dash(ctx, NULL, 0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_get_dash(ctx, NULL, NULL,NULL));
+
+ EXPECT_EQ(0, vkvg_get_line_width(ctx));
+
+ EXPECT_EQ(VKVG_LINE_CAP_BUTT, vkvg_get_line_cap(ctx));
+ EXPECT_EQ(VKVG_LINE_JOIN_MITER, vkvg_get_line_join(ctx));
+ EXPECT_EQ(VKVG_OPERATOR_OVER, vkvg_get_operator(ctx));
+ EXPECT_EQ(VKVG_FILL_RULE_NON_ZERO, vkvg_get_fill_rule(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_get_source(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_get_target(ctx));
+
+ EXPECT_EQ(false, vkvg_has_current_point(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_save(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_restore(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_translate(ctx,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_scale(ctx,0,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_rotate(ctx,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_transform(ctx,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_matrix(ctx,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_get_matrix(ctx,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_identity_matrix(ctx));
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_select_font_face(ctx,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_load_font_from_path(ctx,NULL,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_load_font_from_memory(ctx,NULL,0,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_set_font_size(ctx,0));
+ EXPECT_NO_FATAL_FAILURE(vkvg_show_text(ctx,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_text_extents(ctx,NULL,NULL));
+ EXPECT_NO_FATAL_FAILURE(vkvg_font_extents(ctx,NULL));
+
+ vkvg_text_run_create(ctx,NULL);
+ vkvg_text_run_create_with_length(ctx,NULL,10);
+
+ //vkvg_text_run_destroy(NULL);
+ vkvg_show_text_run(ctx,NULL);
+}
+#define EXPECT_CP(x,y) { \
+ vkvg_get_current_point(ctx, &a, &b);\
+ EXPECT_FLOAT_EQ(x, a);\
+ EXPECT_FLOAT_EQ(y, b);\
+}
+#define EXPECT_NO_CP() {\
+ EXPECT_EQ(false, vkvg_has_current_point(ctx));\
+ EXPECT_CP(0,0);\
+}
+
+TEST_F(ContextTest, CtxBasicPathCommands) {
+ float a = 0.0,b = 0.0,c = 0.0,d = 0.0;
+ VkvgContext ctx = vkvg_create(surf);
+ EXPECT_NO_CP();
+
+ EXPECT_NO_FATAL_FAILURE(vkvg_new_path(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_close_path(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_new_sub_path(ctx));
+ EXPECT_NO_FATAL_FAILURE(vkvg_path_extents(ctx, &a, &b, &c, &d));
+ EXPECT_FLOAT_EQ(0, a && b && c && d);
+
+ EXPECT_NO_CP();
+
+ vkvg_line_to(ctx, 50, 10);
+ EXPECT_CP(50,10);
+
+ vkvg_move_to(ctx, 10, 50);
+ EXPECT_EQ(true, vkvg_has_current_point(ctx));
+ EXPECT_CP(10,50);
+
+ vkvg_line_to(ctx, 50, 10);
+ EXPECT_CP(50,10);
+
+ vkvg_rel_line_to(ctx, 10, 10);
+ EXPECT_CP(60,20);
+
+ vkvg_close_path(ctx);
+
+ EXPECT_NO_CP();
+
+ vkvg_line_to(ctx, 50, 10);
+ vkvg_rel_line_to(ctx, 10, 10);
+ vkvg_new_sub_path(ctx);
+
+ EXPECT_NO_CP();
+
+ vkvg_line_to(ctx, 50, 10);
+ vkvg_rel_line_to(ctx, 10, 10);
+ vkvg_new_path(ctx);
+
+ EXPECT_NO_CP();
+
+ vkvg_destroy(ctx);
+}
* @brief Increment the reference count on this device.
*
* Increment by one the reference count on the device.
- * @param The vkvg device pointer to increment the reference count for.
+ * @param dev The vkvg device pointer to increment the reference count for.
* @return
*/
vkvg_public VkvgDevice vkvg_device_reference(VkvgDevice dev);
vkvg_public vkvg_status_t vkvg_surface_status(VkvgSurface surf);
/**
* @brief Increment reference count on the surface by one.
- * @param The vkvg surface to increment the reference count for.
+ * @param surf The vkvg surface to increment the reference count for.
* @return ?
*/
vkvg_public VkvgSurface vkvg_surface_reference(VkvgSurface surf);
/**
* @brief Get the current reference count on this surface.
- * @param The vkvg surface to get the reference count for.
+ * @param surf The vkvg surface to get the reference count for.
* @return The reference count on the surface.
*/
vkvg_public uint32_t vkvg_surface_get_reference_count(VkvgSurface surf);
/**
* @brief Decrement the reference count on the surface by one. Destroy it if count reach 0.
- * @param The vkvg surface to destroy.
+ * @param surf The vkvg surface to destroy.
*/
vkvg_public void vkvg_surface_destroy(VkvgSurface surf);
/**
* @brief Query the current status of the surface.
- * @param The vkvg surface to query the status for.
+ * @param surf The vkvg surface to query the status for.
* @return The current surface status.
*/
vkvg_public vkvg_status_t vkvg_surface_status(VkvgSurface surf);
*
* @remark Internaly, the vulkan method used to clear the surface is the slowest, prefer using the @ref vkvg_clear
* function of the context that will try to use the render pass load operations when possible.
- * @param The surface to clear.
+ * @param surf The surface to clear.
*/
vkvg_public void vkvg_surface_clear(VkvgSurface surf);
/**
* @brief Get the final single sampled vulkan image of this surface.
- * @param The vkvg surface to get the vulkan texture of.
+ * @param surf The vkvg surface to get the vulkan texture of.
* @return The VkImage object containing the result of the drawing operations on the surface.
*/
vkvg_public VkImage vkvg_surface_get_vk_image(VkvgSurface surf);
/**
* @brief Get the vulkan format of the vulkan texture used as backend for this surface.
- * @param The surface to get the format for.
+ * @param surf The surface to get the format for.
* @return The VkFormat.
*/
vkvg_public VkFormat vkvg_surface_get_vk_format(VkvgSurface surf);
/**
* @brief Get the actual surface width.
- * @param The vkvg surface to get the width for.
+ * @param surf The vkvg surface to get the width for.
* @return The width in pixel of the surface.
*/
vkvg_public uint32_t vkvg_surface_get_width(VkvgSurface surf);
/**
* @brief Get the actual surface height.
- * @param The vkvg surface to get the height for.
+ * @param surf The vkvg surface to get the height for.
* @return The height in pixel of the surface.
*/
vkvg_public uint32_t vkvg_surface_get_height(VkvgSurface surf);
/**
* @brief Write surface content to a png file on disk.
- * @param The surface to save on disk.
- * @param The png file path.
+ * @param surf The surface to save on disk.
+ * @param path The png file path.
* @return SUCCESS or not.
*/
vkvg_public vkvg_status_t vkvg_surface_write_to_png(VkvgSurface surf, const char *path);
/**
* @brief Save surface to memory
- * @param The surface to save
- * @param A valid pointer on cpu memory large enough to contain surface pixels (stride * height)
+ * @param surf The surface to save
+ * @param bitmap A valid pointer on cpu memory large enough to contain surface pixels (stride * height)
* @return SUCCESS or not.
*/
vkvg_public vkvg_status_t vkvg_surface_write_to_memory(VkvgSurface surf, unsigned char *const bitmap);
* @param x2 right of the resulting extents
* @param y2 bottom of the resulting extents
*/
-vkvg_public void vkvg_path_extents(VkvgContext ctx, float *x1, float *y1, float *x2, float *y2);
+vkvg_public void vkvg_path_extents(VkvgContext ctx, float* const x1, float *const y1, float *const x2, float *const y2);
/**
* @brief Get the current point.
*
* @param a the alpha component holding the transparency for the current color.
*/
vkvg_public void vkvg_set_source_rgb(VkvgContext ctx, float r, float g, float b);
+/**
+ * @brief use supplied surface as current pattern.
+ *
+ * set #VkvgSurface as the current context source.
+ * @param ctx a valid vkvg @ref context
+ * @param surf the vkvg surface to use as source.
+ * @param x an x offset to apply for drawing operations using this surface.
+ * @param y an y offset to apply for drawing operations using this surface.
+ */
+vkvg_public void vkvg_set_source_surface(VkvgContext ctx, VkvgSurface surf, float x, float y);
+/**
+ * @brief set supplied pattern as current source.
+ *
+ * set #VkvgPattern as the new source for the targeted context.
+ * @param ctx a valid vkvg @ref context
+ * @param pat the new pattern to use as source for further drawing operations.
+ */
+vkvg_public void vkvg_set_source(VkvgContext ctx, VkvgPattern pat);
/**
* @brief set line width for the next draw command.
*
* @param join new line join as defined in #vkvg_line_joint_t.
*/
vkvg_public void vkvg_set_line_join(VkvgContext ctx, vkvg_line_join_t join);
-/**
- * @brief use supplied surface as current pattern.
- *
- * set #VkvgSurface as the current context source.
- * @param ctx a valid vkvg @ref context
- * @param surf the vkvg surface to use as source.
- * @param x an x offset to apply for drawing operations using this surface.
- * @param y an y offset to apply for drawing operations using this surface.
- */
-vkvg_public void vkvg_set_source_surface(VkvgContext ctx, VkvgSurface surf, float x, float y);
-/**
- * @brief set supplied pattern as current source.
- *
- * set #VkvgPattern as the new source for the targeted context.
- * @param ctx a valid vkvg @ref context
- * @param pat the new pattern to use as source for further drawing operations.
- */
-vkvg_public void vkvg_set_source(VkvgContext ctx, VkvgPattern pat);
/**
* @brief
*
free(ctx->vertexCache);
if (ctx->indexCache)
free(ctx->indexCache);
+ free(ctx);
LOG(VKVG_LOG_ERR, "CREATE context failed, no memory\n");
return (VkvgContext)&_vkvg_status_no_memory;
}
if (vkvg_status(ctx))
return;
+ RECORD(ctx, VKVG_CMD_NEW_PATH);
LOG(VKVG_LOG_INFO_CMD, "\tCMD: new_path:\n");
_clear_path(ctx);
- RECORD(ctx, VKVG_CMD_NEW_PATH);
}
void vkvg_close_path(VkvgContext ctx) {
if (vkvg_status(ctx))
return !_current_path_is_empty(ctx);
}
void vkvg_get_current_point(VkvgContext ctx, float *x, float *y) {
- if (ctx->status || _current_path_is_empty(ctx)) {
+ if (vkvg_status(ctx))
+ return;
+ assert(x);
+ assert(y);
+ if (_current_path_is_empty(ctx)) {
*x = *y = 0;
return;
}
vkvg_close_path(ctx);
}
-void vkvg_path_extents(VkvgContext ctx, float *x1, float *y1, float *x2, float *y2) {
+void vkvg_path_extents(VkvgContext ctx, float* const x1, float *const y1, float *const x2, float *const y2) {
if (vkvg_status(ctx))
return;
_update_cur_pattern(ctx, NULL);
}
void vkvg_set_source_surface(VkvgContext ctx, VkvgSurface surf, float x, float y) {
- if (ctx->status || surf->status)
+ if (vkvg_status(ctx) || vkvg_surface_status(surf))
return;
RECORD(ctx, VKVG_CMD_SET_SOURCE_SURFACE, x, y, surf);
ctx->pushConsts.source.x = x;
ctx->pushCstDirty = true;
}
void vkvg_set_source(VkvgContext ctx, VkvgPattern pat) {
- if (ctx->status || pat->status)
+ if (vkvg_status(ctx) || vkvg_pattern_status(pat))
return;
RECORD(ctx, VKVG_CMD_SET_SOURCE, pat);
_update_cur_pattern(ctx, pat);
return 0;
return ctx->lineWidth;
}
+float vkvg_get_miter_limit(VkvgContext ctx) {
+ if (vkvg_status(ctx))
+ return 0;
+ return ctx->miterLimit;
+}
void vkvg_set_dash(VkvgContext ctx, const float *dashes, uint32_t num_dashes, float offset) {
if (vkvg_status(ctx))
return;
}
vkvg_operator_t vkvg_get_operator(VkvgContext ctx) {
if (vkvg_status(ctx))
- return (vkvg_operator_t)0;
+ return VKVG_OPERATOR_OVER;
return ctx->curOperator;
}
VkvgPattern vkvg_get_source(VkvgContext ctx) {
ctx->pushConsts.mat = (*matrix);
_set_mat_inv_and_vkCmdPush(ctx);
}
-void vkvg_get_matrix(VkvgContext ctx, vkvg_matrix_t *const matrix) { *matrix = ctx->pushConsts.mat; }
+void vkvg_get_matrix(VkvgContext ctx, vkvg_matrix_t *const matrix) {
+ if (vkvg_status(ctx) || !matrix)
+ return;
+ *matrix = ctx->pushConsts.mat;
+}
void vkvg_elliptic_arc_to(VkvgContext ctx, float x2, float y2, bool largeArc, bool sweepFlag, float rx, float ry,
float phi) {
/*
- * Copyright (c) 2018-2022 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+ * Copyright (c) 2018-2025 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
/*
- * Copyright (c) 2018-2022 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+ * Copyright (c) 2018-2025 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-#ifndef VKVG_INTERNAL_H
-#define VKVG_INTERNAL_H
+#pragma once
// disable warning on iostream functions on windows
#define _CRT_SECURE_NO_WARNINGS
static vkvg_status_t _vkvg_status_device_error = VKVG_STATUS_DEVICE_ERROR;
static vkvg_status_t _vkvg_status_invalid_surface = VKVG_STATUS_INVALID_SURFACE;
-#endif
LOG(VKVG_LOG_ERR, "DESTROY surface failed, invalid surface\n");
return;
}
- if (!surf->dev || surf->dev->status) {
+ if (vkvg_device_status(surf->dev)) {
LOG(VKVG_LOG_ERR, "DESTROY surface failed, device error\n");
return;
}
}
VkImage vkvg_surface_get_vk_image(VkvgSurface surf) {
- if (surf->status)
+ if (vkvg_surface_status(surf))
return NULL;
if (surf->dev->deferredResolve)
_explicit_ms_resolve(surf);
return vkh_image_get_vkimage(surf->img);
}
void vkvg_surface_resolve(VkvgSurface surf) {
- if (surf->status || !surf->dev->deferredResolve)
+ if (vkvg_surface_status(surf) || !surf->dev->deferredResolve)
return;
_explicit_ms_resolve(surf);
}
VkFormat vkvg_surface_get_vk_format(VkvgSurface surf) {
- if (surf->status)
+ if (vkvg_surface_status(surf))
return VK_FORMAT_UNDEFINED;
return surf->format;
}
uint32_t vkvg_surface_get_width(VkvgSurface surf) {
- if (surf->status)
+ if (vkvg_surface_status(surf))
return 0;
return surf->width;
}
uint32_t vkvg_surface_get_height(VkvgSurface surf) {
- if (surf->status)
+ if (vkvg_surface_status(surf))
return 0;
return surf->height;
}
vkvg_status_t vkvg_surface_write_to_png(VkvgSurface surf, const char *path) {
- if (surf->status) {
- LOG(VKVG_LOG_ERR, "vkvg_surface_write_to_png failed, invalid status: %d\n", surf->status);
+ if (vkvg_surface_status(surf)) {
+ LOG(VKVG_LOG_ERR, "vkvg_surface_write_to_png failed, invalid status: %d\n", vkvg_surface_status(surf));
return VKVG_STATUS_INVALID_STATUS;
}
- if (surf->dev->status) {
- LOG(VKVG_LOG_ERR, "vkvg_surface_write_to_png failed, invalid device status: %d\n", surf->dev->status);
+ if (vkvg_device_status(surf->dev)) {
+ LOG(VKVG_LOG_ERR, "vkvg_surface_write_to_png failed, invalid device status: %d\n", vkvg_device_status(surf->dev));
return VKVG_STATUS_INVALID_STATUS;
}
if (surf->dev->pngStagFormat == VK_FORMAT_UNDEFINED) {
}
vkvg_status_t vkvg_surface_write_to_memory(VkvgSurface surf, unsigned char *const bitmap) {
- if (surf->status) {
- LOG(VKVG_LOG_ERR, "vkvg_surface_write_to_memory failed, invalid status: %d\n", surf->status);
+ if (vkvg_surface_status(surf)) {
+ LOG(VKVG_LOG_ERR, "vkvg_surface_write_to_memory failed, invalid status: %d\n", vkvg_surface_status(surf));
return VKVG_STATUS_INVALID_STATUS;
}
if (!bitmap) {