API tweaks to support text alignment

main
cosmonaut 2022-04-13 14:31:06 -07:00
parent 5ee517eba5
commit b7d55972c8
2 changed files with 271 additions and 31 deletions

View File

@ -62,12 +62,12 @@ WELLSPRINGAPI uint32_t Wellspring_LinkedVersion(void);
/* Type definitions */ /* Type definitions */
typedef struct Wellspring_Font Wellspring_Font;
typedef struct Wellspring_Packer Wellspring_Packer; typedef struct Wellspring_Packer Wellspring_Packer;
typedef struct Wellspring_TextBatch Wellspring_TextBatch; typedef struct Wellspring_TextBatch Wellspring_TextBatch;
typedef struct Wellspring_FontRange typedef struct Wellspring_FontRange
{ {
float fontSize;
uint32_t firstCodepoint; uint32_t firstCodepoint;
uint32_t numChars; uint32_t numChars;
uint8_t oversampleH; uint8_t oversampleH;
@ -86,11 +86,39 @@ typedef struct Wellspring_Vertex
uint8_t r, g, b, a; uint8_t r, g, b, a;
} Wellspring_Vertex; } Wellspring_Vertex;
typedef struct Wellspring_Rectangle
{
float x;
float y;
float w;
float h;
} Wellspring_Rectangle;
typedef enum Wellspring_HorizontalAlignment
{
WELLSPRING_HORIZONTALALIGNMENT_LEFT,
WELLSPRING_HORIZONTALALIGNMENT_CENTER,
WELLSPRING_HORIZONTALALIGNMENT_RIGHT
} Wellspring_HorizontalAlignment;
typedef enum Wellspring_VerticalAlignment
{
WELLSPRING_VERTICALALIGNMENT_TOP,
WELLSPRING_VERTICALALIGNMENT_MIDDLE,
WELLSPRING_VERTICALALIGNMENT_BASELINE,
WELLSPRING_VERTICALALIGNMENT_BOTTOM
} Wellspring_VerticalAlignment;
/* API definition */ /* API definition */
WELLSPRINGAPI Wellspring_Packer* Wellspring_CreatePacker( WELLSPRINGAPI Wellspring_Font* Wellspring_CreateFont(
const uint8_t *fontBytes, const uint8_t *fontBytes,
uint32_t fontBytesLength, uint32_t fontBytesLength
);
WELLSPRINGAPI Wellspring_Packer* Wellspring_CreatePacker(
Wellspring_Font *font,
float fontSize,
uint32_t width, uint32_t width,
uint32_t height, uint32_t height,
uint32_t strideInBytes, /* 0 means the buffer is tightly packed. */ uint32_t strideInBytes, /* 0 means the buffer is tightly packed. */
@ -121,12 +149,25 @@ WELLSPRINGAPI void Wellspring_StartTextBatch(
Wellspring_Packer *packer Wellspring_Packer *packer
); );
WELLSPRINGAPI uint8_t Wellspring_TextBounds(
Wellspring_TextBatch *textBatch,
float x,
float y,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *strBytes,
uint32_t strLengthInBytes,
Wellspring_Rectangle *pRectangle
);
WELLSPRINGAPI uint8_t Wellspring_Draw( WELLSPRINGAPI uint8_t Wellspring_Draw(
Wellspring_TextBatch *textBatch, Wellspring_TextBatch *textBatch,
float x, float x,
float y, float y,
float depth, float depth,
Wellspring_Color *color, Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *strBytes, const uint8_t *strBytes,
uint32_t strLengthInBytes uint32_t strLengthInBytes
); );
@ -141,6 +182,7 @@ WELLSPRINGAPI void Wellspring_GetBufferData(
WELLSPRINGAPI void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch); WELLSPRINGAPI void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch);
WELLSPRINGAPI void Wellspring_DestroyPacker(Wellspring_Packer *packer); WELLSPRINGAPI void Wellspring_DestroyPacker(Wellspring_Packer *packer);
WELLSPRINGAPI void Wellspring_DestroyFont(Wellspring_Font *font);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -118,6 +118,16 @@ typedef int32_t stbtt_int32;
/* Structs */ /* Structs */
typedef struct Font
{
uint8_t *fontBytes;
stbtt_fontinfo fontInfo;
int32_t ascent;
int32_t descent;
int32_t lineGap;
} Font;
typedef struct CharRange typedef struct CharRange
{ {
stbtt_packedchar *data; stbtt_packedchar *data;
@ -128,8 +138,8 @@ typedef struct CharRange
typedef struct Packer typedef struct Packer
{ {
uint8_t *fontBytes; Font *font;
stbtt_fontinfo fontInfo; float fontSize;
stbtt_pack_context *context; stbtt_pack_context *context;
uint8_t *pixels; uint8_t *pixels;
@ -138,6 +148,8 @@ typedef struct Packer
uint32_t strideInBytes; uint32_t strideInBytes;
uint32_t padding; uint32_t padding;
float scale; /* precomputed at init */
CharRange *ranges; CharRange *ranges;
uint32_t rangeCount; uint32_t rangeCount;
} Packer; } Packer;
@ -200,9 +212,23 @@ uint32_t Wellspring_LinkedVersion(void)
return WELLSPRING_COMPILED_VERSION; return WELLSPRING_COMPILED_VERSION;
} }
Wellspring_Font* Wellspring_CreateFont(
const uint8_t* fontBytes,
uint32_t fontBytesLength
) {
Font *font = Wellspring_malloc(sizeof(Font));
font->fontBytes = Wellspring_malloc(fontBytesLength);
Wellspring_memcpy(font->fontBytes, fontBytes, fontBytesLength);
stbtt_InitFont(&font->fontInfo, font->fontBytes, 0);
stbtt_GetFontVMetrics(&font->fontInfo, &font->ascent, &font->descent, &font->lineGap);
return (Wellspring_Font*) font;
}
Wellspring_Packer* Wellspring_CreatePacker( Wellspring_Packer* Wellspring_CreatePacker(
const uint8_t *fontBytes, Wellspring_Font *font,
uint32_t fontBytesLength, float fontSize,
uint32_t width, uint32_t width,
uint32_t height, uint32_t height,
uint32_t strideInBytes, uint32_t strideInBytes,
@ -210,9 +236,8 @@ Wellspring_Packer* Wellspring_CreatePacker(
) { ) {
Packer *packer = Wellspring_malloc(sizeof(Packer)); Packer *packer = Wellspring_malloc(sizeof(Packer));
packer->fontBytes = Wellspring_malloc(fontBytesLength); packer->font = (Font*) font;
Wellspring_memcpy(packer->fontBytes, fontBytes, fontBytesLength); packer->fontSize = fontSize;
stbtt_InitFont(&packer->fontInfo, packer->fontBytes, 0);
packer->context = Wellspring_malloc(sizeof(stbtt_pack_context)); packer->context = Wellspring_malloc(sizeof(stbtt_pack_context));
packer->pixels = Wellspring_malloc(sizeof(uint8_t) * width * height); packer->pixels = Wellspring_malloc(sizeof(uint8_t) * width * height);
@ -225,6 +250,8 @@ Wellspring_Packer* Wellspring_CreatePacker(
packer->ranges = NULL; packer->ranges = NULL;
packer->rangeCount = 0; packer->rangeCount = 0;
packer->scale = stbtt_ScaleForPixelHeight(&packer->font->fontInfo, fontSize);
stbtt_PackBegin(packer->context, packer->pixels, width, height, strideInBytes, padding, NULL); stbtt_PackBegin(packer->context, packer->pixels, width, height, strideInBytes, padding, NULL);
return (Wellspring_Packer*) packer; return (Wellspring_Packer*) packer;
@ -244,7 +271,7 @@ uint32_t Wellspring_PackFontRanges(
for (i = 0; i < numRanges; i += 1) for (i = 0; i < numRanges; i += 1)
{ {
currentFontRange = &ranges[i]; currentFontRange = &ranges[i];
stbPackRanges[i].font_size = currentFontRange->fontSize; stbPackRanges[i].font_size = myPacker->fontSize;
stbPackRanges[i].first_unicode_codepoint_in_range = currentFontRange->firstCodepoint; stbPackRanges[i].first_unicode_codepoint_in_range = currentFontRange->firstCodepoint;
stbPackRanges[i].array_of_unicode_codepoints = NULL; stbPackRanges[i].array_of_unicode_codepoints = NULL;
stbPackRanges[i].num_chars = currentFontRange->numChars; stbPackRanges[i].num_chars = currentFontRange->numChars;
@ -253,7 +280,7 @@ uint32_t Wellspring_PackFontRanges(
stbPackRanges[i].chardata_for_range = Wellspring_malloc(sizeof(stbtt_packedchar) * currentFontRange->numChars); stbPackRanges[i].chardata_for_range = Wellspring_malloc(sizeof(stbtt_packedchar) * currentFontRange->numChars);
} }
if (!stbtt_PackFontRanges(myPacker->context, myPacker->fontBytes, 0, stbPackRanges, numRanges)) if (!stbtt_PackFontRanges(myPacker->context, myPacker->font->fontBytes, 0, stbPackRanges, numRanges))
{ {
/* Font packing failed, time to bail */ /* Font packing failed, time to bail */
for (i = 0; i < numRanges; i += 1) for (i = 0; i < numRanges; i += 1)
@ -312,31 +339,62 @@ void Wellspring_StartTextBatch(
batch->indexCount = 0; batch->indexCount = 0;
} }
uint8_t Wellspring_Draw( static float Wellspring_INTERNAL_GetVerticalAlignOffset(
Wellspring_TextBatch *textBatch, Font *font,
Wellspring_VerticalAlignment verticalAlignment,
float scale
) {
if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_TOP)
{
return scale * font->ascent;
}
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_MIDDLE)
{
return scale * (font->ascent + font->descent) / 2.0f;
}
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_BASELINE)
{
return 0;
}
else /* BOTTOM */
{
return scale * font->descent;
}
}
uint8_t Wellspring_TextBounds(
Wellspring_TextBatch* textBatch,
float x, float x,
float y, float y,
float depth, Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_Color *color, Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *str, const uint8_t* strBytes,
uint32_t strLength uint32_t strLengthInBytes,
Wellspring_Rectangle *pRectangle
) { ) {
Batch *batch = (Batch*) textBatch; Batch* batch = (Batch*)textBatch;
Packer *myPacker = batch->currentPacker; Packer* myPacker = batch->currentPacker;
uint32_t decodeState = 0; uint32_t decodeState = 0;
uint32_t codepoint; uint32_t codepoint;
uint32_t previousCodepoint; int32_t glyphIndex;
int32_t previousGlyphIndex;
int32_t rangeIndex; int32_t rangeIndex;
stbtt_packedchar *rangeData; stbtt_packedchar* rangeData;
float rangeFontSize; float rangeFontSize;
stbtt_aligned_quad charQuad; stbtt_aligned_quad charQuad;
uint32_t vertexBufferIndex;
uint32_t indexBufferIndex;
uint32_t i, j; uint32_t i, j;
float minX = x;
float minY = y;
float maxX = x;
float maxY = y;
float startX = x;
float advance = 0;
for (i = 0; i < strLength; i += 1) y += Wellspring_INTERNAL_GetVerticalAlignOffset(myPacker->font, verticalAlignment, myPacker->scale);
for (i = 0; i < strLengthInBytes; i += 1)
{ {
if (decode(&decodeState, &codepoint, str[i])) if (decode(&decodeState, &codepoint, strBytes[i]))
{ {
if (decodeState == UTF8_REJECT) if (decodeState == UTF8_REJECT)
{ {
@ -368,11 +426,144 @@ uint8_t Wellspring_Draw(
/* Requested char wasn't packed! */ /* Requested char wasn't packed! */
return 0; return 0;
} }
glyphIndex = stbtt_FindGlyphIndex(&myPacker->font->fontInfo, codepoint);
if (i > 0) if (i > 0)
{ {
float scale = stbtt_ScaleForPixelHeight(&myPacker->fontInfo, rangeFontSize); x += myPacker->scale * stbtt_GetGlyphKernAdvance(&myPacker->font->fontInfo, previousGlyphIndex, glyphIndex);
x += scale * stbtt_GetCodepointKernAdvance(&myPacker->fontInfo, previousCodepoint, codepoint); }
stbtt_GetPackedQuad(
rangeData,
myPacker->width,
myPacker->height,
rangeIndex,
&x,
&y,
&charQuad,
0
);
if (charQuad.x0 < minX) { minX = charQuad.x0; }
if (charQuad.x1 > maxX) { maxX = charQuad.x1; }
if (charQuad.y0 < minY) { minY = charQuad.y0; }
if (charQuad.y1 > maxY) { maxY = charQuad.y1; }
previousGlyphIndex = glyphIndex;
}
advance = x - startX;
if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_RIGHT)
{
minX -= advance;
maxX -= advance;
}
else if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_CENTER)
{
minX -= advance * 0.5f;
maxX -= advance * 0.5f;
}
pRectangle->x = minX;
pRectangle->y = minY;
pRectangle->w = maxX - minX;
pRectangle->h = maxY - minY;
return 1;
}
uint8_t Wellspring_Draw(
Wellspring_TextBatch *textBatch,
float x,
float y,
float depth,
Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *strBytes,
uint32_t strLengthInBytes
) {
Batch *batch = (Batch*) textBatch;
Packer *myPacker = batch->currentPacker;
uint32_t decodeState = 0;
uint32_t codepoint;
int32_t glyphIndex;
int32_t previousGlyphIndex;
int32_t rangeIndex;
stbtt_packedchar *rangeData;
float rangeFontSize;
stbtt_aligned_quad charQuad;
uint32_t vertexBufferIndex;
uint32_t indexBufferIndex;
Wellspring_Rectangle bounds;
uint32_t i, j;
y += Wellspring_INTERNAL_GetVerticalAlignOffset(myPacker->font, verticalAlignment, myPacker->scale);
/* FIXME: If we horizontally align, we have to decode and process glyphs twice, very inefficient. */
if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_RIGHT)
{
if (!Wellspring_TextBounds(textBatch, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{
/* Something went wrong while calculating bounds. */
return 0;
}
x -= bounds.w;
}
else if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_CENTER)
{
if (!Wellspring_TextBounds(textBatch, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{
/* Something went wrong while calculating bounds. */
return 0;
}
x -= bounds.w * 0.5f;
}
for (i = 0; i < strLengthInBytes; i += 1)
{
if (decode(&decodeState, &codepoint, strBytes[i]))
{
if (decodeState == UTF8_REJECT)
{
/* Something went wrong while decoding UTF-8. */
return 0;
}
continue;
}
rangeData = NULL;
/* Find the packed char data */
for (j = 0; j < myPacker->rangeCount; j += 1)
{
if (
codepoint >= myPacker->ranges[j].firstCodepoint &&
codepoint < myPacker->ranges[j].firstCodepoint + myPacker->ranges[j].charCount
) {
rangeData = myPacker->ranges[j].data;
rangeIndex = codepoint - myPacker->ranges[j].firstCodepoint;
rangeFontSize = myPacker->ranges[j].fontSize;
break;
}
}
if (rangeData == NULL)
{
/* Requested char wasn't packed! */
return 0;
}
glyphIndex = stbtt_FindGlyphIndex(&myPacker->font->fontInfo, codepoint);
if (i > 0)
{
x += myPacker->scale * stbtt_GetGlyphKernAdvance(&myPacker->font->fontInfo, previousGlyphIndex, glyphIndex);
} }
stbtt_GetPackedQuad( stbtt_GetPackedQuad(
@ -453,7 +644,7 @@ uint8_t Wellspring_Draw(
batch->vertexCount += 4; batch->vertexCount += 4;
batch->indexCount += 6; batch->indexCount += 6;
previousCodepoint = codepoint; previousGlyphIndex = glyphIndex;
} }
return 1; return 1;
@ -494,7 +685,14 @@ void Wellspring_DestroyPacker(Wellspring_Packer *packer)
} }
Wellspring_free(myPacker->ranges); Wellspring_free(myPacker->ranges);
Wellspring_free(myPacker->fontBytes);
Wellspring_free(myPacker->context); Wellspring_free(myPacker->context);
Wellspring_free(myPacker->pixels); Wellspring_free(myPacker->pixels);
} }
void Wellspring_DestroyFont(Wellspring_Font* font)
{
Font *myFont = (Font*) font;
Wellspring_free(myFont->fontBytes);
Wellspring_free(myFont);
}