Compare commits

...

3 Commits

Author SHA1 Message Date
bjorn 1f8d37a160 Font:getKerning; 2022-06-29 21:49:55 -07:00
bjorn 078b54a7a5 Font:getWrap doesn't return width; 2022-06-29 21:33:39 -07:00
bjorn 7711fe8b65 Font:getWrap; Simplify Font internals; 2022-06-29 20:17:26 -07:00
6 changed files with 207 additions and 126 deletions

View File

@ -142,6 +142,7 @@ struct Blob;
struct Image;
struct Blob* luax_readblob(struct lua_State* L, int index, const char* debug);
struct Image* luax_checkimage(struct lua_State* L, int index);
uint32_t luax_checkcodepoint(struct lua_State* L, int index);
#endif
#ifndef LOVR_DISABLE_EVENT
@ -156,9 +157,11 @@ void* luax_readfile(const char* filename, size_t* bytesRead);
#ifndef LOVR_DISABLE_GRAPHICS
struct Buffer;
struct ColoredString;
void luax_readbufferfield(struct lua_State* L, int index, int type, void* data);
void luax_readbufferdata(struct lua_State* L, int index, struct Buffer* buffer, char* data);
uint32_t luax_checkcomparemode(struct lua_State* L, int index);
struct ColoredString* luax_checkcoloredstrings(struct lua_State* L, int index, uint32_t* count, struct ColoredString* stack);
#endif
#ifndef LOVR_DISABLE_MATH

View File

@ -6,6 +6,20 @@
#include <lauxlib.h>
#include <math.h>
uint32_t luax_checkcodepoint(lua_State* L, int index) {
size_t length;
const char* str;
uint32_t codepoint;
switch (lua_type(L, index)) {
case LUA_TSTRING:
str = lua_tolstring(L, index, &length);
return utf8_decode(str, str + length, &codepoint) ? codepoint : 0;
case LUA_TNUMBER:
return luax_checku32(L, index);
default: return luax_typeerror(L, index, "string or number"), 0;
}
}
static int l_lovrRasterizerGetFontSize(lua_State* L) {
Rasterizer* rasterizer = luax_checktype(L, 1, Rasterizer);
float size = lovrRasterizerGetFontSize(rasterizer);
@ -67,20 +81,6 @@ static int l_lovrRasterizerGetLeading(lua_State* L) {
return 1;
}
static uint32_t luax_checkcodepoint(lua_State* L, int index) {
size_t length;
const char* str;
uint32_t codepoint;
switch (lua_type(L, index)) {
case LUA_TSTRING:
str = lua_tolstring(L, index, &length);
return utf8_decode(str, str + length, &codepoint) ? codepoint : 0;
case LUA_TNUMBER:
return luax_checku32(L, index);
default: return luax_typeerror(L, index, "string or number"), 0;
}
}
static int l_lovrRasterizerGetAdvance(lua_State* L) {
Rasterizer* rasterizer = luax_checktype(L, 1, Rasterizer);
uint32_t codepoint = luax_checkcodepoint(L, 2);

View File

@ -4,6 +4,28 @@
#include "util.h"
#include <lua.h>
#include <lauxlib.h>
#include <stdlib.h>
ColoredString* luax_checkcoloredstrings(lua_State* L, int index, uint32_t* count, ColoredString* stack) {
if (lua_istable(L, index)) {
*count = luax_len(L, index) / 2;
ColoredString* strings = malloc(*count * sizeof(*strings));
lovrAssert(strings, "Out of memory");
for (uint32_t i = 0; i < *count; i++) {
lua_rawgeti(L, index, i * 2 + 1);
lua_rawgeti(L, index, i * 2 + 2);
luax_optcolor(L, -2, strings[i].color);
lovrCheck(lua_isstring(L, -1), "Expected a string to print");
strings[i].string = luaL_checklstring(L, -1, &strings[i].length);
lua_pop(L, 2);
}
return strings;
} else {
stack->string = luaL_checklstring(L, index, &stack->length);
stack->color[0] = stack->color[1] = stack->color[2] = stack->color[3] = 1.f;
return *count = 1, stack;
}
}
static int l_lovrFontGetRasterizer(lua_State* L) {
Font* font = luax_checktype(L, 1, Font);
@ -67,6 +89,35 @@ static int l_lovrFontGetHeight(lua_State* L) {
return 1;
}
static int l_lovrFontGetKerning(lua_State* L) {
Font* font = luax_checktype(L, 1, Font);
uint32_t left = luax_checkcodepoint(L, 2);
uint32_t right = luax_checkcodepoint(L, 3);
float kerning = lovrFontGetKerning(font, left, right);
float density = lovrFontGetPixelDensity(font);
lua_pushnumber(L, kerning / density);
return 1;
}
static void online(void* context, const char* string, size_t length) {
lua_State* L = context;
int index = luax_len(L, -1) + 1;
lua_pushlstring(L, string, length);
lua_rawseti(L, -2, index);
}
static int l_lovrFontGetWrap(lua_State* L) {
Font* font = luax_checktype(L, 1, Font);
uint32_t count;
ColoredString stack;
ColoredString* strings = luax_checkcoloredstrings(L, 2, &count, &stack);
float wrap = luax_checkfloat(L, 3);
lua_newtable(L);
lovrFontGetWrap(font, strings, 1, wrap, online, L);
if (strings != &stack) free(strings);
return 1;
}
const luaL_Reg lovrFont[] = {
{ "getRasterizer", l_lovrFontGetRasterizer },
{ "getPixelDensity", l_lovrFontGetPixelDensity },
@ -76,5 +127,7 @@ const luaL_Reg lovrFont[] = {
{ "getAscent", l_lovrFontGetAscent },
{ "getDescent", l_lovrFontGetDescent },
{ "getHeight", l_lovrFontGetHeight },
{ "getKerning", l_lovrFontGetKerning },
{ "getWrap", l_lovrFontGetWrap },
{ NULL, NULL }
};

View File

@ -556,35 +556,16 @@ static int l_lovrPassText(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
Font* font = luax_totype(L, 2, Font);
int index = font ? 3 : 2;
uint32_t count = 0;
ColoredString string;
ColoredString* strings = &string;
if (lua_istable(L, index)) {
count = luax_len(L, index) / 2;
strings = malloc(count * sizeof(*strings));
lovrAssert(strings, "Out of memory");
for (uint32_t i = 0; i < count; i++) {
lua_rawgeti(L, index, i * 2 + 1);
lua_rawgeti(L, index, i * 2 + 2);
luax_optcolor(L, -2, strings[i].color);
lovrCheck(lua_isstring(L, -1), "Expected a string to print");
strings[i].string = luaL_checklstring(L, -1, &strings[i].length);
lua_pop(L, 2);
}
index++;
} else {
strings[0].string = luaL_checklstring(L, index, &strings[0].length);
strings[0].color[0] = strings[0].color[1] = strings[0].color[2] = strings[0].color[3] = 1.f;
count = 1;
index++;
}
uint32_t count;
ColoredString stack;
ColoredString* strings = luax_checkcoloredstrings(L, index++, &count, &stack);
float transform[16];
index = luax_readmat4(L, index++, transform, 1);
float wrap = luax_optfloat(L, index++, 0.);
HorizontalAlign halign = luax_checkenum(L, index++, HorizontalAlign, "center");
VerticalAlign valign = luax_checkenum(L, index++, VerticalAlign, "middle");
lovrPassText(pass, font, strings, count, transform, wrap, halign, valign);
if (strings != &string) free(strings);
if (strings != &stack) free(strings);
return 0;
}

View File

@ -1826,11 +1826,12 @@ void lovrFontSetLineSpacing(Font* font, float spacing) {
font->lineSpacing = spacing;
}
static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint, bool* resized) {
uint64_t hash = hash64(&codepoint, 4);
uint64_t index = map_get(&font->glyphLookup, hash);
if (index != MAP_NIL) {
if (resized) *resized = false;
return &font->glyphs.data[index];
}
@ -1843,6 +1844,7 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
if (lovrRasterizerIsGlyphEmpty(font->info.rasterizer, codepoint)) {
memset(glyph->box, 0, sizeof(glyph->box));
if (resized) *resized = false;
return glyph;
}
@ -1884,17 +1886,9 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
font->atlasX += pixelWidth;
font->rowHeight = MAX(font->rowHeight, pixelHeight);
return glyph;
}
static void lovrFontUploadNewGlyphs(Font* font, uint32_t start, ColoredString* strings, uint32_t count, GlyphVertex* vertices) {
if (start >= font->glyphs.length) {
return;
}
// Atlas resize
if (!font->atlas || font->atlasWidth > font->atlas->info.width || font->atlasHeight > font->atlas->info.height) {
lovrCheck(font->atlasWidth <= 65536, "Font atlas is too big!");
lovrCheck(font->atlasWidth <= 65536, "Font atlas is way too big!");
Texture* atlas = lovrTextureCreate(&(TextureInfo) {
.type = TEXTURE_2D,
@ -1942,96 +1936,138 @@ static void lovrFontUploadNewGlyphs(Font* font, uint32_t start, ColoredString* s
// Recompute all glyph uvs after atlas resize
for (size_t i = 0; i < font->glyphs.length; i++) {
Glyph* glyph = &font->glyphs.data[i];
glyph->uv[0] = (uint16_t) ((float) glyph->x / font->atlasWidth * 65535.f + .5f);
glyph->uv[1] = (uint16_t) ((float) glyph->y / font->atlasHeight * 65535.f + .5f);
glyph->uv[2] = (uint16_t) ((float) (glyph->x + glyph->box[2] - glyph->box[0]) / font->atlasWidth * 65535.f + .5f);
glyph->uv[3] = (uint16_t) ((float) (glyph->y + glyph->box[3] - glyph->box[1]) / font->atlasHeight * 65535.f + .5f);
Glyph* g = &font->glyphs.data[i];
g->uv[0] = (uint16_t) ((float) g->x / font->atlasWidth * 65535.f + .5f);
g->uv[1] = (uint16_t) ((float) g->y / font->atlasHeight * 65535.f + .5f);
g->uv[2] = (uint16_t) ((float) (g->x + g->box[2] - g->box[0]) / font->atlasWidth * 65535.f + .5f);
g->uv[3] = (uint16_t) ((float) (g->y + g->box[3] - g->box[1]) / font->atlasHeight * 65535.f + .5f);
}
// Adjust current vertex uvs
for (uint32_t i = 0; i < count; i++) {
size_t bytes;
uint32_t codepoint;
const char* str = strings[i].string;
const char* end = strings[i].string + strings[i].length;
while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
if (codepoint == ' ' || codepoint == '\n' || codepoint == '\r' || codepoint== '\t') {
str += bytes;
continue;
}
Glyph* glyph = lovrFontGetGlyph(font, codepoint);
if (glyph->box[2] - glyph->box[0] != 0.f) {
vertices[0].uv.u = glyph->uv[0];
vertices[0].uv.v = glyph->uv[1];
vertices[1].uv.u = glyph->uv[2];
vertices[1].uv.v = glyph->uv[1];
vertices[2].uv.u = glyph->uv[0];
vertices[2].uv.v = glyph->uv[3];
vertices[3].uv.u = glyph->uv[2];
vertices[3].uv.v = glyph->uv[3];
vertices += 4;
}
str += bytes;
}
}
if (resized) *resized = true;
}
gpu_buffer* scratchpad = tempAlloc(gpu_sizeof_buffer());
for (uint32_t i = start; i < font->glyphs.length; i++) {
Glyph* glyph = &font->glyphs.data[i];
float width = glyph->box[2] - glyph->box[0];
float height = glyph->box[3] - glyph->box[1];
if (width == 0.f || height == 0.f) {
continue;
}
uint32_t w = 2 * font->padding + ceilf(width);
uint32_t h = 2 * font->padding + ceilf(height);
uint32_t stack = tempPush();
float* pixels = tempAlloc(w * h * 4 * sizeof(float));
lovrRasterizerGetPixels(font->info.rasterizer, glyph->codepoint, pixels, w, h, font->info.spread);
uint8_t* dst = gpu_map(scratchpad, w * h * 4 * sizeof(uint8_t), 4, GPU_MAP_WRITE);
float* src = pixels;
for (uint32_t y = 0; y < h; y++) {
for (uint32_t x = 0; x < w; x++) {
for (uint32_t c = 0; c < 4; c++) {
float f = *src++; // CLAMP evaluates multiple times
*dst++ = (uint8_t) (CLAMP(f, 0.f, 1.f) * 255.f + .5f);
}
uint32_t stack = tempPush();
float* pixels = tempAlloc(pixelWidth * pixelHeight * 4 * sizeof(float));
lovrRasterizerGetPixels(font->info.rasterizer, glyph->codepoint, pixels, pixelWidth, pixelHeight, font->info.spread);
uint8_t* dst = gpu_map(scratchpad, pixelWidth * pixelHeight * 4 * sizeof(uint8_t), 4, GPU_MAP_WRITE);
float* src = pixels;
for (uint32_t y = 0; y < pixelHeight; y++) {
for (uint32_t x = 0; x < pixelWidth; x++) {
for (uint32_t c = 0; c < 4; c++) {
float f = *src++; // CLAMP would evaluate this multiple times
*dst++ = (uint8_t) (CLAMP(f, 0.f, 1.f) * 255.f + .5f);
}
}
tempPop(stack);
uint32_t dstOffset[4] = { glyph->x - font->padding, glyph->y - font->padding, 0, 0 };
uint32_t extent[3] = { w, h, 1 };
gpu_copy_buffer_texture(state.stream, scratchpad, font->atlas->gpu, 0, dstOffset, extent);
}
uint32_t dstOffset[4] = { glyph->x - font->padding, glyph->y - font->padding, 0, 0 };
uint32_t extent[3] = { pixelWidth, pixelHeight, 1 };
gpu_copy_buffer_texture(state.stream, scratchpad, font->atlas->gpu, 0, dstOffset, extent);
tempPop(stack);
state.hasGlyphUpload = true;
return glyph;
}
static float lovrFontGetKerning(Font* font, uint32_t previous, uint32_t codepoint) {
uint32_t codepoints[] = { previous, codepoint };
float lovrFontGetKerning(Font* font, uint32_t left, uint32_t right) {
uint32_t codepoints[] = { left, right };
uint64_t hash = hash64(codepoints, sizeof(codepoints));
union { float f32; uint64_t u64; } kerning = { .u64 = map_get(&font->kerning, hash) };
if (kerning.u64 == MAP_NIL) {
kerning.f32 = lovrRasterizerGetKerning(font->info.rasterizer, previous, codepoint);
kerning.f32 = lovrRasterizerGetKerning(font->info.rasterizer, left, right);
map_set(&font->kerning, hash, kerning.u64);
}
return kerning.f32;
}
void lovrFontGetWrap(Font* font, ColoredString* strings, uint32_t count, float wrap, void (*callback)(void* context, const char* string, size_t length), void* context) {
size_t totalLength = 0;
for (uint32_t i = 0; i < count; i++) {
totalLength += strings[i].length;
}
uint32_t stack = tempPush();
char* string = tempAlloc(totalLength + 1);
string[totalLength] = '\0';
for (uint32_t i = 0, cursor = 0; i < count; i++, cursor += strings[i].length) {
memcpy(string + cursor, strings[i].string, strings[i].length);
}
float x = 0.f;
float nextWordStartX = 0.f;
wrap *= font->pixelDensity;
size_t bytes;
uint32_t codepoint;
uint32_t previous = '\0';
const char* lineStart = string;
const char* wordStart = string;
const char* end = string + totalLength;
Glyph* space = lovrFontGetGlyph(font, ' ', NULL);
while ((bytes = utf8_decode(string, end, &codepoint)) > 0) {
if (codepoint == ' ' || codepoint == '\t') {
x += codepoint == '\t' ? space->advance * 4.f : space->advance;
nextWordStartX = x;
previous = '\0';
string += bytes;
wordStart = string;
continue;
} else if (codepoint == '\n') {
size_t length = string - lineStart;
while (string[length] == ' ' || string[length] == '\t') length--;
callback(context, lineStart, length);
nextWordStartX = 0.f;
x = 0.f;
previous = '\0';
string += bytes;
lineStart = string;
wordStart = string;
continue;
} else if (codepoint == '\r') {
string += bytes;
continue;
}
Glyph* glyph = lovrFontGetGlyph(font, codepoint, NULL);
// Keming
if (previous) x += lovrFontGetKerning(font, previous, codepoint);
previous = codepoint;
// Ignore bearing for the first character on a line (questionable)
if (string == lineStart) {
x -= glyph->box[0];
} else if (string == wordStart) {
nextWordStartX += glyph->box[0]; // So wrapping accounts for bearing of first glyph
}
// Wrap
if (wordStart != lineStart && x + glyph->box[2] > wrap) {
size_t length = wordStart - lineStart;
while (string[length] == ' ' || string[length] == '\t') length--;
callback(context, lineStart, length);
lineStart = wordStart;
x -= nextWordStartX;
nextWordStartX = 0.f;
previous = '\0';
}
// Advance
x += glyph->advance;
string += bytes;
}
if (end - lineStart > 0) {
callback(context, lineStart, end - lineStart);
}
tempPop(stack);
}
// Pass
Pass* lovrGraphicsGetPass(PassInfo* info) {
@ -3412,14 +3448,15 @@ static void aline(GlyphVertex* vertices, uint32_t head, uint32_t tail, Horizonta
void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count, float* transform, float wrap, HorizontalAlign halign, VerticalAlign valign) {
font = font ? font : lovrGraphicsGetDefaultFont();
uint32_t originalGlyphCount = font->glyphs.length;
Glyph* space = lovrFontGetGlyph(font, ' ', NULL);
uint32_t maxGlyphCount = 0;
size_t totalLength = 0;
for (uint32_t i = 0; i < count; i++) {
maxGlyphCount += strings[i].length;
totalLength += strings[i].length;
}
GlyphVertex* vertices = tempAlloc(maxGlyphCount * 4 * sizeof(GlyphVertex));
uint32_t stack = tempPush();
GlyphVertex* vertices = tempAlloc(totalLength * 4 * sizeof(GlyphVertex));
uint32_t vertexCount = 0;
uint32_t glyphCount = 0;
uint32_t lineCount = 1;
@ -3446,9 +3483,8 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count
while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
if (codepoint == ' ' || codepoint == '\t') {
Glyph* glyph = lovrFontGetGlyph(font, ' ');
wordStart = vertexCount;
x += codepoint == '\t' ? glyph->advance * 4.f : glyph->advance;
x += codepoint == '\t' ? space->advance * 4.f : space->advance;
previous = '\0';
str += bytes;
continue;
@ -3467,7 +3503,14 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count
continue;
}
Glyph* glyph = lovrFontGetGlyph(font, codepoint);
bool resized;
Glyph* glyph = lovrFontGetGlyph(font, codepoint, &resized);
if (resized) {
tempPop(stack);
lovrPassText(pass, font, strings, count, transform, wrap, halign, valign);
return;
}
// Keming
if (previous) x += lovrFontGetKerning(font, previous, codepoint);
@ -3491,7 +3534,7 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count
y -= dy;
}
// Ignore bearing for the first character on a line so it lines up perfectly (questionable)
// Ignore bearing for the first character on a line (questionable)
if (vertexCount == lineStart) {
x -= glyph->box[0];
}
@ -3514,9 +3557,6 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count
// Align last line
aline(vertices, lineStart, vertexCount, halign);
// Resize atlas, rasterize new glyphs, upload them into atlas, recreate material
lovrFontUploadNewGlyphs(font, originalGlyphCount, strings, count, vertices);
mat4_scale(transform, scale, scale, scale);
mat4_translate(transform, 0.f, -ascent + valign / 2.f * (leading * lineCount), 0.f);
@ -3538,6 +3578,8 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count
memcpy(indices, quad, sizeof(quad));
indices += COUNTOF(quad);
}
tempPop(stack);
}
void lovrPassFill(Pass* pass, Texture* texture) {

View File

@ -328,7 +328,7 @@ typedef struct {
double spread;
} FontInfo;
typedef struct {
typedef struct ColoredString {
float color[4];
const char* string;
size_t length;
@ -353,6 +353,8 @@ float lovrFontGetPixelDensity(Font* font);
void lovrFontSetPixelDensity(Font* font, float pixelDensity);
float lovrFontGetLineSpacing(Font* font);
void lovrFontSetLineSpacing(Font* font, float spacing);
float lovrFontGetKerning(Font* font, uint32_t left, uint32_t right);
void lovrFontGetWrap(Font* font, ColoredString* strings, uint32_t count, float wrap, void (*callback)(void* context, const char* string, size_t length), void* context);
// Pass