Compare commits

...

8 Commits
0.1.0 ... main

Author SHA1 Message Date
cosmonaut 56377438cc 1.0.0
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2023-12-15 10:32:06 -08:00
cosmonaut ab238503a3 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
2023-12-15 18:30:00 +00:00
cosmonaut 6e0e4d0fa0 revert OSX deployment target to 10.9
continuous-integration/drone/push Build is passing Details
2023-12-06 09:36:03 -08:00
Evan Hemsley b261163162 bump OSX version to 10.15
continuous-integration/drone/push Build is passing Details
2023-10-14 20:43:38 -07:00
cosmonaut eae5980dbe 0.3.0
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone Build is passing Details
2022-06-30 16:19:02 -07:00
cosmonaut fec606a8c3 change textbounds to take a packer instead of batch
continuous-integration/drone/push Build is passing Details
2022-06-30 16:17:31 -07:00
cosmonaut 4b8975ee2a 0.2.0
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-06-30 13:21:40 -07:00
cosmonaut 51a8fe333d handle whitespace + get batch vertex count
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-06-30 13:18:17 -07:00
8 changed files with 3931 additions and 358 deletions

View File

@ -4,8 +4,8 @@ project(Wellspring C)
option(BUILD_SHARED_LIBS "Build shared library" ON)
option(USE_SDL2 "Use SDL2" ON)
SET(LIB_MAJOR_VERSION "0")
SET(LIB_MINOR_VERSION "1")
SET(LIB_MAJOR_VERSION "1")
SET(LIB_MINOR_VERSION "0")
SET(LIB_REVISION "0")
SET(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}")
@ -46,6 +46,7 @@ add_library(Wellspring
#Public header
include/Wellspring.h
#Source
lib/json.h
lib/stb_rect_pack.h
lib/stb_truetype.h
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
----------------
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.
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
------------
@ -20,7 +30,7 @@ For *nix platforms, use CMake:
$ cmake ../
$ 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
-------

View File

@ -48,8 +48,8 @@ extern "C"
/* Version API */
#define WELLSPRING_MAJOR_VERSION 0
#define WELLSPRING_MINOR_VERSION 1
#define WELLSPRING_MAJOR_VERSION 1
#define WELLSPRING_MINOR_VERSION 0
#define WELLSPRING_PATCH_VERSION 0
#define WELLSPRING_COMPILED_VERSION ( \
@ -63,7 +63,6 @@ WELLSPRINGAPI uint32_t Wellspring_LinkedVersion(void);
/* Type definitions */
typedef struct Wellspring_Font Wellspring_Font;
typedef struct Wellspring_Packer Wellspring_Packer;
typedef struct Wellspring_TextBatch Wellspring_TextBatch;
typedef struct Wellspring_FontRange
@ -113,46 +112,25 @@ typedef enum Wellspring_VerticalAlignment
WELLSPRINGAPI Wellspring_Font* Wellspring_CreateFont(
const uint8_t *fontBytes,
uint32_t fontBytesLength
);
WELLSPRINGAPI Wellspring_Packer* Wellspring_CreatePacker(
Wellspring_Font *font,
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
uint32_t fontBytesLength,
const uint8_t *atlasJsonBytes,
uint32_t atlasJsonBytesLength,
float *pPixelsPerEm,
float *pDistanceRange
);
/* 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 */
WELLSPRINGAPI void Wellspring_StartTextBatch(
Wellspring_TextBatch *textBatch,
Wellspring_Packer *packer
Wellspring_Font *font
);
WELLSPRINGAPI uint8_t Wellspring_TextBounds(
Wellspring_TextBatch *textBatch,
float x,
float y,
Wellspring_Font *font,
int pixelSize,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t *strBytes,
@ -160,11 +138,9 @@ WELLSPRINGAPI uint8_t Wellspring_TextBounds(
Wellspring_Rectangle *pRectangle
);
WELLSPRINGAPI uint8_t Wellspring_Draw(
WELLSPRINGAPI uint8_t Wellspring_AddToTextBatch(
Wellspring_TextBatch *textBatch,
float x,
float y,
float depth,
int pixelSize,
Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
@ -174,6 +150,7 @@ WELLSPRINGAPI uint8_t Wellspring_Draw(
WELLSPRINGAPI void Wellspring_GetBufferData(
Wellspring_TextBatch *textBatch,
uint32_t* pVertexCount,
Wellspring_Vertex **pVertexBuffer,
uint32_t *pVertexBufferLengthInBytes,
uint32_t **pIndexBuffer,
@ -181,7 +158,6 @@ WELLSPRINGAPI void Wellspring_GetBufferData(
);
WELLSPRINGAPI void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch);
WELLSPRINGAPI void Wellspring_DestroyPacker(Wellspring_Packer *packer);
WELLSPRINGAPI void Wellspring_DestroyFont(Wellspring_Font *font);
#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_ASSERT Wellspring_assert
#define SHEREDOM_JSON_H_malloc Wellspring_malloc
#define SHEREDOM_JSON_H_free Wellspring_free
typedef uint8_t stbtt_uint8;
typedef int8_t stbtt_int8;
typedef uint16_t stbtt_uint16;
@ -112,48 +115,54 @@ typedef int32_t stbtt_int32;
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#include "json.h"
#pragma GCC diagnostic warning "-Wunused-function"
#define INITIAL_QUAD_CAPACITY 128
/* 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
{
uint8_t *fontBytes;
stbtt_fontinfo fontInfo;
int32_t ascent;
int32_t descent;
int32_t lineGap;
float ascender;
float descender;
float lineHeight;
float pixelsPerEm;
float distanceRange;
float scale;
float kerningScale; // kerning values from stb_tt are in a different scale
Packer packer;
} 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;
@ -164,9 +173,15 @@ typedef struct Batch
uint32_t indexCount;
uint32_t indexCapacity;
Packer *currentPacker;
Font *currentFont;
} Batch;
typedef struct Quad
{
float x0,y0,s0,t0; // top-left
float x1,y1,s1,t1; // bottom-right
} Quad;
/* UTF-8 Decoder */
/* 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;
}
/* 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 */
uint32_t Wellspring_LinkedVersion(void)
@ -214,107 +349,146 @@ uint32_t Wellspring_LinkedVersion(void)
Wellspring_Font* Wellspring_CreateFont(
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->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);
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;
}
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()
Wellspring_TextBatch* Wellspring_CreateTextBatch(void)
{
Batch *batch = Wellspring_malloc(sizeof(Batch));
@ -331,10 +505,10 @@ Wellspring_TextBatch* Wellspring_CreateTextBatch()
void Wellspring_StartTextBatch(
Wellspring_TextBatch *textBatch,
Wellspring_Packer *packer
Wellspring_Font *font
) {
Batch *batch = (Batch*) textBatch;
batch->currentPacker = (Packer*) packer;
batch->currentFont = (Font*) font;
batch->vertexCount = 0;
batch->indexCount = 0;
}
@ -350,47 +524,98 @@ static float Wellspring_INTERNAL_GetVerticalAlignOffset(
}
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_TOP)
{
return scale * font->ascent;
return scale * font->ascender;
}
else if (verticalAlignment == WELLSPRING_VERTICALALIGNMENT_MIDDLE)
{
return scale * (font->ascent + font->descent) / 2.0f;
return scale * (font->ascender + font->descender) / 2.0f;
}
else /* BOTTOM */
{
return scale * font->descent;
return scale * font->descender;
}
}
uint8_t Wellspring_TextBounds(
Wellspring_TextBatch* textBatch,
float x,
float y,
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 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(
Font* font,
int pixelSize,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t* strBytes,
uint32_t strLengthInBytes,
Wellspring_Rectangle *pRectangle
) {
Batch* batch = (Batch*)textBatch;
Packer* myPacker = batch->currentPacker;
uint32_t decodeState = 0;
uint32_t codepoint;
int32_t glyphIndex;
int32_t previousGlyphIndex;
int32_t previousGlyphIndex = -1;
int32_t rangeIndex;
stbtt_packedchar* rangeData;
float rangeFontSize;
stbtt_aligned_quad charQuad;
PackedChar* rangeData;
Quad charQuad;
uint32_t i, j;
float x = 0, y = 0;
float minX = x;
float minY = y;
float maxX = x;
float maxY = y;
float startX = x;
float advance = 0;
float sizeFactor = pixelSize / font->pixelsPerEm;
y += Wellspring_INTERNAL_GetVerticalAlignOffset(myPacker->font, verticalAlignment, myPacker->scale);
y -= Wellspring_INTERNAL_GetVerticalAlignOffset(font, verticalAlignment, sizeFactor * font->scale);
for (i = 0; i < strLengthInBytes; i += 1)
{
@ -407,16 +632,17 @@ uint8_t Wellspring_TextBounds(
rangeData = NULL;
Packer *packer = &font->packer;
/* Find the packed char data */
for (j = 0; j < myPacker->rangeCount; j += 1)
for (j = 0; j < packer->rangeCount; j += 1)
{
if (
codepoint >= myPacker->ranges[j].firstCodepoint &&
codepoint < myPacker->ranges[j].firstCodepoint + myPacker->ranges[j].charCount
codepoint >= packer->ranges[j].firstCodepoint &&
codepoint < packer->ranges[j].firstCodepoint + packer->ranges[j].charCount
) {
rangeData = myPacker->ranges[j].data;
rangeIndex = codepoint - myPacker->ranges[j].firstCodepoint;
rangeFontSize = myPacker->ranges[j].fontSize;
rangeData = packer->ranges[j].data;
rangeIndex = codepoint - packer->ranges[j].firstCodepoint;
break;
}
}
@ -427,22 +653,31 @@ uint8_t Wellspring_TextBounds(
return 0;
}
glyphIndex = stbtt_FindGlyphIndex(&myPacker->font->fontInfo, codepoint);
if (i > 0)
if (IsWhitespace(codepoint))
{
x += myPacker->scale * stbtt_GetGlyphKernAdvance(&myPacker->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,
myPacker->width,
myPacker->height,
sizeFactor * font->scale,
packer->width,
packer->height,
rangeIndex,
&x,
&y,
&charQuad,
0
&charQuad
);
if (charQuad.x0 < minX) { minX = charQuad.x0; }
@ -474,11 +709,29 @@ uint8_t Wellspring_TextBounds(
return 1;
}
uint8_t Wellspring_Draw(
uint8_t Wellspring_TextBounds(
Wellspring_Font *font,
int pixelSize,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
const uint8_t* strBytes,
uint32_t strLengthInBytes,
Wellspring_Rectangle* pRectangle
) {
return Wellspring_Internal_TextBounds(
(Font*) font,
pixelSize,
horizontalAlignment,
verticalAlignment,
strBytes,
strLengthInBytes,
pRectangle
);
}
uint8_t Wellspring_AddToTextBatch(
Wellspring_TextBatch *textBatch,
float x,
float y,
float depth,
int pixelSize,
Wellspring_Color *color,
Wellspring_HorizontalAlignment horizontalAlignment,
Wellspring_VerticalAlignment verticalAlignment,
@ -486,26 +739,28 @@ uint8_t Wellspring_Draw(
uint32_t strLengthInBytes
) {
Batch *batch = (Batch*) textBatch;
Packer *myPacker = batch->currentPacker;
Font *font = batch->currentFont;
Packer *myPacker = &font->packer;
uint32_t decodeState = 0;
uint32_t codepoint;
int32_t glyphIndex;
int32_t previousGlyphIndex;
int32_t previousGlyphIndex = -1;
int32_t rangeIndex;
stbtt_packedchar *rangeData;
float rangeFontSize;
stbtt_aligned_quad charQuad;
PackedChar *rangeData;
Quad charQuad;
uint32_t vertexBufferIndex;
uint32_t indexBufferIndex;
Wellspring_Rectangle bounds;
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. */
if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_RIGHT)
{
if (!Wellspring_TextBounds(textBatch, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
if (!Wellspring_Internal_TextBounds(font, pixelSize, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{
/* Something went wrong while calculating bounds. */
return 0;
@ -515,7 +770,7 @@ uint8_t Wellspring_Draw(
}
else if (horizontalAlignment == WELLSPRING_HORIZONTALALIGNMENT_CENTER)
{
if (!Wellspring_TextBounds(textBatch, x, y, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
if (!Wellspring_Internal_TextBounds(font, pixelSize, horizontalAlignment, verticalAlignment, strBytes, strLengthInBytes, &bounds))
{
/* Something went wrong while calculating bounds. */
return 0;
@ -548,7 +803,6 @@ uint8_t Wellspring_Draw(
) {
rangeData = myPacker->ranges[j].data;
rangeIndex = codepoint - myPacker->ranges[j].firstCodepoint;
rangeFontSize = myPacker->ranges[j].fontSize;
break;
}
}
@ -558,23 +812,31 @@ uint8_t Wellspring_Draw(
/* Requested char wasn't packed! */
return 0;
}
glyphIndex = stbtt_FindGlyphIndex(&myPacker->font->fontInfo, codepoint);
if (i > 0)
if (IsWhitespace(codepoint))
{
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,
sizeFactor * font->scale,
myPacker->width,
myPacker->height,
rangeIndex,
&x,
&y,
&charQuad,
0
&charQuad
);
if (batch->vertexCount >= batch->vertexCapacity)
@ -589,14 +851,12 @@ uint8_t Wellspring_Draw(
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].z = 0;
batch->vertices[vertexBufferIndex].u = charQuad.s0;
batch->vertices[vertexBufferIndex].v = charQuad.t0;
batch->vertices[vertexBufferIndex].r = color->r;
@ -606,7 +866,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 1].x = charQuad.x0;
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].v = charQuad.t1;
batch->vertices[vertexBufferIndex + 1].r = color->r;
@ -616,7 +876,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 2].x = charQuad.x1;
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].v = charQuad.t0;
batch->vertices[vertexBufferIndex + 2].r = color->r;
@ -626,7 +886,7 @@ uint8_t Wellspring_Draw(
batch->vertices[vertexBufferIndex + 3].x = charQuad.x1;
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].v = charQuad.t1;
batch->vertices[vertexBufferIndex + 3].r = color->r;
@ -652,12 +912,14 @@ uint8_t Wellspring_Draw(
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;
@ -672,27 +934,15 @@ void Wellspring_DestroyTextBatch(Wellspring_TextBatch *textBatch)
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;
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);
}

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>