You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3418 lines
116 KiB
C

// Public Domain. See "unlicense" statement at the end of this file.
// ABOUT
//
// dr_2d is a simple library for drawing simple 2D graphics.
//
//
//
// USAGE
//
// This is a single-file library. To use it, do something like the following in one .c file.
// #define DR_2D_IMPLEMENTATION
// #include "dr_2d.h"
//
// You can then #include dr_2d.h in other parts of the program as you would with any other header file.
//
//
//
// QUICK NOTES
//
// - Drawing must be done inside a dr2d_begin_draw() and dr2d_end_draw() pair. Rationale: 1) required for compatibility
// with GDI's BeginPaint() and EndPaint() APIs; 2) gives implementations opportunity to save and restore state, such as
// OpenGL state and whatnot.
// - This library is not thread safe.
//
//
//
// OPTIONS
//
// #define DR2D_NO_GDI
// Excludes the GDI back-end.
//
// #define DR2D_NO_CAIRO
// Excludes the Cairo back-end.
//
//
//
// TODO
// - Document resource management.
#ifndef dr_2d_h
#define dr_2d_h
#include <stdlib.h>
#include <stdbool.h>
#if defined(__WIN32__) || defined(_WIN32) || defined(_WIN64)
#include <windows.h>
// No Cairo on Win32 builds.
#ifndef DR2D_NO_CAIRO
#define DR2D_NO_CAIRO
#endif
#else
// No GDI on non-Win32 builds.
#ifndef DR2D_NO_GDI
#define DR2D_NO_GDI
#endif
#endif
#ifndef DR2D_MAX_FONT_FAMILY_LENGTH
#define DR2D_MAX_FONT_FAMILY_LENGTH 128
#endif
#ifdef __cplusplus
extern "C" {
#endif
/////////////////////////////////////////////////////////////////
//
// CORE 2D API
//
/////////////////////////////////////////////////////////////////
typedef unsigned char dr2d_byte;
typedef struct dr2d_context dr2d_context;
typedef struct dr2d_surface dr2d_surface;
typedef struct dr2d_font dr2d_font;
typedef struct dr2d_image dr2d_image;
typedef struct dr2d_color dr2d_color;
typedef struct dr2d_font_metrics dr2d_font_metrics;
typedef struct dr2d_glyph_metrics dr2d_glyph_metrics;
typedef struct dr2d_drawing_callbacks dr2d_drawing_callbacks;
/// Structure representing an RGBA color. Color components are specified in the range of 0 - 255.
struct dr2d_color
{
dr2d_byte r;
dr2d_byte g;
dr2d_byte b;
dr2d_byte a;
};
struct dr2d_font_metrics
{
int ascent;
int descent;
int lineHeight;
int spaceWidth;
};
struct dr2d_glyph_metrics
{
int width;
int height;
int originX;
int originY;
int advanceX;
int advanceY;
};
typedef enum
{
dr2d_font_weight_medium = 0,
dr2d_font_weight_thin,
dr2d_font_weight_extra_light,
dr2d_font_weight_light,
dr2d_font_weight_semi_light,
dr2d_font_weight_book,
dr2d_font_weight_semi_bold,
dr2d_font_weight_bold,
dr2d_font_weight_extra_bold,
dr2d_font_weight_heavy,
dr2d_font_weight_extra_heavy,
dr2d_font_weight_normal = dr2d_font_weight_medium,
dr2d_font_weight_default = dr2d_font_weight_medium
} dr2d_font_weight;
typedef enum
{
dr2d_font_slant_none = 0,
dr2d_font_slant_italic,
dr2d_font_slant_oblique
} dr2d_font_slant;
typedef enum
{
dr2d_image_format_rgba8,
dr2d_image_format_bgra8,
dr2d_image_format_argb8,
} dr2d_image_format;
#define DR2D_IMAGE_DRAW_BACKGROUND (1 << 0)
#define DR2D_IMAGE_HINT_NO_ALPHA (1 << 1)
#define DR2D_READ (1 << 0)
#define DR2D_WRITE (1 << 1)
#define DR2D_FONT_NO_CLEARTYPE (1 << 0)
typedef struct
{
/// The destination position on the x axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set.
float dstX;
/// The destination position on the y axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set.
float dstY;
/// The destination width.
float dstWidth;
/// The destination height.
float dstHeight;
/// The source offset on the x axis.
float srcX;
/// The source offset on the y axis.
float srcY;
/// The source width.
float srcWidth;
/// The source height.
float srcHeight;
/// The foreground tint color. This is not applied to the background color, and the alpha component is ignored.
dr2d_color foregroundTint;
/// The background color. Only used if the DR2D_IMAGE_DRAW_BACKGROUND option is set.
dr2d_color backgroundColor;
/// Flags for controlling how the image should be drawn.
unsigned int options;
} dr2d_draw_image_args;
typedef bool (* dr2d_on_create_context_proc) (dr2d_context* pContext, const void* pUserData);
typedef void (* dr2d_on_delete_context_proc) (dr2d_context* pContext);
typedef bool (* dr2d_on_create_surface_proc) (dr2d_surface* pSurface, float width, float height);
typedef void (* dr2d_on_delete_surface_proc) (dr2d_surface* pSurface);
typedef bool (* dr2d_on_create_font_proc) (dr2d_font* pFont);
typedef void (* dr2d_on_delete_font_proc) (dr2d_font* pFont);
typedef bool (* dr2d_on_create_image_proc) (dr2d_image* pImage, unsigned int stride, const void* pData);
typedef void (* dr2d_on_delete_image_proc) (dr2d_image* pImage);
typedef void (* dr2d_begin_draw_proc) (dr2d_surface* pSurface);
typedef void (* dr2d_end_draw_proc) (dr2d_surface* pSurface);
typedef void (* dr2d_clear_proc) (dr2d_surface* pSurface, dr2d_color color);
typedef void (* dr2d_draw_rect_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color);
typedef void (* dr2d_draw_rect_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth);
typedef void (* dr2d_draw_rect_with_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor);
typedef void (* dr2d_draw_round_rect_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float width);
typedef void (* dr2d_draw_round_rect_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float width, float outlineWidth);
typedef void (* dr2d_draw_round_rect_with_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float width, float outlineWidth, dr2d_color outlineColor);
typedef void (* dr2d_draw_text_proc) (dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor);
typedef void (* dr2d_draw_image_proc) (dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs);
typedef void (* dr2d_set_clip_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom);
typedef void (* dr2d_get_clip_proc) (dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut);
typedef dr2d_image_format (* dr2d_get_optimal_image_format_proc) (dr2d_context* pContext);
typedef void* (* dr2d_map_image_data_proc) (dr2d_image* pImage, unsigned int accessFlags);
typedef void (* dr2d_unmap_image_data_proc) (dr2d_image* pImage);
typedef bool (* dr2d_get_font_metrics_proc) (dr2d_font* pFont, dr2d_font_metrics* pMetricsOut);
typedef bool (* dr2d_get_glyph_metrics_proc) (dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pMetricsOut);
typedef bool (* dr2d_measure_string_proc) (dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut);
typedef bool (* dr2d_get_text_cursor_position_from_point_proc)(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
typedef bool (* dr2d_get_text_cursor_position_from_char_proc) (dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
struct dr2d_drawing_callbacks
{
dr2d_on_create_context_proc on_create_context;
dr2d_on_delete_context_proc on_delete_context;
dr2d_on_create_surface_proc on_create_surface;
dr2d_on_delete_surface_proc on_delete_surface;
dr2d_on_create_font_proc on_create_font;
dr2d_on_delete_font_proc on_delete_font;
dr2d_on_create_image_proc on_create_image;
dr2d_on_delete_image_proc on_delete_image;
dr2d_begin_draw_proc begin_draw;
dr2d_end_draw_proc end_draw;
dr2d_clear_proc clear;
dr2d_draw_rect_proc draw_rect;
dr2d_draw_rect_outline_proc draw_rect_outline;
dr2d_draw_rect_with_outline_proc draw_rect_with_outline;
dr2d_draw_round_rect_proc draw_round_rect;
dr2d_draw_round_rect_outline_proc draw_round_rect_outline;
dr2d_draw_round_rect_with_outline_proc draw_round_rect_with_outline;
dr2d_draw_text_proc draw_text;
dr2d_draw_image_proc draw_image;
dr2d_set_clip_proc set_clip;
dr2d_get_clip_proc get_clip;
dr2d_get_optimal_image_format_proc get_optimal_image_format;
dr2d_map_image_data_proc map_image_data;
dr2d_unmap_image_data_proc unmap_image_data;
dr2d_get_font_metrics_proc get_font_metrics;
dr2d_get_glyph_metrics_proc get_glyph_metrics;
dr2d_measure_string_proc measure_string;
dr2d_get_text_cursor_position_from_point_proc get_text_cursor_position_from_point;
dr2d_get_text_cursor_position_from_char_proc get_text_cursor_position_from_char;
};
struct dr2d_image
{
/// A pointer to the context that owns the image.
dr2d_context* pContext;
/// The width of the image.
unsigned int width;
/// The height of the image.
unsigned int height;
/// The format of the image data.
dr2d_image_format format;
/// Whether or not the image's data is already mapped.
bool isMapped;
/// The extra bytes. The size of this buffer is equal to pContext->imageExtraBytes.
dr2d_byte pExtraData[1];
};
struct dr2d_font
{
/// A pointer to the context that owns the font.
dr2d_context* pContext;
/// The font family.
char family[DR2D_MAX_FONT_FAMILY_LENGTH];
/// The size of the font.
unsigned int size;
/// The font's weight.
dr2d_font_weight weight;
/// The font's slant.
dr2d_font_slant slant;
/// The font's rotation, in degrees.
float rotation;
/// Flags. Can be a combination of the following.
/// DR2D_FONT_NO_CLEARTYPE
unsigned int flags;
/// The extra bytes. The size of this buffer is equal to pContext->fontExtraBytes.
dr2d_byte pExtraData[1];
};
struct dr2d_surface
{
/// A pointer to the context that owns the surface.
dr2d_context* pContext;
/// The width of the surface.
float width;
/// The height of the surface.
float height;
/// The extra bytes. The size of this buffer is equal to pContext->surfaceExtraBytes.
dr2d_byte pExtraData[1];
};
struct dr2d_context
{
/// The drawing callbacks.
dr2d_drawing_callbacks drawingCallbacks;
/// The number of extra bytes to allocate for each image.
size_t imageExtraBytes;
/// The number of extra bytes to allocate for each font.
size_t fontExtraBytes;
/// The number of extra bytes to allocate for each surface.
size_t surfaceExtraBytes;
/// The number of extra bytes to allocate for the context.
size_t contextExtraBytes;
/// The extra bytes.
dr2d_byte pExtraData[1];
};
/// Creats a context.
dr2d_context* dr2d_create_context(dr2d_drawing_callbacks drawingCallbacks, size_t contextExtraBytes, size_t surfaceExtraBytes, size_t fontExtraBytes, size_t imageExtraBytes, const void* pUserData);
/// Deletes the given context.
void dr2d_delete_context(dr2d_context* pContext);
/// Retrieves a pointer to the given context's extra data buffer.
void* dr2d_get_context_extra_data(dr2d_context* pContext);
/// Creates a surface.
dr2d_surface* dr2d_create_surface(dr2d_context* pContext, float width, float height);
/// Deletes the given surface.
void dr2d_delete_surface(dr2d_surface* pSurface);
/// Retrieves the width of the surface.
float dr2d_get_surface_width(const dr2d_surface* pSurface);
/// Retrieves the height of the surface.
float dr2d_get_surface_height(const dr2d_surface* pSurface);
/// Retrieves a pointer to the given surface's extra data buffer.
void* dr2d_get_surface_extra_data(dr2d_surface* pSurface);
//// Drawing ////
/// Marks the beginning of a paint operation.
void dr2d_begin_draw(dr2d_surface* pSurface);
/// Marks the end of a paint operation.
void dr2d_end_draw(dr2d_surface* pSurface);
/// Clears the given surface with the given color.
void dr2d_clear(dr2d_surface* pSurface, dr2d_color color);
/// Draws a filled rectangle without an outline.
void dr2d_draw_rect(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color);
/// Draws the outline of the given rectangle.
void dr2d_draw_rect_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth);
/// Draws a filled rectangle with an outline.
void dr2d_draw_rect_with_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor);
/// Draws a filled rectangle without an outline with rounded corners.
void dr2d_draw_round_rect(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius);
/// Draws the outline of the given rectangle with rounded corners.
void dr2d_draw_round_rect_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth);
/// Draws a filled rectangle with an outline.
void dr2d_draw_round_rect_with_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor);
/// Draws a run of text.
void dr2d_draw_text(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor);
/// Draws an image.
void dr2d_draw_image(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs);
/// Sets the clipping rectangle.
void dr2d_set_clip(dr2d_surface* pSurface, float left, float top, float right, float bottom);
/// Retrieves the clipping rectangle.
void dr2d_get_clip(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut);
/// Creates a font that can be passed to dr2d_draw_text().
dr2d_font* dr2d_create_font(dr2d_context* pContext, const char* family, unsigned int size, dr2d_font_weight weight, dr2d_font_slant slant, float rotation, unsigned int flags);
/// Deletes a font that was previously created with dr2d_create_font()
void dr2d_delete_font(dr2d_font* pFont);
/// Retrieves a pointer to the given font's extra data buffer.
void* dr2d_get_font_extra_data(dr2d_font* pFont);
/// Retrieves the size of the given font.
unsigned int dr2d_get_font_size(dr2d_font* pFont);
/// Retrieves the metrics of the given font.
bool dr2d_get_font_metrics(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut);
/// Retrieves the metrics of the glyph for the given character when rendered with the given font.
bool dr2d_get_glyph_metrics(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pMetricsOut);
/// Retrieves the dimensions of the given string when drawn with the given font.
bool dr2d_measure_string(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut);
/// Retrieves the position to place a text cursor based on the given point for the given string when drawn with the given font.
bool dr2d_get_text_cursor_position_from_point(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
/// Retrieves the position to palce a text cursor based on the character at the given index for the given string when drawn with the given font.
bool dr2d_get_text_cursor_position_from_char(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
/// Creates an image that can be passed to dr2d_draw_image().
///
/// @remarks
/// The dimensions and format of an image are immutable. If these need to change, then the image needs to be deleted and re-created.
/// @par
/// If pData is NULL, the default image data is undefined.
/// @par
/// Use dr2d_map_image_data() and dr2d_unmap_image_data() to update or retrieve image data.
dr2d_image* dr2d_create_image(dr2d_context* pContext, unsigned int width, unsigned int height, dr2d_image_format format, unsigned int stride, const void* pData);
/// Deletes the given image.
void dr2d_delete_image(dr2d_image* pImage);
/// Retrieves a pointer to the given image's extra data buffer.
void* dr2d_get_image_extra_data(dr2d_image* pImage);
/// Retrieves the size of the given image.
void dr2d_get_image_size(dr2d_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut);
/// Retrieves a pointer to a buffer representing the given image's data.
///
/// Call dr2d_unmap_image_data() when you are done with this function.
///
/// Use this function to access an image's data. The returned pointer does not necessarilly point to the image's actual data, so when
/// writing to this pointer, nothing is guaranteed to be updated until dr2d_unmap_image_data() is called.
///
/// The returned data will contain the image data at the time of the mapping.
///
/// This will fail if the image's data is already mapped.
void* dr2d_map_image_data(dr2d_image* pImage, unsigned int accessFlags);
/// Unmaps the given image's data.
///
/// A flush is done at this point to ensure the actual underlying image data is updated.
void dr2d_unmap_image_data(dr2d_image* pImage);
/// Retrieves the optimal image format for the given context. This depends on the backend.
dr2d_image_format dr2d_get_optimal_image_format(dr2d_context* pContext);
/////////////////////////////////////////////////////////////////
//
// UTILITY API
//
/////////////////////////////////////////////////////////////////
/// Creates a color object from a set of RGBA color components.
dr2d_color dr2d_rgba(dr2d_byte r, dr2d_byte g, dr2d_byte b, dr2d_byte a);
/// Creates a fully opaque color object from a set of RGB color components.
dr2d_color dr2d_rgb(dr2d_byte r, dr2d_byte g, dr2d_byte b);
/////////////////////////////////////////////////////////////////
//
// WINDOWS GDI 2D API
//
// When using GDI as the rendering back-end you will usually want to only call drawing functions in response to a WM_PAINT message.
//
/////////////////////////////////////////////////////////////////
#ifndef DR2D_NO_GDI
/// Creates a 2D context with GDI as the backend and, optionally, a custom HDC.
///
/// hDC [in, optional] The main device context. Can be null.
dr2d_context* dr2d_create_context_gdi(HDC hDC);
/// Creates a surface that draws directly to the given window.
///
/// @remarks
/// When using this kind of surface, the internal HBITMAP is not used.
dr2d_surface* dr2d_create_surface_gdi_HWND(dr2d_context* pContext, HWND hWnd);
dr2d_surface* dr2d_create_surface_gdi_HDC(dr2d_context* pContext, HDC hDC);
/// Retrieves the internal HDC that we have been rendering to for the given surface.
///
/// @remarks
/// This assumes the given surface was created from a context that was created from dr2d_create_context_gdi()
HDC dr2d_get_HDC(dr2d_surface* pSurface);
/// Retrieves the internal HBITMAP object that we have been rendering to.
///
/// @remarks
/// This assumes the given surface was created from a context that was created from dr2d_create_context_gdi().
HBITMAP dr2d_get_HBITMAP(dr2d_surface* pSurface);
/// Retrieves the internal HFONT object from the given dr2d_font object.
HFONT dr2d_get_HFONT(dr2d_font* pFont);
#endif // GDI
/////////////////////////////////////////////////////////////////
//
// CAIRO 2D API
//
/////////////////////////////////////////////////////////////////
#ifndef DR2D_NO_CAIRO
#include <cairo/cairo.h>
/// Creates a 2D context with Cairo as the backend.
dr2d_context* dr2d_create_context_cairo();
/// Creates a surface that draws directly to the given cairo context.
dr2d_surface* dr2d_create_surface_cairo(dr2d_context* pContext, cairo_t* cr);
/// Retrieves the internal cairo_surface_t object from the given surface.
///
/// @remarks
/// This assumes the given surface was created from a context that was created with dr2d_create_context_cairo().
cairo_surface_t* dr2d_get_cairo_surface_t(dr2d_surface* pSurface);
/// Retrieves the internal cairo_t object from the given surface.
cairo_t* dr2d_get_cairo_t(dr2d_surface* pSurface);
#endif // Cairo
#ifdef __cplusplus
}
#endif
#endif //dr_2d_h
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
#ifdef DR_2D_IMPLEMENTATION
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#ifndef DR2D_PRIVATE
#define DR2D_PRIVATE static
#endif
static int dr2d_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
{
#ifdef _WIN32
return strcpy_s(dst, dstSizeInBytes, src);
#else
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
size_t i;
for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (i < dstSizeInBytes) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return ERANGE;
#endif
}
dr2d_context* dr2d_create_context(dr2d_drawing_callbacks drawingCallbacks, size_t contextExtraBytes, size_t surfaceExtraBytes, size_t fontExtraBytes, size_t imageExtraBytes, const void* pUserData)
{
dr2d_context* pContext = (dr2d_context*)malloc(sizeof(dr2d_context) + contextExtraBytes);
if (pContext != NULL)
{
pContext->drawingCallbacks = drawingCallbacks;
pContext->imageExtraBytes = imageExtraBytes;
pContext->fontExtraBytes = fontExtraBytes;
pContext->surfaceExtraBytes = surfaceExtraBytes;
pContext->contextExtraBytes = contextExtraBytes;
memset(pContext->pExtraData, 0, contextExtraBytes);
// The create_context callback is optional. If it is set to NULL, we just finish up here and return successfully. Otherwise
// we want to call the create_context callback and check it's return value. If it's return value if false it means there
// was an error and we need to return null.
if (pContext->drawingCallbacks.on_create_context != NULL)
{
if (!pContext->drawingCallbacks.on_create_context(pContext, pUserData))
{
// An error was thrown from the callback.
free(pContext);
pContext = NULL;
}
}
}
return pContext;
}
void dr2d_delete_context(dr2d_context* pContext)
{
if (pContext != NULL) {
if (pContext->drawingCallbacks.on_delete_context != NULL) {
pContext->drawingCallbacks.on_delete_context(pContext);
}
free(pContext);
}
}
void* dr2d_get_context_extra_data(dr2d_context* pContext)
{
return pContext->pExtraData;
}
dr2d_surface* dr2d_create_surface(dr2d_context* pContext, float width, float height)
{
if (pContext != NULL)
{
dr2d_surface* pSurface = (dr2d_surface*)malloc(sizeof(dr2d_surface) + pContext->surfaceExtraBytes);
if (pSurface != NULL)
{
pSurface->pContext = pContext;
pSurface->width = width;
pSurface->height = height;
memset(pSurface->pExtraData, 0, pContext->surfaceExtraBytes);
// The create_surface callback is optional. If it is set to NULL, we just finish up here and return successfully. Otherwise
// we want to call the create_surface callback and check it's return value. If it's return value if false it means there
// was an error and we need to return null.
if (pContext->drawingCallbacks.on_create_surface != NULL)
{
if (!pContext->drawingCallbacks.on_create_surface(pSurface, width, height))
{
free(pSurface);
pSurface = NULL;
}
}
}
return pSurface;
}
return NULL;
}
void dr2d_delete_surface(dr2d_surface* pSurface)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.on_delete_surface != NULL) {
pSurface->pContext->drawingCallbacks.on_delete_surface(pSurface);
}
free(pSurface);
}
}
float dr2d_get_surface_width(const dr2d_surface* pSurface)
{
if (pSurface != NULL) {
return pSurface->width;
}
return 0;
}
float dr2d_get_surface_height(const dr2d_surface* pSurface)
{
if (pSurface != NULL) {
return pSurface->height;
}
return 0;
}
void* dr2d_get_surface_extra_data(dr2d_surface* pSurface)
{
if (pSurface != NULL) {
return pSurface->pExtraData;
}
return NULL;
}
void dr2d_begin_draw(dr2d_surface* pSurface)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.begin_draw != NULL) {
pSurface->pContext->drawingCallbacks.begin_draw(pSurface);
}
}
}
void dr2d_end_draw(dr2d_surface* pSurface)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.end_draw != NULL) {
pSurface->pContext->drawingCallbacks.end_draw(pSurface);
}
}
}
void dr2d_clear(dr2d_surface * pSurface, dr2d_color color)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.clear != NULL) {
pSurface->pContext->drawingCallbacks.clear(pSurface, color);
}
}
}
void dr2d_draw_rect(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_rect != NULL) {
pSurface->pContext->drawingCallbacks.draw_rect(pSurface, left, top, right, bottom, color);
}
}
}
void dr2d_draw_rect_outline(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_rect_outline != NULL) {
pSurface->pContext->drawingCallbacks.draw_rect_outline(pSurface, left, top, right, bottom, color, outlineWidth);
}
}
}
void dr2d_draw_rect_with_outline(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_rect_with_outline != NULL) {
pSurface->pContext->drawingCallbacks.draw_rect_with_outline(pSurface, left, top, right, bottom, color, outlineWidth, outlineColor);
}
}
}
void dr2d_draw_round_rect(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_round_rect != NULL) {
pSurface->pContext->drawingCallbacks.draw_round_rect(pSurface, left, top, right, bottom, color, radius);
}
}
}
void dr2d_draw_round_rect_outline(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_round_rect_outline != NULL) {
pSurface->pContext->drawingCallbacks.draw_round_rect_outline(pSurface, left, top, right, bottom, color, radius, outlineWidth);
}
}
}
void dr2d_draw_round_rect_with_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_round_rect_with_outline != NULL) {
pSurface->pContext->drawingCallbacks.draw_round_rect_with_outline(pSurface, left, top, right, bottom, color, radius, outlineWidth, outlineColor);
}
}
}
void dr2d_draw_text(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_text != NULL) {
pSurface->pContext->drawingCallbacks.draw_text(pSurface, pFont, text, textSizeInBytes, posX, posY, color, backgroundColor);
}
}
}
void dr2d_draw_image(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs)
{
if (pSurface == NULL || pImage == NULL || pArgs == NULL) {
return;
}
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.draw_image) {
pSurface->pContext->drawingCallbacks.draw_image(pSurface, pImage, pArgs);
}
}
void dr2d_set_clip(dr2d_surface* pSurface, float left, float top, float right, float bottom)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.set_clip != NULL) {
pSurface->pContext->drawingCallbacks.set_clip(pSurface, left, top, right, bottom);
}
}
}
void dr2d_get_clip(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut)
{
if (pSurface != NULL)
{
assert(pSurface->pContext != NULL);
if (pSurface->pContext->drawingCallbacks.get_clip != NULL) {
pSurface->pContext->drawingCallbacks.get_clip(pSurface, pLeftOut, pTopOut, pRightOut, pBottomOut);
}
}
}
dr2d_font* dr2d_create_font(dr2d_context* pContext, const char* family, unsigned int size, dr2d_font_weight weight, dr2d_font_slant slant, float rotation, unsigned int flags)
{
if (pContext == NULL) {
return NULL;
}
dr2d_font* pFont = (dr2d_font*)malloc(sizeof(dr2d_font) + pContext->fontExtraBytes);
if (pFont == NULL) {
return NULL;
}
pFont->pContext = pContext;
pFont->family[0] = '\0';
pFont->size = size;
pFont->weight = weight;
pFont->slant = slant;
pFont->rotation = rotation;
pFont->flags = flags;
if (family != NULL) {
dr2d_strcpy_s(pFont->family, sizeof(pFont->family), family);
}
if (pContext->drawingCallbacks.on_create_font != NULL) {
if (!pContext->drawingCallbacks.on_create_font(pFont)) {
free(pFont);
return NULL;
}
}
return pFont;
}
void dr2d_delete_font(dr2d_font* pFont)
{
if (pFont == NULL) {
return;
}
assert(pFont->pContext != NULL);
if (pFont->pContext->drawingCallbacks.on_delete_font != NULL) {
pFont->pContext->drawingCallbacks.on_delete_font(pFont);
}
free(pFont);
}
void* dr2d_get_font_extra_data(dr2d_font* pFont)
{
if (pFont == NULL) {
return NULL;
}
return pFont->pExtraData;
}
unsigned int dr2d_get_font_size(dr2d_font* pFont)
{
if (pFont == NULL) {
return 0;
}
return pFont->size;
}
bool dr2d_get_font_metrics(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut)
{
if (pFont == NULL) {
return false;
}
assert(pFont->pContext != NULL);
if (pFont->pContext->drawingCallbacks.get_font_metrics != NULL) {
return pFont->pContext->drawingCallbacks.get_font_metrics(pFont, pMetricsOut);
}
return false;
}
bool dr2d_get_glyph_metrics(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pMetricsOut)
{
if (pFont == NULL || pMetricsOut == NULL) {
return false;
}
assert(pFont->pContext != NULL);
if (pFont->pContext->drawingCallbacks.get_glyph_metrics != NULL) {
return pFont->pContext->drawingCallbacks.get_glyph_metrics(pFont, utf32, pMetricsOut);
}
return false;
}
bool dr2d_measure_string(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut)
{
if (pFont == NULL) {
return false;
}
assert(pFont->pContext != NULL);
if (pFont->pContext->drawingCallbacks.measure_string != NULL) {
return pFont->pContext->drawingCallbacks.measure_string(pFont, text, textSizeInBytes, pWidthOut, pHeightOut);
}
return false;
}
bool dr2d_get_text_cursor_position_from_point(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut)
{
if (pFont == NULL) {
return false;
}
assert(pFont->pContext != NULL);
if (pFont->pContext->drawingCallbacks.get_text_cursor_position_from_point) {
return pFont->pContext->drawingCallbacks.get_text_cursor_position_from_point(pFont, text, textSizeInBytes, maxWidth, inputPosX, pTextCursorPosXOut, pCharacterIndexOut);
}
return false;
}
bool dr2d_get_text_cursor_position_from_char(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut)
{
if (pFont == NULL) {
return false;
}
assert(pFont->pContext != NULL);
if (pFont->pContext->drawingCallbacks.get_text_cursor_position_from_char) {
return pFont->pContext->drawingCallbacks.get_text_cursor_position_from_char(pFont, text, characterIndex, pTextCursorPosXOut);
}
return false;
}
dr2d_image* dr2d_create_image(dr2d_context* pContext, unsigned int width, unsigned int height, dr2d_image_format format, unsigned int stride, const void* pData)
{
if (pContext == NULL || width == 0 || height == 0) {
return NULL;
}
dr2d_image* pImage = (dr2d_image*)malloc(sizeof(dr2d_image) + pContext->imageExtraBytes);
if (pImage == NULL) {
return NULL;
}
pImage->pContext = pContext;
pImage->width = width;
pImage->height = height;
pImage->format = format;
pImage->isMapped = false;
if (pContext->drawingCallbacks.on_create_image != NULL) {
if (!pContext->drawingCallbacks.on_create_image(pImage, stride, pData)) {
free(pImage);
return NULL;
}
}
return pImage;
}
void dr2d_delete_image(dr2d_image* pImage)
{
if (pImage == NULL) {
return;
}
assert(pImage->pContext != NULL);
if (pImage->pContext->drawingCallbacks.on_delete_image != NULL) {
pImage->pContext->drawingCallbacks.on_delete_image(pImage);
}
free(pImage);
}
void* dr2d_get_image_extra_data(dr2d_image* pImage)
{
if (pImage == NULL) {
return NULL;
}
return pImage->pExtraData;
}
void dr2d_get_image_size(dr2d_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut)
{
if (pImage == NULL) {
return;
}
if (pWidthOut) {
*pWidthOut = pImage->width;
}
if (pHeightOut) {
*pHeightOut = pImage->height;
}
}
void* dr2d_map_image_data(dr2d_image* pImage, unsigned int accessFlags)
{
if (pImage == NULL || pImage->pContext->drawingCallbacks.map_image_data == NULL || pImage->pContext->drawingCallbacks.unmap_image_data == NULL || pImage->isMapped) {
return NULL;
}
void* pImageData = pImage->pContext->drawingCallbacks.map_image_data(pImage, accessFlags);
if (pImageData != NULL) {
pImage->isMapped = true;
}
return pImageData;
}
void dr2d_unmap_image_data(dr2d_image* pImage)
{
if (pImage == NULL || pImage->pContext->drawingCallbacks.unmap_image_data == NULL || !pImage->isMapped) {
return;
}
pImage->pContext->drawingCallbacks.unmap_image_data(pImage);
pImage->isMapped = false;
}
dr2d_image_format dr2d_get_optimal_image_format(dr2d_context* pContext)
{
if (pContext == NULL || pContext->drawingCallbacks.get_optimal_image_format == NULL) {
return dr2d_image_format_rgba8;
}
return pContext->drawingCallbacks.get_optimal_image_format(pContext);
}
/////////////////////////////////////////////////////////////////
//
// UTILITY API
//
/////////////////////////////////////////////////////////////////
dr2d_color dr2d_rgba(dr2d_byte r, dr2d_byte g, dr2d_byte b, dr2d_byte a)
{
dr2d_color color;
color.r = r;
color.g = g;
color.b = b;
color.a = a;
return color;
}
dr2d_color dr2d_rgb(dr2d_byte r, dr2d_byte g, dr2d_byte b)
{
dr2d_color color;
color.r = r;
color.g = g;
color.b = b;
color.a = 255;
return color;
}
/////////////////////////////////////////////////////////////////
//
// PRIVATE UTILITY API
//
/////////////////////////////////////////////////////////////////
// RGBA8 <-> BGRA8 swap with alpha pre-multiply.
void dr2d__rgba8_bgra8_swap__premul(const void* pSrc, void* pDst, unsigned int width, unsigned int height, unsigned int srcStride, unsigned int dstStride)
{
assert(pSrc != NULL);
assert(pDst != NULL);
const unsigned int srcStride32 = srcStride/4;
const unsigned int dstStride32 = dstStride/4;
const unsigned int* pSrcRow = (const unsigned int*)pSrc;
unsigned int* pDstRow = (unsigned int*)pDst;
for (unsigned int iRow = 0; iRow < height; ++iRow)
{
for (unsigned int iCol = 0; iCol < width; ++iCol)
{
unsigned int srcTexel = pSrcRow[iCol];
unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24;
unsigned int srcTexelB = (srcTexel & 0x00FF0000) >> 16;
unsigned int srcTexelG = (srcTexel & 0x0000FF00) >> 8;
unsigned int srcTexelR = (srcTexel & 0x000000FF) >> 0;
srcTexelB = (unsigned int)(srcTexelB * (srcTexelA / 255.0f));
srcTexelG = (unsigned int)(srcTexelG * (srcTexelA / 255.0f));
srcTexelR = (unsigned int)(srcTexelR * (srcTexelA / 255.0f));
pDstRow[iCol] = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24);
}
pSrcRow += srcStride32;
pDstRow += dstStride32;
}
}
/////////////////////////////////////////////////////////////////
//
// WINDOWS GDI 2D API
//
/////////////////////////////////////////////////////////////////
#ifndef DR2D_NO_GDI
typedef struct
{
/// The device context that owns every surface HBITMAP object. All drawing is done through this DC.
HDC hDC;
/// The buffer used to store wchar strings when converting from char* to wchar_t* strings. We just use a global buffer for
/// this so we can avoid unnecessary allocations.
wchar_t* wcharBuffer;
/// The size of wcharBuffer (including the null terminator).
unsigned int wcharBufferLength;
/// The cache of glyph character positions.
int* pGlyphCache;
size_t glyphCacheSize;
/// Whether or not the context owns the device context.
bool ownsDC;
} gdi_context_data;
typedef struct
{
/// The window to draw to. The can be null, which is the case when creating the surface with dr2d_create_surface(). When this
/// is non-null the size of the surface is always tied to the window.
HWND hWnd;
/// The HDC to use when drawing to the surface.
HDC hDC;
/// The intermediate DC to use when drawing bitmaps.
HDC hIntermediateDC;
/// The PAINTSTRUCT object that is filled by BeginPaint(). Only used when hWnd is non-null.
PAINTSTRUCT ps;
/// The bitmap to render to. This is created with GDI's CreateDIBSection() API, using the DC above. This is only used
/// when hDC is NULL, which is the default.
HBITMAP hBitmap;
/// A pointer to the raw bitmap data. This is allocated CreateDIBSection().
void* pBitmapData;
/// The stock DC brush.
HGDIOBJ hStockDCBrush;
/// The stock null brush.
HGDIOBJ hStockNullBrush;
/// The stock DC pen.
HGDIOBJ hStockDCPen;
/// The stock null pen.
HGDIOBJ hStockNullPen;
/// The pen that was active at the start of drawing. This is restored at the end of drawing.
HGDIOBJ hPrevPen;
/// The brush that was active at the start of drawing.
HGDIOBJ hPrevBrush;
/// The brush color at the start of drawing.
COLORREF prevBrushColor;
/// The previous font.
HGDIOBJ hPrevFont;
/// The previous text background mode.
int prevBkMode;
/// The previous text background color.
COLORREF prevBkColor;
} gdi_surface_data;
typedef struct
{
/// A handle to the Win32 font.
HFONT hFont;
/// The font metrics for faster retrieval. We cache the metrics when the font is loaded.
dr2d_font_metrics metrics;
} gdi_font_data;
typedef struct
{
/// A handle to the primary bitmap object.
HBITMAP hSrcBitmap;
/// A pointer to the raw bitmap data.
unsigned int* pSrcBitmapData;
/// A handle to the secondary bitmap object that we use when we need to change the color values of
/// the primary bitmap before drawing.
HBITMAP hIntermediateBitmap;
/// A pointer to the raw data of the intermediate bitmap.
unsigned int* pIntermediateBitmapData;
/// A pointer to the mapped data. This is null when the image data is not mapped.
void* pMappedImageData;
/// The mapping flags.
unsigned int mapAccessFlags;
} gdi_image_data;
bool dr2d_on_create_context_gdi(dr2d_context* pContext, const void* pUserData);
void dr2d_on_delete_context_gdi(dr2d_context* pContext);
bool dr2d_on_create_surface_gdi(dr2d_surface* pSurface, float width, float height);
void dr2d_on_delete_surface_gdi(dr2d_surface* pSurface);
bool dr2d_on_create_font_gdi(dr2d_font* pFont);
void dr2d_on_delete_font_gdi(dr2d_font* pFont);
bool dr2d_on_create_image_gdi(dr2d_image* pImage, unsigned int stride, const void* pData);
void dr2d_on_delete_image_gdi(dr2d_image* pImage);
void dr2d_begin_draw_gdi(dr2d_surface* pSurface);
void dr2d_end_draw_gdi(dr2d_surface* pSurface);
void dr2d_clear_gdi(dr2d_surface* pSurface, dr2d_color color);
void dr2d_draw_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color);
void dr2d_draw_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth);
void dr2d_draw_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor);
void dr2d_draw_round_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius);
void dr2d_draw_round_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth);
void dr2d_draw_round_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor);
void dr2d_draw_text_gdi(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor);
void dr2d_draw_image_gdi(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs);
void dr2d_set_clip_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom);
void dr2d_get_clip_gdi(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut);
dr2d_image_format dr2d_get_optimal_image_format_gdi(dr2d_context* pContext);
void* dr2d_map_image_data_gdi(dr2d_image* pImage, unsigned accessFlags);
void dr2d_unmap_image_data_gdi(dr2d_image* pImage);
bool dr2d_get_font_metrics_gdi(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut);
bool dr2d_get_glyph_metrics_gdi(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics);
bool dr2d_measure_string_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut);
bool dr2d_get_text_cursor_position_from_point_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
bool dr2d_get_text_cursor_position_from_char_gdi(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
/// Converts a char* to a wchar_t* string.
wchar_t* dr2d_to_wchar_gdi(dr2d_context* pContext, const char* text, size_t textSizeInBytes, unsigned int* characterCountOut);
/// Converts a UTF-32 character to a UTF-16.
static int dr2d_utf32_to_utf16(unsigned int utf32, unsigned short utf16[2])
{
if (utf16 == NULL) {
return 0;
}
if (utf32 < 0xD800 || (utf32 >= 0xE000 && utf32 <= 0xFFFF))
{
utf16[0] = (unsigned short)utf32;
utf16[1] = 0;
return 1;
}
else
{
if (utf32 >= 0x10000 && utf32 <= 0x10FFFF)
{
utf16[0] = (unsigned short)(0xD7C0 + (unsigned short)(utf32 >> 10));
utf16[1] = (unsigned short)(0xDC00 + (unsigned short)(utf32 & 0x3FF));
return 2;
}
else
{
// Invalid.
utf16[0] = 0;
utf16[0] = 0;
return 0;
}
}
}
dr2d_context* dr2d_create_context_gdi(HDC hDC)
{
dr2d_drawing_callbacks callbacks;
callbacks.on_create_context = dr2d_on_create_context_gdi;
callbacks.on_delete_context = dr2d_on_delete_context_gdi;
callbacks.on_create_surface = dr2d_on_create_surface_gdi;
callbacks.on_delete_surface = dr2d_on_delete_surface_gdi;
callbacks.on_create_font = dr2d_on_create_font_gdi;
callbacks.on_delete_font = dr2d_on_delete_font_gdi;
callbacks.on_create_image = dr2d_on_create_image_gdi;
callbacks.on_delete_image = dr2d_on_delete_image_gdi;
callbacks.begin_draw = dr2d_begin_draw_gdi;
callbacks.end_draw = dr2d_end_draw_gdi;
callbacks.clear = dr2d_clear_gdi;
callbacks.draw_rect = dr2d_draw_rect_gdi;
callbacks.draw_rect_outline = dr2d_draw_rect_outline_gdi;
callbacks.draw_rect_with_outline = dr2d_draw_rect_with_outline_gdi;
callbacks.draw_round_rect = dr2d_draw_round_rect_gdi;
callbacks.draw_round_rect_outline = dr2d_draw_round_rect_outline_gdi;
callbacks.draw_round_rect_with_outline = dr2d_draw_round_rect_with_outline_gdi;
callbacks.draw_text = dr2d_draw_text_gdi;
callbacks.draw_image = dr2d_draw_image_gdi;
callbacks.set_clip = dr2d_set_clip_gdi;
callbacks.get_clip = dr2d_get_clip_gdi;
callbacks.get_optimal_image_format = dr2d_get_optimal_image_format_gdi;
callbacks.map_image_data = dr2d_map_image_data_gdi;
callbacks.unmap_image_data = dr2d_unmap_image_data_gdi;
callbacks.get_font_metrics = dr2d_get_font_metrics_gdi;
callbacks.get_glyph_metrics = dr2d_get_glyph_metrics_gdi;
callbacks.measure_string = dr2d_measure_string_gdi;
callbacks.get_text_cursor_position_from_point = dr2d_get_text_cursor_position_from_point_gdi;
callbacks.get_text_cursor_position_from_char = dr2d_get_text_cursor_position_from_char_gdi;
return dr2d_create_context(callbacks, sizeof(gdi_context_data), sizeof(gdi_surface_data), sizeof(gdi_font_data), sizeof(gdi_image_data), &hDC);
}
dr2d_surface* dr2d_create_surface_gdi_HWND(dr2d_context* pContext, HWND hWnd)
{
dr2d_surface* pSurface = dr2d_create_surface(pContext, 0, 0);
if (pSurface != NULL) {
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL) {
pGDIData->hWnd = hWnd;
}
}
return pSurface;
}
dr2d_surface* dr2d_create_surface_gdi_HDC(dr2d_context* pContext, HDC hDC)
{
dr2d_surface* pSurface = dr2d_create_surface(pContext, 0, 0);
if (pSurface != NULL) {
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL) {
pGDIData->hDC = hDC;
}
}
return pSurface;
}
HDC dr2d_get_HDC(dr2d_surface* pSurface)
{
if (pSurface != NULL) {
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL) {
return pGDIData->hDC;
}
}
return NULL;
}
HBITMAP dr2d_get_HBITMAP(dr2d_surface* pSurface)
{
if (pSurface != NULL) {
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL) {
return pGDIData->hBitmap;
}
}
return NULL;
}
HFONT dr2d_get_HFONT(dr2d_font* pFont)
{
gdi_font_data* pGDIData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIData == NULL) {
return NULL;
}
return pGDIData->hFont;
}
bool dr2d_on_create_context_gdi(dr2d_context* pContext, const void* pUserData)
{
assert(pContext != NULL);
HDC hDC = NULL;
if (pUserData != NULL) {
hDC = *(HDC*)pUserData;
}
bool ownsDC = false;
if (hDC == NULL) {
hDC = CreateCompatibleDC(GetDC(GetDesktopWindow()));
ownsDC = true;
}
// We need to create the DC that all of our rendering commands will be drawn to.
gdi_context_data* pGDIData = (gdi_context_data*)dr2d_get_context_extra_data(pContext);
if (pGDIData == NULL) {
return false;
}
pGDIData->hDC = hDC;
if (pGDIData->hDC == NULL) {
return false;
}
pGDIData->ownsDC = ownsDC;
// We want to use the advanced graphics mode so that GetTextExtentPoint32() performs the conversions for font rotation for us.
SetGraphicsMode(pGDIData->hDC, GM_ADVANCED);
pGDIData->wcharBuffer = NULL;
pGDIData->wcharBufferLength = 0;
pGDIData->pGlyphCache = NULL;
pGDIData->glyphCacheSize = 0;
return true;
}
void dr2d_on_delete_context_gdi(dr2d_context* pContext)
{
assert(pContext != NULL);
gdi_context_data* pGDIData = (gdi_context_data*)dr2d_get_context_extra_data(pContext);
if (pGDIData != NULL)
{
free(pGDIData->pGlyphCache);
pGDIData->glyphCacheSize = 0;
free(pGDIData->wcharBuffer);
pGDIData->wcharBuffer = 0;
pGDIData->wcharBufferLength = 0;
if (pGDIData->ownsDC) {
DeleteDC(pGDIData->hDC);
}
pGDIData->hDC = NULL;
}
}
bool dr2d_on_create_surface_gdi(dr2d_surface* pSurface, float width, float height)
{
assert(pSurface != NULL);
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pSurface->pContext);
if (pGDIContextData == NULL) {
return false;
}
gdi_surface_data* pGDISurfaceData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDISurfaceData == NULL) {
return false;
}
HDC hDC = pGDIContextData->hDC;
if (hDC == NULL) {
return false;
}
HDC hIntermediateDC = CreateCompatibleDC(hDC);
if (hIntermediateDC == NULL) {
return false;
}
pGDISurfaceData->hIntermediateDC = hIntermediateDC;
pGDISurfaceData->hWnd = NULL;
if (width != 0 && height != 0)
{
pGDISurfaceData->hDC = hDC;
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = (LONG)width;
bmi.bmiHeader.biHeight = (LONG)height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
pGDISurfaceData->hBitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pGDISurfaceData->pBitmapData, NULL, 0);
if (pGDISurfaceData->hBitmap == NULL) {
return false;
}
}
else
{
pGDISurfaceData->hBitmap = NULL;
pGDISurfaceData->hDC = NULL;
}
return true;
}
void dr2d_on_delete_surface_gdi(dr2d_surface* pSurface)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
DeleteObject(pGDIData->hBitmap);
pGDIData->hBitmap = NULL;
DeleteDC(pGDIData->hIntermediateDC);
pGDIData->hIntermediateDC = NULL;
}
}
bool dr2d_on_create_font_gdi(dr2d_font* pFont)
{
assert(pFont != NULL);
gdi_font_data* pGDIData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIData == NULL) {
return false;
}
LONG weightGDI = FW_REGULAR;
switch (pFont->weight)
{
case dr2d_font_weight_medium: weightGDI = FW_MEDIUM; break;
case dr2d_font_weight_thin: weightGDI = FW_THIN; break;
case dr2d_font_weight_extra_light: weightGDI = FW_EXTRALIGHT; break;
case dr2d_font_weight_light: weightGDI = FW_LIGHT; break;
case dr2d_font_weight_semi_bold: weightGDI = FW_SEMIBOLD; break;
case dr2d_font_weight_bold: weightGDI = FW_BOLD; break;
case dr2d_font_weight_extra_bold: weightGDI = FW_EXTRABOLD; break;
case dr2d_font_weight_heavy: weightGDI = FW_HEAVY; break;
default: break;
}
BYTE slantGDI = FALSE;
if (pFont->slant == dr2d_font_slant_italic || pFont->slant == dr2d_font_slant_oblique) {
slantGDI = TRUE;
}
LOGFONTA logfont;
memset(&logfont, 0, sizeof(logfont));
logfont.lfHeight = -(LONG)pFont->size;
logfont.lfWeight = weightGDI;
logfont.lfItalic = slantGDI;
logfont.lfCharSet = DEFAULT_CHARSET;
//logfont.lfQuality = (pFont->size > 36) ? ANTIALIASED_QUALITY : CLEARTYPE_QUALITY;
logfont.lfQuality = (pFont->flags & DR2D_FONT_NO_CLEARTYPE) ? ANTIALIASED_QUALITY : CLEARTYPE_QUALITY;
logfont.lfEscapement = (LONG)pFont->rotation * 10;
logfont.lfOrientation = (LONG)pFont->rotation * 10;
size_t familyLength = strlen(pFont->family);
memcpy(logfont.lfFaceName, pFont->family, (familyLength < 31) ? familyLength : 31);
pGDIData->hFont = CreateFontIndirectA(&logfont);
if (pGDIData->hFont == NULL) {
return false;
}
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext);
if (pGDIContextData == NULL) {
return false;
}
// Cache the font metrics.
HGDIOBJ hPrevFont = SelectObject(pGDIContextData->hDC, pGDIData->hFont);
{
TEXTMETRIC metrics;
GetTextMetrics(pGDIContextData->hDC, &metrics);
pGDIData->metrics.ascent = metrics.tmAscent;
pGDIData->metrics.descent = metrics.tmDescent;
pGDIData->metrics.lineHeight = metrics.tmHeight;
const MAT2 transform = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; // <-- Identity matrix
GLYPHMETRICS spaceMetrics;
DWORD bitmapBufferSize = GetGlyphOutlineW(pGDIContextData->hDC, ' ', GGO_NATIVE, &spaceMetrics, 0, NULL, &transform);
if (bitmapBufferSize == GDI_ERROR) {
pGDIData->metrics.spaceWidth = 4;
} else {
pGDIData->metrics.spaceWidth = spaceMetrics.gmCellIncX;
}
}
SelectObject(pGDIContextData->hDC, hPrevFont);
return true;
}
void dr2d_on_delete_font_gdi(dr2d_font* pFont)
{
assert(pFont != NULL);
gdi_font_data* pGDIData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIData == NULL) {
return;
}
DeleteObject(pGDIData->hFont);
}
bool dr2d_on_create_image_gdi(dr2d_image* pImage, unsigned int stride, const void* pData)
{
assert(pImage != NULL);
gdi_image_data* pGDIData = (gdi_image_data*)dr2d_get_image_extra_data(pImage);
if (pGDIData == NULL) {
return false;
}
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pImage->pContext);
if (pGDIContextData == NULL) {
return false;
}
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = pImage->width;
bmi.bmiHeader.biHeight = pImage->height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // Only supporting 32-bit formats.
bmi.bmiHeader.biCompression = BI_RGB;
pGDIData->hSrcBitmap = CreateDIBSection(pGDIContextData->hDC, &bmi, DIB_RGB_COLORS, (void**)&pGDIData->pSrcBitmapData, NULL, 0);
if (pGDIData->hSrcBitmap == NULL) {
return false;
}
pGDIData->hIntermediateBitmap = CreateDIBSection(pGDIContextData->hDC, &bmi, DIB_RGB_COLORS, (void**)&pGDIData->pIntermediateBitmapData, NULL, 0);
if (pGDIData->hIntermediateBitmap == NULL) {
DeleteObject(pGDIData->hSrcBitmap);
return false;
}
// We need to convert the data so it renders correctly with AlphaBlend().
if (pData != NULL) {
dr2d__rgba8_bgra8_swap__premul(pData, pGDIData->pSrcBitmapData, pImage->width, pImage->height, stride, pImage->width*4);
}
// Flush GDI to let it know we are finished with the bitmap object's data.
GdiFlush();
pGDIData->pMappedImageData = NULL;
pGDIData->mapAccessFlags = 0;
// At this point everything should be good.
return true;
}
void dr2d_on_delete_image_gdi(dr2d_image* pImage)
{
assert(pImage != NULL);
gdi_image_data* pGDIData = (gdi_image_data*)dr2d_get_image_extra_data(pImage);
if (pGDIData == NULL) {
return;
}
DeleteObject(pGDIData->hSrcBitmap);
pGDIData->hSrcBitmap = NULL;
DeleteObject(pGDIData->hIntermediateBitmap);
pGDIData->hIntermediateBitmap = NULL;
}
void dr2d_begin_draw_gdi(dr2d_surface* pSurface)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL) {
if (pGDIData->hWnd != NULL) {
pGDIData->hDC = BeginPaint(pGDIData->hWnd, &pGDIData->ps);
} else {
SelectObject(dr2d_get_HDC(pSurface), pGDIData->hBitmap);
}
HDC hDC = dr2d_get_HDC(pSurface);
pGDIData->hStockDCBrush = GetStockObject(DC_BRUSH);
pGDIData->hStockNullBrush = GetStockObject(NULL_BRUSH);
pGDIData->hStockDCPen = GetStockObject(DC_PEN);
pGDIData->hStockNullPen = GetStockObject(NULL_PEN);
// Retrieve the defaults so they can be restored later.
pGDIData->hPrevPen = GetCurrentObject(hDC, OBJ_PEN);
pGDIData->hPrevBrush = GetCurrentObject(hDC, OBJ_BRUSH);
pGDIData->prevBrushColor = GetDCBrushColor(hDC);
pGDIData->hPrevFont = GetCurrentObject(hDC, OBJ_FONT);
pGDIData->prevBkMode = GetBkMode(hDC);
pGDIData->prevBkColor = GetBkColor(hDC);
}
}
void dr2d_end_draw_gdi(dr2d_surface* pSurface)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL) {
HDC hDC = dr2d_get_HDC(pSurface);
SelectClipRgn(hDC, NULL);
SelectObject(hDC, pGDIData->hPrevPen);
SelectObject(hDC, pGDIData->hPrevBrush);
SetDCBrushColor(hDC, pGDIData->prevBrushColor);
SelectObject(hDC, pGDIData->hPrevFont);
SetBkMode(hDC, pGDIData->prevBkMode);
SetBkColor(hDC, pGDIData->prevBkColor);
if (pGDIData->hWnd != NULL) {
EndPaint(pGDIData->hWnd, &pGDIData->ps);
}
}
}
void dr2d_clear_gdi(dr2d_surface* pSurface, dr2d_color color)
{
assert(pSurface != NULL);
dr2d_draw_rect_gdi(pSurface, 0, 0, pSurface->width, pSurface->height, color);
}
void dr2d_draw_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
SelectObject(hDC, pGDIData->hStockNullPen);
SelectObject(hDC, pGDIData->hStockDCBrush);
SetDCBrushColor(hDC, RGB(color.r, color.g, color.b));
// Now draw the rectangle. The documentation for this says that the width and height is 1 pixel less when the pen is null. Therefore we will
// increase the width and height by 1 since we have got the pen set to null.
Rectangle(hDC, (int)left, (int)top, (int)right + 1, (int)bottom + 1);
}
}
void dr2d_draw_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
SelectObject(hDC, pGDIData->hStockNullPen);
SelectObject(hDC, pGDIData->hStockDCBrush);
SetDCBrushColor(hDC, RGB(color.r, color.g, color.b));
// Now draw the rectangle. The documentation for this says that the width and height is 1 pixel less when the pen is null. Therefore we will
// increase the width and height by 1 since we have got the pen set to null.
Rectangle(hDC, (int)left, (int)top, (int)(left + outlineWidth + 1), (int)(bottom + 1)); // Left.
Rectangle(hDC, (int)(right - outlineWidth), (int)top, (int)(right + 1), (int)(bottom + 1)); // Right.
Rectangle(hDC, (int)(left + outlineWidth), (int)top, (int)(right - outlineWidth + 1), (int)(top + outlineWidth + 1)); // Top
Rectangle(hDC, (int)(left + outlineWidth), (int)(bottom - outlineWidth), (int)(right - outlineWidth + 1), (int)(bottom + 1)); // Bottom
}
}
void dr2d_draw_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
HPEN hPen = CreatePen(PS_SOLID | PS_INSIDEFRAME, (int)outlineWidth, RGB(outlineColor.r, outlineColor.g, outlineColor.b));
if (hPen != NULL)
{
SelectObject(hDC, hPen);
SelectObject(hDC, pGDIData->hStockDCBrush);
SetDCBrushColor(hDC, RGB(color.r, color.g, color.b));
Rectangle(hDC, (int)left, (int)top, (int)right, (int)bottom);
DeleteObject(hPen);
}
}
}
void dr2d_draw_round_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
SelectObject(hDC, pGDIData->hStockNullPen);
SelectObject(hDC, pGDIData->hStockDCBrush);
SetDCBrushColor(hDC, RGB(color.r, color.g, color.b));
RoundRect(hDC, (int)left, (int)top, (int)right + 1, (int)bottom + 1, (int)(radius*2), (int)(radius*2));
}
}
void dr2d_draw_round_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
HPEN hPen = CreatePen(PS_SOLID | PS_INSIDEFRAME, (int)outlineWidth, RGB(color.r, color.g, color.b));
if (hPen != NULL)
{
SelectObject(hDC, pGDIData->hStockNullBrush);
SelectObject(hDC, hPen);
RoundRect(hDC, (int)left, (int)top, (int)right, (int)bottom, (int)(radius*2), (int)(radius*2));
DeleteObject(hPen);
}
}
}
void dr2d_draw_round_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
HPEN hPen = CreatePen(PS_SOLID | PS_INSIDEFRAME, (int)outlineWidth, RGB(outlineColor.r, outlineColor.g, outlineColor.b));
if (hPen != NULL)
{
SelectObject(hDC, hPen);
SelectObject(hDC, pGDIData->hStockDCBrush);
SetDCBrushColor(hDC, RGB(color.r, color.g, color.b));
RoundRect(hDC, (int)left, (int)top, (int)right, (int)bottom, (int)(radius*2), (int)(radius*2));
DeleteObject(hPen);
}
}
}
void dr2d_draw_text_gdi(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor)
{
gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIFontData == NULL) {
return;
}
HDC hDC = dr2d_get_HDC(pSurface);
HFONT hFontGDI = pGDIFontData->hFont;
if (hFontGDI != NULL)
{
// We actually want to use the W version of TextOut because otherwise unicode doesn't work properly.
unsigned int textWLength;
wchar_t* textW = dr2d_to_wchar_gdi(pSurface->pContext, text, textSizeInBytes, &textWLength);
if (textW != NULL)
{
SelectObject(hDC, hFontGDI);
UINT options = 0;
RECT rect = {0, 0, 0, 0};
if (backgroundColor.a == 0) {
SetBkMode(hDC, TRANSPARENT);
} else {
SetBkMode(hDC, OPAQUE);
SetBkColor(hDC, RGB(backgroundColor.r, backgroundColor.g, backgroundColor.b));
// There is an issue with the way GDI draws the background of a string of text. When ClearType is enabled, the rectangle appears
// to be wider than it is supposed to be. As a result, drawing text right next to each other results in the most recent one being
// drawn slightly on top of the previous one. To fix this we need to use ExtTextOut() with the ETO_CLIPPED parameter enabled.
options |= ETO_CLIPPED;
SIZE textSize = {0, 0};
GetTextExtentPoint32W(hDC, textW, textWLength, &textSize);
rect.left = (LONG)posX;
rect.top = (LONG)posY;
rect.right = (LONG)(posX + textSize.cx);
rect.bottom = (LONG)(posY + textSize.cy);
}
SetTextColor(hDC, RGB(color.r, color.g, color.b));
ExtTextOutW(hDC, (int)posX, (int)posY, options, &rect, textW, textWLength, NULL);
}
}
}
void dr2d_draw_image_gdi(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs)
{
gdi_image_data* pGDIImageData = (gdi_image_data*)dr2d_get_image_extra_data(pImage);
if (pGDIImageData == NULL) {
return;
}
gdi_surface_data* pGDISurfaceData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDISurfaceData == NULL) {
return;
}
bool drawFlipped = false;
HBITMAP hSrcBitmap = NULL;
if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) == 0 && (pArgs->options & DR2D_IMAGE_HINT_NO_ALPHA) != 0 && pArgs->foregroundTint.r == 255 && pArgs->foregroundTint.g == 255 && pArgs->foregroundTint.b == 255)
{
// Fast path. No tint, no background, no alpha.
hSrcBitmap = pGDIImageData->hSrcBitmap;
drawFlipped = true;
}
else
{
// Slow path. We need to manually change the color values of the intermediate bitmap and use that as the source when drawing it. This is also flipped.
unsigned int* pSrcBitmapData = pGDIImageData->pSrcBitmapData;
unsigned int* pDstBitmapData = pGDIImageData->pIntermediateBitmapData;
for (unsigned int iRow = 0; iRow < pImage->height; ++iRow)
{
for (unsigned int iCol = 0; iCol < pImage->width; ++iCol)
{
unsigned int srcTexel = *(pSrcBitmapData + (iRow * pImage->width) + iCol);
unsigned int* dstTexel = (pDstBitmapData + ((pImage->height - iRow - 1) * pImage->width) + iCol);
unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24;
unsigned int srcTexelR = (unsigned int)(((srcTexel & 0x00FF0000) >> 16) * (pArgs->foregroundTint.r / 255.0f));
unsigned int srcTexelG = (unsigned int)(((srcTexel & 0x0000FF00) >> 8) * (pArgs->foregroundTint.g / 255.0f));
unsigned int srcTexelB = (unsigned int)(((srcTexel & 0x000000FF) >> 0) * (pArgs->foregroundTint.b / 255.0f));
if (srcTexelR > 255) srcTexelR = 255;
if (srcTexelG > 255) srcTexelG = 255;
if (srcTexelB > 255) srcTexelB = 255;
if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) != 0)
{
srcTexelB += (unsigned int)(pArgs->backgroundColor.b * ((255 - srcTexelA) / 255.0f));
srcTexelG += (unsigned int)(pArgs->backgroundColor.g * ((255 - srcTexelA) / 255.0f));
srcTexelR += (unsigned int)(pArgs->backgroundColor.r * ((255 - srcTexelA) / 255.0f));
srcTexelA = 0xFF;
}
*dstTexel = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24);
}
}
// Flush GDI to let it know we are finished with the bitmap object's data.
GdiFlush();
// If we have drawn the background manually we don't need to do an alpha blend.
if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) != 0) {
pArgs->options |= DR2D_IMAGE_HINT_NO_ALPHA;
}
hSrcBitmap = pGDIImageData->hIntermediateBitmap;
}
HGDIOBJ hPrevBitmap = SelectObject(pGDISurfaceData->hIntermediateDC, hSrcBitmap);
if ((pArgs->options & DR2D_IMAGE_HINT_NO_ALPHA) != 0)
{
if (drawFlipped) {
StretchBlt(pGDISurfaceData->hDC, (int)pArgs->dstX, (int)pArgs->dstY + (int)pArgs->dstHeight - 1, (int)pArgs->dstWidth, -(int)pArgs->dstHeight, pGDISurfaceData->hIntermediateDC, (int)pArgs->srcX, (int)pArgs->srcY, (int)pArgs->srcWidth, (int)pArgs->srcHeight, SRCCOPY);
} else {
StretchBlt(pGDISurfaceData->hDC, (int)pArgs->dstX, (int)pArgs->dstY, (int)pArgs->dstWidth, (int)pArgs->dstHeight, pGDISurfaceData->hIntermediateDC, (int)pArgs->srcX, (int)pArgs->srcY, (int)pArgs->srcWidth, (int)pArgs->srcHeight, SRCCOPY);
}
}
else
{
assert(drawFlipped == false); // <-- Error if this is hit.
BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
AlphaBlend(pGDISurfaceData->hDC, (int)pArgs->dstX, (int)pArgs->dstY, (int)pArgs->dstWidth, (int)pArgs->dstHeight, pGDISurfaceData->hIntermediateDC, (int)pArgs->srcX, (int)pArgs->srcY, (int)pArgs->srcWidth, (int)pArgs->srcHeight, blend);
}
SelectObject(pGDISurfaceData->hIntermediateDC, hPrevBitmap);
}
void dr2d_set_clip_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
HDC hDC = dr2d_get_HDC(pSurface);
SelectClipRgn(hDC, NULL);
IntersectClipRect(hDC, (int)left, (int)top, (int)right, (int)bottom);
}
}
void dr2d_get_clip_gdi(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut)
{
assert(pSurface != NULL);
gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pGDIData != NULL)
{
RECT rect;
GetClipBox(dr2d_get_HDC(pSurface), &rect);
if (pLeftOut != NULL) {
*pLeftOut = (float)rect.left;
}
if (pTopOut != NULL) {
*pTopOut = (float)rect.top;
}
if (pRightOut != NULL) {
*pRightOut = (float)rect.right;
}
if (pBottomOut != NULL) {
*pBottomOut = (float)rect.bottom;
}
}
}
dr2d_image_format dr2d_get_optimal_image_format_gdi(dr2d_context* pContext)
{
(void)pContext;
return dr2d_image_format_bgra8;
}
void* dr2d_map_image_data_gdi(dr2d_image* pImage, unsigned accessFlags)
{
assert(pImage != NULL);
gdi_image_data* pGDIImageData = (gdi_image_data*)dr2d_get_image_extra_data(pImage);
if (pGDIImageData == NULL) {
return NULL;
}
assert(pGDIImageData->pMappedImageData == NULL); // This function should never be called while the image is already mapped.
pGDIImageData->mapAccessFlags = accessFlags;
if (pImage->format == dr2d_image_format_bgra8)
{
pGDIImageData->pMappedImageData = pGDIImageData->pSrcBitmapData;
}
else
{
pGDIImageData->pMappedImageData = malloc(pImage->width * pImage->height * 4);
if (pGDIImageData->pMappedImageData == NULL) {
return NULL;
}
for (unsigned int iRow = 0; iRow < pImage->height; ++iRow)
{
const unsigned int iRowSrc = pImage->height - (iRow + 1);
const unsigned int iRowDst = iRow;
for (unsigned int iCol = 0; iCol < pImage->width; ++iCol)
{
unsigned int srcTexel = ((const unsigned int*)(pGDIImageData->pSrcBitmapData))[ (iRowSrc * pImage->width) + iCol];
unsigned int* dstTexel = (( unsigned int*)(pGDIImageData->pMappedImageData)) + (iRowDst * pImage->width) + iCol;
unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24;
unsigned int srcTexelB = (srcTexel & 0x00FF0000) >> 16;
unsigned int srcTexelG = (srcTexel & 0x0000FF00) >> 8;
unsigned int srcTexelR = (srcTexel & 0x000000FF) >> 0;
srcTexelB = (unsigned int)(srcTexelB * (srcTexelA / 255.0f));
srcTexelG = (unsigned int)(srcTexelG * (srcTexelA / 255.0f));
srcTexelR = (unsigned int)(srcTexelR * (srcTexelA / 255.0f));
*dstTexel = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24);
}
}
}
return pGDIImageData->pMappedImageData;
}
void dr2d_unmap_image_data_gdi(dr2d_image* pImage)
{
assert(pImage != NULL);
gdi_image_data* pGDIImageData = (gdi_image_data*)dr2d_get_image_extra_data(pImage);
if (pGDIImageData == NULL) {
return;
}
assert(pGDIImageData->pMappedImageData != NULL); // This function should never be called while the image is not mapped.
if (pImage->format == dr2d_image_format_bgra8)
{
// It's in the native format, so just do a flush.
GdiFlush();
}
else
{
// Update the actual image data if applicable.
if (pGDIImageData->mapAccessFlags & DR2D_WRITE) {
dr2d__rgba8_bgra8_swap__premul(pGDIImageData->pMappedImageData, pGDIImageData->pSrcBitmapData, pImage->width, pImage->height, pImage->width*4, pImage->width*4);
}
free(pGDIImageData->pMappedImageData);
}
pGDIImageData->pMappedImageData = NULL;
pGDIImageData->mapAccessFlags = 0;
}
bool dr2d_get_font_metrics_gdi(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut)
{
assert(pFont != NULL);
assert(pMetricsOut != NULL);
gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIFontData == NULL) {
return false;
}
*pMetricsOut = pGDIFontData->metrics;
return true;
}
bool dr2d_get_glyph_metrics_gdi(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics)
{
assert(pFont != NULL);
assert(pGlyphMetrics != NULL);
gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIFontData == NULL) {
return false;
}
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext);
if (pGDIContextData == NULL) {
return false;
}
SelectObject(pGDIContextData->hDC, pGDIFontData->hFont);
const MAT2 transform = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; // <-- Identity matrix
unsigned short utf16[2];
int utf16Len = dr2d_utf32_to_utf16(utf32, utf16);
WCHAR glyphIndices[2];
GCP_RESULTSW glyphResults;
ZeroMemory(&glyphResults, sizeof(glyphResults));
glyphResults.lStructSize = sizeof(glyphResults);
glyphResults.lpGlyphs = glyphIndices;
glyphResults.nGlyphs = 2;
if (GetCharacterPlacementW(pGDIContextData->hDC, (LPCWSTR)utf16, utf16Len, 0, &glyphResults, 0) != 0)
{
GLYPHMETRICS metrics;
DWORD bitmapBufferSize = GetGlyphOutlineW(pGDIContextData->hDC, glyphIndices[0], GGO_NATIVE | GGO_GLYPH_INDEX, &metrics, 0, NULL, &transform);
if (bitmapBufferSize != GDI_ERROR)
{
pGlyphMetrics->width = metrics.gmBlackBoxX;
pGlyphMetrics->height = metrics.gmBlackBoxY;
pGlyphMetrics->originX = metrics.gmptGlyphOrigin.x;
pGlyphMetrics->originY = metrics.gmptGlyphOrigin.y;
pGlyphMetrics->advanceX = metrics.gmCellIncX;
pGlyphMetrics->advanceY = metrics.gmCellIncY;
return true;
}
}
return false;
}
bool dr2d_measure_string_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut)
{
assert(pFont != NULL);
gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIFontData == NULL) {
return false;
}
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext);
if (pGDIContextData == NULL) {
return false;
}
SelectObject(pGDIContextData->hDC, pGDIFontData->hFont);
unsigned int textWLength;
wchar_t* textW = dr2d_to_wchar_gdi(pFont->pContext, text, textSizeInBytes, &textWLength);
if (textW != NULL)
{
SIZE sizeWin32;
if (GetTextExtentPoint32W(pGDIContextData->hDC, textW, textWLength, &sizeWin32))
{
if (pWidthOut != NULL) {
*pWidthOut = (float)sizeWin32.cx;
}
if (pHeightOut != NULL) {
*pHeightOut = (float)sizeWin32.cy;
}
return true;
}
}
return false;
}
bool dr2d_get_text_cursor_position_from_point_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut)
{
bool successful = false;
assert(pFont != NULL);
gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIFontData == NULL) {
return false;
}
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext);
if (pGDIContextData == NULL) {
return false;
}
SelectObject(pGDIContextData->hDC, pGDIFontData->hFont);
GCP_RESULTSW results;
ZeroMemory(&results, sizeof(results));
results.lStructSize = sizeof(results);
results.nGlyphs = (UINT)textSizeInBytes;
unsigned int textWLength;
wchar_t* textW = dr2d_to_wchar_gdi(pFont->pContext, text, textSizeInBytes, &textWLength);
if (textW != NULL)
{
if (results.nGlyphs > pGDIContextData->glyphCacheSize) {
free(pGDIContextData->pGlyphCache);
pGDIContextData->pGlyphCache = (int*)malloc(sizeof(int) * results.nGlyphs);
pGDIContextData->glyphCacheSize = results.nGlyphs;
}
results.lpCaretPos = pGDIContextData->pGlyphCache;
if (results.lpCaretPos != NULL)
{
if (GetCharacterPlacementW(pGDIContextData->hDC, textW, results.nGlyphs, (int)maxWidth, &results, GCP_MAXEXTENT | GCP_USEKERNING) != 0)
{
float textCursorPosX = 0;
unsigned int iChar;
for (iChar = 0; iChar < results.nGlyphs; ++iChar)
{
float charBoundsLeft = charBoundsLeft = (float)results.lpCaretPos[iChar];
float charBoundsRight = 0;
if (iChar < results.nGlyphs - 1) {
charBoundsRight = (float)results.lpCaretPos[iChar + 1];
} else {
charBoundsRight = maxWidth;
}
if (inputPosX >= charBoundsLeft && inputPosX <= charBoundsRight)
{
// The input position is somewhere on top of this character. If it's positioned on the left side of the character, set the output
// value to the character at iChar. Otherwise it should be set to the character at iChar + 1.
float charBoundsRightHalf = charBoundsLeft + ceilf(((charBoundsRight - charBoundsLeft) / 2.0f));
if (inputPosX <= charBoundsRightHalf) {
break;
} else {
textCursorPosX = charBoundsRight;
iChar += 1;
break;
}
}
textCursorPosX = charBoundsRight;
}
if (pTextCursorPosXOut) {
*pTextCursorPosXOut = textCursorPosX;
}
if (pCharacterIndexOut) {
*pCharacterIndexOut = iChar;
}
successful = true;
}
}
}
return successful;
}
bool dr2d_get_text_cursor_position_from_char_gdi(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut)
{
bool successful = false;
assert(pFont != NULL);
gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont);
if (pGDIFontData == NULL) {
return false;
}
gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext);
if (pGDIContextData == NULL) {
return false;
}
SelectObject(pGDIContextData->hDC, pGDIFontData->hFont);
GCP_RESULTSW results;
ZeroMemory(&results, sizeof(results));
results.lStructSize = sizeof(results);
results.nGlyphs = (DWORD)(characterIndex + 1);
unsigned int textWLength;
wchar_t* textW = dr2d_to_wchar_gdi(pFont->pContext, text, (int)results.nGlyphs, &textWLength);
if (textW != NULL)
{
if (results.nGlyphs > pGDIContextData->glyphCacheSize) {
free(pGDIContextData->pGlyphCache);
pGDIContextData->pGlyphCache = (int*)malloc(sizeof(int) * results.nGlyphs);
pGDIContextData->glyphCacheSize = results.nGlyphs;
}
results.lpCaretPos = pGDIContextData->pGlyphCache;
if (results.lpCaretPos != NULL)
{
if (GetCharacterPlacementW(pGDIContextData->hDC, textW, results.nGlyphs, 0, &results, GCP_USEKERNING) != 0)
{
if (pTextCursorPosXOut) {
*pTextCursorPosXOut = (float)results.lpCaretPos[characterIndex];
}
successful = true;
}
}
}
return successful;
}
wchar_t* dr2d_to_wchar_gdi(dr2d_context* pContext, const char* text, size_t textSizeInBytes, unsigned int* characterCountOut)
{
if (pContext == NULL || text == NULL) {
return NULL;
}
gdi_context_data* pGDIData = (gdi_context_data*)dr2d_get_context_extra_data(pContext);
if (pGDIData == NULL) {
return NULL;
}
int wcharCount = 0;
// We first try to copy the string into the already-allocated buffer. If it fails we fall back to the slow path which requires
// two conversions.
if (pGDIData->wcharBuffer == NULL) {
goto fallback;
}
wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textSizeInBytes, pGDIData->wcharBuffer, pGDIData->wcharBufferLength);
if (wcharCount != 0) {
if (characterCountOut) *characterCountOut = wcharCount;
return pGDIData->wcharBuffer;
}
fallback:;
wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textSizeInBytes, NULL, 0);
if (wcharCount == 0) {
return NULL;
}
if (pGDIData->wcharBufferLength < (unsigned int)wcharCount + 1) {
free(pGDIData->wcharBuffer);
pGDIData->wcharBuffer = (wchar_t*)malloc(sizeof(wchar_t) * (wcharCount + 1));
pGDIData->wcharBufferLength = wcharCount + 1;
}
wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textSizeInBytes, pGDIData->wcharBuffer, pGDIData->wcharBufferLength);
if (wcharCount == 0) {
return NULL;
}
if (characterCountOut != NULL) {
*characterCountOut = wcharCount;
}
return pGDIData->wcharBuffer;
}
#endif // GDI
/////////////////////////////////////////////////////////////////
//
// CAIRO 2D API
//
/////////////////////////////////////////////////////////////////
#ifndef DR2D_NO_CAIRO
typedef struct
{
cairo_surface_t* pCairoSurface;
cairo_t* pCairoContext;
float clipRectLeft;
float clipRectTop;
float clipRectRight;
float clipRectBottom;
} cairo_surface_data;
typedef struct
{
cairo_font_face_t* pFace;
cairo_scaled_font_t* pFont;
// The font metrics. This is initialized when the font is created.
dr2d_font_metrics metrics;
} cairo_font_data;
typedef struct
{
/// Images in Cairo are implemented as surfaces.
cairo_surface_t* pCairoSurface;
/// A pointer to the raw data.
unsigned char* pData;
} cairo_image_data;
bool dr2d_on_create_context_cairo(dr2d_context* pContext, const void* pUserData);
void dr2d_on_delete_context_cairo(dr2d_context* pContext);
bool dr2d_on_create_surface_cairo(dr2d_surface* pSurface, float width, float height);
void dr2d_on_delete_surface_cairo(dr2d_surface* pSurface);
bool dr2d_on_create_font_cairo(dr2d_font* pFont);
void dr2d_on_delete_font_cairo(dr2d_font* pFont);
bool dr2d_on_create_image_cairo(dr2d_image* pImage, unsigned int stride, const void* pData);
void dr2d_on_delete_image_cairo(dr2d_image* pImage);
void dr2d_begin_draw_cairo(dr2d_surface* pSurface);
void dr2d_end_draw_cairo(dr2d_surface* pSurface);
void dr2d_clear_cairo(dr2d_surface* pSurface, dr2d_color color);
void dr2d_draw_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color);
void dr2d_draw_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth);
void dr2d_draw_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor);
void dr2d_draw_round_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius);
void dr2d_draw_round_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth);
void dr2d_draw_round_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor);
void dr2d_draw_text_cairo(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor);
void dr2d_draw_image_cairo(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs);
void dr2d_set_clip_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom);
void dr2d_get_clip_cairo(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut);
dr2d_image_format dr2d_get_optimal_image_format_cairo(dr2d_context* pContext);
void* dr2d_map_image_data_cairo(dr2d_image* pImage, unsigned accessFlags);
void dr2d_unmap_image_data_cairo(dr2d_image* pImage);
bool dr2d_get_font_metrics_cairo(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut);
bool dr2d_get_glyph_metrics_cairo(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics);
bool dr2d_measure_string_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut);
bool dr2d_get_text_cursor_position_from_point_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
bool dr2d_get_text_cursor_position_from_char_cairo(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
dr2d_context* dr2d_create_context_cairo()
{
dr2d_drawing_callbacks callbacks;
callbacks.on_create_context = dr2d_on_create_context_cairo;
callbacks.on_delete_context = dr2d_on_delete_context_cairo;
callbacks.on_create_surface = dr2d_on_create_surface_cairo;
callbacks.on_delete_surface = dr2d_on_delete_surface_cairo;
callbacks.on_create_font = dr2d_on_create_font_cairo;
callbacks.on_delete_font = dr2d_on_delete_font_cairo;
callbacks.on_create_image = dr2d_on_create_image_cairo;
callbacks.on_delete_image = dr2d_on_delete_image_cairo;
callbacks.begin_draw = dr2d_begin_draw_cairo;
callbacks.end_draw = dr2d_end_draw_cairo;
callbacks.clear = dr2d_clear_cairo;
callbacks.draw_rect = dr2d_draw_rect_cairo;
callbacks.draw_rect_outline = dr2d_draw_rect_outline_cairo;
callbacks.draw_rect_with_outline = dr2d_draw_rect_with_outline_cairo;
callbacks.draw_round_rect = dr2d_draw_round_rect_cairo;
callbacks.draw_round_rect_outline = dr2d_draw_round_rect_outline_cairo;
callbacks.draw_round_rect_with_outline = dr2d_draw_round_rect_with_outline_cairo;
callbacks.draw_text = dr2d_draw_text_cairo;
callbacks.draw_image = dr2d_draw_image_cairo;
callbacks.set_clip = dr2d_set_clip_cairo;
callbacks.get_clip = dr2d_get_clip_cairo;
callbacks.map_image_data = dr2d_map_image_data_cairo;
callbacks.unmap_image_data = dr2d_unmap_image_data_cairo;
callbacks.get_font_metrics = dr2d_get_font_metrics_cairo;
callbacks.get_glyph_metrics = dr2d_get_glyph_metrics_cairo;
callbacks.measure_string = dr2d_measure_string_cairo;
callbacks.get_text_cursor_position_from_point = dr2d_get_text_cursor_position_from_point_cairo;
callbacks.get_text_cursor_position_from_char = dr2d_get_text_cursor_position_from_char_cairo;
return dr2d_create_context(callbacks, 0, sizeof(cairo_surface_data), sizeof(cairo_font_data), sizeof(cairo_image_data), NULL);
}
dr2d_surface* dr2d_create_surface_cairo(dr2d_context* pContext, cairo_t* cr)
{
if (cr == NULL) {
return NULL;
}
dr2d_surface* pSurface = dr2d_create_surface(pContext, 0, 0);
if (pSurface != NULL) {
cairo_surface_data* pCairoData = (cairo_surface_data*)dr2d_get_surface_extra_data(pSurface);
if (pCairoData != NULL) {
pCairoData->pCairoContext = cairo_reference(cr);
pCairoData->pCairoSurface = cairo_surface_reference(cairo_get_target(cr));
}
}
return pSurface;
}
cairo_surface_t* dr2d_get_cairo_surface_t(dr2d_surface* pSurface)
{
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData != NULL) {
return pCairoData->pCairoSurface;
}
return NULL;
}
cairo_t* dr2d_get_cairo_t(dr2d_surface* pSurface)
{
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData != NULL) {
return pCairoData->pCairoContext;
}
return NULL;
}
bool dr2d_on_create_context_cairo(dr2d_context* pContext, const void* pUserData)
{
assert(pContext != NULL);
(void)pContext;
(void)pUserData;
return true;
}
void dr2d_on_delete_context_cairo(dr2d_context* pContext)
{
assert(pContext != NULL);
(void)pContext;
}
bool dr2d_on_create_surface_cairo(dr2d_surface* pSurface, float width, float height)
{
assert(pSurface != NULL);
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData == NULL) {
return false;
}
if (width != 0 && height != 0) {
pCairoData->pCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, (int)width, (int)height);
if (pCairoData->pCairoSurface == NULL) {
return false;
}
pCairoData->pCairoContext = cairo_create(pCairoData->pCairoSurface);
if (pCairoData->pCairoContext == NULL) {
cairo_surface_destroy(pCairoData->pCairoSurface);
return false;
}
} else {
pCairoData->pCairoSurface = NULL;
pCairoData->pCairoContext = NULL;
}
return true;
}
void dr2d_on_delete_surface_cairo(dr2d_surface* pSurface)
{
assert(pSurface != NULL);
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData != NULL)
{
cairo_destroy(pCairoData->pCairoContext);
cairo_surface_destroy(pCairoData->pCairoSurface);
}
}
bool dr2d_on_create_font_cairo(dr2d_font* pFont)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return false;
}
cairo_font_slant_t cairoSlant = CAIRO_FONT_SLANT_NORMAL;
if (pFont->slant == dr2d_font_slant_italic) {
cairoSlant = CAIRO_FONT_SLANT_ITALIC;
} else if (pFont->slant == dr2d_font_slant_oblique) {
cairoSlant = CAIRO_FONT_SLANT_OBLIQUE;
}
cairo_font_weight_t cairoWeight = CAIRO_FONT_WEIGHT_NORMAL;
if (pFont->weight == dr2d_font_weight_bold || pFont->weight == dr2d_font_weight_semi_bold || pFont->weight == dr2d_font_weight_extra_bold || pFont->weight == dr2d_font_weight_heavy) {
cairoWeight = CAIRO_FONT_WEIGHT_BOLD;
}
pCairoFont->pFace = cairo_toy_font_face_create(pFont->family, cairoSlant, cairoWeight);
if (pCairoFont->pFace == NULL) {
return false;
}
cairo_matrix_t fontMatrix;
cairo_matrix_init_scale(&fontMatrix, (double)pFont->size, (double)pFont->size);
cairo_matrix_rotate(&fontMatrix, pFont->rotation * (3.14159265 / 180.0));
cairo_matrix_t ctm;
cairo_matrix_init_identity(&ctm);
cairo_font_options_t* options = cairo_font_options_create();
cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_SUBPIXEL); // TODO: Control this with option flags in pFont.
pCairoFont->pFont = cairo_scaled_font_create(pCairoFont->pFace, &fontMatrix, &ctm, options);
if (pCairoFont->pFont == NULL) {
cairo_font_face_destroy(pCairoFont->pFace);
return false;
}
// Metrics.
cairo_font_extents_t fontMetrics;
cairo_scaled_font_extents(pCairoFont->pFont, &fontMetrics);
pCairoFont->metrics.ascent = fontMetrics.ascent;
pCairoFont->metrics.descent = fontMetrics.descent;
//pCairoFont->metrics.lineHeight = fontMetrics.height;
pCairoFont->metrics.lineHeight = fontMetrics.ascent + fontMetrics.descent;
// The width of a space needs to be retrieved via glyph metrics.
const char space[] = " ";
cairo_text_extents_t spaceMetrics;
cairo_scaled_font_text_extents(pCairoFont->pFont, space, &spaceMetrics);
pCairoFont->metrics.spaceWidth = spaceMetrics.x_advance;
return true;
}
void dr2d_on_delete_font_cairo(dr2d_font* pFont)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return;
}
cairo_scaled_font_destroy(pCairoFont->pFont);
cairo_font_face_destroy(pCairoFont->pFace);
}
bool dr2d_on_create_image_cairo(dr2d_image* pImage, unsigned int stride, const void* pData)
{
cairo_image_data* pCairoImage = dr2d_get_image_extra_data(pImage);
if (pCairoImage == NULL) {
return false;
}
size_t dataSize = pImage->height * pImage->width * 4;
pCairoImage->pData = malloc(dataSize);
if (pCairoImage->pData == NULL) {
return false;
}
if (pData != NULL)
{
for (unsigned int iRow = 0; iRow < pImage->height; ++iRow)
{
const unsigned int iRowSrc = iRow; //pImage->height - (iRow + 1);
const unsigned int iRowDst = iRow;
for (unsigned int iCol = 0; iCol < pImage->width; ++iCol)
{
unsigned int srcTexel = ((const unsigned int*)(pData ))[ (iRowSrc * (stride/4)) + iCol];
unsigned int* dstTexel = (( unsigned int*)(pCairoImage->pData)) + (iRowDst * pImage->width) + iCol;
unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24;
unsigned int srcTexelB = (srcTexel & 0x00FF0000) >> 16;
unsigned int srcTexelG = (srcTexel & 0x0000FF00) >> 8;
unsigned int srcTexelR = (srcTexel & 0x000000FF) >> 0;
srcTexelB = (unsigned int)(srcTexelB * (srcTexelA / 255.0f));
srcTexelG = (unsigned int)(srcTexelG * (srcTexelA / 255.0f));
srcTexelR = (unsigned int)(srcTexelR * (srcTexelA / 255.0f));
*dstTexel = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24);
}
}
}
pCairoImage->pCairoSurface = cairo_image_surface_create_for_data(pCairoImage->pData, CAIRO_FORMAT_ARGB32, (int)pImage->width, (int)pImage->height, (int)pImage->width*4);
if (pCairoImage->pCairoSurface == NULL) {
free(pCairoImage->pData);
return false;
}
return true;
}
void dr2d_on_delete_image_cairo(dr2d_image* pImage)
{
cairo_image_data* pCairoImage = dr2d_get_image_extra_data(pImage);
if (pCairoImage == NULL) {
return;
}
cairo_surface_destroy(pCairoImage->pCairoSurface);
free(pCairoImage->pData);
}
void dr2d_begin_draw_cairo(dr2d_surface* pSurface)
{
assert(pSurface != NULL);
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData == NULL) {
return;
}
cairo_set_antialias(pCairoData->pCairoContext, CAIRO_ANTIALIAS_NONE);
}
void dr2d_end_draw_cairo(dr2d_surface* pSurface)
{
assert(pSurface != NULL);
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData == NULL) {
return;
}
cairo_set_antialias(pCairoData->pCairoContext, CAIRO_ANTIALIAS_DEFAULT);
}
void dr2d_clear_cairo(dr2d_surface* pSurface, dr2d_color color)
{
// TODO: I forget... is this supposed to ignore the current clip? If so, this needs to be changed so that
// the clip is reset first then restored afterwards.
dr2d_draw_rect_cairo(pSurface, 0, 0, dr2d_get_surface_width(pSurface), dr2d_get_surface_height(pSurface), color);
}
void dr2d_draw_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color)
{
assert(pSurface != NULL);
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData != NULL)
{
cairo_set_source_rgba(pCairoData->pCairoContext, color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0);
cairo_rectangle(pCairoData->pCairoContext, left, top, (right - left), (bottom - top));
cairo_fill(pCairoData->pCairoContext);
}
}
void dr2d_draw_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth)
{
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData == NULL) {
return;
}
cairo_t* cr = pCairoData->pCairoContext;
cairo_set_source_rgba(cr, color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0);
// We do this as 4 separate rectangles.
cairo_rectangle(cr, left, top, outlineWidth, bottom - top); // Left
cairo_fill(cr);
cairo_rectangle(cr, right - outlineWidth, top, outlineWidth, bottom - top); // Right
cairo_fill(cr);
cairo_rectangle(cr, left + outlineWidth, top, right - left - (outlineWidth*2), outlineWidth); // Top
cairo_fill(cr);
cairo_rectangle(cr, left + outlineWidth, bottom - outlineWidth, right - left - (outlineWidth*2), outlineWidth); // Bottom
cairo_fill(cr);
}
void dr2d_draw_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor)
{
dr2d_draw_rect_cairo(pSurface, left + outlineWidth, top + outlineWidth, right - outlineWidth, bottom - outlineWidth, color);
dr2d_draw_rect_outline_cairo(pSurface, left, top, right, bottom, outlineColor, outlineWidth);
}
void dr2d_draw_round_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius)
{
// FIXME: This does not draw rounded corners.
(void)radius;
dr2d_draw_rect_cairo(pSurface, left, top, right, bottom, color);
}
void dr2d_draw_round_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth)
{
// FIXME: This does not draw rounded corners.
(void)radius;
dr2d_draw_rect_outline_cairo(pSurface, left, top, right, bottom, color, outlineWidth);
}
void dr2d_draw_round_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor)
{
// FIXME: This does not draw rounded corners.
(void)radius;
dr2d_draw_rect_with_outline_cairo(pSurface, left, top, right, bottom, color, outlineWidth, outlineColor);
}
void dr2d_draw_text_cairo(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor)
{
cairo_surface_data* pCairoSurface = dr2d_get_surface_extra_data(pSurface);
if (pCairoSurface == NULL) {
return;
}
cairo_t* cr = pCairoSurface->pCairoContext;
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return;
}
// Cairo expends null terminated strings, however the input string is not guaranteed to be null terminated.
char* textNT;
if (textSizeInBytes != (size_t)-1) {
textNT = malloc(textSizeInBytes + 1);
memcpy(textNT, text, textSizeInBytes);
textNT[textSizeInBytes] = '\0';
} else {
textNT = (char*)text;
}
cairo_set_scaled_font(cr, pCairoFont->pFont);
// Background.
cairo_text_extents_t textMetrics;
cairo_text_extents(cr, textNT, &textMetrics);
cairo_set_source_rgba(cr, backgroundColor.r / 255.0, backgroundColor.g / 255.0, backgroundColor.b / 255.0, backgroundColor.a / 255.0);
cairo_rectangle(cr, posX, posY, textMetrics.x_advance, pCairoFont->metrics.lineHeight);
cairo_fill(cr);
// Text.
cairo_move_to(cr, posX, posY + pCairoFont->metrics.ascent);
cairo_set_source_rgba(cr, color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0);
cairo_show_text(cr, textNT);
if (textNT != text) {
free(textNT);
}
}
void dr2d_draw_image_cairo(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs)
{
cairo_surface_data* pCairoSurface = dr2d_get_surface_extra_data(pSurface);
if (pCairoSurface == NULL) {
return;
}
cairo_image_data* pCairoImage = dr2d_get_image_extra_data(pImage);
if (pCairoImage == NULL) {
return;
}
cairo_t* cr = pCairoSurface->pCairoContext;
cairo_save(cr);
cairo_translate(cr, pArgs->dstX, pArgs->dstY);
// Background.
if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) != 0)
{
cairo_set_source_rgba(cr, pArgs->backgroundColor.r / 255.0, pArgs->backgroundColor.g / 255.0, pArgs->backgroundColor.b / 255.0, pArgs->backgroundColor.a / 255.0);
cairo_rectangle(cr, 0, 0, pArgs->dstWidth, pArgs->dstHeight);
cairo_fill(cr);
}
if (pArgs->foregroundTint.r == 255 && pArgs->foregroundTint.g == 255 && pArgs->foregroundTint.b == 255 && pArgs->foregroundTint.a == 255) {
cairo_scale(cr, pArgs->dstWidth / pArgs->srcWidth, pArgs->dstHeight / pArgs->srcHeight);
cairo_set_source_surface(cr, pCairoImage->pCairoSurface, pArgs->srcX, pArgs->srcY);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
} else {
// Slower path. The image needs to be tinted. We create a temporary image for this.
// NOTE: This is incorrect. It's just a temporary solution until I figure out a better way.
cairo_surface_t* pTempImageSurface = cairo_surface_create_similar_image(pCairoImage->pCairoSurface, CAIRO_FORMAT_ARGB32,
cairo_image_surface_get_width(pCairoImage->pCairoSurface), cairo_image_surface_get_height(pCairoImage->pCairoSurface));
if (pTempImageSurface != NULL) {
cairo_t* cr2 = cairo_create(pTempImageSurface);
cairo_set_operator(cr2, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(cr2, pCairoImage->pCairoSurface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr2), CAIRO_FILTER_NEAREST);
cairo_paint(cr2);
// Tint.
cairo_set_operator(cr2, CAIRO_OPERATOR_ATOP);
cairo_set_source_rgba(cr2, pArgs->foregroundTint.r / 255.0, pArgs->foregroundTint.g / 255.0, pArgs->foregroundTint.b / 255.0, 1);
cairo_rectangle(cr2, 0, 0, pArgs->dstWidth, pArgs->dstHeight);
cairo_fill(cr2);
/*cairo_set_operator(cr2, CAIRO_OPERATOR_MULTIPLY);
cairo_set_source_surface(cr2, pCairoImage->pCairoSurface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr2), CAIRO_FILTER_NEAREST);
cairo_paint(cr2);*/
// Draw the temporary surface onto the main surface.
cairo_scale(cr, pArgs->dstWidth / pArgs->srcWidth, pArgs->dstHeight / pArgs->srcHeight);
cairo_set_source_surface(cr, pTempImageSurface, pArgs->srcX, pArgs->srcY);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
//cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_paint(cr);
cairo_destroy(cr2);
cairo_surface_destroy(pTempImageSurface);
}
}
cairo_restore(cr);
}
void dr2d_set_clip_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom)
{
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData == NULL) {
return;
}
pCairoData->clipRectLeft = left;
pCairoData->clipRectTop = top;
pCairoData->clipRectRight = right;
pCairoData->clipRectBottom = bottom;
cairo_reset_clip(pCairoData->pCairoContext);
cairo_rectangle(pCairoData->pCairoContext, left, top, right - left, bottom - top);
cairo_clip(pCairoData->pCairoContext);
}
void dr2d_get_clip_cairo(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut)
{
(void)pSurface;
(void)pLeftOut;
(void)pTopOut;
(void)pRightOut;
(void)pBottomOut;
cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface);
if (pCairoData == NULL) {
return;
}
if (pLeftOut) { *pLeftOut = pCairoData->clipRectLeft; }
if (pTopOut) { *pTopOut = pCairoData->clipRectTop; }
if (pRightOut) { *pRightOut = pCairoData->clipRectRight; }
if (pBottomOut) { *pBottomOut = pCairoData->clipRectBottom; }
}
dr2d_image_format dr2d_get_optimal_image_format_cairo(dr2d_context* pContext)
{
(void)pContext;
return dr2d_image_format_argb8;
}
void* dr2d_map_image_data_cairo(dr2d_image* pImage, unsigned accessFlags)
{
(void)pImage;
(void)accessFlags;
return NULL;
}
void dr2d_unmap_image_data_cairo(dr2d_image* pImage)
{
(void)pImage;
}
bool dr2d_get_font_metrics_cairo(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return false;
}
if (pMetricsOut) {
*pMetricsOut = pCairoFont->metrics;
}
return true;
}
static size_t dr2d__utf32_to_utf8(unsigned int utf32, char* utf8, size_t utf8Size)
{
// NOTE: This function is untested.
size_t utf8ByteCount = 0;
if (utf32 < 0x80) {
utf8ByteCount = 1;
} else if (utf32 < 0x800) {
utf8ByteCount = 2;
} else if (utf32 < 0x10000) {
utf8ByteCount = 3;
} else if (utf32 < 0x110000) {
utf8ByteCount = 4;
}
if (utf8ByteCount > utf8Size) {
if (utf8 != NULL && utf8Size > 0) {
utf8[0] = '\0';
}
return 0;
}
utf8 += utf8ByteCount;
if (utf8ByteCount < utf8Size) {
utf8[0] = '\0'; // Null terminate.
}
const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC};
switch (utf8ByteCount)
{
case 4: *--utf8 = (char)((utf32 | 0x80) & 0xBF); utf32 >>= 6;
case 3: *--utf8 = (char)((utf32 | 0x80) & 0xBF); utf32 >>= 6;
case 2: *--utf8 = (char)((utf32 | 0x80) & 0xBF); utf32 >>= 6;
case 1: *--utf8 = (char)(utf32 | firstByteMark[utf8ByteCount]);
default: break;
}
return utf8ByteCount;
}
bool dr2d_get_glyph_metrics_cairo(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return false;
}
// The UTF-32 code point needs to be converted to a UTF-8 character.
char utf8[16];
size_t utf8len = dr2d__utf32_to_utf8(utf32, utf8, sizeof(utf8)); // This will null-terminate.
if (utf8len == 0) {
return false; // Error converting UTF-32 to UTF-8.
}
cairo_text_extents_t glyphExtents;
cairo_scaled_font_text_extents(pCairoFont->pFont, utf8, &glyphExtents);
if (pGlyphMetrics)
{
pGlyphMetrics->width = glyphExtents.width;
pGlyphMetrics->height = glyphExtents.height;
pGlyphMetrics->originX = glyphExtents.x_bearing;
pGlyphMetrics->originY = glyphExtents.y_bearing;
pGlyphMetrics->advanceX = glyphExtents.x_advance;
pGlyphMetrics->advanceY = glyphExtents.y_advance;
}
return true;
}
bool dr2d_measure_string_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return false;
}
// Cairo expends null terminated strings, however the input string is not guaranteed to be null terminated.
char* textNT;
if (textSizeInBytes != (size_t)-1) {
textNT = malloc(textSizeInBytes + 1);
if (textNT == NULL) {
return false;
}
memcpy(textNT, text, textSizeInBytes);
textNT[textSizeInBytes] = '\0';
} else {
textNT = (char*)text;
}
cairo_text_extents_t textMetrics;
cairo_scaled_font_text_extents(pCairoFont->pFont, textNT, &textMetrics);
if (pWidthOut) {
*pWidthOut = textMetrics.x_advance;
}
if (pHeightOut) {
//*pHeightOut = textMetrics.height;
*pHeightOut = pCairoFont->metrics.ascent + pCairoFont->metrics.descent;
}
if (textNT != text) {
free(textNT);
}
return true;
}
bool dr2d_get_text_cursor_position_from_point_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return false;
}
cairo_glyph_t* pGlyphs = NULL;
int glyphCount = 0;
cairo_status_t result = cairo_scaled_font_text_to_glyphs(pCairoFont->pFont, 0, 0, text, textSizeInBytes, &pGlyphs, &glyphCount, NULL, NULL, NULL);
if (result != CAIRO_STATUS_SUCCESS) {
return false;
}
float cursorPosX = 0;
int charIndex = 0;
// We just iterate over each glyph until we find the one sitting under <inputPosX>.
float runningPosX = 0;
for (int iGlyph = 0; iGlyph < glyphCount; ++iGlyph)
{
cairo_text_extents_t glyphMetrics;
cairo_scaled_font_glyph_extents(pCairoFont->pFont, pGlyphs + iGlyph, 1, &glyphMetrics);
float glyphLeft = runningPosX;
float glyphRight = glyphLeft + glyphMetrics.x_advance;
// Are we sitting on top of inputPosX?
if (inputPosX >= glyphLeft && inputPosX <= glyphRight)
{
float glyphHalf = glyphLeft + ceilf(((glyphRight - glyphLeft) / 2.0f));
if (inputPosX <= glyphHalf) {
cursorPosX = glyphLeft;
charIndex = iGlyph;
} else {
cursorPosX = glyphRight;
charIndex = iGlyph + 1;
}
break;
}
else
{
// Have we moved past maxWidth?
if (glyphRight > maxWidth)
{
cursorPosX = maxWidth;
charIndex = iGlyph;
break;
}
else
{
runningPosX = glyphRight;
cursorPosX = runningPosX;
charIndex = iGlyph;
}
}
}
cairo_glyph_free(pGlyphs);
if (pTextCursorPosXOut) {
*pTextCursorPosXOut = cursorPosX;
}
if (pCharacterIndexOut) {
*pCharacterIndexOut = charIndex;
}
return true;
}
bool dr2d_get_text_cursor_position_from_char_cairo(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut)
{
cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont);
if (pCairoFont == NULL) {
return false;
}
cairo_glyph_t* pGlyphs = NULL;
int glyphCount = 0;
cairo_status_t result = cairo_scaled_font_text_to_glyphs(pCairoFont->pFont, 0, 0, text, -1, &pGlyphs, &glyphCount, NULL, NULL, NULL);
if (result != CAIRO_STATUS_SUCCESS) {
return false;
}
float cursorPosX = 0;
// We just iterate over each glyph until we find the one sitting under <inputPosX>.
for (int iGlyph = 0; iGlyph < glyphCount; ++iGlyph)
{
if (iGlyph == (int)characterIndex) {
break;
}
cairo_text_extents_t glyphMetrics;
cairo_scaled_font_glyph_extents(pCairoFont->pFont, pGlyphs + iGlyph, 1, &glyphMetrics);
cursorPosX += glyphMetrics.x_advance;
}
cairo_glyph_free(pGlyphs);
if (pTextCursorPosXOut) {
*pTextCursorPosXOut = cursorPosX;
}
return true;
}
#endif // Cairo
#endif
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/