MSDF Rewrite (#1)
continuous-integration/drone/push Build is passing Details

- Wellspring is now a MSDF font rendering library, accepting output from msdf-atlas-gen for this purpose
- Renamed Wellspring_Draw to Wellspring_AddToTextBatch
- TextBounds and AddToTextBatch no longer accept position and depth values
- Removed Packer
- Removed CreatePacker, PackFontRanges, and GetPixelDataPointer. Font packing is now handled by msdf-atlas-gen
- Removed visualc solution in favor of CMake everywhere

Reviewed-on: #1
main
cosmonaut 2023-12-15 18:30:00 +00:00
parent 6e0e4d0fa0
commit ab238503a3
8 changed files with 3878 additions and 367 deletions

View File

@ -46,6 +46,7 @@ add_library(Wellspring
#Public header #Public header
include/Wellspring.h include/Wellspring.h
#Source #Source
lib/json.h
lib/stb_rect_pack.h lib/stb_rect_pack.h
lib/stb_truetype.h lib/stb_truetype.h
src/Wellspring.c src/Wellspring.c

View File

@ -1,11 +1,21 @@
This is Wellspring, an immediate mode font rendering system in C. This is Wellspring, an immediate mode multiple-channel signed distance field font rendering system in C.
About Wellspring About Wellspring
---------------- ----------------
Wellspring is inspired by the design of Dear ImGui. Wellspring is inspired by the design of Dear ImGui.
It outputs pixel data that you can upload to a texture and vertex buffers that you can render anytime in your 3D application. It outputs buffer data that you can upload and render anytime in your 3D application.
This means that you can integrate it easily using the graphics library of your choice. This means that you can integrate it easily using the graphics library of your choice.
Wellspring uses stb_truetype to rasterize and pack fonts quickly.
Wellspring uses JSON output from [msdf-atlas-gen](https://github.com/Chlumsky/msdf-atlas-gen) to generate buffers. It also uses [stb_truetype](https://github.com/nothings/stb/blob/master/stb_truetype.h) for additional kerning support. At render time, bind the image data from msdf-atlas-gen with buffers generated by Wellspring for beautiful MSDF font rendering.
Using msdf-atlas-gen
--------------
A full explanation of msdf-atlas-gen is beyond the scope of this project, but note that Wellspring only accepts MSDF atlas types with JSON output.
Your atlas generation might look like this:
```sh
msdf-atlas-gen -yorigin top -font ~/mygame/myfont.otf -imageout ~/mygame/content/forgotten_dream.png -json ~/mygame/content/forgotten_dream.json
```
Dependencies Dependencies
------------ ------------
@ -20,7 +30,7 @@ For *nix platforms, use CMake:
$ cmake ../ $ cmake ../
$ make $ make
For Windows, see the 'visualc/' directory. For Windows, you can use cmake-gui to generate a Visual Studio solution or use VSCode with the CMake and C/C++ Tools extensions.
License License
------- -------

View File

@ -63,7 +63,6 @@ WELLSPRINGAPI uint32_t Wellspring_LinkedVersion(void);
/* Type definitions */ /* Type definitions */
typedef struct Wellspring_Font Wellspring_Font; typedef struct Wellspring_Font Wellspring_Font;
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
@ -113,46 +112,25 @@ typedef enum Wellspring_VerticalAlignment
WELLSPRINGAPI Wellspring_Font* Wellspring_CreateFont( WELLSPRINGAPI Wellspring_Font* Wellspring_CreateFont(
const uint8_t *fontBytes, const uint8_t *fontBytes,
uint32_t fontBytesLength uint32_t fontBytesLength,
); const uint8_t *atlasJsonBytes,
uint32_t atlasJsonBytesLength,
WELLSPRINGAPI Wellspring_Packer* Wellspring_CreatePacker( float *pPixelsPerEm,
Wellspring_Font *font, float *pDistanceRange
float fontSize,
uint32_t width,
uint32_t height,
uint32_t strideInBytes, /* 0 means the buffer is tightly packed. */
uint32_t padding /* A sensible value here is 1 to allow bilinear filtering. */
);
WELLSPRINGAPI uint32_t Wellspring_PackFontRanges(
Wellspring_Packer *packer,
Wellspring_FontRange *ranges,
uint32_t numRanges
);
/* Returns a pointer to an array of rasterized pixels of the packed font.
* This data must be uploaded to a texture before you render!
* The pixel data becomes outdated if you call PackFontRanges.
* Length is width * height.
*/
WELLSPRINGAPI uint8_t* Wellspring_GetPixelDataPointer(
Wellspring_Packer *packer
); );
/* Batches are not thread-safe, recommend one batch per thread. */ /* Batches are not thread-safe, recommend one batch per thread. */
WELLSPRINGAPI Wellspring_TextBatch* Wellspring_CreateTextBatch(); WELLSPRINGAPI Wellspring_TextBatch* Wellspring_CreateTextBatch(void);
/* Also restarts the batch */ /* Also restarts the batch */
WELLSPRINGAPI void Wellspring_StartTextBatch( WELLSPRINGAPI void Wellspring_StartTextBatch(
Wellspring_TextBatch *textBatch, Wellspring_TextBatch *textBatch,
Wellspring_Packer *packer Wellspring_Font *font
); );
WELLSPRINGAPI uint8_t Wellspring_TextBounds( WELLSPRINGAPI uint8_t Wellspring_TextBounds(
Wellspring_Packer* packer, Wellspring_Font *font,
float x, int pixelSize,
float y,
Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment, Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *strBytes, const uint8_t *strBytes,
@ -160,11 +138,9 @@ WELLSPRINGAPI uint8_t Wellspring_TextBounds(
Wellspring_Rectangle *pRectangle Wellspring_Rectangle *pRectangle
); );
WELLSPRINGAPI uint8_t Wellspring_Draw( WELLSPRINGAPI uint8_t Wellspring_AddToTextBatch(
Wellspring_TextBatch *textBatch, Wellspring_TextBatch *textBatch,
float x, int pixelSize,
float y,
float depth,
Wellspring_Color *color, Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment, Wellspring_VerticalAlignment verticalAlignment,
@ -182,7 +158,6 @@ 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_DestroyFont(Wellspring_Font *font); WELLSPRINGAPI void Wellspring_DestroyFont(Wellspring_Font *font);
#ifdef __cplusplus #ifdef __cplusplus

3455
lib/json.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -95,6 +95,9 @@
#define STBRP_SORT Wellspring_sort #define STBRP_SORT Wellspring_sort
#define STBRP_ASSERT Wellspring_assert #define STBRP_ASSERT Wellspring_assert
#define SHEREDOM_JSON_H_malloc Wellspring_malloc
#define SHEREDOM_JSON_H_free Wellspring_free
typedef uint8_t stbtt_uint8; typedef uint8_t stbtt_uint8;
typedef int8_t stbtt_int8; typedef int8_t stbtt_int8;
typedef uint16_t stbtt_uint16; typedef uint16_t stbtt_uint16;
@ -112,48 +115,54 @@ typedef int32_t stbtt_int32;
#define STB_TRUETYPE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h" #include "stb_truetype.h"
#include "json.h"
#pragma GCC diagnostic warning "-Wunused-function" #pragma GCC diagnostic warning "-Wunused-function"
#define INITIAL_QUAD_CAPACITY 128 #define INITIAL_QUAD_CAPACITY 128
/* Structs */ /* Structs */
typedef struct PackedChar
{
float atlasLeft, atlasTop, atlasRight, atlasBottom;
float planeLeft, planeTop, planeRight, planeBottom;
float xAdvance;
} PackedChar;
typedef struct CharRange
{
PackedChar *data;
uint32_t firstCodepoint;
uint32_t charCount;
} CharRange;
typedef struct Packer
{
uint32_t width;
uint32_t height;
CharRange *ranges;
uint32_t rangeCount;
} Packer;
typedef struct Font typedef struct Font
{ {
uint8_t *fontBytes; uint8_t *fontBytes;
stbtt_fontinfo fontInfo; stbtt_fontinfo fontInfo;
int32_t ascent; float ascender;
int32_t descent; float descender;
int32_t lineGap; float lineHeight;
float pixelsPerEm;
float distanceRange;
float scale;
float kerningScale; // kerning values from stb_tt are in a different scale
Packer packer;
} Font; } 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 typedef struct Batch
{ {
Wellspring_Vertex *vertices; Wellspring_Vertex *vertices;
@ -164,9 +173,15 @@ typedef struct Batch
uint32_t indexCount; uint32_t indexCount;
uint32_t indexCapacity; uint32_t indexCapacity;
Packer *currentPacker; Font *currentFont;
} Batch; } Batch;
typedef struct Quad
{
float x0,y0,s0,t0; // top-left
float x1,y1,s1,t1; // bottom-right
} Quad;
/* UTF-8 Decoder */ /* UTF-8 Decoder */
/* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> /* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
@ -205,6 +220,126 @@ decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
return *state; return *state;
} }
/* JSON helpers */
static uint8_t json_object_has_key(const json_object_t *object, const char* name)
{
json_object_element_t *currentElement = object->start;
const char* currentName = currentElement->name->string;
while (SDL_strcmp(currentName, name) != 0)
{
if (currentElement->next == NULL)
{
return 0;
}
currentElement = currentElement->next;
currentName = currentElement->name->string;
}
return 1;
}
static json_object_element_t* json_object_get_element_by_name(const json_object_t *object, const char* name)
{
json_object_element_t *currentElement = object->start;
const char* currentName = currentElement->name->string;
while (SDL_strcmp(currentName, name) != 0)
{
if (currentElement->next == NULL)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Key %s not found in JSON!", name);
return NULL;
}
currentElement = currentElement->next;
currentName = currentElement->name->string;
}
return currentElement;
}
static json_object_t* json_object_get_object(const json_object_t *object, const char* name)
{
json_object_element_t *element = json_object_get_element_by_name(object, name);
if (element == NULL)
{
return NULL;
}
json_object_t *obj = json_value_as_object(element->value);
if (obj == NULL)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not an object!", name);
}
return obj;
}
static const char* json_object_get_string(const json_object_t *object, const char* name)
{
json_object_element_t *element = json_object_get_element_by_name(object, name);
if (element == NULL)
{
return NULL;
}
json_string_t *str = json_value_as_string(element->value);
if (str == NULL)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not a string!", name);
return NULL;
}
return str->string;
}
static uint32_t json_object_get_uint(const json_object_t *object, const char* name)
{
json_object_element_t *element = json_object_get_element_by_name(object, name);
if (element == NULL)
{
return 0;
}
json_number_t *num = json_value_as_number(element->value);
if (num == NULL)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not a number!", name);
return 0;
}
return (uint32_t) SDL_strtoul(num->number, NULL, 10);
}
static double json_object_get_double(const json_object_t *object, const char* name)
{
json_object_element_t *element = json_object_get_element_by_name(object, name);
if (element == NULL)
{
return 0;
}
json_number_t *num = json_value_as_number(element->value);
if (num == NULL)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Value with key %s was not a string!", name);
return 0;
}
return SDL_atof(num->number);
}
/* API */ /* API */
uint32_t Wellspring_LinkedVersion(void) uint32_t Wellspring_LinkedVersion(void)
@ -214,107 +349,146 @@ uint32_t Wellspring_LinkedVersion(void)
Wellspring_Font* Wellspring_CreateFont( Wellspring_Font* Wellspring_CreateFont(
const uint8_t* fontBytes, const uint8_t* fontBytes,
uint32_t fontBytesLength uint32_t fontBytesLength,
const uint8_t *atlasJsonBytes,
uint32_t atlasJsonBytesLength,
float *pPixelsPerEm,
float *pDistanceRange
) { ) {
Font *font = Wellspring_malloc(sizeof(Font)); Font *font = Wellspring_malloc(sizeof(Font));
font->fontBytes = Wellspring_malloc(fontBytesLength); font->fontBytes = Wellspring_malloc(fontBytesLength);
Wellspring_memcpy(font->fontBytes, fontBytes, fontBytesLength); Wellspring_memcpy(font->fontBytes, fontBytes, fontBytesLength);
stbtt_InitFont(&font->fontInfo, font->fontBytes, 0); stbtt_InitFont(&font->fontInfo, font->fontBytes, 0);
stbtt_GetFontVMetrics(&font->fontInfo, &font->ascent, &font->descent, &font->lineGap); int stbAscender, stbDescender, stbLineHeight;
stbtt_GetFontVMetrics(&font->fontInfo, &stbAscender, &stbDescender, &stbLineHeight);
json_value_t *jsonRoot = json_parse(atlasJsonBytes, atlasJsonBytesLength);
json_object_t *jsonObject = jsonRoot->payload;
if (jsonObject == NULL)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Atlas JSON is invalid! Bailing!");
Wellspring_free(font->fontBytes);
Wellspring_free(font);
return NULL;
}
if (SDL_strcmp(jsonObject->start->name->string, "atlas") != 0)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Atlas JSON is invalid! Bailing!");
Wellspring_free(jsonRoot);
Wellspring_free(font->fontBytes);
Wellspring_free(font);
return NULL;
}
json_object_t *atlasObject = json_value_as_object(jsonObject->start->value);
json_object_t *metricsObject = json_value_as_object(jsonObject->start->next->value);
json_array_t *glyphsArray = json_value_as_array(jsonObject->start->next->next->value);
const char* atlasType = json_object_get_string(atlasObject, "type");
if (SDL_strcmp(atlasType, "msdf") != 0)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Atlas is not MSDF! Bailing!");
Wellspring_free(jsonRoot);
Wellspring_free(font->fontBytes);
Wellspring_free(font);
return NULL;
}
font->packer.width = json_object_get_uint(atlasObject, "width");
font->packer.height = json_object_get_uint(atlasObject, "height");
font->pixelsPerEm = json_object_get_double(atlasObject, "size");
font->distanceRange = json_object_get_double(atlasObject, "distanceRange");
font->ascender = json_object_get_double(metricsObject, "ascender");
font->descender = json_object_get_double(metricsObject, "descender");
font->lineHeight = json_object_get_double(metricsObject, "lineHeight");
font->scale = font->pixelsPerEm * 4 / 3; // converting from "points" (dpi) to pixels
/* Pack unicode ranges */
font->packer.ranges = Wellspring_malloc(sizeof(CharRange));
font->packer.rangeCount = 1;
font->packer.ranges[0].data = NULL;
font->packer.ranges[0].charCount = 0;
int32_t charRangeIndex = 0;
json_array_element_t *currentGlyphElement = glyphsArray->start;
while (currentGlyphElement != NULL)
{
json_object_t *currentGlyphObject = json_value_as_object(currentGlyphElement->value);
uint32_t codepoint = json_object_get_uint(currentGlyphObject, "unicode");
if (font->packer.ranges[charRangeIndex].charCount == 0)
{
// first codepoint on first range
font->packer.ranges[charRangeIndex].firstCodepoint = codepoint;
}
else if (codepoint != font->packer.ranges[charRangeIndex].firstCodepoint + font->packer.ranges[charRangeIndex].charCount)
{
// codepoint is not continuous, start a new range
charRangeIndex += 1;
font->packer.rangeCount += 1;
font->packer.ranges = Wellspring_realloc(font->packer.ranges, sizeof(PackedChar) * (charRangeIndex + 1));
font->packer.ranges[charRangeIndex].firstCodepoint = codepoint;
}
font->packer.ranges[charRangeIndex].charCount += 1;
font->packer.ranges[charRangeIndex].data = Wellspring_realloc(font->packer.ranges[charRangeIndex].data, sizeof(PackedChar) * font->packer.ranges[charRangeIndex].charCount);
PackedChar *packedChar = &font->packer.ranges[charRangeIndex].data[font->packer.ranges[charRangeIndex].charCount - 1];
packedChar->atlasLeft = 0;
packedChar->atlasRight = 0;
packedChar->atlasTop = 0;
packedChar->atlasBottom = 0;
packedChar->planeLeft = 0;
packedChar->planeRight = 0;
packedChar->planeTop = 0;
packedChar->planeBottom = 0;
packedChar->xAdvance = json_object_get_double(currentGlyphObject, "advance");
if (json_object_has_key(currentGlyphObject, "atlasBounds"))
{
json_object_t *boundsObject = json_object_get_object(currentGlyphObject, "atlasBounds");
packedChar->atlasLeft = json_object_get_double(boundsObject, "left");
packedChar->atlasRight = json_object_get_double(boundsObject, "right");
packedChar->atlasTop = json_object_get_double(boundsObject, "top");
packedChar->atlasBottom = json_object_get_double(boundsObject, "bottom");
json_object_t *planeObject = json_object_get_object(currentGlyphObject, "planeBounds");
packedChar->planeLeft = json_object_get_double(planeObject, "left");
packedChar->planeRight = json_object_get_double(planeObject, "right");
packedChar->planeTop = json_object_get_double(planeObject, "top");
packedChar->planeBottom = json_object_get_double(planeObject, "bottom");
}
currentGlyphElement = currentGlyphElement->next;
}
int advanceWidth, bearing;
stbtt_GetCodepointHMetrics(&font->fontInfo, font->packer.ranges[0].firstCodepoint, &advanceWidth, &bearing);
font->kerningScale = font->packer.ranges[0].data[0].xAdvance / advanceWidth;
Wellspring_free(jsonRoot);
*pPixelsPerEm = font->pixelsPerEm;
*pDistanceRange = font->distanceRange;
return (Wellspring_Font*) font; return (Wellspring_Font*) font;
} }
Wellspring_Packer* Wellspring_CreatePacker( Wellspring_TextBatch* Wellspring_CreateTextBatch(void)
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 *batch = Wellspring_malloc(sizeof(Batch));
@ -331,10 +505,10 @@ Wellspring_TextBatch* Wellspring_CreateTextBatch()
void Wellspring_StartTextBatch( void Wellspring_StartTextBatch(
Wellspring_TextBatch *textBatch, Wellspring_TextBatch *textBatch,
Wellspring_Packer *packer Wellspring_Font *font
) { ) {
Batch *batch = (Batch*) textBatch; Batch *batch = (Batch*) textBatch;
batch->currentPacker = (Packer*) packer; batch->currentFont = (Font*) font;
batch->vertexCount = 0; batch->vertexCount = 0;
batch->indexCount = 0; batch->indexCount = 0;
} }
@ -350,15 +524,15 @@ static float Wellspring_INTERNAL_GetVerticalAlignOffset(
} }
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_TOP) else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_TOP)
{ {
return scale * font->ascent; return scale * font->ascender;
} }
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_MIDDLE) else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_MIDDLE)
{ {
return scale * (font->ascent + font->descent) / 2.0f; return scale * (font->ascender + font->descender) / 2.0f;
} }
else /* BOTTOM */ else /* BOTTOM */
{ {
return scale * font->descent; return scale * font->descender;
} }
} }
@ -384,10 +558,40 @@ static inline uint32_t IsWhitespace(uint32_t codepoint)
} }
} }
static void GetPackedQuad(PackedChar *charData, float scale, int packerWidth, int packerHeight, int charIndex, float *xPos, float *yPos, Quad *q)
{
float texelWidth = 1.0f / packerWidth, texelHeight = 1.0f / packerHeight;
PackedChar *b = charData + charIndex;
float pl, pb, pr, pt;
float il, ib, ir, it;
pl = *xPos + b->planeLeft * scale;
pb = *yPos + b->planeBottom * scale;
pr = *xPos + b->planeRight * scale;
pt = *yPos + b->planeTop * scale;
il = b->atlasLeft * texelWidth;
ib = b->atlasBottom * texelHeight;
ir = b->atlasRight * texelWidth;
it = b->atlasTop * texelHeight;
q->x0 = pl;
q->y0 = pt;
q->x1 = pr;
q->y1 = pb;
q->s0 = il;
q->t0 = it;
q->s1 = ir;
q->t1 = ib;
*xPos += b->xAdvance * scale;
}
static uint8_t Wellspring_Internal_TextBounds( static uint8_t Wellspring_Internal_TextBounds(
Packer* packer, Font* font,
float x, int pixelSize,
float y,
Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment, Wellspring_VerticalAlignment verticalAlignment,
const uint8_t* strBytes, const uint8_t* strBytes,
@ -397,20 +601,21 @@ static uint8_t Wellspring_Internal_TextBounds(
uint32_t decodeState = 0; uint32_t decodeState = 0;
uint32_t codepoint; uint32_t codepoint;
int32_t glyphIndex; int32_t glyphIndex;
int32_t previousGlyphIndex; int32_t previousGlyphIndex = -1;
int32_t rangeIndex; int32_t rangeIndex;
stbtt_packedchar* rangeData; PackedChar* rangeData;
float rangeFontSize; Quad charQuad;
stbtt_aligned_quad charQuad;
uint32_t i, j; uint32_t i, j;
float x = 0, y = 0;
float minX = x; float minX = x;
float minY = y; float minY = y;
float maxX = x; float maxX = x;
float maxY = y; float maxY = y;
float startX = x; float startX = x;
float advance = 0; float advance = 0;
float sizeFactor = pixelSize / font->pixelsPerEm;
y += Wellspring_INTERNAL_GetVerticalAlignOffset(packer->font, verticalAlignment, packer->scale); y -= Wellspring_INTERNAL_GetVerticalAlignOffset(font, verticalAlignment, sizeFactor * font->scale);
for (i = 0; i < strLengthInBytes; i += 1) for (i = 0; i < strLengthInBytes; i += 1)
{ {
@ -425,17 +630,10 @@ static uint8_t Wellspring_Internal_TextBounds(
continue; 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; rangeData = NULL;
Packer *packer = &font->packer;
/* Find the packed char data */ /* Find the packed char data */
for (j = 0; j < packer->rangeCount; j += 1) for (j = 0; j < packer->rangeCount; j += 1)
{ {
@ -445,7 +643,6 @@ static uint8_t Wellspring_Internal_TextBounds(
) { ) {
rangeData = packer->ranges[j].data; rangeData = packer->ranges[j].data;
rangeIndex = codepoint - packer->ranges[j].firstCodepoint; rangeIndex = codepoint - packer->ranges[j].firstCodepoint;
rangeFontSize = packer->ranges[j].fontSize;
break; break;
} }
} }
@ -456,22 +653,31 @@ static uint8_t Wellspring_Internal_TextBounds(
return 0; return 0;
} }
glyphIndex = stbtt_FindGlyphIndex(&packer->font->fontInfo, codepoint); if (IsWhitespace(codepoint))
if (i > 0)
{ {
x += packer->scale * stbtt_GetGlyphKernAdvance(&packer->font->fontInfo, previousGlyphIndex, glyphIndex); PackedChar *packedChar = rangeData + rangeIndex;
x += sizeFactor * font->scale * packedChar->xAdvance;
maxX += sizeFactor * font->scale * packedChar->xAdvance;
previousGlyphIndex = -1;
continue;
} }
stbtt_GetPackedQuad( glyphIndex = stbtt_FindGlyphIndex(&font->fontInfo, codepoint);
if (previousGlyphIndex != -1)
{
x += sizeFactor * font->kerningScale * font->scale * stbtt_GetGlyphKernAdvance(&font->fontInfo, previousGlyphIndex, glyphIndex);
}
GetPackedQuad(
rangeData, rangeData,
sizeFactor * font->scale,
packer->width, packer->width,
packer->height, packer->height,
rangeIndex, rangeIndex,
&x, &x,
&y, &y,
&charQuad, &charQuad
0
); );
if (charQuad.x0 < minX) { minX = charQuad.x0; } if (charQuad.x0 < minX) { minX = charQuad.x0; }
@ -504,9 +710,8 @@ static uint8_t Wellspring_Internal_TextBounds(
} }
uint8_t Wellspring_TextBounds( uint8_t Wellspring_TextBounds(
Wellspring_Packer* packer, Wellspring_Font *font,
float x, int pixelSize,
float y,
Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment, Wellspring_VerticalAlignment verticalAlignment,
const uint8_t* strBytes, const uint8_t* strBytes,
@ -514,9 +719,8 @@ uint8_t Wellspring_TextBounds(
Wellspring_Rectangle* pRectangle Wellspring_Rectangle* pRectangle
) { ) {
return Wellspring_Internal_TextBounds( return Wellspring_Internal_TextBounds(
(Packer*) packer, (Font*) font,
x, pixelSize,
y,
horizontalAlignment, horizontalAlignment,
verticalAlignment, verticalAlignment,
strBytes, strBytes,
@ -525,11 +729,9 @@ uint8_t Wellspring_TextBounds(
); );
} }
uint8_t Wellspring_Draw( uint8_t Wellspring_AddToTextBatch(
Wellspring_TextBatch *textBatch, Wellspring_TextBatch *textBatch,
float x, int pixelSize,
float y,
float depth,
Wellspring_Color *color, Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment, Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment, Wellspring_VerticalAlignment verticalAlignment,
@ -537,26 +739,28 @@ uint8_t Wellspring_Draw(
uint32_t strLengthInBytes uint32_t strLengthInBytes
) { ) {
Batch *batch = (Batch*) textBatch; Batch *batch = (Batch*) textBatch;
Packer *myPacker = batch->currentPacker; Font *font = batch->currentFont;
Packer *myPacker = &font->packer;
uint32_t decodeState = 0; uint32_t decodeState = 0;
uint32_t codepoint; uint32_t codepoint;
int32_t glyphIndex; int32_t glyphIndex;
int32_t previousGlyphIndex; int32_t previousGlyphIndex = -1;
int32_t rangeIndex; int32_t rangeIndex;
stbtt_packedchar *rangeData; PackedChar *rangeData;
float rangeFontSize; Quad charQuad;
stbtt_aligned_quad charQuad;
uint32_t vertexBufferIndex; uint32_t vertexBufferIndex;
uint32_t indexBufferIndex; uint32_t indexBufferIndex;
Wellspring_Rectangle bounds; Wellspring_Rectangle bounds;
uint32_t i, j; uint32_t i, j;
float sizeFactor = pixelSize / font->pixelsPerEm;
float x = 0, y = 0;
y += Wellspring_INTERNAL_GetVerticalAlignOffset(myPacker->font, verticalAlignment, myPacker->scale); y -= Wellspring_INTERNAL_GetVerticalAlignOffset(font, verticalAlignment, sizeFactor * font->scale);
/* FIXME: If we horizontally align, we have to decode and process glyphs twice, very inefficient. */ /* FIXME: If we horizontally align, we have to decode and process glyphs twice, very inefficient. */
if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_RIGHT) if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_RIGHT)
{ {
if (!Wellspring_Internal_TextBounds(myPacker, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds)) if (!Wellspring_Internal_TextBounds(font, pixelSize, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{ {
/* Something went wrong while calculating bounds. */ /* Something went wrong while calculating bounds. */
return 0; return 0;
@ -566,7 +770,7 @@ uint8_t Wellspring_Draw(
} }
else if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_CENTER) else if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_CENTER)
{ {
if (!Wellspring_Internal_TextBounds(myPacker, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds)) if (!Wellspring_Internal_TextBounds(font, pixelSize, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{ {
/* Something went wrong while calculating bounds. */ /* Something went wrong while calculating bounds. */
return 0; return 0;
@ -588,14 +792,6 @@ uint8_t Wellspring_Draw(
continue; 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; rangeData = NULL;
/* Find the packed char data */ /* Find the packed char data */
@ -607,7 +803,6 @@ uint8_t Wellspring_Draw(
) { ) {
rangeData = myPacker->ranges[j].data; rangeData = myPacker->ranges[j].data;
rangeIndex = codepoint - myPacker->ranges[j].firstCodepoint; rangeIndex = codepoint - myPacker->ranges[j].firstCodepoint;
rangeFontSize = myPacker->ranges[j].fontSize;
break; break;
} }
} }
@ -618,22 +813,30 @@ uint8_t Wellspring_Draw(
return 0; return 0;
} }
glyphIndex = stbtt_FindGlyphIndex(&myPacker->font->fontInfo, codepoint); if (IsWhitespace(codepoint))
if (i > 0)
{ {
x += myPacker->scale * stbtt_GetGlyphKernAdvance(&myPacker->font->fontInfo, previousGlyphIndex, glyphIndex); PackedChar *packedChar = rangeData + rangeIndex;
x += sizeFactor * font->scale * packedChar->xAdvance;
previousGlyphIndex = -1;
continue;
} }
stbtt_GetPackedQuad( glyphIndex = stbtt_FindGlyphIndex(&font->fontInfo, codepoint);
if (previousGlyphIndex != -1)
{
x += sizeFactor * font->kerningScale * font->scale * stbtt_GetGlyphKernAdvance(&font->fontInfo, previousGlyphIndex, glyphIndex);
}
GetPackedQuad(
rangeData, rangeData,
sizeFactor * font->scale,
myPacker->width, myPacker->width,
myPacker->height, myPacker->height,
rangeIndex, rangeIndex,
&x, &x,
&y, &y,
&charQuad, &charQuad
0
); );
if (batch->vertexCount >= batch->vertexCapacity) if (batch->vertexCount >= batch->vertexCapacity)
@ -648,14 +851,12 @@ uint8_t Wellspring_Draw(
batch->indices = Wellspring_realloc(batch->indices, sizeof(uint32_t) * batch->indexCapacity); batch->indices = Wellspring_realloc(batch->indices, sizeof(uint32_t) * batch->indexCapacity);
} }
/* TODO: kerning and alignment */
vertexBufferIndex = batch->vertexCount; vertexBufferIndex = batch->vertexCount;
indexBufferIndex = batch->indexCount; indexBufferIndex = batch->indexCount;
batch->vertices[vertexBufferIndex].x = charQuad.x0; batch->vertices[vertexBufferIndex].x = charQuad.x0;
batch->vertices[vertexBufferIndex].y = charQuad.y0; batch->vertices[vertexBufferIndex].y = charQuad.y0;
batch->vertices[vertexBufferIndex].z = depth; batch->vertices[vertexBufferIndex].z = 0;
batch->vertices[vertexBufferIndex].u = charQuad.s0; batch->vertices[vertexBufferIndex].u = charQuad.s0;
batch->vertices[vertexBufferIndex].v = charQuad.t0; batch->vertices[vertexBufferIndex].v = charQuad.t0;
batch->vertices[vertexBufferIndex].r = color->r; batch->vertices[vertexBufferIndex].r = color->r;
@ -665,7 +866,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 1].x = charQuad.x0; batch->vertices[vertexBufferIndex + 1].x = charQuad.x0;
batch->vertices[vertexBufferIndex + 1].y = charQuad.y1; batch->vertices[vertexBufferIndex + 1].y = charQuad.y1;
batch->vertices[vertexBufferIndex + 1].z = depth; batch->vertices[vertexBufferIndex + 1].z = 0;
batch->vertices[vertexBufferIndex + 1].u = charQuad.s0; batch->vertices[vertexBufferIndex + 1].u = charQuad.s0;
batch->vertices[vertexBufferIndex + 1].v = charQuad.t1; batch->vertices[vertexBufferIndex + 1].v = charQuad.t1;
batch->vertices[vertexBufferIndex + 1].r = color->r; batch->vertices[vertexBufferIndex + 1].r = color->r;
@ -675,7 +876,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 2].x = charQuad.x1; batch->vertices[vertexBufferIndex + 2].x = charQuad.x1;
batch->vertices[vertexBufferIndex + 2].y = charQuad.y0; batch->vertices[vertexBufferIndex + 2].y = charQuad.y0;
batch->vertices[vertexBufferIndex + 2].z = depth; batch->vertices[vertexBufferIndex + 2].z = 0;
batch->vertices[vertexBufferIndex + 2].u = charQuad.s1; batch->vertices[vertexBufferIndex + 2].u = charQuad.s1;
batch->vertices[vertexBufferIndex + 2].v = charQuad.t0; batch->vertices[vertexBufferIndex + 2].v = charQuad.t0;
batch->vertices[vertexBufferIndex + 2].r = color->r; batch->vertices[vertexBufferIndex + 2].r = color->r;
@ -685,7 +886,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 3].x = charQuad.x1; batch->vertices[vertexBufferIndex + 3].x = charQuad.x1;
batch->vertices[vertexBufferIndex + 3].y = charQuad.y1; batch->vertices[vertexBufferIndex + 3].y = charQuad.y1;
batch->vertices[vertexBufferIndex + 3].z = depth; batch->vertices[vertexBufferIndex + 3].z = 0;
batch->vertices[vertexBufferIndex + 3].u = charQuad.s1; batch->vertices[vertexBufferIndex + 3].u = charQuad.s1;
batch->vertices[vertexBufferIndex + 3].v = charQuad.t1; batch->vertices[vertexBufferIndex + 3].v = charQuad.t1;
batch->vertices[vertexBufferIndex + 3].r = color->r; batch->vertices[vertexBufferIndex + 3].r = color->r;
@ -733,27 +934,15 @@ void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch)
Wellspring_free(batch); 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) void Wellspring_DestroyFont(Wellspring_Font* font)
{ {
Font *myFont = (Font*) font; Font *myFont = (Font*) font;
for (int i = 0; i < myFont->packer.rangeCount; i += 1)
{
Wellspring_free(myFont->packer.ranges[i].data);
}
Wellspring_free(myFont->packer.ranges);
Wellspring_free(myFont->fontBytes); Wellspring_free(myFont->fontBytes);
Wellspring_free(myFont); Wellspring_free(myFont);
} }

View File

@ -1,31 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Wellspring", "Wellspring.vcxproj", "{6DB15344-E000-45CB-A48A-1D72F7D6E945}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
MinSizeRel|x64 = MinSizeRel|x64
Release|x64 = Release|x64
RelWithDebInfo|x64 = RelWithDebInfo|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.Debug|x64.ActiveCfg = Debug|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.Debug|x64.Build.0 = Debug|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.Release|x64.ActiveCfg = Release|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.Release|x64.Build.0 = Release|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{6DB15344-E000-45CB-A48A-1D72F7D6E945}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7B2DB465-0A55-3811-9EF4-A520B47653D2}
EndGlobalSection
EndGlobal

View File

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6DB15344-E000-45CB-A48A-1D72F7D6E945}</ProjectGuid>
<RootNamespace>FNA3D</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<IncludePath>..\lib\;..\..\SDL2\include;..\include;$(IncludePath)</IncludePath>
<LibraryPath>..\..\SDL2\lib\$(PlatformShortName);$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>USE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>USE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\src\Wellspring.c" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\lib\stb_rect_pack.h" />
<ClCompile Include="..\lib\stb_truetype.h" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\Wellspring.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>