Fallback fonts

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-10-07 04:16:28 +03:00
parent c377baf3c9
commit 278f4c6df3
8 changed files with 427 additions and 122 deletions

2
.clangd Normal file
View File

@@ -0,0 +1,2 @@
CompileFlags:
Add: [-Wno-c23-extensions]

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
[Bb]uild* [Bb]uild*
CMakeLists.txt.*
result result
.cache .cache
.direnv .direnv

View File

@@ -16,7 +16,19 @@
system: system:
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
nativeBuildInputs = [ ]; nativeBuildInputs = with pkgs; [
pkg-config
wayland
wayland-protocols
wlr-protocols
wayland-scanner
libGL
libportal
glib
libxkbcommon
fontconfig
harfbuzz
];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
cmake cmake
ninja ninja
@@ -31,23 +43,12 @@
[ [
llvmPackages_21.clang-tools llvmPackages_21.clang-tools
lldb lldb
gdb
codespell codespell
doxygen doxygen
gtest gtest
cppcheck cppcheck
inotify-tools inotify-tools
pkg-config
wayland
wayland-protocols
wlr-protocols
wayland-scanner
libGL
libportal
glib
libxkbcommon
fontconfig
harfbuzz
] ]
++ buildInputs ++ buildInputs
++ nativeBuildInputs ++ nativeBuildInputs

View File

@@ -5,6 +5,8 @@
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <span>
#include <vector>
#include <poll.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <signal.h> #include <signal.h>
@@ -300,7 +302,19 @@ auto App::init_egl() -> void
m_tr = TextRenderer(); m_tr = TextRenderer();
auto const font = find_font_path(); auto const font = find_font_path();
assert(font && "Could not find font"); assert(font && "Could not find font");
auto const font_handle = m_tr->load_font(*font); std::vector<std::filesystem::path> fallback_paths;
constexpr char const *fallback_candidates[] = {
"Noto Sans CJK JP:style=Regular",
"Noto Sans CJK SC:style=Regular",
"Noto Sans CJK KR:style=Regular",
"Noto Sans CJK TC:style=Regular",
};
for (auto const *name : fallback_candidates) {
if (auto fallback = find_font_path(name))
fallback_paths.push_back(*fallback);
}
auto const font_handle
= m_tr->load_font(*font, std::span(fallback_paths));
assert(font_handle && "Could not load font"); assert(font_handle && "Could not load font");
m_font = *font_handle; m_font = *font_handle;
} }

38
src/FreetypeHooks.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include <cstdio>
#include <dlfcn.h>
#include <ft2build.h>
#include FT_FREETYPE_H
using FtInitFn = FT_Error (*)(FT_Library *);
using FtDoneFn = FT_Error (*)(FT_Library);
extern "C" FT_Error FT_Init_FreeType(FT_Library *library)
{
static FtInitFn real_fn
= reinterpret_cast<FtInitFn>(dlsym(RTLD_NEXT, "FT_Init_FreeType"));
if (!real_fn) {
std::fprintf(stderr,
"FT_Init_FreeType hook: failed to locate real symbol\n");
return FT_Err_Invalid_Handle;
}
FT_Error error = real_fn(library);
std::fprintf(stderr, "FT_Init_FreeType -> %p (err=%d)\n",
library ? static_cast<void *>(*library) : nullptr, error);
return error;
}
extern "C" FT_Error FT_Done_FreeType(FT_Library library)
{
static FtDoneFn real_fn
= reinterpret_cast<FtDoneFn>(dlsym(RTLD_NEXT, "FT_Done_FreeType"));
std::fprintf(stderr, "FT_Done_FreeType(%p)\n",
static_cast<void *>(library));
if (!real_fn) {
std::fprintf(stderr,
"FT_Done_FreeType hook: failed to locate real symbol\n");
return FT_Err_Invalid_Handle;
}
return real_fn(library);
}

View File

@@ -1,7 +1,6 @@
#include "TextRenderer.hpp" #include "TextRenderer.hpp"
#include <algorithm> #include <algorithm>
#include <array>
#include <cassert> #include <cassert>
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
@@ -41,7 +40,6 @@ namespace {
constexpr int kAtlasDimension = 1024; constexpr int kAtlasDimension = 1024;
constexpr int kAtlasPadding = 2; constexpr int kAtlasPadding = 2;
constexpr float kDefaultPxRange = 4.0f;
constexpr float kDefaultEmScale = 48.0f; constexpr float kDefaultEmScale = 48.0f;
constexpr float hb_to_em(hb_position_t value, unsigned upem) constexpr float hb_to_em(hb_position_t value, unsigned upem)
@@ -66,6 +64,78 @@ auto ft_library() -> FT_Library
return library; return library;
} }
struct CodepointSpan {
uint32_t codepoint {};
size_t start {};
size_t end {};
};
auto decode_utf8(std::string_view text) -> std::vector<CodepointSpan>
{
std::vector<CodepointSpan> spans;
size_t i = 0;
while (i < text.size()) {
u8 const byte = static_cast<u8>(text[i]);
size_t const start = i;
size_t 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<u8>(text[i + 1]);
if ((b1 & 0xC0) == 0x80) {
uint32_t t
= ((byte & 0x1F) << 6) | (static_cast<uint32_t>(b1) & 0x3F);
if (t >= 0x80) {
cp = t;
length = 2;
}
}
}
} else if ((byte & 0xF0) == 0xE0) {
if (i + 2 < text.size()) {
u8 const b1 = static_cast<u8>(text[i + 1]);
u8 const b2 = static_cast<u8>(text[i + 2]);
if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80) {
uint32_t t = ((byte & 0x0F) << 12)
| ((static_cast<uint32_t>(b1) & 0x3F) << 6)
| (static_cast<uint32_t>(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<u8>(text[i + 1]);
u8 const b2 = static_cast<u8>(text[i + 2]);
u8 const b3 = static_cast<u8>(text[i + 3]);
if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80
&& (b3 & 0xC0) == 0x80) {
uint32_t t = ((byte & 0x07) << 18)
| ((static_cast<uint32_t>(b1) & 0x3F) << 12)
| ((static_cast<uint32_t>(b2) & 0x3F) << 6)
| (static_cast<uint32_t>(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 } // namespace
auto TextRenderer::flush_font(FontRuntime &rt, FontData &fd) -> void auto TextRenderer::flush_font(FontRuntime &rt, FontData &fd) -> void
@@ -253,50 +323,57 @@ TextRenderer::TextRenderer()
TextRenderer::~TextRenderer() TextRenderer::~TextRenderer()
{ {
for (usize i = 0; i < m_font_runtime.size(); ++i) { for (usize i = 0; i < m_font_sets.size(); ++i) {
if (m_font_runtime[i]) { FontHandle handle;
FontHandle handle; handle.id = i;
handle.id = i; unload_font(handle);
unload_font(handle); }
} // Not unloading the shader... I have no clue why, but there's some sort of double free. I love C interop!!!!
}
UnloadShader(m_msdf_shader);
} }
auto TextRenderer::measure_text(FontHandle const font, auto TextRenderer::measure_text(FontHandle const font,
std::string_view const text, int const size) -> Vector2 std::string_view const text, int const size) -> Vector2
{ {
usize const font_id = font(); usize const handle_id = font();
if (font_id >= m_font_runtime.size() || !m_font_runtime[font_id] if (handle_id >= m_font_sets.size())
|| !m_font_runtime[font_id]->hb_font) 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 }; return Vector2 { 0.0f, 0.0f };
auto &rt = *m_font_runtime[font_id]; auto placements = shape_text(font, text);
auto &fd = m_font_data[font_id];
hb_buffer_t *buffer = hb_buffer_create(); auto primary_runtime_index = font_set.font_indices.front();
hb_buffer_add_utf8(buffer, text.data(), static_cast<int>(text.size()), 0, if (placements.empty()) {
static_cast<int>(text.size())); if (primary_runtime_index >= m_font_runtime.size()
hb_buffer_guess_segment_properties(buffer); || !m_font_runtime[primary_runtime_index])
hb_shape(rt.hb_font, buffer, nullptr, 0); 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<float>(size) };
}
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);
float advance_em = 0.0f; float advance_em = 0.0f;
float min_x_em = 0.0f; float min_x_em = 0.0f;
float max_x_em = 0.0f; float max_x_em = 0.0f;
bool first = true; bool first = true;
bool have_metrics = false;
float max_ascent = 0.0f;
float min_descent = 0.0f;
for (unsigned i = 0; i < length; ++i) { for (auto const &placement : placements) {
u32 glyph_index = infos[i].codepoint; usize const runtime_index = placement.runtime_index;
if (glyph_index == 0) if (runtime_index >= m_font_runtime.size()
|| !m_font_runtime[runtime_index])
continue; continue;
auto *entry = ensure_glyph(rt, fd, glyph_index, false); 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) if (!entry || entry->width == 0 || entry->height == 0)
continue; continue;
float const x_offset_em float const x_offset_em
= hb_to_em(positions[i].x_offset, rt.units_per_em); = hb_to_em(placement.x_offset, rt.units_per_em);
float const left float const left
= advance_em + x_offset_em + entry->glyph.plane_bounds.left; = advance_em + x_offset_em + entry->glyph.plane_bounds.left;
float const right float const right
@@ -309,17 +386,36 @@ auto TextRenderer::measure_text(FontHandle const font,
min_x_em = std::min(min_x_em, left); min_x_em = std::min(min_x_em, left);
max_x_em = std::max(max_x_em, right); max_x_em = std::max(max_x_em, right);
} }
advance_em += hb_to_em(positions[i].x_advance, rt.units_per_em); 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);
} }
hb_buffer_destroy(buffer); if (first) {
if (primary_runtime_index >= m_font_runtime.size()
if (first) || !m_font_runtime[primary_runtime_index])
return Vector2 { 0.0f, return Vector2 { 0.0f, 0.0f };
(rt.ascent - rt.descent) * static_cast<float>(size) }; auto const &rt = *m_font_runtime[primary_runtime_index];
float height_em = rt.ascent - rt.descent;
return Vector2 { 0.0f, height_em * static_cast<float>(size) };
}
float width_em = std::max(max_x_em, advance_em) - min_x_em; float width_em = std::max(max_x_em, advance_em) - min_x_em;
float height_em = rt.ascent - rt.descent; 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<float>(size), return Vector2 { width_em * static_cast<float>(size),
height_em * static_cast<float>(size) }; height_em * static_cast<float>(size) };
} }
@@ -330,51 +426,47 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
auto const draw_start = std::chrono::steady_clock::now(); auto const draw_start = std::chrono::steady_clock::now();
int const pos_x = pos.x; int const pos_x = pos.x;
int const pos_y = pos.y; int const pos_y = pos.y;
// Don't use pos from here on out! usize const handle_id = font();
usize const font_id = font(); if (handle_id >= m_font_sets.size())
if (font_id >= m_font_runtime.size() || !m_font_runtime[font_id] return;
|| !m_font_runtime[font_id]->hb_font) auto const &font_set = m_font_sets[handle_id];
if (font_set.font_indices.empty())
return; return;
auto &rt = *m_font_runtime[font_id];
auto &fd = m_font_data[font_id];
hb_buffer_t *buffer = hb_buffer_create(); auto placements = shape_text(font, text);
hb_buffer_add_utf8(buffer, text.data(), static_cast<int>(text.size()), 0, if (placements.empty())
static_cast<int>(text.size())); return;
hb_buffer_guess_segment_properties(buffer);
hb_shape(rt.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);
float const size_f = static_cast<float>(size); float const size_f = static_cast<float>(size);
float pen_x_em = 0.0f; float pen_x_em = 0.0f;
float pen_y_em = 0.0f; float pen_y_em = 0.0f;
rt.frame_stamp++; std::vector<usize> updated_stamp;
updated_stamp.reserve(font_set.font_indices.size());
// FIXME: Figure out shader for (auto const &placement : placements) {
// BeginShaderMode(m_msdf_shader); usize const runtime_index = placement.runtime_index;
// if (m_px_range_uniform >= 0) { if (runtime_index >= m_font_runtime.size()
// float shader_px_range = rt.px_range; || !m_font_runtime[runtime_index])
// SetShaderValue(m_msdf_shader, m_px_range_uniform, &shader_px_range, continue;
// SHADER_UNIFORM_FLOAT); 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);
}
for (unsigned i = 0; i < length; ++i) { auto *entry
u32 glyph_index = infos[i].codepoint; = ensure_glyph(rt, fd, placement.glyph_index, true);
if (glyph_index == 0) if (!entry || entry->width == 0 || entry->height == 0)
continue;
auto *entry = ensure_glyph(rt, fd, glyph_index, true);
if (!entry)
continue;
if (entry->width == 0 || entry->height == 0)
continue; continue;
float const advance_em float const advance_em
= hb_to_em(positions[i].x_advance, rt.units_per_em); = hb_to_em(placement.x_advance, rt.units_per_em);
float const x_offset_em float const x_offset_em
= hb_to_em(positions[i].x_offset, rt.units_per_em); = hb_to_em(placement.x_offset, rt.units_per_em);
float const y_offset_em float const y_offset_em
= hb_to_em(positions[i].y_offset, rt.units_per_em); = hb_to_em(placement.y_offset, rt.units_per_em);
float const x_base_em = pen_x_em + x_offset_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 y_base_em = pen_y_em + y_offset_em;
float const scale_px = size_f / static_cast<float>(rt.em_scale); float const scale_px = size_f / static_cast<float>(rt.em_scale);
@@ -397,23 +489,20 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
fd.atlas, source, dest, Vector2 { 0.0f, 0.0f }, 0.0f, color); fd.atlas, source, dest, Vector2 { 0.0f, 0.0f }, 0.0f, color);
pen_x_em += advance_em; pen_x_em += advance_em;
pen_y_em += hb_to_em(positions[i].y_advance, rt.units_per_em); pen_y_em += hb_to_em(placement.y_advance, rt.units_per_em);
} }
// EndShaderMode();
hb_buffer_destroy(buffer);
auto const draw_end = std::chrono::steady_clock::now(); auto const draw_end = std::chrono::steady_clock::now();
auto const draw_ms auto const draw_ms
= std::chrono::duration<double, std::milli>(draw_end - draw_start) = std::chrono::duration<double, std::milli>(draw_end - draw_start)
.count(); .count();
if (draw_ms > 5.0) if (draw_ms > 5.0)
TraceLog(LOG_INFO, "draw_text took %.2f ms for %zu glyphs", draw_ms, TraceLog(LOG_INFO, "draw_text took %.2f ms for %zu glyphs", draw_ms,
static_cast<size_t>(length)); placements.size());
} }
auto TextRenderer::load_font(std::filesystem::path const &path) auto TextRenderer::load_single_font(std::filesystem::path const &path)
-> std::optional<FontHandle> -> std::optional<usize>
{ {
FT_Library const ft = ft_library(); FT_Library const ft = ft_library();
if (!ft) if (!ft)
@@ -422,7 +511,10 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
FT_Face face = nullptr; FT_Face face = nullptr;
if (FT_New_Face(ft, path.string().c_str(), 0, &face) != 0) if (FT_New_Face(ft, path.string().c_str(), 0, &face) != 0)
return std::nullopt; return std::nullopt;
FT_Select_Charmap(face, FT_ENCODING_UNICODE); if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) {
FT_Done_Face(face);
return std::nullopt;
}
auto runtime = std::make_unique<FontRuntime>(); auto runtime = std::make_unique<FontRuntime>();
runtime->face = face; runtime->face = face;
@@ -446,10 +538,13 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
runtime->line_gap = std::max(0.0f, line_height - adv_height); runtime->line_gap = std::max(0.0f, line_height - adv_height);
runtime->hb_face = hb_ft_face_create_referenced(face); 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); runtime->hb_font = hb_ft_font_create_referenced(face);
if (!runtime->hb_font) { if (!runtime->hb_font) {
if (runtime->hb_face) hb_face_destroy(runtime->hb_face);
hb_face_destroy(runtime->hb_face);
FT_Done_Face(face); FT_Done_Face(face);
return std::nullopt; return std::nullopt;
} }
@@ -472,18 +567,18 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
= GenImageColor(runtime->atlas_width, runtime->atlas_height, BLANK); = GenImageColor(runtime->atlas_width, runtime->atlas_height, BLANK);
if (!font_data.atlas_img.data) { if (!font_data.atlas_img.data) {
msdfgen::destroyFont(runtime->msdf_font); msdfgen::destroyFont(runtime->msdf_font);
runtime->msdf_font = nullptr;
hb_font_destroy(runtime->hb_font); hb_font_destroy(runtime->hb_font);
hb_face_destroy(runtime->hb_face); hb_face_destroy(runtime->hb_face);
FT_Done_Face(face);
return std::nullopt; return std::nullopt;
} }
font_data.atlas = LoadTextureFromImage(font_data.atlas_img); font_data.atlas = LoadTextureFromImage(font_data.atlas_img);
if (font_data.atlas.id == 0) { if (font_data.atlas.id == 0) {
UnloadImage(font_data.atlas_img); UnloadImage(font_data.atlas_img);
msdfgen::destroyFont(runtime->msdf_font); msdfgen::destroyFont(runtime->msdf_font);
runtime->msdf_font = nullptr;
hb_font_destroy(runtime->hb_font); hb_font_destroy(runtime->hb_font);
hb_face_destroy(runtime->hb_face); hb_face_destroy(runtime->hb_face);
FT_Done_Face(face);
return std::nullopt; return std::nullopt;
} }
SetTextureFilter(font_data.atlas, TEXTURE_FILTER_BILINEAR); SetTextureFilter(font_data.atlas, TEXTURE_FILTER_BILINEAR);
@@ -492,40 +587,170 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
m_font_data.emplace_back(std::move(font_data)); m_font_data.emplace_back(std::move(font_data));
m_font_runtime.emplace_back(std::move(runtime)); 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<std::filesystem::path const> fallback_fonts)
-> std::optional<FontHandle>
{
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; FontHandle handle;
handle.id = m_font_data.size() - 1; handle.id = m_font_sets.size() - 1;
return handle; return handle;
} }
auto TextRenderer::shape_text(FontHandle const font,
std::string_view const text) -> std::vector<GlyphPlacement>
{
std::vector<GlyphPlacement> 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;
std::vector<usize> selections(codepoints.size(), 0);
for (size_t i = 0; i < codepoints.size(); ++i) {
bool matched = false;
for (size_t 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] = 0;
}
size_t idx = 0;
while (idx < codepoints.size()) {
size_t font_choice = selections[idx];
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;
}
size_t segment_start = codepoints[idx].start;
size_t segment_end = codepoints[idx].end;
size_t 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<int>(segment.size()), 0,
static_cast<int>(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 auto TextRenderer::unload_font(FontHandle const font) -> void
{ {
usize const font_id = font(); usize const handle_id = font();
if (font_id >= m_font_runtime.size()) if (handle_id >= m_font_sets.size())
return; return;
if (m_font_runtime[font_id]) { auto &font_set = m_font_sets[handle_id];
auto &rt = *m_font_runtime[font_id]; for (usize runtime_index : font_set.font_indices) {
rt.glyph_cache.clear(); if (runtime_index >= m_font_runtime.size())
if (rt.msdf_font) continue;
msdfgen::destroyFont(rt.msdf_font);
if (rt.hb_font)
hb_font_destroy(rt.hb_font);
if (rt.hb_face)
hb_face_destroy(rt.hb_face);
if (rt.face)
FT_Done_Face(rt.face);
}
m_font_runtime[font_id].reset();
if (font_id < m_font_data.size()) { if (auto &runtime_ptr = m_font_runtime[runtime_index]) {
auto &fd = m_font_data[font_id]; auto &rt = *runtime_ptr;
if (fd.atlas.id != 0) rt.glyph_cache.clear();
UnloadTexture(fd.atlas); // No freeing here because they are already cleaned up somewhere...
if (fd.atlas_img.data) // idk. fml.
UnloadImage(fd.atlas_img); rt.face = nullptr;
fd.glyphs.clear(); }
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) auto find_font_path(std::string_view path)

View File

@@ -3,6 +3,7 @@
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -44,7 +45,8 @@ struct TextRenderer {
Vector2 const pos, int const size = 16, Color const color = WHITE) Vector2 const pos, int const size = 16, Color const color = WHITE)
-> void; -> void;
auto load_font(std::filesystem::path const &path) auto load_font(std::filesystem::path const &path,
std::span<std::filesystem::path const> fallback_fonts)
-> std::optional<FontHandle>; -> std::optional<FontHandle>;
auto unload_font(FontHandle const font) -> void; auto unload_font(FontHandle const font) -> void;
@@ -103,6 +105,19 @@ private:
}; };
std::vector<std::unique_ptr<FontRuntime>> m_font_runtime; std::vector<std::unique_ptr<FontRuntime>> m_font_runtime;
struct FontSet {
std::vector<usize> font_indices;
};
std::vector<FontSet> m_font_sets;
struct GlyphPlacement {
usize runtime_index {};
u32 glyph_index {};
i32 x_advance {};
i32 y_advance {};
i32 x_offset {};
i32 y_offset {};
};
static auto flush_font(FontRuntime &rt, FontData &fd) -> void; static auto flush_font(FontRuntime &rt, FontData &fd) -> void;
static auto allocate_region( static auto allocate_region(
@@ -114,6 +129,10 @@ private:
-> std::optional<GlyphCacheEntry>; -> std::optional<GlyphCacheEntry>;
static auto ensure_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index, static auto ensure_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index,
bool mark_usage) -> GlyphCacheEntry *; bool mark_usage) -> GlyphCacheEntry *;
auto load_single_font(std::filesystem::path const &path)
-> std::optional<usize>;
auto shape_text(FontHandle const font, std::string_view const text)
-> std::vector<GlyphPlacement>;
}; };
auto find_font_path(std::string_view path = "sans-serif:style=Regular") auto find_font_path(std::string_view path = "sans-serif:style=Regular")

View File

@@ -10,12 +10,12 @@
#include "App.hpp" #include "App.hpp"
bool check_or_signal_running(); bool signal_running();
std::optional<App> g_app{}; std::optional<App> g_app{};
auto main() -> int { auto main() -> int {
if (check_or_signal_running()) { if (signal_running()) {
return 0; return 0;
} }
@@ -28,7 +28,7 @@ auto main() -> int {
g_app->run(); g_app->run();
} }
bool check_or_signal_running() { bool signal_running() {
const char *lock_path = "/tmp/waylight.lock"; const char *lock_path = "/tmp/waylight.lock";
int fd = open(lock_path, O_CREAT | O_RDWR, 0666); int fd = open(lock_path, O_CREAT | O_RDWR, 0666);
if (fd == -1) if (fd == -1)
@@ -46,7 +46,12 @@ bool check_or_signal_running() {
return true; return true;
} }
ftruncate(fd, 0); if (ftruncate(fd, 0) == -1) {
close(fd);
unlink(lock_path);
return false;
}
dprintf(fd, "%d\n", getpid()); dprintf(fd, "%d\n", getpid());
return false; return false;
} }