Improved string rendering with text advance metric
This commit is contained in:
parent
d8ee46611f
commit
5ef54e16be
@ -120,13 +120,12 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
stbtt_InitFont(&fontInfo, fontFileRead.buffer,
|
stbtt_InitFont(&fontInfo, fontFileRead.buffer,
|
||||||
stbtt_GetFontOffsetForIndex(fontFileRead.buffer, 0));
|
stbtt_GetFontOffsetForIndex(fontFileRead.buffer, 0));
|
||||||
|
|
||||||
|
/* Initialise Assetmanager Font */
|
||||||
Font *font = &assetManager->font;
|
Font *font = &assetManager->font;
|
||||||
font->codepointRange = V2i(32, 127);
|
font->codepointRange = V2i(32, 127);
|
||||||
|
|
||||||
v2i codepointRange = font->codepointRange;
|
v2i codepointRange = font->codepointRange;
|
||||||
const i32 numGlyphs = codepointRange.y - codepointRange.x;
|
const i32 numGlyphs = codepointRange.y - codepointRange.x;
|
||||||
|
|
||||||
i32 glyphIndex = 0;
|
|
||||||
GlyphBitmap *glyphBitmaps =
|
GlyphBitmap *glyphBitmaps =
|
||||||
CAST(GlyphBitmap *) calloc(numGlyphs, sizeof(GlyphBitmap));
|
CAST(GlyphBitmap *) calloc(numGlyphs, sizeof(GlyphBitmap));
|
||||||
v2i largestGlyphDimension = V2i(0, 0);
|
v2i largestGlyphDimension = V2i(0, 0);
|
||||||
@ -134,7 +133,20 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
const f32 targetFontHeight = 64.0f;
|
const f32 targetFontHeight = 64.0f;
|
||||||
f32 scaleY = stbtt_ScaleForPixelHeight(&fontInfo, targetFontHeight);
|
f32 scaleY = stbtt_ScaleForPixelHeight(&fontInfo, targetFontHeight);
|
||||||
|
|
||||||
|
i32 ascent, descent, lineGap;
|
||||||
|
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
|
||||||
|
|
||||||
|
ascent = CAST(i32)(ascent * scaleY);
|
||||||
|
descent = CAST(i32)(descent * scaleY);
|
||||||
|
lineGap = CAST(i32)(lineGap * scaleY);
|
||||||
|
|
||||||
|
font->metrics = CAST(FontMetrics){ascent, descent, lineGap};
|
||||||
|
|
||||||
|
font->charMetrics =
|
||||||
|
CAST(CharMetrics *) calloc(numGlyphs, sizeof(CharMetrics));
|
||||||
|
|
||||||
/* Use STB_TrueType to generate a series of bitmap characters */
|
/* Use STB_TrueType to generate a series of bitmap characters */
|
||||||
|
i32 glyphIndex = 0;
|
||||||
for (i32 codepoint = codepointRange.x; codepoint < codepointRange.y;
|
for (i32 codepoint = codepointRange.x; codepoint < codepointRange.y;
|
||||||
codepoint++)
|
codepoint++)
|
||||||
{
|
{
|
||||||
@ -161,7 +173,22 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get individual character metrics */
|
||||||
|
i32 advance, leftSideBearing;
|
||||||
|
stbtt_GetCodepointHMetrics(&fontInfo, codepoint, &advance,
|
||||||
|
&leftSideBearing);
|
||||||
|
|
||||||
|
advance = CAST(i32)(advance * scaleY);
|
||||||
|
leftSideBearing = CAST(i32)(leftSideBearing * scaleY);
|
||||||
|
|
||||||
|
font->charMetrics[glyphIndex] =
|
||||||
|
CAST(CharMetrics){advance, leftSideBearing, NULL,
|
||||||
|
V2i(xOffset, yOffset), V2i(width, height)};
|
||||||
|
|
||||||
|
/* Store bitmap into intermediate storage */
|
||||||
stbtt_FreeBitmap(monoBitmap, NULL);
|
stbtt_FreeBitmap(monoBitmap, NULL);
|
||||||
|
|
||||||
|
// TODO(doyle): Dimensions is used twice in font->trueSize and this
|
||||||
glyphBitmaps[glyphIndex].dimensions = V2i(width, height);
|
glyphBitmaps[glyphIndex].dimensions = V2i(width, height);
|
||||||
glyphBitmaps[glyphIndex].codepoint = codepoint;
|
glyphBitmaps[glyphIndex].codepoint = codepoint;
|
||||||
glyphBitmaps[glyphIndex++].pixels = colorBitmap;
|
glyphBitmaps[glyphIndex++].pixels = colorBitmap;
|
||||||
@ -180,7 +207,6 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
"unoptimal\n");
|
"unoptimal\n");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -199,12 +225,12 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
if ((largestGlyphDimension.h & 1) == 1)
|
if ((largestGlyphDimension.h & 1) == 1)
|
||||||
largestGlyphDimension.h += 1;
|
largestGlyphDimension.h += 1;
|
||||||
|
|
||||||
font->charSize = largestGlyphDimension;
|
font->maxSize= largestGlyphDimension;
|
||||||
|
|
||||||
i32 glyphsPerRow = (MAX_TEXTURE_SIZE / font->charSize.w) + 1;
|
i32 glyphsPerRow = (MAX_TEXTURE_SIZE / font->maxSize.w) + 1;
|
||||||
|
|
||||||
#ifdef WT_DEBUG
|
#ifdef WT_DEBUG
|
||||||
i32 glyphsPerCol = MAX_TEXTURE_SIZE / font->charSize.h;
|
i32 glyphsPerCol = MAX_TEXTURE_SIZE / font->maxSize.h;
|
||||||
if ((glyphsPerRow * glyphsPerCol) <= numGlyphs)
|
if ((glyphsPerRow * glyphsPerCol) <= numGlyphs)
|
||||||
{
|
{
|
||||||
printf(
|
printf(
|
||||||
@ -244,15 +270,22 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
ASSERT(activeGlyph.codepoint < ARRAY_COUNT(fontAtlas->texRect));
|
ASSERT(activeGlyph.codepoint < ARRAY_COUNT(fontAtlas->texRect));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
v2 origin = V2(CAST(f32)(glyphIndex * font->charSize.w),
|
v2 origin =
|
||||||
CAST(f32) row);
|
V2(CAST(f32)(glyphIndex * font->maxSize.w), CAST(f32) row);
|
||||||
|
#if 1
|
||||||
fontAtlas->texRect[atlasIndex++] =
|
fontAtlas->texRect[atlasIndex++] =
|
||||||
getRect(origin, V2(CAST(f32) font->charSize.w,
|
getRect(origin, V2(CAST(f32) font->maxSize.w,
|
||||||
CAST(f32) font->charSize.h));
|
CAST(f32) font->maxSize.h));
|
||||||
|
#else
|
||||||
|
v2i fontSize =
|
||||||
|
font->charMetrics[activeGlyph.codepoint - 32].trueSize;
|
||||||
|
fontAtlas->texRect[atlasIndex++] =
|
||||||
|
getRect(origin, V2(CAST(f32)fontSize.x, CAST(f32)fontSize.y));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy over exactly one row of pixels */
|
/* Copy over exactly one row of pixels */
|
||||||
i32 numPixelsToPad = font->charSize.w;
|
i32 numPixelsToPad = font->maxSize.w;
|
||||||
if (verticalPixelsBlitted < activeGlyph.dimensions.h)
|
if (verticalPixelsBlitted < activeGlyph.dimensions.h)
|
||||||
{
|
{
|
||||||
const i32 srcPitch =
|
const i32 srcPitch =
|
||||||
@ -270,14 +303,13 @@ const i32 asset_loadTTFont(AssetManager *assetManager, const char *filePath)
|
|||||||
* (NULL/mixes up rows), instead just advance the final bitmap
|
* (NULL/mixes up rows), instead just advance the final bitmap
|
||||||
* pointer by the remaining distance
|
* pointer by the remaining distance
|
||||||
*/
|
*/
|
||||||
numPixelsToPad =
|
numPixelsToPad = font->maxSize.w - activeGlyph.dimensions.w;
|
||||||
font->charSize.w - activeGlyph.dimensions.w;
|
|
||||||
}
|
}
|
||||||
destRow += numPixelsToPad;
|
destRow += numPixelsToPad;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A row of glyphs has been fully formed on the atlas */
|
/* A row of glyphs has been fully formed on the atlas */
|
||||||
if (verticalPixelsBlitted++ >= font->charSize.h)
|
if (verticalPixelsBlitted++ >= font->maxSize.h)
|
||||||
{
|
{
|
||||||
verticalPixelsBlitted = 0;
|
verticalPixelsBlitted = 0;
|
||||||
startingGlyphIndex += glyphsPerRow;
|
startingGlyphIndex += glyphsPerRow;
|
||||||
|
@ -28,6 +28,12 @@ void renderer_object(Renderer *renderer, v2 pos, v2 size, f32 rotate, v3 color,
|
|||||||
// TODO(doyle): Unimplemented
|
// TODO(doyle): Unimplemented
|
||||||
// this->shader->uniformSetVec3f("spriteColor", color);
|
// this->shader->uniformSetVec3f("spriteColor", color);
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
glBindVertexArray(renderer->vao);
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
#endif
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
if (tex)
|
if (tex)
|
||||||
@ -40,6 +46,7 @@ void renderer_object(Renderer *renderer, v2 pos, v2 size, f32 rotate, v3 color,
|
|||||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
glCheckError();
|
glCheckError();
|
||||||
}
|
}
|
||||||
|
@ -343,26 +343,42 @@ void worldTraveller_gameUpdateAndRender(GameState *state, const f32 dt)
|
|||||||
V3(0, 0, 0), worldTex);
|
V3(0, 0, 0), worldTex);
|
||||||
|
|
||||||
Font *font = &assetManager->font;
|
Font *font = &assetManager->font;
|
||||||
char *string = "hello world";
|
char *string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
i32 strLen = 11;
|
i32 strLen = 52;
|
||||||
quadIndex = 0;
|
quadIndex = 0;
|
||||||
RenderQuad *stringQuads = CAST(RenderQuad *)calloc(strLen, sizeof(RenderQuad));
|
RenderQuad *stringQuads = CAST(RenderQuad *)calloc(strLen, sizeof(RenderQuad));
|
||||||
|
|
||||||
|
f32 baseline = 100.0f;
|
||||||
f32 xPosOnScreen = 20.0f;
|
f32 xPosOnScreen = 20.0f;
|
||||||
|
f32 yPosOnScreen = baseline;
|
||||||
for (i32 i = 0; i < strLen; i++)
|
for (i32 i = 0; i < strLen; i++)
|
||||||
{
|
{
|
||||||
// NOTE(doyle): Atlas packs fonts tightly, so offset the codepoint to
|
// NOTE(doyle): Atlas packs fonts tightly, so offset the codepoint to
|
||||||
// its actual atlas index, i.e. we skip the first 31 glyphs
|
// its actual atlas index, i.e. we skip the first 31 glyphs
|
||||||
i32 atlasIndex = string[i] - font->codepointRange.x;
|
i32 codepoint = string[i];
|
||||||
|
i32 relativeIndex = codepoint - font->codepointRange.x;
|
||||||
|
CharMetrics charMetric = font->charMetrics[relativeIndex];
|
||||||
|
|
||||||
v4 charTexRect = font->atlas->texRect[atlasIndex];
|
i32 charBaseline = (font->maxSize.h - charMetric.trueSize.h);
|
||||||
|
if (codepoint == 'a')
|
||||||
|
{
|
||||||
|
yPosOnScreen = baseline - charBaseline;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yPosOnScreen = baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
const v4 charRectOnScreen = getRect(
|
||||||
|
V2(xPosOnScreen, yPosOnScreen),
|
||||||
|
V2(CAST(f32) font->maxSize.w, CAST(f32) font->maxSize.h));
|
||||||
|
|
||||||
|
xPosOnScreen += charMetric.advance;
|
||||||
|
|
||||||
|
/* Get texture out */
|
||||||
|
v4 charTexRect = font->atlas->texRect[relativeIndex];
|
||||||
renderer_flipTexCoord(&charTexRect, FALSE, TRUE);
|
renderer_flipTexCoord(&charTexRect, FALSE, TRUE);
|
||||||
|
|
||||||
const v4 charRectOnScreen =
|
|
||||||
getRect(V2(xPosOnScreen, 100.0f),
|
|
||||||
V2(CAST(f32) font->charSize.w, CAST(f32) font->charSize.w));
|
|
||||||
xPosOnScreen += font->charSize.w;
|
|
||||||
|
|
||||||
RenderQuad charQuad = renderer_createQuad(
|
RenderQuad charQuad = renderer_createQuad(
|
||||||
&state->renderer, charRectOnScreen, charTexRect, font->tex);
|
&state->renderer, charRectOnScreen, charTexRect, font->tex);
|
||||||
stringQuads[quadIndex++] = charQuad;
|
stringQuads[quadIndex++] = charQuad;
|
||||||
|
@ -32,12 +32,33 @@ typedef struct TexAtlas
|
|||||||
v4 texRect[128];
|
v4 texRect[128];
|
||||||
} TexAtlas;
|
} TexAtlas;
|
||||||
|
|
||||||
|
typedef struct FontMetrics
|
||||||
|
{
|
||||||
|
i32 ascent;
|
||||||
|
i32 descent;
|
||||||
|
i32 lineGap;
|
||||||
|
} FontMetrics;
|
||||||
|
|
||||||
|
typedef struct CharMetrics
|
||||||
|
{
|
||||||
|
i32 advance;
|
||||||
|
i32 leftSideBearing;
|
||||||
|
i32 *kerning;
|
||||||
|
v2i offset;
|
||||||
|
v2i trueSize;
|
||||||
|
} CharMetrics;
|
||||||
|
|
||||||
typedef struct Font
|
typedef struct Font
|
||||||
{
|
{
|
||||||
TexAtlas *atlas;
|
TexAtlas *atlas;
|
||||||
Texture *tex;
|
Texture *tex;
|
||||||
|
|
||||||
|
FontMetrics metrics;
|
||||||
|
CharMetrics *charMetrics;
|
||||||
|
|
||||||
v2i codepointRange;
|
v2i codepointRange;
|
||||||
v2i charSize;
|
v2i maxSize;
|
||||||
|
|
||||||
} Font;
|
} Font;
|
||||||
|
|
||||||
// TODO(doyle): Switch to hash based lookup
|
// TODO(doyle): Switch to hash based lookup
|
||||||
|
Loading…
Reference in New Issue
Block a user