Wrong Thread Area
So some people were wondering how to measure engine strings, so today I have a treat for those people. When writing up my new sdk I decided I was going to write my own wrappers/helper functions to manually draw strings, functions that would act much in the manner of the Q3 wrapper functions, and that's what I get to share with you..
So to begin, we're going to need some structures and info on the engine fonts.
#define GLYPH_START 0
#define GLYPH_END 255
#define GLYPH_CHARSTART 32
#define GLYPH_CHAREND 127
#define GLYPHS_PER_FONT GLYPH_END - GLYPH_START + 1
typedef struct {
char number; // glyph number
char top; // top of glyph in buffer
char bottom; // bottom of glyph in buffer
char pitch; // width for copying
char xSkip; // x adjustment
char imageWidth; // width of actual image
char imageHeight; // height of actual image
float s; // x offset in image where glyph starts
float t; // y offset in image where glyph starts
float s2;
float t2;
} glyphInfo_t;
typedef void *fontInfo_t;Very similar to the Q3 structure, the glyphInfo_t is going to give us almost all of our measurement information.
Next you'll need this function to get the glyphInfo_t[] from the fontInfo_t* you get from R_RegisterFont(...)
// this is terrible, terrible stuff.. but brute-forcing is the 'easy' way i guess.
// if you have a better solution, let me know.. because i want to punch myself in the face.
static __inline glyphInfo_t *H_GetGlyphInfo( fontInfo_t *font ) {
const int size = sizeof(glyphInfo_t);
int i, f;
f = *((int*)font);
if (font == 0 || f == 0) {
return 0;
}
for (i = 0; i < size * GLYPHS_PER_FONT; i++) {
if (*(char*)(f+i) == GLYPH_CHARSTART && *(char*)(f+i+size) == GLYPH_CHARSTART+1) {
return (glyphInfo_t*)(f+i);
}
}
return 0;
}This horrible thing is what I made the get the start of the glyphInfo_t[] ( As I didn't feel like spending a lot of time reversing the fontInfo_t,
which is why it's a void* ), if you have more information on the fontInfo_t struct and/or a better way to get the glyphInfo, please let me know .
Next up are the helper functions to measure and draw our strings..
A couple notes on these functions..float H_GetTextWidth( const char *text, fontInfo_t *font, float scale )
{
glyphInfo_t *glyphs;
int count, len;
float w, t;
w = t = count = 0;
glyphs = H_GetGlyphInfo(font);
if (text && glyphs) {
const char *s = text;
len = strlen(text);
while (s && *s && count < len) {
if (Q_IsColorString(s)) {
s+=2, count+=2;
continue;
} else if (*s == '\n') {
w = 0;
} else if (*s >= GLYPH_CHARSTART && *s < GLYPH_CHAREND) {
w += glyphs[*s - GLYPH_CHARSTART].xSkip;
t = max(t, w);
}
s++;
count++;
}
}
return t * scale;
}
float H_GetTextHeight( const char *text, fontInfo_t *font, float scale )
{
glyphInfo_t *glyphs;
int count, len;
float h, t;
h = t = count = 0;
glyphs = H_GetGlyphInfo(font);
if (text && glyphs) {
const char *s = text;
len = strlen(text);
while (s && *s && count < len) {
if (Q_IsColorString(s)) {
s+=2, count+=2;
continue;
} else if (*s == '\n') {
t += h; // add our height to our total height
h = 0; // reset our height
}
else if (*s >= GLYPH_CHARSTART && *s < GLYPH_CHAREND) {
h = max(h, glyphs[*s - GLYPH_CHARSTART].imageHeight);
}
s++;
count++;
}
t += h; // add our last line height
}
return t * scale;
}
float H_GetLineWidth( const char *text, fontInfo_t *font, float scale )
{
glyphInfo_t *glyphs;
int count, len;
float w;
w = count = 0;
glyphs = H_GetGlyphInfo(font);
if (text && glyphs) {
const char *s = text;
len = strlen(text);
while (s && *s && count < len) {
if (Q_IsColorString(s)) {
s+=2, count+=2;
continue;
} else if (*s == '\n') {
break;
} else if (*s >= GLYPH_CHARSTART && *s < GLYPH_CHAREND) {
w += glyphs[*s - GLYPH_CHARSTART].xSkip;
}
s++;
count++;
}
}
return w * scale;
}
float H_GetLineHeight( const char *text, fontInfo_t *font, float scale )
{
glyphInfo_t *glyphs;
int count, len;
float h;
h = count = 0;
glyphs = H_GetGlyphInfo(font);
if (text && glyphs) {
const char *s = text;
len = strlen(text);
while (s && *s && count < len) {
if (Q_IsColorString(s)) {
s+=2, count+=2;
continue;
} else if (*s == '\n') {
break;
} else if (*s >= GLYPH_CHARSTART && *s < GLYPH_CHAREND) {
h = max(h, glyphs[*s - GLYPH_CHARSTART].imageHeight);
}
s++;
count++;
}
}
return h * scale;
}
void H_DrawString( const char *text, fontInfo_t *font, float x, float y, float scale, vec4_t color )
{
glyphInfo_t *glyphs, *glyph;
float h, xadj, yadj;
vec4_t newColor;
int len, count;
glyphs = H_GetGlyphInfo(font);
if (text && glyphs) {
char *s = (char*)text, c[2];
len = strlen(text);
c[1] = h = xadj = yadj = count = 0;
memcpy(&newColor[0], &color[0], sizeof(vec4_t));
while (s && *s && count < len) {
glyph = &glyphs[*s - GLYPH_CHARSTART];
if (h == 0) {
h = H_GetLineHeight(s, font, scale);
}
if (Q_IsColorString(s)) {
memcpy(&newColor[0], &g_color_table[ColorIndex(*(s+1))][0], sizeof(vec4_t));
s+=2, count+=2;
continue;
} else if (*s == '\n') {
y += h;
h = xadj = 0;
} else {
c[0] = *s; // this makes me sad, need to find DrawChar stuff
yadj = glyph->top + (h - (glyph->imageHeight * scale));
CG_DrawString(c, 0x7FFFFFFF, font, x + xadj, y + yadj - (glyph->pitch * scale), scale, scale, newColor, 0);
xadj += glyph->xSkip * scale;
}
s++;
count++;
}
}
}
H_GetTextWidth: Gets the width of a string of text ( returns the widest line of a multi-line string ), string must be null terminated, multi-line supported.
H_GetTextHeight: Gets the height of a string of text, string must be null terminated, multi-line supported, relies on how H_DrawString pads multi-line strings.
H_GetLineWidth: Gets the width of the first line of a string of text, string must be multi-line or null terminated.
H_GetLineHeight: Gets the height of the first line of a string of text, string must be multi-line or null terminated.
H_DrawString: Manually draws a string of characters, string must be null terminated, multi-line and inline-coloring supported.
Now when drawing a measured string it is vital you use the helper function H_DrawString, this is because it pads multi-line strings by the height of the previous string; The default CG_DrawString function handles multi-line padding differently, I did this because the default padding is not worth my time completely reversing and is annoying at best ( in my opinion ).
I hope this is helpful to at least one person, and it should work on other Call of Duty games just fine also..
As always, have fun!