lovr/src/graphics/font.c

331 lines
8.6 KiB
C
Raw Normal View History

2017-02-03 23:16:30 +00:00
#include "graphics/font.h"
#include "graphics/graphics.h"
#include "graphics/texture.h"
2018-01-22 16:28:33 +00:00
#include "data/rasterizer.h"
2018-02-11 23:22:04 +00:00
#include "data/textureData.h"
2017-02-06 09:54:11 +00:00
#include "util.h"
2017-02-03 23:16:30 +00:00
#include <string.h>
2018-07-04 20:51:35 +00:00
#include <stdbool.h>
2017-02-06 09:54:11 +00:00
#include <stdlib.h>
#include <stdio.h>
2017-02-03 23:16:30 +00:00
2018-03-22 05:03:03 +00:00
static float* lovrFontAlignLine(float* x, float* lineEnd, float width, HorizontalAlign halign) {
while(x < lineEnd) {
2017-03-16 03:12:56 +00:00
if (halign == ALIGN_CENTER) {
2018-03-22 05:03:03 +00:00
*x -= width / 2.f;
2017-03-16 03:12:56 +00:00
} else if (halign == ALIGN_RIGHT) {
2018-03-22 05:03:03 +00:00
*x -= width;
2017-03-16 03:12:56 +00:00
}
2018-03-22 05:03:03 +00:00
x += 8;
2017-03-16 03:12:56 +00:00
}
2018-03-22 05:03:03 +00:00
return x;
2017-03-16 03:12:56 +00:00
}
2018-01-22 16:28:33 +00:00
Font* lovrFontCreate(Rasterizer* rasterizer) {
2018-07-25 02:14:29 +00:00
Font* font = lovrAlloc(Font, lovrFontDestroy);
2017-02-03 23:16:30 +00:00
if (!font) return NULL;
2018-02-26 08:59:03 +00:00
lovrRetain(rasterizer);
2018-01-22 16:28:33 +00:00
font->rasterizer = rasterizer;
2017-02-12 11:14:10 +00:00
font->lineHeight = 1.f;
2018-01-22 16:28:33 +00:00
font->pixelDensity = (float) font->rasterizer->height;
2017-02-08 06:11:20 +00:00
map_init(&font->kerning);
2017-02-06 04:30:17 +00:00
// Atlas
2017-02-05 06:21:41 +00:00
int padding = 1;
font->atlas.x = padding;
font->atlas.y = padding;
2017-02-12 11:14:10 +00:00
font->atlas.width = 128;
font->atlas.height = 128;
2017-02-05 06:21:41 +00:00
font->atlas.padding = padding;
map_init(&font->atlas.glyphs);
2017-02-12 11:14:10 +00:00
// Set initial atlas size
2018-01-22 16:28:33 +00:00
while (font->atlas.height < 4 * rasterizer->size) {
2017-02-12 11:14:10 +00:00
lovrFontExpandTexture(font);
2017-02-09 02:57:47 +00:00
}
// Create the texture
lovrFontCreateTexture(font);
2017-04-16 23:56:49 +00:00
2017-02-03 23:16:30 +00:00
return font;
}
2018-02-26 08:59:03 +00:00
void lovrFontDestroy(void* ref) {
Font* font = ref;
lovrRelease(font->rasterizer);
lovrRelease(font->texture);
2018-08-02 10:22:27 +00:00
const char* key;
map_iter_t iter = map_iter(&font->atlas.glyphs);
while ((key = map_next(&font->atlas.glyphs, &iter)) != NULL) {
Glyph* glyph = map_get(&font->atlas.glyphs, key);
lovrRelease(glyph->data);
}
2017-02-05 06:21:41 +00:00
map_deinit(&font->atlas.glyphs);
2017-02-08 06:11:20 +00:00
map_deinit(&font->kerning);
2017-02-03 23:16:30 +00:00
free(font);
}
2018-03-22 05:03:03 +00:00
void lovrFontRender(Font* font, const char* str, float wrap, HorizontalAlign halign, VerticalAlign valign, VertexPointer vertices, float* offsety, uint32_t* vertexCount) {
2017-02-05 06:21:41 +00:00
FontAtlas* atlas = &font->atlas;
2017-02-03 23:16:30 +00:00
2017-02-17 00:23:52 +00:00
float cx = 0;
2018-01-22 16:28:33 +00:00
float cy = -font->rasterizer->height * .8;
2017-02-06 04:30:17 +00:00
float u = atlas->width;
float v = atlas->height;
2017-03-16 03:51:16 +00:00
float scale = 1 / font->pixelDensity;
2017-02-03 23:16:30 +00:00
2017-02-16 23:42:33 +00:00
int len = strlen(str);
2017-02-12 11:14:10 +00:00
const char* start = str;
2017-02-16 23:42:33 +00:00
const char* end = str + len;
2017-02-08 06:11:20 +00:00
unsigned int previous = '\0';
2017-02-06 09:54:11 +00:00
unsigned int codepoint;
2017-02-16 23:42:33 +00:00
size_t bytes;
2017-02-06 09:54:11 +00:00
2018-08-02 08:21:53 +00:00
float* cursor = vertices.floats;
2018-03-22 05:03:03 +00:00
float* lineStart = vertices.floats;
2017-03-16 03:12:56 +00:00
int lineCount = 1;
2018-03-22 05:03:03 +00:00
*vertexCount = 0;
2017-02-06 09:54:11 +00:00
while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
2017-02-08 06:11:20 +00:00
// Newlines
2017-03-16 03:12:56 +00:00
if (codepoint == '\n' || (wrap && cx * scale > wrap && codepoint == ' ')) {
2018-08-02 08:21:53 +00:00
lineStart = lovrFontAlignLine(lineStart, cursor, cx, halign);
2017-03-16 03:12:56 +00:00
lineCount++;
2017-02-17 00:23:52 +00:00
cx = 0;
2018-01-22 16:28:33 +00:00
cy -= font->rasterizer->height * font->lineHeight;
2017-02-08 06:11:20 +00:00
previous = '\0';
2017-02-06 09:54:11 +00:00
str += bytes;
2017-02-06 04:30:17 +00:00
continue;
}
2017-02-08 06:11:20 +00:00
// Kerning
2017-02-17 00:23:52 +00:00
cx += lovrFontGetKerning(font, previous, codepoint);
2017-02-08 06:11:20 +00:00
previous = codepoint;
2017-02-05 06:21:41 +00:00
2017-02-12 11:14:10 +00:00
// Get glyph
2017-02-08 06:11:20 +00:00
Glyph* glyph = lovrFontGetGlyph(font, codepoint);
2017-02-12 11:14:10 +00:00
// Start over if texture was repacked
if (u != atlas->width || v != atlas->height) {
2018-03-22 05:03:03 +00:00
lovrFontRender(font, start, wrap, halign, valign, vertices, offsety, vertexCount);
2017-02-16 23:42:33 +00:00
return;
2017-02-12 11:14:10 +00:00
}
2017-02-08 06:11:20 +00:00
// Triangles
2017-02-17 00:23:52 +00:00
if (glyph->w > 0 && glyph->h > 0) {
2017-07-14 16:53:05 +00:00
float x1 = cx + glyph->dx - GLYPH_PADDING;
float y1 = cy + glyph->dy + GLYPH_PADDING;
float x2 = x1 + glyph->tw;
float y2 = y1 - glyph->th;
2017-02-17 00:23:52 +00:00
float s1 = glyph->x / u;
2017-07-14 16:53:05 +00:00
float t1 = (glyph->y + glyph->th) / v;
float s2 = (glyph->x + glyph->tw) / u;
2017-07-14 15:18:25 +00:00
float t2 = glyph->y / v;
2017-02-17 00:23:52 +00:00
2018-03-22 05:03:03 +00:00
float quad[48] = {
x1, y1, 0, 0, 0, 0, s1, t1,
x1, y2, 0, 0, 0, 0, s1, t2,
x2, y1, 0, 0, 0, 0, s2, t1,
x2, y1, 0, 0, 0, 0, s2, t1,
x1, y2, 0, 0, 0, 0, s1, t2,
x2, y2, 0, 0, 0, 0, s2, t2
2017-02-05 06:21:41 +00:00
};
2018-08-02 08:21:53 +00:00
memcpy(cursor, quad, 6 * 8 * sizeof(float));
cursor += 48;
2018-03-22 05:03:03 +00:00
*vertexCount += 6;
2017-02-05 06:21:41 +00:00
}
2017-02-08 06:11:20 +00:00
// Advance cursor
2017-02-17 00:23:52 +00:00
cx += glyph->advance;
2017-02-06 09:54:11 +00:00
str += bytes;
2017-02-03 23:16:30 +00:00
}
2017-03-16 03:12:56 +00:00
// Align the last line
2018-08-02 08:21:53 +00:00
lovrFontAlignLine(lineStart, cursor, cx, halign);
2017-03-16 03:12:56 +00:00
// Calculate vertical offset
if (valign == ALIGN_MIDDLE) {
2018-01-22 16:28:33 +00:00
*offsety = lineCount * font->rasterizer->height * font->lineHeight * .5f;
2017-03-16 03:12:56 +00:00
} else if (valign == ALIGN_BOTTOM) {
2018-01-22 16:28:33 +00:00
*offsety = lineCount * font->rasterizer->height * font->lineHeight;
2017-08-09 04:02:28 +00:00
} else {
*offsety = 0;
2017-02-16 23:42:33 +00:00
}
2017-02-03 23:16:30 +00:00
}
2017-03-16 08:12:32 +00:00
float lovrFontGetWidth(Font* font, const char* str, float wrap) {
float width = 0;
float x = 0;
const char* end = str + strlen(str);
size_t bytes;
unsigned int previous = '\0';
unsigned int codepoint;
float scale = 1 / font->pixelDensity;
while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
if (codepoint == '\n' || (wrap && x * scale > wrap && codepoint == ' ')) {
2017-03-26 01:41:25 +00:00
width = MAX(width, x * scale);
2017-03-16 08:12:32 +00:00
x = 0;
previous = '\0';
str += bytes;
continue;
}
Glyph* glyph = lovrFontGetGlyph(font, codepoint);
x += glyph->advance + lovrFontGetKerning(font, previous, codepoint);
previous = codepoint;
str += bytes;
}
2017-03-26 01:41:25 +00:00
return MAX(width, x * scale);
2017-03-16 08:12:32 +00:00
}
float lovrFontGetHeight(Font* font) {
2018-01-22 16:28:33 +00:00
return font->rasterizer->height / font->pixelDensity;
2017-03-16 08:12:32 +00:00
}
float lovrFontGetAscent(Font* font) {
2018-01-22 16:28:33 +00:00
return font->rasterizer->ascent / font->pixelDensity;
2017-03-16 08:12:32 +00:00
}
float lovrFontGetDescent(Font* font) {
2018-01-22 16:28:33 +00:00
return font->rasterizer->descent / font->pixelDensity;
2017-03-16 08:12:32 +00:00
}
float lovrFontGetBaseline(Font* font) {
2018-01-22 16:28:33 +00:00
return font->rasterizer->height * .8 / font->pixelDensity;
2017-03-16 08:12:32 +00:00
}
2017-02-12 11:14:10 +00:00
float lovrFontGetLineHeight(Font* font) {
return font->lineHeight;
}
void lovrFontSetLineHeight(Font* font, float lineHeight) {
font->lineHeight = lineHeight;
}
2017-02-08 06:11:20 +00:00
int lovrFontGetKerning(Font* font, unsigned int left, unsigned int right) {
char key[12];
snprintf(key, 12, "%d,%d", left, right);
int* entry = map_get(&font->kerning, key);
if (entry) {
return *entry;
}
2018-01-22 16:28:33 +00:00
int kerning = lovrRasterizerGetKerning(font->rasterizer, left, right);
2017-02-08 06:11:20 +00:00
map_set(&font->kerning, key, kerning);
return kerning;
}
2017-03-16 03:51:16 +00:00
float lovrFontGetPixelDensity(Font* font) {
return font->pixelDensity;
}
void lovrFontSetPixelDensity(Font* font, float pixelDensity) {
if (pixelDensity <= 0) {
2018-01-22 16:28:33 +00:00
pixelDensity = font->rasterizer->height;
2017-03-16 03:51:16 +00:00
}
font->pixelDensity = pixelDensity;
}
2017-02-06 09:54:11 +00:00
Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
char key[6];
snprintf(key, 6, "%d", codepoint);
2017-02-05 06:21:41 +00:00
FontAtlas* atlas = &font->atlas;
2017-02-19 09:54:58 +00:00
map_glyph_t* glyphs = &atlas->glyphs;
2017-02-05 06:21:41 +00:00
Glyph* glyph = map_get(glyphs, key);
2017-02-03 23:16:30 +00:00
2017-02-06 04:30:17 +00:00
// Add the glyph to the atlas if it isn't there
2017-02-05 06:21:41 +00:00
if (!glyph) {
Glyph g;
2018-01-22 16:28:33 +00:00
lovrRasterizerLoadGlyph(font->rasterizer, codepoint, &g);
2017-02-05 06:21:41 +00:00
map_set(glyphs, key, g);
glyph = map_get(glyphs, key);
2017-02-06 04:30:17 +00:00
lovrFontAddGlyph(font, glyph);
2017-02-05 06:21:41 +00:00
}
2017-02-03 23:16:30 +00:00
2017-02-05 06:21:41 +00:00
return glyph;
}
2017-02-03 23:16:30 +00:00
2017-02-06 04:30:17 +00:00
void lovrFontAddGlyph(Font* font, Glyph* glyph) {
FontAtlas* atlas = &font->atlas;
// Don't waste space on empty glyphs
if (glyph->w == 0 && glyph->h == 0) {
return;
2017-02-05 06:21:41 +00:00
}
2017-02-03 23:16:30 +00:00
2017-02-06 04:30:17 +00:00
// If the glyph does not fit, you must acquit (new row)
2017-07-14 16:53:05 +00:00
if (atlas->x + glyph->tw > atlas->width - 2 * atlas->padding) {
2017-02-06 04:30:17 +00:00
atlas->x = atlas->padding;
atlas->y += atlas->rowHeight + atlas->padding;
atlas->rowHeight = 0;
}
// Expand the texture if needed. Expanding the texture re-adds all the glyphs, so we can return.
2017-07-14 16:53:05 +00:00
if (atlas->y + glyph->th > atlas->height - 2 * atlas->padding) {
2017-02-06 04:30:17 +00:00
lovrFontExpandTexture(font);
return;
}
// Keep track of glyph's position in the atlas
glyph->x = atlas->x;
glyph->y = atlas->y;
// Paste glyph into texture
2018-07-23 02:42:39 +00:00
lovrTextureReplacePixels(font->texture, glyph->data, atlas->x, atlas->y, 0, 0);
2017-02-06 04:30:17 +00:00
// Advance atlas cursor
2017-07-14 16:53:05 +00:00
atlas->x += glyph->tw + atlas->padding;
atlas->rowHeight = MAX(atlas->rowHeight, glyph->th);
2017-02-06 04:30:17 +00:00
}
void lovrFontExpandTexture(Font* font) {
2017-02-05 06:21:41 +00:00
FontAtlas* atlas = &font->atlas;
2017-02-06 04:30:17 +00:00
2017-02-12 11:14:10 +00:00
if (atlas->width == atlas->height) {
atlas->width *= 2;
} else {
atlas->height *= 2;
}
if (!font->texture) {
return;
}
// Recreate the texture
lovrFontCreateTexture(font);
2017-02-06 04:30:17 +00:00
// Reset the cursor
atlas->x = atlas->padding;
atlas->y = atlas->padding;
atlas->rowHeight = 0;
// Re-pack all the glyphs
const char* key;
map_iter_t iter = map_iter(&atlas->glyphs);
while ((key = map_next(&atlas->glyphs, &iter)) != NULL) {
Glyph* glyph = map_get(&atlas->glyphs, key);
lovrFontAddGlyph(font, glyph);
}
2017-02-03 23:16:30 +00:00
}
// TODO we only need the TextureData here to clear the texture, but it's a big waste of memory.
// Could look into using glClearTexImage when supported to make this more efficient.
void lovrFontCreateTexture(Font* font) {
2018-02-26 08:59:03 +00:00
lovrRelease(font->texture);
2018-08-21 01:14:33 +00:00
TextureData* textureData = lovrTextureDataCreate(font->atlas.width, font->atlas.height, 0x0, FORMAT_RGB);
font->texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, false, false, 0);
2018-08-02 10:22:27 +00:00
lovrTextureSetFilter(font->texture, (TextureFilter) { .mode = FILTER_BILINEAR });
lovrTextureSetWrap(font->texture, (TextureWrap) { .s = WRAP_CLAMP, .t = WRAP_CLAMP });
lovrRelease(textureData);
}