Vastly simplify GDISP mcufont interface code.

Fix boundary cases.
Prevent overwriting of defined display area.
Improve performance.
Remove hardware acceleration for fonts (unlikely anyway unles the hardware understood our software font structures)
ugfx_release_2.6
inmarket 2013-07-29 16:29:25 +10:00
parent 12085b8014
commit 7c303eb72e
6 changed files with 156 additions and 415 deletions

View File

@ -36,12 +36,6 @@ typedef int16_t coord_t;
#if GFX_USE_GDISP || defined(__DOXYGEN__)
/*===========================================================================*/
/* Include the low level driver configuration information */
/*===========================================================================*/
#include "gdisp_lld_config.h"
/*===========================================================================*/
/* Type definitions */
/*===========================================================================*/
@ -480,35 +474,6 @@ extern "C" {
void gdispFillArc(coord_t x, coord_t y, coord_t radius, coord_t startangle, coord_t endangle, color_t color);
#endif
/* Basic Text Rendering Functions */
#if GDISP_NEED_TEXT || defined(__DOXYGEN__)
/**
* @brief Draw a text character.
*
* @param[in] x,y The position for the text
* @param[in] c The character to draw
* @param[in] font The font to use
* @param[in] color The color to use
*
* @api
*/
void gdispDrawChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color);
/**
* @brief Draw a text character with a filled background.
*
* @param[in] x,y The position for the text
* @param[in] c The character to draw
* @param[in] font The font to use
* @param[in] color The color to use
* @param[in] bgcolor The background color to use
*
* @api
*/
void gdispFillChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color, color_t bgcolor);
#endif
/* Read a pixel Function */
#if GDISP_NEED_PIXELREAD || defined(__DOXYGEN__)
@ -591,8 +556,6 @@ extern "C" {
#define gdispFillArc(x, y, radius, sangle, eangle, color) gdisp_lld_fill_arc(x, y, radius, sangle, eangle, color)
#define gdispDrawEllipse(x, y, a, b, color) gdisp_lld_draw_ellipse(x, y, a, b, color)
#define gdispFillEllipse(x, y, a, b, color) gdisp_lld_fill_ellipse(x, y, a, b, color)
#define gdispDrawChar(x, y, c, font, color) gdisp_lld_draw_char(x, y, c, font, color)
#define gdispFillChar(x, y, c, font, color, bgcolor) gdisp_lld_fill_char(x, y, c, font, color, bgcolor)
#define gdispGetPixelColor(x, y) gdisp_lld_get_pixel_color(x, y)
#define gdispVerticalScroll(x, y, cx, cy, lines, bgcolor) gdisp_lld_vertical_scroll(x, y, cx, cy, lines, bgcolor)
#define gdispControl(what, value) gdisp_lld_control(what, value)
@ -652,9 +615,34 @@ void gdispDrawBox(coord_t x, coord_t y, coord_t cx, coord_t cy, color_t color);
void gdispFillConvexPoly(coord_t tx, coord_t ty, const point *pntarray, unsigned cnt, color_t color);
#endif
/* Extra Text Functions */
/* Text Functions */
#if GDISP_NEED_TEXT || defined(__DOXYGEN__)
/**
* @brief Draw a text character.
*
* @param[in] x,y The position for the text
* @param[in] c The character to draw
* @param[in] font The font to use
* @param[in] color The color to use
*
* @api
*/
void gdispDrawChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color);
/**
* @brief Draw a text character with a filled background.
*
* @param[in] x,y The position for the text
* @param[in] c The character to draw
* @param[in] font The font to use
* @param[in] color The color to use
* @param[in] bgcolor The background color to use
*
* @api
*/
void gdispFillChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color, color_t bgcolor);
/**
* @brief Draw a text string.
*

View File

@ -461,135 +461,6 @@ GDISPDriver GDISP;
}
#endif
#if GDISP_NEED_TEXT && !GDISP_HARDWARE_TEXT
#include "mcufont.h"
#endif
#if GDISP_NEED_TEXT && !GDISP_HARDWARE_TEXT
static void gdisp_lld_draw_char_callback(int16_t x, int16_t y, uint8_t count, uint8_t alpha, void *state) {
color_t color = *(color_t*)state;
if (alpha == 255)
{
if (count == 1)
gdisp_lld_draw_pixel(x, y, color);
else
gdisp_lld_fill_area(x, y, count, 1, color);
}
#if GDISP_NEED_ANTIALIAS
else
{
while (count--)
{
color_t oldColor;
oldColor = gdisp_lld_get_pixel_color(x, y);
oldColor = gdispBlendColor(color, oldColor, alpha);
gdisp_lld_draw_pixel(x, y, oldColor);
x++;
}
}
#endif
}
void gdisp_lld_draw_char(coord_t x, coord_t y, uint16_t c, font_t font, color_t color) {
mf_render_character(font, x, y, c, gdisp_lld_draw_char_callback, &color);
}
#endif
#if GDISP_NEED_TEXT && !GDISP_HARDWARE_TEXTFILLS
typedef struct {
int16_t x0;
int16_t y0;
int16_t x1;
int16_t y1;
int16_t x;
int16_t y;
color_t fg;
color_t bg;
} gdisp_lld_fill_char_state_t;
static void gdisp_lld_fill_char_callback(int16_t x, int16_t y, uint8_t count, uint8_t alpha, void *state) {
gdisp_lld_fill_char_state_t *s = state;
if (y > s->y && s->x != s->x0)
{
/* Fill background to the end of current line */
gdisp_lld_fill_area(s->x, s->y, s->x1 - s->x, 1, s->bg);
s->y++;
s->x = s->x0;
}
if (y > s->y)
{
/* Fill background for given number of full lines */
gdisp_lld_fill_area(s->x0, s->y, s->x1 - s->x0, y - s->y, s->bg);
s->y = y;
}
if (x > s->x)
{
/* Fill background from previous X position */
gdisp_lld_fill_area(s->x, s->y, x - s->x, 1, s->bg);
s->x = x;
}
if (!count)
return;
if (y == s->y)
{
s->x += count;
if (s->x == s->x1)
{
s->x = s->x0;
s->y++;
}
}
/* Draw the foreground */
if (alpha == 255 || alpha == 0)
{
if (count == 1)
gdisp_lld_draw_pixel(x, y, alpha ? s->fg : s->bg);
else
gdisp_lld_fill_area(x, y, count, 1, alpha ? s->fg : s->bg);
}
#if GDISP_NEED_ANTIALIAS
else
{
while (count--)
{
color_t color;
color = gdispBlendColor(s->fg, s->bg, alpha);
gdisp_lld_draw_pixel(x, y, color);
x++;
}
}
#endif
}
void gdisp_lld_fill_char(coord_t x, coord_t y, uint16_t c, font_t font, color_t color, color_t bgcolor) {
gdisp_lld_fill_char_state_t state;
state.x0 = x;
state.y0 = y;
state.x1 = x + mf_character_width(font, c) + font->baseline_x;
state.y1 = y + font->height;
state.x = x;
state.y = y;
state.fg = color;
state.bg = bgcolor;
mf_render_character(font, x, y, c, gdisp_lld_fill_char_callback, &state);
gdisp_lld_fill_char_callback(state.x0, state.y1, 0, 0, &state);
}
#endif
#if GDISP_NEED_CONTROL && !GDISP_HARDWARE_CONTROL
void gdisp_lld_control(unsigned what, void *value) {
(void)what;
@ -657,14 +528,6 @@ void *gdisp_lld_query(unsigned what) {
gdisp_lld_fill_circle(msg->fillarc.x, msg->fillarc.y, msg->fillarc.radius, msg->fillarc.startangle, msg->fillarc.endangle, msg->fillarc.color);
break;
#endif
#if GDISP_NEED_TEXT
case GDISP_LLD_MSG_DRAWCHAR:
gdisp_lld_draw_char(msg->drawchar.x, msg->drawchar.y, msg->drawchar.c, msg->drawchar.font, msg->drawchar.color);
break;
case GDISP_LLD_MSG_FILLCHAR:
gdisp_lld_fill_char(msg->fillchar.x, msg->fillchar.y, msg->fillchar.c, msg->fillchar.font, msg->fillchar.color, msg->fillchar.bgcolor);
break;
#endif
#if GDISP_NEED_PIXELREAD
case GDISP_LLD_MSG_GETPIXELCOLOR:
msg->getpixelcolor.result = gdisp_lld_get_pixel_color(msg->getpixelcolor.x, msg->getpixelcolor.y);

View File

@ -106,22 +106,6 @@
#define GDISP_HARDWARE_ARCFILLS FALSE
#endif
/**
* @brief Hardware accelerated text drawing.
* @details If set to @p FALSE software emulation is used.
*/
#ifndef GDISP_HARDWARE_TEXT
#define GDISP_HARDWARE_TEXT FALSE
#endif
/**
* @brief Hardware accelerated text drawing with a filled background.
* @details If set to @p FALSE software emulation is used.
*/
#ifndef GDISP_HARDWARE_TEXTFILLS
#define GDISP_HARDWARE_TEXTFILLS FALSE
#endif
/**
* @brief Hardware accelerated scrolling.
* @details If set to @p FALSE there is no support for scrolling.
@ -167,26 +151,6 @@
* @name GDISP software algorithm choices
* @{
*/
/**
* @brief For filled text drawing, use a background fill and then draw
* the text instead of using a blit or direct pixel drawing.
* @details If set to @p TRUE background fill and then text draw is used.
* @note This is ignored if hardware accelerated text is supported.
*/
#ifndef GDISP_SOFTWARE_TEXTFILLDRAW
#define GDISP_SOFTWARE_TEXTFILLDRAW FALSE
#endif
/**
* @brief For filled text drawing, when using a bitmap blit
* use a column by column buffer rather than a full character
* buffer to save memory at a small performance cost.
* @details If set to @p TRUE background fill one character column at a time.
* @note This is ignored if software text using blit is not being used.
*/
#ifndef GDISP_SOFTWARE_TEXTBLITCOLUMN
#define GDISP_SOFTWARE_TEXTBLITCOLUMN FALSE
#endif
/** @} */
/**

View File

@ -42,10 +42,6 @@ typedef enum gdisp_msgaction {
GDISP_LLD_MSG_DRAWARC,
GDISP_LLD_MSG_FILLARC,
#endif
#if GDISP_NEED_TEXT
GDISP_LLD_MSG_DRAWCHAR,
GDISP_LLD_MSG_FILLCHAR,
#endif
#if GDISP_NEED_PIXELREAD
GDISP_LLD_MSG_GETPIXELCOLOR,
#endif
@ -151,23 +147,6 @@ typedef union gdisp_lld_msg {
coord_t startangle, endangle;
color_t color;
} fillarc;
struct gdisp_lld_msg_drawchar {
gfxQueueItem qi;
gdisp_msgaction_t action; // GDISP_LLD_MSG_DRAWCHAR
coord_t x, y;
uint16_t c;
font_t font;
color_t color;
} drawchar;
struct gdisp_lld_msg_fillchar {
gfxQueueItem qi;
gdisp_msgaction_t action; // GDISP_LLD_MSG_FILLCHAR
coord_t x, y;
uint16_t c;
font_t font;
color_t color;
color_t bgcolor;
} fillchar;
struct gdisp_lld_msg_getpixelcolor {
gfxQueueItem qi;
gdisp_msgaction_t action; // GDISP_LLD_MSG_GETPIXELCOLOR

View File

@ -296,6 +296,10 @@
/* #define GDISP_USE_GPIO */
/** @} */
#if GFX_USE_GDISP
#include "gdisp_lld_config.h"
#endif
#endif /* _GDISP_OPTIONS_H */
/** @} */

View File

@ -16,10 +16,6 @@
#if GFX_USE_GDISP
#ifdef GDISP_NEED_TEXT
#include "mcufont.h"
#endif
/* Include the low level driver information */
#include "gdisp/lld/gdisp_lld.h"
@ -399,43 +395,6 @@ void gdispFillRoundedBox(coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t r
}
#endif
#if (GDISP_NEED_TEXT && GDISP_NEED_MULTITHREAD)
void gdispDrawChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color) {
gfxMutexEnter(&gdispMutex);
gdisp_lld_draw_char(x, y, c, font, color);
gfxMutexExit(&gdispMutex);
}
#elif GDISP_NEED_TEXT && GDISP_NEED_ASYNC
void gdispDrawChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color) {
gdisp_lld_msg_t *p = gdispAllocMsg(GDISP_LLD_MSG_DRAWCHAR);
p->drawchar.x = x;
p->drawchar.y = y;
p->drawchar.c = c;
p->drawchar.font = font;
p->drawchar.color = color;
gfxQueuePut(&gdispQueue, &p->qi, TIME_IMMEDIATE);
}
#endif
#if (GDISP_NEED_TEXT && GDISP_NEED_MULTITHREAD)
void gdispFillChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color, color_t bgcolor) {
gfxMutexEnter(&gdispMutex);
gdisp_lld_fill_char(x, y, c, font, color, bgcolor);
gfxMutexExit(&gdispMutex);
}
#elif GDISP_NEED_TEXT && GDISP_NEED_ASYNC
void gdispFillChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color, color_t bgcolor) {
gdisp_lld_msg_t *p = gdispAllocMsg(GDISP_LLD_MSG_FILLCHAR);
p->fillchar.x = x;
p->fillchar.y = y;
p->fillchar.c = c;
p->fillchar.font = font;
p->fillchar.color = color;
p->fillchar.bgcolor = bgcolor;
gfxQueuePut(&gdispQueue, &p->qi, TIME_IMMEDIATE);
}
#endif
#if (GDISP_NEED_PIXELREAD && (GDISP_NEED_MULTITHREAD || GDISP_NEED_ASYNC))
color_t gdispGetPixelColor(coord_t x, coord_t y) {
color_t c;
@ -620,21 +579,84 @@ void gdispDrawBox(coord_t x, coord_t y, coord_t cx, coord_t cy, color_t color) {
#endif
#if GDISP_NEED_TEXT
#include "mcufont.h"
#if GDISP_NEED_ANTIALIAS && GDISP_NEED_PIXELREAD
static void text_draw_char_callback(int16_t x, int16_t y, uint8_t count, uint8_t alpha, void *state) {
if (alpha == 255) {
if (count == 1)
gdispDrawPixel(x, y, ((color_t *)state)[0]);
else
gdispFillArea(x, y, count, 1, ((color_t *)state)[0]);
} else {
while (count--) {
gdispDrawPixel(x, y, gdispBlendColor(((color_t *)state)[0], gdispGetPixelColor(x, y), alpha));
x++;
}
}
}
#else
static void text_draw_char_callback(int16_t x, int16_t y, uint8_t count, uint8_t alpha, void *state) {
if (alpha > 0x80) { // A best approximation when using anti-aliased fonts but we can't actually draw them anti-aliased
if (count == 1)
gdispDrawPixel(x, y, ((color_t *)state)[0]);
else
gdispFillArea(x, y, count, 1, ((color_t *)state)[0]);
}
}
#endif
void gdispDrawChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color) {
/* No mutex required as we only call high level functions which have their own mutex */
mf_render_character(font, x, y, c, text_draw_char_callback, &color);
}
#if GDISP_NEED_ANTIALIAS
static void text_fill_char_callback(int16_t x, int16_t y, uint8_t count, uint8_t alpha, void *state) {
if (alpha == 255) {
if (count == 1)
gdispDrawPixel(x, y, ((color_t *)state)[0]);
else
gdispFillArea(x, y, count, 1, ((color_t *)state)[0]);
} else {
while (count--) {
gdispDrawPixel(x, y, gdispBlendColor(((color_t *)state)[0], ((color_t *)state)[1], alpha));
x++;
}
}
}
#else
#define text_fill_char_callback text_draw_char_callback
#endif
void gdispFillChar(coord_t x, coord_t y, uint16_t c, font_t font, color_t color, color_t bgcolor) {
/* No mutex required as we only call high level functions which have their own mutex */
color_t state[2];
state[0] = color;
state[1] = bgcolor;
gdispFillArea(x, y, mf_character_width(font, c) + font->baseline_x, font->height, bgcolor);
mf_render_character(font, x, y, c, text_fill_char_callback, state);
}
typedef struct
{
font_t font;
color_t color;
coord_t x, y;
coord_t cx, cy;
} gdispDrawString_state_t;
/* Callback to render characters. */
static uint8_t gdispDrawString_callback(int16_t x, int16_t y,
mf_char character, void *state)
static uint8_t gdispDrawString_callback(int16_t x, int16_t y, mf_char character, void *state)
{
gdispDrawString_state_t *s = state;
uint8_t w;
gdispDrawChar(x, y, character, s->font, s->color);
w = mf_character_width(s->font, character);
if (x >= s->x && x+w < s->x + s->cx && y >= s->y && y+s->font->height <= s->y + s->cy)
mf_render_character(s->font, x, y, character, text_draw_char_callback, &s->color);
return w;
}
@ -644,80 +666,31 @@ void gdispDrawBox(coord_t x, coord_t y, coord_t cx, coord_t cy, color_t color) {
state.font = font;
state.color = color;
state.x = x;
state.y = y;
state.cx = GDISP.Width - x;
state.cy = GDISP.Height - y;
mf_render_aligned(font, x, y, MF_ALIGN_LEFT, str, 0,
gdispDrawString_callback, &state);
mf_render_aligned(font, x, y, MF_ALIGN_LEFT, str, 0, gdispDrawString_callback, &state);
}
#endif
#if GDISP_NEED_TEXT
typedef struct
{
coord_t y0;
coord_t prev_x;
font_t font;
color_t color;
color_t bgcolor;
bool_t rightalign;
color_t color[2];
coord_t x, y;
coord_t cx, cy;
} gdispFillString_state_t;
/* Callback to render characters. */
static uint8_t gdispFillString_callback(int16_t x, int16_t y,
mf_char character, void *state)
static uint8_t gdispFillString_callback(int16_t x, int16_t y, mf_char character, void *state)
{
gdispFillString_state_t *s = state;
uint8_t w;
int16_t right_edge;
w = mf_character_width(s->font, character);
right_edge = x + w + s->font->baseline_x;
if (!s->rightalign)
{
if (s->prev_x < x)
{
/* Fill any space between characters */
gdispFillArea(s->prev_x, s->y0, x - s->prev_x, s->font->height,
s->bgcolor);
}
else if (s->prev_x > x)
{
/* Uh, looks like there is some kerning going on. If we would
* just call gdispFillChar() here, it would overwrite part of
* the previous character. Instead, fill background separately.
*/
gdispFillArea(s->prev_x, s->y0, right_edge - s->prev_x,
s->font->height, s->bgcolor);
gdispDrawChar(x, y, character, s->font, s->color);
s->prev_x = right_edge;
return w;
}
s->prev_x = right_edge;
}
else
{
/* When rendering right-aligned text, the characters are drawn
* from right to left. */
if (s->prev_x > right_edge)
{
/* Fill any space between characters */
gdispFillArea(right_edge, s->y0, s->prev_x - right_edge,
s->font->height, s->bgcolor);
}
else if (s->prev_x < right_edge)
{
gdispFillArea(x, s->y0, s->prev_x - x,
s->font->height, s->bgcolor);
gdispDrawChar(x, y, character, s->font, s->color);
s->prev_x = x;
return w;
}
s->prev_x = x;
}
gdispFillChar(x, y, character, s->font, s->color, s->bgcolor);
w = mf_character_width(s->font, character);
if (x >= s->x && x+w < s->x + s->cx && y >= s->y && y+s->font->height <= s->y + s->cy)
mf_render_character(s->font, x, y, character, text_fill_char_callback, s->color);
return w;
}
@ -725,104 +698,78 @@ void gdispDrawBox(coord_t x, coord_t y, coord_t cx, coord_t cy, color_t color) {
/* No mutex required as we only call high level functions which have their own mutex */
gdispFillString_state_t state;
state.y0 = y;
state.prev_x = x;
state.font = font;
state.color = color;
state.bgcolor = bgcolor;
state.rightalign = false;
state.color[0] = color;
state.color[1] = bgcolor;
state.x = x;
state.y = y;
state.cx = mf_get_string_width(font, str, 0, 0);
state.cy = font->height;
x += font->baseline_x;
mf_render_aligned(font, x, y, MF_ALIGN_LEFT, str, 0,
gdispFillString_callback, &state);
gdispFillArea(x, y, state.cx, state.cy, bgcolor);
mf_render_aligned(font, x+font->baseline_x, y, MF_ALIGN_LEFT, str, 0, gdispFillString_callback, &state);
}
#endif
#if GDISP_NEED_TEXT
void gdispDrawStringBox(coord_t x, coord_t y, coord_t cx, coord_t cy, const char* str, font_t font, color_t color, justify_t justify) {
/* No mutex required as we only call high level functions which have their own mutex */
gdispDrawString_state_t state;
state.font = font;
state.color = color;
state.x = x;
state.y = y;
state.cx = cx;
state.cy = cy;
/* Select the anchor position */
if (justify == justifyLeft)
x += font->baseline_x;
else if (justify == justifyCenter)
switch(justify) {
case justifyCenter:
x += (cx + 1) / 2;
else if (justify == justifyRight)
break;
case justifyRight:
x += cx;
break;
default: // justifyLeft
x += font->baseline_x;
break;
}
y += (cy+1 - font->height)/2;
mf_render_aligned(font, x, y, justify, str, 0,
gdispDrawString_callback, &state);
mf_render_aligned(font, x, y, justify, str, 0, gdispDrawString_callback, &state);
}
#endif
#if GDISP_NEED_TEXT
void gdispFillStringBox(coord_t x, coord_t y, coord_t cx, coord_t cy, const char* str, font_t font, color_t color, color_t bgcolor, justify_t justify) {
/* No mutex required as we only call high level functions which have their own mutex */
gdispFillString_state_t state;
int16_t min_x, max_x;
min_x = x;
max_x = x + cx;
state.y0 = y + (cy - font->height + 1) / 2;
state.prev_x = x;
state.font = font;
state.color = color;
state.bgcolor = bgcolor;
state.rightalign = false;
/* Fill above the text */
if (state.y0 > y)
{
gdispFillArea(x, y, cx, state.y0 - y, bgcolor);
}
/* Fill below the text */
if (state.y0 + font->height < y + cy)
{
gdispFillArea(x, state.y0 + font->height, cx,
(y + cy) - (state.y0 + font->height), bgcolor);
}
state.color[0] = color;
state.color[1] = bgcolor;
state.x = x;
state.y = y;
state.cx = cx;
state.cy = cy;
gdispFillArea(x, y, cx, cy, bgcolor);
/* Select the anchor position */
if (justify == justifyLeft)
{
x += font->baseline_x;
}
else if (justify == justifyCenter)
{
switch(justify) {
case justifyCenter:
x += (cx + 1) / 2;
}
else if (justify == justifyRight)
{
state.rightalign = true;
state.prev_x = x + cx;
break;
case justifyRight:
x += cx;
break;
default: // justifyLeft
x += font->baseline_x;
break;
}
y += (cy+1 - font->height)/2;
/* Render */
mf_render_aligned(font, x, state.y0, justify, str, 0,
gdispFillString_callback, &state);
/* Fill any space left */
if (!state.rightalign && state.prev_x < max_x)
{
gdispFillArea(state.prev_x, state.y0, max_x - state.prev_x,
state.font->height, bgcolor);
}
else if (state.rightalign && state.prev_x > min_x)
{
gdispFillArea(min_x, state.y0, state.prev_x - min_x,
state.font->height, bgcolor);
}
mf_render_aligned(font, x, y, justify, str, 0, gdispFillString_callback, &state);
}
#endif
#if GDISP_NEED_TEXT
coord_t gdispGetFontMetric(font_t font, fontmetric_t metric) {
/* No mutex required as we only read static data */
switch(metric) {
@ -835,16 +782,12 @@ void gdispDrawBox(coord_t x, coord_t y, coord_t cx, coord_t cy, color_t color) {
}
return 0;
}
#endif
#if GDISP_NEED_TEXT
coord_t gdispGetCharWidth(char c, font_t font) {
/* No mutex required as we only read static data */
return mf_character_width(font, c);
}
#endif
#if GDISP_NEED_TEXT
coord_t gdispGetStringWidth(const char* str, font_t font) {
/* No mutex required as we only read static data */
return mf_get_string_width(font, str, 0, 0);