#include "twn_rendering_c.h" #include "twn_rendering.h" #include "twn_util.h" #include "twn_config.h" #include "twn_engine_context_c.h" #include #define ASCII_START 32 #define ASCII_END 128 #define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1) static FontData *text_load_font_data(const char *path, int height_px) { FontData *font_data = ccalloc(1, sizeof *font_data); font_data->file_path = path; font_data->height_px = height_px; unsigned char* bitmap = ccalloc(TEXT_FONT_TEXTURE_SIZE * TEXT_FONT_TEXTURE_SIZE, 1); { unsigned char *buf = NULL; int64_t buf_len = file_to_bytes(path, &buf); stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0)); /* might as well get these now, for later */ font_data->file_bytes = buf; font_data->file_bytes_len = buf_len; font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px); stbtt_GetFontVMetrics( &font_data->info, &font_data->ascent, &font_data->descent, &font_data->line_gap ); font_data->ascent = (int)((float)font_data->ascent * font_data->scale_factor); font_data->descent = (int)((float)font_data->descent * font_data->scale_factor); font_data->line_gap = (int)((float)font_data->line_gap * font_data->scale_factor); stbtt_pack_context pctx; stbtt_PackBegin(&pctx, bitmap, TEXT_FONT_TEXTURE_SIZE, TEXT_FONT_TEXTURE_SIZE, 0, 1, NULL); stbtt_PackSetOversampling(&pctx, TEXT_FONT_OVERSAMPLING, TEXT_FONT_OVERSAMPLING); stbtt_PackFontRange(&pctx, buf, 0, (float)height_px, ASCII_START, NUM_DISPLAY_ASCII, font_data->char_data); stbtt_PackEnd(&pctx); } font_data->texture = create_gpu_texture(TEXT_FONT_FILTERING, true); upload_gpu_texture( font_data->texture, bitmap, 1, TEXT_FONT_TEXTURE_SIZE, TEXT_FONT_TEXTURE_SIZE ); SDL_free(bitmap); return font_data; } static void text_destroy_font_data(FontData *font_data) { SDL_free(font_data->file_bytes); delete_gpu_texture(font_data->texture); SDL_free(font_data); } static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) { VertexBuffer vertex_array = 0; if (vertex_array == 0) vertex_array = create_vertex_buffer(); const size_t len = SDL_strlen(text); VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_text_payload_size() * len); for (size_t i = 0; i < len; ++i) { const char c = text[i]; /* outside the range of what we want to display */ //if (c < ASCII_START || c > ASCII_END) if (c < ASCII_START) continue; /* stb_truetype.h conveniently provides everything we need to draw here! */ stbtt_aligned_quad quad; stbtt_GetPackedQuad( font_data->char_data, TEXT_FONT_TEXTURE_SIZE, TEXT_FONT_TEXTURE_SIZE, c - ASCII_START, &position.x, &position.y, &quad, true ); /* have to do this so the "origin" is at the top left */ /* maybe there's a better way, or maybe this isn't a good idea... */ /* who knows */ quad.y0 += (float)font_data->ascent; quad.y1 += (float)font_data->ascent; push_text_payload_to_vertex_buffer_builder(font_data, &payload, quad); } finally_draw_text(font_data, len, color, vertex_array); } static void ensure_font_cache(const char *font_path, int height_px) { /* HACK: stupid, bad, don't do this */ bool is_cached = false; for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { FontData *font_data = ctx.text_cache.data[i]; if ((SDL_strcmp(font_path, font_data->file_path) == 0) && height_px == font_data->height_px) { is_cached = true; break; } } if (!is_cached) { FontData *new_font_data = text_load_font_data(font_path, height_px); arrput(ctx.text_cache.data, new_font_data); } } static FontData *get_font_data(const char *font_path, int height_px) { FontData *font_data = NULL; for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { font_data = ctx.text_cache.data[i]; if ((SDL_strcmp(font_path, font_data->file_path) == 0) && height_px == font_data->height_px) { break; } } return font_data; } void render_text(const TextPrimitive *text) { FontData *font_data = get_font_data(text->font, text->height_px); text_draw_with(font_data, text->text, text->position, text->color); } void text_cache_init(TextCache *cache) { arrsetlen(cache->data, 0); } void text_cache_deinit(TextCache *cache) { for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { text_destroy_font_data(ctx.text_cache.data[i]); } arrfree(cache->data); } void push_text(char *string, Vec2 position, int height_px, Color color, const char *font_path) { ensure_font_cache(font_path, height_px); /* the original string might not be around by the time it's used, so copy it */ /* TODO: arena */ char *dup_string = SDL_strdup(string); TextPrimitive text = { .color = color, .position = position, .text = dup_string, .font = font_path, .height_px = height_px, }; Primitive2D primitive = { .type = PRIMITIVE_2D_TEXT, .text = text, }; arrput(ctx.render_queue_2d, primitive); } int text_get_width(char *string, int height_px, const char *font_path) { ensure_font_cache(font_path, height_px); FontData *font_data = get_font_data(font_path, height_px); int length = 0; for (const char *p = string; *p != '\0'; ++p) { const char c = *p; int advance_width = 0; int left_side_bearing = 0; stbtt_GetCodepointHMetrics(&font_data->info, (int)c, &advance_width, &left_side_bearing); length += advance_width; } return (int)((float)length * font_data->scale_factor); }