#include "text.h" #include #include #include "atlas.h" #include "cglm/types.h" #include "cglm/vec2.h" #include "freetype/freetype.h" #include "freetype/fttypes.h" #include "geometry.h" #include "hash.h" #include "shaders.h" #include "utils.h" // Linear filtering for smooth glyph rendering, clamped UVs to prevent atlas bleeding static void wayc_font_context_sampler_desc(struct sg_sampler_desc* desc) { wayc_notnull(desc); desc->min_filter = SG_FILTER_LINEAR; desc->mag_filter = SG_FILTER_LINEAR; desc->wrap_u = SG_WRAP_CLAMP_TO_EDGE; desc->wrap_v = SG_WRAP_CLAMP_TO_EDGE; } static void wayc_font_context_layout(struct sg_vertex_layout_state* layout) { wayc_notnull(layout); layout->buffers[0].stride = sizeof(text_vertex_s); layout->attrs[ATTR_text_in_position].format = SG_VERTEXFORMAT_FLOAT2; layout->attrs[ATTR_text_in_position].buffer_index = 0; layout->attrs[ATTR_text_in_position].offset = offsetof(text_vertex_s, pos); layout->attrs[ATTR_text_in_uv].format = SG_VERTEXFORMAT_FLOAT2; layout->attrs[ATTR_text_in_uv].buffer_index = 0; layout->attrs[ATTR_text_in_uv].offset = offsetof(text_vertex_s, uv); } // Standard alpha blending for transparent text rendering static void wayc_font_context_color(struct sg_color_target_state* color) { wayc_notnull(color); struct sg_blend_state blend = {}; blend.enabled = true; blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; blend.src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA; blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; color->blend = blend; } static void font_context_pipeline_desc(struct sg_pipeline_desc* desc, struct sg_shader shader) { wayc_notnull(desc); struct sg_vertex_layout_state vertex_layout = {}; wayc_font_context_layout(&vertex_layout); struct sg_color_target_state color = {}; wayc_font_context_color(&color); desc->layout = vertex_layout; desc->shader = shader; desc->index_type = SG_INDEXTYPE_UINT16; desc->colors[0] = color; desc->color_count = 1; } static void wayc_text_quad(struct text_vertex_s* verts, u16* indices, u16 base, const struct glyph_s* glyph, vec2 cursor) { f32 x0 = WAYC_X(cursor) + WAYC_X(glyph->bearing); f32 y0 = WAYC_Y(cursor) - WAYC_Y(glyph->bearing); f32 x1 = x0 + WAYC_X(glyph->size); f32 y1 = y0 + WAYC_Y(glyph->size); struct quad_s quad; wayc_quad_init(&quad, x0, y0, x1, y1); vec2 uvs[WAYC_QUAD_NVERTS] = { {WAYC_X(glyph->uv0), WAYC_Y(glyph->uv0)}, {WAYC_X(glyph->uv1), WAYC_Y(glyph->uv0)}, {WAYC_X(glyph->uv1), WAYC_Y(glyph->uv1)}, {WAYC_X(glyph->uv0), WAYC_Y(glyph->uv1)}, }; for (usize j = 0; j < WAYC_QUAD_NVERTS; j++) { glm_vec2_copy(quad.vertices[j], verts[base + j].pos); glm_vec2_copy(uvs[j], verts[base + j].uv); } for (usize j = 0; j < WAYC_QUAD_NINDICES; j++) indices[j] = (u16)(base + quad.indices[j]); } enum font_context_error_e wayc_font_context_init( struct font_context_s* context) { wayc_notnull(context); memset(context, 0, sizeof(*context)); bool success = false; FT_Library ft; FT_Error fterr = FT_Init_FreeType(&ft); if (fterr) return FONT_CONTEXT_ERROR_LOAD_LIBRARY; wayc_defer_cond(FT_Done_FreeType(ft), success, true); struct sg_shader shader = sg_make_shader(text_shader_desc(sg_query_backend())); if (sg_query_shader_state(shader) != SG_RESOURCESTATE_VALID) return FONT_CONTEXT_ERROR_CREATE_SHADER; wayc_defer_cond(sg_destroy_shader(shader), success, true); struct sg_sampler_desc sampler_desc = {}; wayc_font_context_sampler_desc(&sampler_desc); struct sg_pipeline_desc pipeline_desc = {}; font_context_pipeline_desc(&pipeline_desc, shader); struct sg_sampler sampler = sg_make_sampler(&sampler_desc); if (sg_query_sampler_state(sampler) != SG_RESOURCESTATE_VALID) return FONT_CONTEXT_ERROR_CREATE_SAMPLER; wayc_defer_cond(sg_destroy_sampler(sampler), success, true); struct sg_pipeline pipeline = sg_make_pipeline(&pipeline_desc); if (sg_query_pipeline_state(pipeline) != SG_RESOURCESTATE_VALID) return FONT_CONTEXT_ERROR_CREATE_PIPELINE; context->ft = ft; context->sampler = sampler; context->pipeline = pipeline; success = true; return FONT_CONTEXT_ERROR_NONE; } void wayc_font_context_deinit(struct font_context_s* context) { wayc_notnull(context); sg_destroy_pipeline(context->pipeline); sg_destroy_sampler(context->sampler); FT_Done_FreeType(context->ft); } enum font_error_e wayc_font_init(struct font_s* font, struct font_context_s* context, const char* path, u32 font_size, u32 atlas_width, u32 atlas_height) { wayc_notnull(font); wayc_notnull(context); memset(font, 0, sizeof(*font)); bool success = false; FT_Face face; FT_Error fterr = FT_New_Face(context->ft, path, 0, &face); if (fterr) return FONT_ERROR_LOAD_FACE; wayc_defer_cond(FT_Done_Face(face), success, true); fterr = FT_Set_Pixel_Sizes(face, 0, font_size); if (fterr) return FONT_ERROR_LOAD_FACE; struct atlas_s atlas; enum atlas_error_e aterr = wayc_atlas_init(&atlas, atlas_width, atlas_height, SG_PIXELFORMAT_R8); if (aterr) return FONT_ERROR_ATLAS_FAILED; font->context = context; font->face = face; font->atlas = atlas; wayc_hashmap_init(&font->cached); success = true; return FONT_ERROR_NONE; } enum font_error_e wayc_font_raster(struct font_s* font, codepoint_t codepoint, struct glyph_s* glyph) { wayc_notnull(font); wayc_notnull(glyph); // Return cached glyph if already rasterized to avoid redundant FreeType calls struct glyph_s got; if (wayc_hashmap_get(&font->cached, &codepoint, &got)) { *glyph = got; return FONT_ERROR_NONE; } u32 index = FT_Get_Char_Index(font->face, codepoint); if (index == 0) return FONT_ERROR_LOAD_GLYPH; FT_Error fterr = FT_Load_Glyph(font->face, index, FT_LOAD_RENDER); if (fterr) return FONT_ERROR_LOAD_GLYPH; FT_GlyphSlot slot = font->face->glyph; FT_Bitmap bitmap = slot->bitmap; // Pack glyph bitmap into atlas texture and get UV coordinates vec2 uv0, uv1; enum atlas_error_e aterr = wayc_atlas_insert( &font->atlas, bitmap.width, bitmap.rows, bitmap.buffer, &uv0, &uv1); if (aterr) return FONT_ERROR_ATLAS_FAILED; vec2 bearing = {(f32)slot->bitmap_left, (f32)slot->bitmap_top}; vec2 size = {(f32)bitmap.width, (f32)bitmap.rows}; f32 advance = (f32)slot->advance.x / WAYC_SCALE; glm_vec2_copy(uv0, got.uv0); glm_vec2_copy(uv1, got.uv1); glm_vec2_copy(size, got.size); glm_vec2_copy(bearing, got.bearing); got.advance = advance; wayc_hashmap_insert(&font->cached, &codepoint, &got); *glyph = got; return FONT_ERROR_NONE; } enum font_error_e wayc_font_raster_text(struct font_s* font, const char* text, usize text_len, struct glyph_s* glyphs, usize max_glyphs) { wayc_notnull(font); wayc_notnull(text); wayc_notnull(glyphs); memset(glyphs, 0, sizeof(*glyphs) * max_glyphs); usize count = 0; for (usize i = 0; i < text_len && count < max_glyphs; i++) { codepoint_t codepoint = (codepoint_t)text[i]; // TODO: handle UTF-8 struct glyph_s glyph; enum font_error_e err = wayc_font_raster(font, codepoint, &glyph); if (err != FONT_ERROR_NONE) return err; glyphs[count++] = glyph; } return FONT_ERROR_NONE; } void wayc_font_deinit(struct font_s* font) { wayc_notnull(font); wayc_hashmap_deinit(&font->cached); wayc_atlas_deinit(&font->atlas); FT_Done_Face(font->face); } bool wayc_text_mesh_init(struct text_mesh_s* mesh, const char* text, usize text_len, struct font_s* font) { wayc_notnull(mesh); wayc_notnull(text); wayc_notnull(font); memset(mesh, 0, sizeof(*mesh)); bool success = false; usize nverts = text_len * WAYC_QUAD_NVERTS; usize nindices = text_len * WAYC_QUAD_NINDICES; usize bytes_verts = nverts * sizeof(struct text_vertex_s); usize bytes_indices = nindices * sizeof(u16); usize bytes_glyphs = text_len * sizeof(struct glyph_s); struct text_vertex_s* verts = (struct text_vertex_s*)mi_malloc(bytes_verts); wayc_defer_cond(mi_free(verts), success, true); u16* indices = (u16*)mi_malloc(bytes_indices); wayc_defer_cond(mi_free(indices), success, true); // Temporary storage for glyph metrics, freed after mesh generation struct glyph_s* glyphs = (struct glyph_s*)mi_malloc(bytes_glyphs); wayc_defer(mi_free(glyphs)); enum font_error_e err = wayc_font_raster_text(font, text, text_len, glyphs, text_len); if (err != FONT_ERROR_NONE) return false; // Build quads for each character, advancing cursor by glyph width vec2 cursor = {0.0f, 0.0f}; for (usize i = 0; i < text_len; i++) { u16 base = (u16)(i * WAYC_QUAD_NVERTS); wayc_text_quad(verts, &indices[i * WAYC_QUAD_NINDICES], base, &glyphs[i], cursor); WAYC_X(cursor) += glyphs[i].advance; } mesh->vertices = verts; mesh->vertex_count = nverts; mesh->indices = indices; mesh->index_count = nindices; success = true; return true; } void wayc_text_mesh_deinit(struct text_mesh_s* mesh) { wayc_notnull(mesh); if (mesh->vertices == nullptr || mesh->indices == nullptr) return; mi_free(mesh->vertices); mi_free(mesh->indices); mesh->vertices = nullptr; mesh->indices = nullptr; }