/* Wellspring - An immediate mode font rendering system in C * * Copyright (c) 2022 Evan Hemsley * * This software is provided 'as-is', without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in a * product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * * Evan "cosmonaut" Hemsley * */ #include "Wellspring.h" /* Function defines */ #ifdef USE_SDL2 #define Wellspring_malloc SDL_malloc #define Wellspring_realloc SDL_realloc #define Wellspring_free SDL_free #define Wellspring_memcpy SDL_memcpy #define Wellspring_memset SDL_memset #define Wellspring_ifloor(x) ((int) SDL_floorf(x)) #define Wellspring_iceil(x) ((int) SDL_ceilf(x)) #define Wellspring_sqrt SDL_sqrt #define Wellspring_pow SDL_pow #define Wellspring_fmod SDL_fmod #define Wellspring_cos SDL_cos #define Wellspring_acos SDL_acos #define Wellspring_fabs SDL_fabs #define Wellspring_assert SDL_assert #define Wellspring_strlen SDL_strlen #define Wellspring_sort SDL_qsort #else #ifdef _MSC_VER #include #include #include #include #include #endif #define Wellspring_malloc malloc #define Wellspring_realloc realloc #define Wellspring_free free #define Wellspring_memcpy memcpy #define Wellspring_memset memset #define Wellspring_ifloor(x) ((int) floor(x)) #define Wellspring_iceil(x) ((int) ceil(x)) #define Wellspring_sqrt sqrt #define Wellspring_pow pow #define Wellspring_fmod fmod #define Wellspring_cos cos #define Wellspring_acos acos #define Wellspring_fabs fabs #define Wellspring_assert assert #define Wellspring_strlen strlen #define Wellspring_sort qsort #endif #define STBTT_malloc(x,u) ((void)(u),Wellspring_malloc(x)) #define STBTT_free(x,u) ((void)(u),Wellspring_free(x)) #define STBTT_memcpy Wellspring_memcpy #define STBTT_memset Wellspring_memset #define STBTT_ifloor Wellspring_ifloor #define STBTT_iceil Wellspring_iceil #define STBTT_sqrt Wellspring_sqrt #define STBTT_pow Wellspring_pow #define STBTT_fmod Wellspring_fmod #define STBTT_cos Wellspring_cos #define STBTT_acos Wellspring_acos #define STBTT_fabs Wellspring_fabs #define STBTT_assert Wellspring_assert #define STBTT_strlen Wellspring_strlen #define STBRP_SORT Wellspring_sort #define STBRP_ASSERT Wellspring_assert typedef uint8_t stbtt_uint8; typedef int8_t stbtt_int8; typedef uint16_t stbtt_uint16; typedef int16_t stbtt_int16; typedef uint32_t stbtt_uint32; typedef int32_t stbtt_int32; #pragma GCC diagnostic ignored "-Wunused-function" #define STBRP_STATIC #define STB_RECT_PACK_IMPLEMENTATION #include "stb_rect_pack.h" #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" #pragma GCC diagnostic warning "-Wunused-function" #define INITIAL_QUAD_CAPACITY 128 /* Structs */ typedef struct Font { uint8_t *fontBytes; stbtt_fontinfo fontInfo; int32_t ascent; int32_t descent; int32_t lineGap; } Font; typedef struct CharRange { stbtt_packedchar *data; uint32_t firstCodepoint; uint32_t charCount; float fontSize; } CharRange; typedef struct Packer { Font *font; float fontSize; stbtt_pack_context *context; uint8_t *pixels; uint32_t width; uint32_t height; uint32_t strideInBytes; uint32_t padding; float scale; /* precomputed at init */ CharRange *ranges; uint32_t rangeCount; } Packer; typedef struct Batch { Wellspring_Vertex *vertices; uint32_t vertexCount; uint32_t vertexCapacity; uint32_t *indices; uint32_t indexCount; uint32_t indexCapacity; Packer *currentPacker; } Batch; /* UTF-8 Decoder */ /* Copyright (c) 2008-2009 Bjoern Hoehrmann * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */ #define UTF8_ACCEPT 0 #define UTF8_REJECT 1 static const uint8_t utf8d[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 }; static uint32_t inline decode(uint32_t* state, uint32_t* codep, uint32_t byte) { uint32_t type = utf8d[byte]; *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); *state = utf8d[256 + *state*16 + type]; return *state; } /* API */ uint32_t Wellspring_LinkedVersion(void) { 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_Font *font, float fontSize, uint32_t width, uint32_t height, uint32_t strideInBytes, uint32_t padding ) { Packer *packer = Wellspring_malloc(sizeof(Packer)); packer->font = (Font*) font; packer->fontSize = fontSize; packer->context = Wellspring_malloc(sizeof(stbtt_pack_context)); packer->pixels = Wellspring_malloc(sizeof(uint8_t) * width * height); packer->width = width; packer->height = height; packer->strideInBytes = strideInBytes; packer->padding = padding; packer->ranges = NULL; packer->rangeCount = 0; packer->scale = stbtt_ScaleForPixelHeight(&packer->font->fontInfo, fontSize); stbtt_PackBegin(packer->context, packer->pixels, width, height, strideInBytes, padding, NULL); return (Wellspring_Packer*) packer; } uint32_t Wellspring_PackFontRanges( Wellspring_Packer *packer, Wellspring_FontRange *ranges, uint32_t numRanges ) { Packer *myPacker = (Packer*) packer; Wellspring_FontRange *currentFontRange; stbtt_pack_range* stbPackRanges = Wellspring_malloc(sizeof(stbtt_pack_range) * numRanges); CharRange *currentCharRange; uint32_t i; for (i = 0; i < numRanges; i += 1) { currentFontRange = &ranges[i]; stbPackRanges[i].font_size = myPacker->fontSize; stbPackRanges[i].first_unicode_codepoint_in_range = currentFontRange->firstCodepoint; stbPackRanges[i].array_of_unicode_codepoints = NULL; stbPackRanges[i].num_chars = currentFontRange->numChars; stbPackRanges[i].h_oversample = currentFontRange->oversampleH; stbPackRanges[i].v_oversample = currentFontRange->oversampleV; stbPackRanges[i].chardata_for_range = Wellspring_malloc(sizeof(stbtt_packedchar) * currentFontRange->numChars); } if (!stbtt_PackFontRanges(myPacker->context, myPacker->font->fontBytes, 0, stbPackRanges, numRanges)) { /* Font packing failed, time to bail */ for (i = 0; i < numRanges; i += 1) { Wellspring_free(stbPackRanges[i].chardata_for_range); } return 0; } myPacker->ranges = Wellspring_realloc(myPacker->ranges, sizeof(CharRange) * (myPacker->rangeCount + numRanges)); for (i = 0; i < numRanges; i += 1) { currentCharRange = &myPacker->ranges[myPacker->rangeCount + i]; currentCharRange->data = stbPackRanges[i].chardata_for_range; currentCharRange->firstCodepoint = stbPackRanges[i].first_unicode_codepoint_in_range; currentCharRange->charCount = stbPackRanges[i].num_chars; currentCharRange->fontSize = stbPackRanges[i].font_size; } myPacker->rangeCount += numRanges; Wellspring_free(stbPackRanges); return 1; } uint8_t* Wellspring_GetPixelDataPointer( Wellspring_Packer *packer ) { Packer* myPacker = (Packer*) packer; return myPacker->pixels; } Wellspring_TextBatch* Wellspring_CreateTextBatch() { Batch *batch = Wellspring_malloc(sizeof(Batch)); batch->vertexCapacity = INITIAL_QUAD_CAPACITY * 4; batch->vertices = Wellspring_malloc(sizeof(Wellspring_Vertex) * batch->vertexCapacity); batch->vertexCount = 0; batch->indexCapacity = INITIAL_QUAD_CAPACITY * 6; batch->indices = Wellspring_malloc(sizeof(uint32_t) * batch->indexCapacity); batch->indexCount = 0; return (Wellspring_TextBatch*) batch; } void Wellspring_StartTextBatch( Wellspring_TextBatch *textBatch, Wellspring_Packer *packer ) { Batch *batch = (Batch*) textBatch; batch->currentPacker = (Packer*) packer; batch->vertexCount = 0; batch->indexCount = 0; } static float Wellspring_INTERNAL_GetVerticalAlignOffset( Font *font, Wellspring_VerticalAlignment verticalAlignment, float scale ) { if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_BASELINE) { return 0; } else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_TOP) { return scale * font->ascent; } else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_MIDDLE) { return scale * (font->ascent + font->descent) / 2.0f; } else /* BOTTOM */ { return scale * font->descent; } } static inline uint32_t IsWhitespace(uint32_t codepoint) { switch (codepoint) { case 0x0020: case 0x00A0: case 0x1680: case 0x202F: case 0x205F: case 0x3000: return 1; default: if (codepoint > 0x2000 && codepoint <= 0x200A) { return 1; } return 0; } } static uint8_t Wellspring_Internal_TextBounds( Packer* packer, float x, float y, Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_VerticalAlignment verticalAlignment, const uint8_t* strBytes, uint32_t strLengthInBytes, Wellspring_Rectangle *pRectangle ) { 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 i, j; float minX = x; float minY = y; float maxX = x; float maxY = y; float startX = x; float advance = 0; y += Wellspring_INTERNAL_GetVerticalAlignOffset(packer->font, verticalAlignment, packer->scale); for (i = 0; i < strLengthInBytes; i += 1) { if (decode(&decodeState, &codepoint, strBytes[i])) { if (decodeState == UTF8_REJECT) { /* Something went very wrong */ return 0; } continue; } if (IsWhitespace(codepoint)) { int32_t ws_adv, ws_bearing; stbtt_GetCodepointHMetrics(&packer->font->fontInfo, codepoint, &ws_adv, &ws_bearing); x += packer->scale * ws_adv; maxX += packer->scale * ws_adv; continue; } rangeData = NULL; /* Find the packed char data */ for (j = 0; j < packer->rangeCount; j += 1) { if ( codepoint >= packer->ranges[j].firstCodepoint && codepoint < packer->ranges[j].firstCodepoint + packer->ranges[j].charCount ) { rangeData = packer->ranges[j].data; rangeIndex = codepoint - packer->ranges[j].firstCodepoint; rangeFontSize = packer->ranges[j].fontSize; break; } } if (rangeData == NULL) { /* Requested char wasn't packed! */ return 0; } glyphIndex = stbtt_FindGlyphIndex(&packer->font->fontInfo, codepoint); if (i > 0) { x += packer->scale * stbtt_GetGlyphKernAdvance(&packer->font->fontInfo, previousGlyphIndex, glyphIndex); } stbtt_GetPackedQuad( rangeData, packer->width, packer->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_TextBounds( Wellspring_Packer* packer, float x, float y, Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_VerticalAlignment verticalAlignment, const uint8_t* strBytes, uint32_t strLengthInBytes, Wellspring_Rectangle* pRectangle ) { return Wellspring_Internal_TextBounds( (Packer*) packer, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, pRectangle ); } 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_Internal_TextBounds(myPacker, 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_Internal_TextBounds(myPacker, 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; } if (IsWhitespace(codepoint)) { int32_t ws_adv, ws_bearing; stbtt_GetCodepointHMetrics(&myPacker->font->fontInfo, codepoint, &ws_adv, &ws_bearing); x += myPacker->scale * ws_adv; 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( rangeData, myPacker->width, myPacker->height, rangeIndex, &x, &y, &charQuad, 0 ); if (batch->vertexCount >= batch->vertexCapacity) { batch->vertexCapacity *= 2; batch->vertices = Wellspring_realloc(batch->vertices, sizeof(Wellspring_Vertex) * batch->vertexCapacity); } if (batch->indexCount >= batch->indexCapacity) { batch->indexCapacity *= 2; batch->indices = Wellspring_realloc(batch->indices, sizeof(uint32_t) * batch->indexCapacity); } /* TODO: kerning and alignment */ vertexBufferIndex = batch->vertexCount; indexBufferIndex = batch->indexCount; batch->vertices[vertexBufferIndex].x = charQuad.x0; batch->vertices[vertexBufferIndex].y = charQuad.y0; batch->vertices[vertexBufferIndex].z = depth; batch->vertices[vertexBufferIndex].u = charQuad.s0; batch->vertices[vertexBufferIndex].v = charQuad.t0; batch->vertices[vertexBufferIndex].r = color->r; batch->vertices[vertexBufferIndex].g = color->g; batch->vertices[vertexBufferIndex].b = color->b; batch->vertices[vertexBufferIndex].a = color->a; batch->vertices[vertexBufferIndex + 1].x = charQuad.x0; batch->vertices[vertexBufferIndex + 1].y = charQuad.y1; batch->vertices[vertexBufferIndex + 1].z = depth; batch->vertices[vertexBufferIndex + 1].u = charQuad.s0; batch->vertices[vertexBufferIndex + 1].v = charQuad.t1; batch->vertices[vertexBufferIndex + 1].r = color->r; batch->vertices[vertexBufferIndex + 1].g = color->g; batch->vertices[vertexBufferIndex + 1].b = color->b; batch->vertices[vertexBufferIndex + 1].a = color->a; batch->vertices[vertexBufferIndex + 2].x = charQuad.x1; batch->vertices[vertexBufferIndex + 2].y = charQuad.y0; batch->vertices[vertexBufferIndex + 2].z = depth; batch->vertices[vertexBufferIndex + 2].u = charQuad.s1; batch->vertices[vertexBufferIndex + 2].v = charQuad.t0; batch->vertices[vertexBufferIndex + 2].r = color->r; batch->vertices[vertexBufferIndex + 2].g = color->g; batch->vertices[vertexBufferIndex + 2].b = color->b; batch->vertices[vertexBufferIndex + 2].a = color->a; batch->vertices[vertexBufferIndex + 3].x = charQuad.x1; batch->vertices[vertexBufferIndex + 3].y = charQuad.y1; batch->vertices[vertexBufferIndex + 3].z = depth; batch->vertices[vertexBufferIndex + 3].u = charQuad.s1; batch->vertices[vertexBufferIndex + 3].v = charQuad.t1; batch->vertices[vertexBufferIndex + 3].r = color->r; batch->vertices[vertexBufferIndex + 3].g = color->g; batch->vertices[vertexBufferIndex + 3].b = color->b; batch->vertices[vertexBufferIndex + 3].a = color->a; batch->indices[indexBufferIndex] = vertexBufferIndex; batch->indices[indexBufferIndex + 1] = vertexBufferIndex + 1; batch->indices[indexBufferIndex + 2] = vertexBufferIndex + 2; batch->indices[indexBufferIndex + 3] = vertexBufferIndex + 2; batch->indices[indexBufferIndex + 4] = vertexBufferIndex + 1; batch->indices[indexBufferIndex + 5] = vertexBufferIndex + 3; batch->vertexCount += 4; batch->indexCount += 6; previousGlyphIndex = glyphIndex; } return 1; } void Wellspring_GetBufferData( Wellspring_TextBatch *textBatch, uint32_t *pVertexCount, Wellspring_Vertex **pVertexBuffer, uint32_t *pVertexBufferLengthInBytes, uint32_t **pIndexBuffer, uint32_t *pIndexBufferLengthInBytes ) { Batch *batch = (Batch*) textBatch; *pVertexCount = batch->vertexCount; *pVertexBuffer = batch->vertices; *pVertexBufferLengthInBytes = batch->vertexCount * sizeof(Wellspring_Vertex); *pIndexBuffer = batch->indices; *pIndexBufferLengthInBytes = batch->indexCount * sizeof(uint32_t); } void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch) { Batch *batch = (Batch*) textBatch; Wellspring_free(batch->vertices); Wellspring_free(batch->indices); Wellspring_free(batch); } void Wellspring_DestroyPacker(Wellspring_Packer *packer) { Packer* myPacker = (Packer*) packer; uint32_t i; stbtt_PackEnd(myPacker->context); for (i = 0; i < myPacker->rangeCount; i += 1) { Wellspring_free(myPacker->ranges[i].data); } Wellspring_free(myPacker->ranges); Wellspring_free(myPacker->context); Wellspring_free(myPacker->pixels); } void Wellspring_DestroyFont(Wellspring_Font* font) { Font *myFont = (Font*) font; Wellspring_free(myFont->fontBytes); Wellspring_free(myFont); }