482 changes: 482 additions & 0 deletions lib/ivis_opengl/textdraw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,412 @@
#define ASCII_NEWLINE ('@')
#define ASCII_COLOURMODE ('#')

#ifdef WZ_OS_MAC
# include <QuesoGLC/glc.h>
#else
# include <GL/glc.h>
#endif

static char font_family[128];
static char font_face_regular[128];
static char font_face_bold[128];

static float font_size = 12.f;
// Contains the font color in the following order: red, green, blue, alpha
static float font_colour[4] = {1.f, 1.f, 1.f, 1.f};

static GLint _glcContext = 0;
static GLint _glcFont_Regular = 0;
static GLint _glcFont_Bold = 0;

/***************************************************************************/
/*
* Source
*/
/***************************************************************************/

void iV_font(const char *fontName, const char *fontFace, const char *fontFaceBold)
{
if (_glcContext)
{
debug(LOG_ERROR, "Cannot change font in running game, yet.");
return;
}
if (fontName)
{
sstrcpy(font_family, fontName);
}
if (fontFace)
{
sstrcpy(font_face_regular, fontFace);
}
if (fontFaceBold)
{
sstrcpy(font_face_bold, fontFaceBold);
}
}

static inline void iV_printFontList(void)
{
unsigned int i;
unsigned int font_count = glcGeti(GLC_CURRENT_FONT_COUNT);
debug(LOG_NEVER, "GLC_CURRENT_FONT_COUNT = %d", font_count);

if (font_count == 0)
{
debug(LOG_ERROR, "The required font (%s) isn't loaded", font_family);

// Fall back to unselected fonts since the requested font apparently
// isn't available.
glcEnable(GLC_AUTO_FONT);
}

for (i = 0; i < font_count; ++i)
{
GLint font = glcGetListi(GLC_CURRENT_FONT_LIST, i);
/* The output of the family name and the face is printed using 2 steps
* because glcGetFontc and glcGetFontFace return their result in the
* same buffer (according to GLC specs).
*/
char prBuffer[1024];
snprintf(prBuffer, sizeof(prBuffer), "Font #%d : %s ", (int)font, (const char*)glcGetFontc(font, GLC_FAMILY));
prBuffer[sizeof(prBuffer) - 1] = 0;
sstrcat(prBuffer, (char const *)glcGetFontFace(font));
debug(LOG_NEVER, "%s", prBuffer);
}
}

static void iV_initializeGLC(void)
{
if (_glcContext)
{
return;
}

_glcContext = glcGenContext();
if (!_glcContext)
{
debug(LOG_ERROR, "Failed to initialize");
}
else
{
debug(LOG_NEVER, "Successfully initialized. _glcContext = %d", (int)_glcContext);
}

glcContext(_glcContext);

glcEnable(GLC_AUTO_FONT); // We *do* want font fall-backs
glcRenderStyle(GLC_TEXTURE);
glcStringType(GLC_UTF8_QSO); // Set GLC's string type to UTF-8 FIXME should be UCS4 to avoid conversions

#ifdef WZ_OS_MAC
{
char resourcePath[PATH_MAX];
CFURLRef resourceURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
if (CFURLGetFileSystemRepresentation(resourceURL, true, (UInt8 *) resourcePath, PATH_MAX))
{
sstrcat(resourcePath, "/Fonts");
glcAppendCatalog(resourcePath);
}
else
{
debug(LOG_ERROR, "Could not change to resources directory.");
}

if (resourceURL != NULL)
{
CFRelease(resourceURL);
}
}
#endif

_glcFont_Regular = glcGenFontID();
_glcFont_Bold = glcGenFontID();

if (!glcNewFontFromFamily(_glcFont_Regular, font_family))
{
debug(LOG_ERROR, "Failed to select font family %s as regular font", font_family);
}
else
{
debug(LOG_NEVER, "Successfully selected font family %s as regular font", font_family);
}

if (!glcFontFace(_glcFont_Regular, font_face_regular))
{
debug(LOG_WARNING, "Failed to select the \"%s\" font face of font family %s", font_face_regular, font_family);
}
else
{
debug(LOG_NEVER, "Successfully selected the \"%s\" font face of font family %s", font_face_regular, font_family);
}

if (!glcNewFontFromFamily(_glcFont_Bold, font_family))
{
debug(LOG_ERROR, "iV_initializeGLC: Failed to select font family %s for the bold font", font_family);
}
else
{
debug(LOG_NEVER, "Successfully selected font family %s for the bold font", font_family);
}

if (!glcFontFace(_glcFont_Bold, font_face_bold))
{
debug(LOG_WARNING, "Failed to select the \"%s\" font face of font family %s", font_face_bold, font_family);
}
else
{
debug(LOG_NEVER, "Successfully selected the \"%s\" font face of font family %s", font_face_bold, font_family);
}

debug(LOG_NEVER, "Finished initializing GLC");
}

void iV_TextInit()
{
iV_initializeGLC();
iV_SetFont(font_regular);

#ifdef DEBUG
iV_printFontList();
#endif
}

void iV_TextShutdown()
{
if (_glcFont_Regular)
{
glcDeleteFont(_glcFont_Regular);
}

if (_glcFont_Bold)
{
glcDeleteFont(_glcFont_Bold);
}

glcContext(0);

if (_glcContext)
{
glcDeleteContext(_glcContext);
}
}

void iV_SetFont(enum iV_fonts FontID)
{
switch (FontID)
{
default:
case font_scaled:
case font_regular:
iV_SetTextSize(12.f);
glcFont(_glcFont_Regular);
break;

case font_large:
iV_SetTextSize(21.f);
glcFont(_glcFont_Bold);
break;

case font_small:
iV_SetTextSize(9.f);
glcFont(_glcFont_Regular);
break;
}
}

static inline float getGLCResolution(void)
{
float resolution = glcGetf(GLC_RESOLUTION);

// The default resolution as used by OpenGLC is 72 dpi
if (resolution == 0.f)
{
return 72.f;
}

return resolution;
}

static inline float getGLCPixelSize(void)
{
float pixel_size = font_size * getGLCResolution() / 72.f;
return pixel_size;
}

static inline float getGLCPointWidth(const float* boundingbox)
{
// boundingbox contains: [ xlb ylb xrb yrb xrt yrt xlt ylt ]
// l = left; r = right; b = bottom; t = top;
float rightTopX = boundingbox[4];
float leftTopX = boundingbox[6];

float point_width = rightTopX - leftTopX;

return point_width;
}

static inline float getGLCPointHeight(const float* boundingbox)
{
// boundingbox contains: [ xlb ylb xrb yrb xrt yrt xlt ylt ]
// l = left; r = right; b = bottom; t = top;
float leftBottomY = boundingbox[1];
float leftTopY = boundingbox[7];

float point_height = fabsf(leftTopY - leftBottomY);

return point_height;
}

static inline float getGLCPointToPixel(float point_width)
{
float pixel_width = point_width * getGLCPixelSize();

return pixel_width;
}

unsigned int iV_GetTextWidth(const char* string)
{
float boundingbox[8];
float pixel_width, point_width;

glcMeasureString(GL_FALSE, string);
if (!glcGetStringMetric(GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the string \"%s\"", string);
return 0;
}

point_width = getGLCPointWidth(boundingbox);
pixel_width = getGLCPointToPixel(point_width);
return (unsigned int)pixel_width;
}

unsigned int iV_GetCountedTextWidth(const char* string, size_t string_length)
{
float boundingbox[8];
float pixel_width, point_width;

glcMeasureCountedString(GL_FALSE, string_length, string);
if (!glcGetStringMetric(GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the string \"%s\" of length %u", string, (unsigned int)string_length);
return 0;
}

point_width = getGLCPointWidth(boundingbox);
pixel_width = getGLCPointToPixel(point_width);
return (unsigned int)pixel_width;
}

unsigned int iV_GetTextHeight(const char* string)
{
float boundingbox[8];
float pixel_height, point_height;

glcMeasureString(GL_FALSE, string);
if (!glcGetStringMetric(GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the string \"%s\"", string);
return 0;
}

point_height = getGLCPointHeight(boundingbox);
pixel_height = getGLCPointToPixel(point_height);
return (unsigned int)pixel_height;
}

unsigned int iV_GetCharWidth(uint32_t charCode)
{
float boundingbox[8];
float pixel_width, point_width;

if (!glcGetCharMetric(charCode, GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the character code %u", charCode);
return 0;
}

point_width = getGLCPointWidth(boundingbox);
pixel_width = getGLCPointToPixel(point_width);
return (unsigned int)pixel_width;
}

int iV_GetTextLineSize()
{
float boundingbox[8];
float pixel_height, point_height;

if (!glcGetMaxCharMetric(GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the character");
return 0;
}

point_height = getGLCPointHeight(boundingbox);
pixel_height = getGLCPointToPixel(point_height);
return (unsigned int)pixel_height;
}

static float iV_GetMaxCharBaseY(void)
{
float base_line[4]; // [ xl yl xr yr ]

if (!glcGetMaxCharMetric(GLC_BASELINE, base_line))
{
debug(LOG_ERROR, "Couldn't retrieve the baseline for the character");
return 0;
}

return base_line[1];
}

int iV_GetTextAboveBase(void)
{
float point_base_y = iV_GetMaxCharBaseY();
float point_top_y;
float boundingbox[8];
float pixel_height, point_height;

if (!glcGetMaxCharMetric(GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the character");
return 0;
}

point_top_y = boundingbox[7];
point_height = point_base_y - point_top_y;
pixel_height = getGLCPointToPixel(point_height);
return (int)pixel_height;
}

int iV_GetTextBelowBase(void)
{
float point_base_y = iV_GetMaxCharBaseY();
float point_bottom_y;
float boundingbox[8];
float pixel_height, point_height;

if (!glcGetMaxCharMetric(GLC_BOUNDS, boundingbox))
{
debug(LOG_ERROR, "Couldn't retrieve a bounding box for the character");
return 0;
}

point_bottom_y = boundingbox[1];
point_height = point_bottom_y - point_base_y;
pixel_height = getGLCPointToPixel(point_height);
return (int)pixel_height;
}

void iV_SetTextColour(PIELIGHT colour)
{
font_colour[0] = colour.byte.r / 255.0f;
font_colour[1] = colour.byte.g / 255.0f;
font_colour[2] = colour.byte.b / 255.0f;
font_colour[3] = colour.byte.a / 255.0f;
}

/** Draws formatted text with word wrap, long word splitting, embedded newlines
* (uses '@' rather than '\n') and colour toggle mode ('#') which enables or
* disables font colouring.
Expand Down Expand Up @@ -195,3 +601,79 @@ int iV_DrawFormattedText(const char* String, UDWORD x, UDWORD y, UDWORD Width, U

return jy;
}

void iV_DrawTextRotated(const char* string, float XPos, float YPos, float rotation)
{
GLint matrix_mode = 0;
ASSERT_OR_RETURN( , string, "Couldn't render string!");
pie_SetTexturePage(TEXPAGE_EXTERN);

glGetIntegerv(GL_MATRIX_MODE, &matrix_mode);
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();

if (rotation != 0.f)
{
rotation = 360.f - rotation;
}

glTranslatef(XPos, YPos, 0.f);
glRotatef(180.f, 1.f, 0.f, 0.f);
glRotatef(rotation, 0.f, 0.f, 1.f);
glScalef(font_size, font_size, 0.f);

glColor4fv(font_colour);

glFrontFace(GL_CW);
glcRenderString(string);
glFrontFace(GL_CCW);

glPopMatrix();
glMatrixMode(GL_TEXTURE);
glPopMatrix();
glMatrixMode(matrix_mode);

// Reset the current model view matrix
glLoadIdentity();
}

static void iV_DrawTextRotatedFv(float x, float y, float rotation, const char* format, va_list ap)
{
va_list aq;
size_t size;
char* str;

/* Required because we're using the va_list ap twice otherwise, which
* results in undefined behaviour. See stdarg(3) for details.
*/
va_copy(aq, ap);

// Allocate a buffer large enough to hold our string on the stack
size = vsnprintf(NULL, 0, format, ap);
str = (char *)alloca(size + 1);

// Print into our newly created string buffer
vsprintf(str, format, aq);

va_end(aq);

// Draw the produced string to the screen at the given position and rotation
iV_DrawTextRotated(str, x, y, rotation);
}

void iV_DrawTextF(float x, float y, const char* format, ...)
{
va_list ap;

va_start(ap, format);
iV_DrawTextRotatedFv(x, y, 0.f, format, ap);
va_end(ap);
}

void iV_SetTextSize(float size)
{
font_size = size;
}
5 changes: 5 additions & 0 deletions lib/ivis_opengl/textdraw.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ enum {
};

extern int iV_DrawFormattedText(const char* String, UDWORD x, UDWORD y, UDWORD Width, UDWORD Justify);

extern void iV_SetTextSize(float size);

extern void iV_DrawTextRotated(const char* string, float x, float y, float rotation);

/** Draws text with a printf syntax to the screen.
Expand All @@ -64,4 +67,6 @@ static inline void iV_DrawText(const char* string, float x, float y)
iV_DrawTextRotated(string, x, y, 0.f);
}

extern void iV_DrawTextF(float x, float y, const char* format, ...) WZ_DECL_FORMAT(printf, 3, 4);

#endif // _INCLUDED_TEXTDRAW_