#include "TextRenderer.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef BLACK #undef WHITE #undef RED #undef GREEN #undef BLUE #undef YELLOW #undef MAGENTA #include #include FT_FREETYPE_H #include FT_GLYPH_H #include #include #include #include namespace { constexpr int ATLAS_DIMENSION = 1024; constexpr int ATLAS_PADDING = 2; constexpr float DEFAULT_EM_SCALE = 48.0f; constexpr float hb_to_em(hb_position_t value, unsigned upem) { return static_cast(value) / (64.0f * static_cast(upem ? upem : 1)); } auto ft_library() -> FT_Library { static FT_Library library = nullptr; static std::once_flag once; std::call_once(once, [] { if (FT_Init_FreeType(&library) != 0) library = nullptr; else std::atexit([] { if (library) FT_Done_FreeType(library); }); }); return library; } struct CodepointSpan { uint32_t codepoint {}; usize start {}; usize end {}; }; auto decode_utf8(std::string_view text) -> std::vector { std::vector spans; usize i = 0; while (i < text.size()) { u8 const byte = static_cast(text[i]); usize const start = i; usize length = 1; uint32_t cp = 0xFFFD; if (byte < 0x80) { cp = byte; } else if ((byte & 0xE0) == 0xC0) { if (i + 1 < text.size()) { u8 const b1 = static_cast(text[i + 1]); if ((b1 & 0xC0) == 0x80) { uint32_t t = ((byte & 0x1F) << 6) | (static_cast(b1) & 0x3F); if (t >= 0x80) { cp = t; length = 2; } } } } else if ((byte & 0xF0) == 0xE0) { if (i + 2 < text.size()) { u8 const b1 = static_cast(text[i + 1]); u8 const b2 = static_cast(text[i + 2]); if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80) { uint32_t t = ((byte & 0x0F) << 12) | ((static_cast(b1) & 0x3F) << 6) | (static_cast(b2) & 0x3F); if (t >= 0x800 && (t < 0xD800 || t > 0xDFFF)) { cp = t; length = 3; } } } } else if ((byte & 0xF8) == 0xF0) { if (i + 3 < text.size()) { u8 const b1 = static_cast(text[i + 1]); u8 const b2 = static_cast(text[i + 2]); u8 const b3 = static_cast(text[i + 3]); if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80) { uint32_t t = ((byte & 0x07) << 18) | ((static_cast(b1) & 0x3F) << 12) | ((static_cast(b2) & 0x3F) << 6) | (static_cast(b3) & 0x3F); if (t >= 0x10000 && t <= 0x10FFFF) { cp = t; length = 4; } } } } spans.push_back(CodepointSpan { .codepoint = cp, .start = start, .end = std::min(text.size(), start + length), }); i += length; } return spans; } } // namespace auto TextRenderer::flush_font(FontRuntime &rt, FontData &fd) -> void { rt.glyph_cache.clear(); fd.glyphs.clear(); rt.pen_x = ATLAS_PADDING; rt.pen_y = ATLAS_PADDING; rt.row_height = 0; if (fd.atlas_img.data) ImageClearBackground(&fd.atlas_img, BLANK); if (fd.atlas.id != 0 && fd.atlas_img.data) UpdateTexture(fd.atlas, fd.atlas_img.data); } auto TextRenderer::allocate_region(FontRuntime &rt, FontData &fd, int width, int height) -> std::optional> { (void)fd; int padded_w = width + ATLAS_PADDING; if (padded_w > rt.atlas_width || height + ATLAS_PADDING > rt.atlas_height) return std::nullopt; if (rt.pen_x + padded_w > rt.atlas_width) { rt.pen_x = ATLAS_PADDING; rt.pen_y += rt.row_height; rt.row_height = 0; } if (rt.pen_y + height + ATLAS_PADDING > rt.atlas_height) return std::nullopt; int x = rt.pen_x; int y = rt.pen_y; rt.pen_x += padded_w; rt.row_height = std::max(rt.row_height, height + ATLAS_PADDING); return std::pair { x, y }; } auto TextRenderer::upload_region(FontData &fd, int dst_x, int dst_y, int width, int height, std::vector const &buffer) -> void { Rectangle rec { static_cast(dst_x), static_cast(dst_y), static_cast(width), static_cast(height) }; if (fd.atlas.id != 0) UpdateTextureRec(fd.atlas, rec, buffer.data()); if (!fd.atlas_img.data) return; auto *pixels = static_cast(fd.atlas_img.data); for (int row = 0; row < height; ++row) { auto *dst = pixels + (dst_y + row) * fd.atlas_img.width + dst_x; std::memcpy(dst, buffer.data() + row * width, sizeof(Color) * width); } } auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index) -> std::optional { auto const gen_start = std::chrono::steady_clock::now(); msdfgen::Shape shape; double advance_em = 0.0; msdfgen::GlyphIndex const index(glyph_index); if (!rt.msdf_font || !msdfgen::loadGlyph(shape, rt.msdf_font, index, msdfgen::FONT_SCALING_EM_NORMALIZED, &advance_em)) return std::nullopt; shape.normalize(); // FIXME: Figure out shader // msdfgen::edgeColoringInkTrap(shape, 3.0); auto bounds = shape.getBounds(); float const width_em = static_cast(bounds.r - bounds.l); float const height_em = static_cast(bounds.t - bounds.b); double const scale = rt.em_scale; int bmp_w = std::max( 1, static_cast(std::ceil(width_em * scale + 2.0 * rt.px_range))); int bmp_h = std::max( 1, static_cast(std::ceil(height_em * scale + 2.0 * rt.px_range))); if (bmp_w + ATLAS_PADDING > rt.atlas_width || bmp_h + ATLAS_PADDING > rt.atlas_height) { TraceLog(LOG_WARNING, "Glyph %u bitmap %dx%d exceeds atlas %dx%d", glyph_index, bmp_w, bmp_h, rt.atlas_width, rt.atlas_height); GlyphCacheEntry too_large {}; too_large.width = 0; too_large.height = 0; return too_large; } auto place = allocate_region(rt, fd, bmp_w, bmp_h); if (!place) { TraceLog(LOG_INFO, "Atlas full, flushing before glyph %u", glyph_index); flush_font(rt, fd); place = allocate_region(rt, fd, bmp_w, bmp_h); if (!place) return std::nullopt; } msdfgen::Bitmap msdf_bitmap(bmp_w, bmp_h); msdfgen::Vector2 scale_vec(scale, scale); double const inv_scale = 1.0 / scale; msdfgen::Vector2 translate(-bounds.l + rt.px_range * inv_scale, -bounds.b + rt.px_range * inv_scale); msdfgen::generateMSDF( msdf_bitmap, shape, rt.px_range, scale_vec, translate); std::vector buffer(static_cast(bmp_w) * bmp_h); // FIXME: Figure out shader // for (int y = 0; y < bmp_h; ++y) { // int const dst_y = bmp_h - 1 - y; // for (int x = 0; x < bmp_w; ++x) { // float const *px = msdf_bitmap(x, y); // auto const r = msdfgen::pixelFloatToByte(px[0]); // auto const g = msdfgen::pixelFloatToByte(px[1]); // auto const b = msdfgen::pixelFloatToByte(px[2]); // buffer[static_cast(dst_y) * bmp_w + x] // = Color { r, g, b, 255 }; // } //} auto c1 { (int)std::round(msdf_bitmap(0, 0)[3]) }; auto c4 { (int)std::round(msdf_bitmap(bmp_w - 1, bmp_h - 1)[3]) }; auto sum_white = 0; auto sum_black = 0; for (int y = 0; y < bmp_h; ++y) { for (int x = 0; x < bmp_w; ++x) { float const *px = msdf_bitmap(x, y); auto const r = msdfgen::pixelFloatToByte(px[0]); if (r > 127) { sum_white++; } else { sum_black++; } } } bool flip { sum_white > sum_black && (float)bmp_w / (float)bmp_h > 0.6 }; if (c1 == c4) { flip = false; } // This really isn't the most accurate thing in the world but should work // for now. Things like commas might be fucked. for (int y = 0; y < bmp_h; ++y) { int const dst_y = bmp_h - 1 - y; for (int x = 0; x < bmp_w; ++x) { float const *px = msdf_bitmap(x, y); auto const r = msdfgen::pixelFloatToByte(px[0]); if (flip) { buffer[static_cast(dst_y) * bmp_w + x] = Color { 255, 255, 255, static_cast(255 - r) }; } else { buffer[static_cast(dst_y) * bmp_w + x] = Color { 255, 255, 255, r }; } } } upload_region(fd, place->first, place->second, bmp_w, bmp_h, buffer); GlyphCacheEntry entry; entry.atlas_x = place->first; entry.atlas_y = place->second; entry.width = bmp_w; entry.height = bmp_h; entry.glyph.advance = static_cast(advance_em); entry.glyph.plane_bounds.left = static_cast(bounds.l); entry.glyph.plane_bounds.right = static_cast(bounds.r); entry.glyph.plane_bounds.top = static_cast(bounds.t); entry.glyph.plane_bounds.bottom = static_cast(bounds.b); entry.glyph.glyph_bounds.left = static_cast(entry.atlas_x); entry.glyph.glyph_bounds.top = static_cast(entry.atlas_y); entry.glyph.glyph_bounds.right = static_cast(entry.atlas_x + entry.width); entry.glyph.glyph_bounds.bottom = static_cast(entry.atlas_y + entry.height); auto const gen_end = std::chrono::steady_clock::now(); auto const gen_ms = std::chrono::duration(gen_end - gen_start) .count(); if (gen_ms > 2.0) TraceLog(LOG_INFO, "Generated glyph %u in %.2f ms (%dx%d texels)", glyph_index, gen_ms, entry.width, entry.height); return entry; } auto TextRenderer::ensure_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index, bool mark_usage) -> GlyphCacheEntry * { auto it = rt.glyph_cache.find(glyph_index); if (it != rt.glyph_cache.end()) { if (mark_usage) it->second.stamp = rt.frame_stamp; return &it->second; } auto entry = generate_glyph(rt, fd, glyph_index); if (!entry) return nullptr; auto [inserted_it, ok] = rt.glyph_cache.emplace(glyph_index, std::move(*entry)); if (!ok) return nullptr; inserted_it->second.stamp = mark_usage ? rt.frame_stamp : inserted_it->second.stamp; fd.glyphs[glyph_index] = inserted_it->second.glyph; return &inserted_it->second; } TextRenderer::TextRenderer() { static char const msdf_vs_data[] { #embed "base.vert" , 0 // cppcheck-suppress syntaxError }; static char const msdf_fs_data[] { #embed "msdf.frag" , 0 // cppcheck-suppress syntaxError }; m_msdf_shader = LoadShaderFromMemory(msdf_vs_data, msdf_fs_data); assert(IsShaderValid(m_msdf_shader)); m_px_range_uniform = GetShaderLocation(m_msdf_shader, "pxRange"); } TextRenderer::~TextRenderer() { for (usize i = 0; i < m_font_sets.size(); ++i) { FontHandle handle; handle.id = i; unload_font(handle); } // Not unloading the shader... I have no clue why, but there's some sort of // double free. I love C interop!!!! } auto TextRenderer::measure_text(FontHandle const font, std::string_view const text, int const size) -> Vector2 { usize const handle_id = font(); if (handle_id >= m_font_sets.size()) return Vector2 { 0.0f, 0.0f }; auto const &font_set = m_font_sets[handle_id]; if (font_set.font_indices.empty()) return Vector2 { 0.0f, 0.0f }; auto placements = shape_text(font, text); auto primary_runtime_index = font_set.font_indices.front(); if (placements.empty()) { if (primary_runtime_index >= m_font_runtime.size() || !m_font_runtime[primary_runtime_index]) return Vector2 { 0.0f, 0.0f }; auto const &rt_primary = *m_font_runtime[primary_runtime_index]; float height_em = rt_primary.ascent - rt_primary.descent; return Vector2 { 0.0f, height_em * static_cast(size) }; } float advance_em = 0.0f; float min_x_em = 0.0f; float max_x_em = 0.0f; bool first = true; bool have_metrics = false; float max_ascent = 0.0f; float min_descent = 0.0f; for (auto const &placement : placements) { usize const runtime_index = placement.runtime_index; if (runtime_index >= m_font_runtime.size() || !m_font_runtime[runtime_index]) continue; auto &rt = *m_font_runtime[runtime_index]; auto &fd = m_font_data[runtime_index]; auto *entry = ensure_glyph(rt, fd, placement.glyph_index, false); if (!entry || entry->width == 0 || entry->height == 0) continue; float const x_offset_em = hb_to_em(placement.x_offset, rt.units_per_em); float const left = advance_em + x_offset_em + entry->glyph.plane_bounds.left; float const right = advance_em + x_offset_em + entry->glyph.plane_bounds.right; if (first) { min_x_em = left; max_x_em = right; first = false; } else { min_x_em = std::min(min_x_em, left); max_x_em = std::max(max_x_em, right); } if (!have_metrics) { max_ascent = rt.ascent; min_descent = rt.descent; have_metrics = true; } else { max_ascent = std::max(max_ascent, rt.ascent); min_descent = std::min(min_descent, rt.descent); } advance_em += hb_to_em(placement.x_advance, rt.units_per_em); } if (first) { if (primary_runtime_index >= m_font_runtime.size() || !m_font_runtime[primary_runtime_index]) return Vector2 { 0.0f, 0.0f }; auto const &rt = *m_font_runtime[primary_runtime_index]; float height_em = rt.ascent - rt.descent; return Vector2 { 0.0f, height_em * static_cast(size) }; } float width_em = std::max(max_x_em, advance_em) - min_x_em; float height_em = 0.0f; if (have_metrics) { height_em = max_ascent - min_descent; } else if (primary_runtime_index < m_font_runtime.size() && m_font_runtime[primary_runtime_index]) { auto const &rt = *m_font_runtime[primary_runtime_index]; height_em = rt.ascent - rt.descent; } return Vector2 { width_em * static_cast(size), height_em * static_cast(size) }; } auto TextRenderer::draw_text(FontHandle const font, std::string_view const text, Vector2 const pos, int const size, Color const color) -> void { auto const draw_start = std::chrono::steady_clock::now(); int const pos_x = pos.x; int const pos_y = pos.y; usize const handle_id = font(); if (handle_id >= m_font_sets.size()) return; auto const &font_set = m_font_sets[handle_id]; if (font_set.font_indices.empty()) return; auto placements = shape_text(font, text); if (placements.empty()) return; float const size_f = static_cast(size); float pen_x_em = 0.0f; float pen_y_em = 0.0f; std::vector updated_stamp; updated_stamp.reserve(font_set.font_indices.size()); for (auto const &placement : placements) { usize const runtime_index = placement.runtime_index; if (runtime_index >= m_font_runtime.size() || !m_font_runtime[runtime_index]) continue; auto &rt = *m_font_runtime[runtime_index]; auto &fd = m_font_data[runtime_index]; if (std::find(updated_stamp.begin(), updated_stamp.end(), runtime_index) == updated_stamp.end()) { rt.frame_stamp++; updated_stamp.push_back(runtime_index); } auto *entry = ensure_glyph(rt, fd, placement.glyph_index, true); if (!entry || entry->width == 0 || entry->height == 0) continue; float const advance_em = hb_to_em(placement.x_advance, rt.units_per_em); float const x_offset_em = hb_to_em(placement.x_offset, rt.units_per_em); float const y_offset_em = hb_to_em(placement.y_offset, rt.units_per_em); float const x_base_em = pen_x_em + x_offset_em; float const y_base_em = pen_y_em + y_offset_em; float const scale_px = size_f / static_cast(rt.em_scale); float const margin_px = static_cast(rt.px_range) * scale_px; float const dest_x = pos_x + (x_base_em + entry->glyph.plane_bounds.left) * size_f - margin_px; float const dest_y = pos_y - (y_base_em + entry->glyph.plane_bounds.top) * size_f - margin_px; float const dest_w = static_cast(entry->width) * scale_px; float const dest_h = static_cast(entry->height) * scale_px; Rectangle source { entry->glyph.glyph_bounds.left, entry->glyph.glyph_bounds.top, static_cast(entry->width), static_cast(entry->height), }; Rectangle dest { dest_x, dest_y, dest_w, dest_h }; DrawTexturePro( fd.atlas, source, dest, Vector2 { 0.0f, 0.0f }, 0.0f, color); pen_x_em += advance_em; pen_y_em += hb_to_em(placement.y_advance, rt.units_per_em); } auto const draw_end = std::chrono::steady_clock::now(); auto const draw_ms = std::chrono::duration(draw_end - draw_start) .count(); if (draw_ms > 5.0) TraceLog(LOG_INFO, "draw_text took %.2f ms for %zu glyphs", draw_ms, placements.size()); } auto TextRenderer::load_single_font(std::filesystem::path const &path) -> std::optional { FT_Library const ft = ft_library(); if (!ft) return std::nullopt; FT_Face face = nullptr; if (FT_New_Face(ft, path.string().c_str(), 0, &face) != 0) return std::nullopt; if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) { FT_Done_Face(face); return std::nullopt; } auto runtime = std::make_unique(); runtime->face = face; runtime->atlas_width = ATLAS_DIMENSION; runtime->atlas_height = ATLAS_DIMENSION; runtime->pen_x = ATLAS_PADDING; runtime->pen_y = ATLAS_PADDING; runtime->row_height = 0; runtime->px_range = 0.05; // kDefaultPxRange; runtime->em_scale = DEFAULT_EM_SCALE; runtime->frame_stamp = 0; runtime->units_per_em = static_cast(face->units_per_EM ? face->units_per_EM : 2048); runtime->ascent = static_cast(face->ascender) / (64.0f * static_cast(runtime->units_per_em)); runtime->descent = static_cast(face->descender) / (64.0f * static_cast(runtime->units_per_em)); float line_height = static_cast(face->height) / (64.0f * static_cast(runtime->units_per_em)); float adv_height = runtime->ascent - runtime->descent; runtime->line_gap = std::max(0.0f, line_height - adv_height); runtime->hb_face = hb_ft_face_create_referenced(face); if (!runtime->hb_face) { FT_Done_Face(face); return std::nullopt; } runtime->hb_font = hb_ft_font_create_referenced(face); if (!runtime->hb_font) { hb_face_destroy(runtime->hb_face); FT_Done_Face(face); return std::nullopt; } hb_font_set_scale(runtime->hb_font, static_cast(runtime->units_per_em) << 6, static_cast(runtime->units_per_em) << 6); hb_ft_font_set_funcs(runtime->hb_font); runtime->msdf_font = msdfgen::adoptFreetypeFont(face); if (!runtime->msdf_font) { hb_font_destroy(runtime->hb_font); hb_face_destroy(runtime->hb_face); FT_Done_Face(face); return std::nullopt; } FontData font_data {}; font_data.font_path = path; font_data.atlas_img = GenImageColor(runtime->atlas_width, runtime->atlas_height, BLANK); if (!font_data.atlas_img.data) { msdfgen::destroyFont(runtime->msdf_font); runtime->msdf_font = nullptr; hb_font_destroy(runtime->hb_font); hb_face_destroy(runtime->hb_face); return std::nullopt; } font_data.atlas = LoadTextureFromImage(font_data.atlas_img); if (font_data.atlas.id == 0) { UnloadImage(font_data.atlas_img); msdfgen::destroyFont(runtime->msdf_font); runtime->msdf_font = nullptr; hb_font_destroy(runtime->hb_font); hb_face_destroy(runtime->hb_face); return std::nullopt; } SetTextureFilter(font_data.atlas, TEXTURE_FILTER_BILINEAR); SetTextureWrap(font_data.atlas, TEXTURE_WRAP_CLAMP); flush_font(*runtime, font_data); m_font_data.emplace_back(std::move(font_data)); m_font_runtime.emplace_back(std::move(runtime)); return m_font_data.size() - 1; } auto TextRenderer::load_font(std::filesystem::path const &path, std::span fallback_fonts) -> std::optional { auto primary_index = load_single_font(path); if (!primary_index) return std::nullopt; FontSet set; set.font_indices.push_back(*primary_index); for (auto const &fallback_path : fallback_fonts) { auto fallback_index = load_single_font(fallback_path); if (!fallback_index) { TraceLog(LOG_WARNING, "Failed to load fallback font: %s", fallback_path.string().c_str()); continue; } set.font_indices.push_back(*fallback_index); } m_font_sets.emplace_back(std::move(set)); FontHandle handle; handle.id = m_font_sets.size() - 1; return handle; } auto TextRenderer::shape_text(FontHandle const font, std::string_view const text) -> std::vector { std::vector shaped; if (text.empty()) return shaped; usize const handle_id = font(); if (handle_id >= m_font_sets.size()) return shaped; auto const &font_set = m_font_sets[handle_id]; if (font_set.font_indices.empty()) return shaped; auto codepoints = decode_utf8(text); if (codepoints.empty()) return shaped; constexpr usize kNoFont = std::numeric_limits::max(); std::vector selections(codepoints.size(), kNoFont); for (usize i = 0; i < codepoints.size(); ++i) { bool matched = false; for (usize candidate = 0; candidate < font_set.font_indices.size(); ++candidate) { usize runtime_index = font_set.font_indices[candidate]; if (runtime_index >= m_font_runtime.size()) continue; auto const &runtime_ptr = m_font_runtime[runtime_index]; if (!runtime_ptr || !runtime_ptr->face) continue; FT_UInt glyph = FT_Get_Char_Index(runtime_ptr->face, codepoints[i].codepoint); if (glyph != 0) { selections[i] = candidate; matched = true; break; } } if (!matched) selections[i] = kNoFont; } usize idx = 0; while (idx < codepoints.size()) { usize font_choice = selections[idx]; if (font_choice == kNoFont) { ++idx; continue; } if (font_choice >= font_set.font_indices.size()) font_choice = 0; usize runtime_index = font_set.font_indices[font_choice]; if (runtime_index >= m_font_runtime.size() || !m_font_runtime[runtime_index] || !m_font_runtime[runtime_index]->hb_font) { ++idx; continue; } usize segment_start = codepoints[idx].start; usize segment_end = codepoints[idx].end; usize end_idx = idx + 1; while ( end_idx < codepoints.size() && selections[end_idx] == font_choice) { segment_end = codepoints[end_idx].end; ++end_idx; } if (segment_end <= segment_start) { idx = end_idx; continue; } std::string_view segment = text.substr(segment_start, segment_end - segment_start); if (segment.empty()) { idx = end_idx; continue; } hb_buffer_t *buffer = hb_buffer_create(); hb_buffer_add_utf8(buffer, segment.data(), static_cast(segment.size()), 0, static_cast(segment.size())); hb_buffer_guess_segment_properties(buffer); hb_shape(m_font_runtime[runtime_index]->hb_font, buffer, nullptr, 0); unsigned length = hb_buffer_get_length(buffer); auto *infos = hb_buffer_get_glyph_infos(buffer, nullptr); auto *positions = hb_buffer_get_glyph_positions(buffer, nullptr); for (unsigned i = 0; i < length; ++i) { GlyphPlacement placement; placement.runtime_index = runtime_index; placement.glyph_index = infos[i].codepoint; placement.x_advance = positions[i].x_advance; placement.y_advance = positions[i].y_advance; placement.x_offset = positions[i].x_offset; placement.y_offset = positions[i].y_offset; shaped.emplace_back(placement); } hb_buffer_destroy(buffer); idx = end_idx; } return shaped; } auto TextRenderer::unload_font(FontHandle const font) -> void { usize const handle_id = font(); if (handle_id >= m_font_sets.size()) return; auto &font_set = m_font_sets[handle_id]; for (usize runtime_index : font_set.font_indices) { if (runtime_index >= m_font_runtime.size()) continue; if (auto &runtime_ptr = m_font_runtime[runtime_index]) { auto &rt = *runtime_ptr; rt.glyph_cache.clear(); // No freeing here because they are already cleaned up somewhere... // idk. fml. rt.face = nullptr; } m_font_runtime[runtime_index].reset(); if (runtime_index < m_font_data.size()) { auto &fd = m_font_data[runtime_index]; if (fd.atlas.id != 0) UnloadTexture(fd.atlas); if (fd.atlas_img.data) UnloadImage(fd.atlas_img); fd.atlas = Texture2D {}; fd.atlas_img = Image {}; fd.glyphs.clear(); } } font_set.font_indices.clear(); } auto find_font_path(std::string_view path) -> std::optional { static std::once_flag fc_once; std::call_once(fc_once, []() { if (FcInit()) std::atexit([] { FcFini(); }); }); static std::mutex m; static std::unordered_map> cache; std::string const key(path); { std::scoped_lock lock(m); if (auto it = cache.find(key); it != cache.end()) return it->second; } FcPattern *pattern = FcNameParse(reinterpret_cast(key.c_str())); if (!pattern) { std::scoped_lock lock(m); return cache[key] = std::nullopt; } FcConfigSubstitute(nullptr, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern *font = FcFontMatch(nullptr, pattern, &result); std::optional final_path; if (font) { FcChar8 *file; if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) final_path = reinterpret_cast(file); FcPatternDestroy(font); } FcPatternDestroy(pattern); { std::scoped_lock lock(m); cache[key] = final_path; } return final_path; }