Compare commits
2 Commits
c377baf3c9
...
64979c6e5c
| Author | SHA1 | Date | |
|---|---|---|---|
| 64979c6e5c | |||
| 278f4c6df3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
[Bb]uild*
|
||||
CMakeLists.txt.*
|
||||
result
|
||||
.cache
|
||||
.direnv
|
||||
|
||||
27
flake.nix
27
flake.nix
@@ -16,7 +16,19 @@
|
||||
system:
|
||||
let
|
||||
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; [
|
||||
cmake
|
||||
ninja
|
||||
@@ -31,23 +43,12 @@
|
||||
[
|
||||
llvmPackages_21.clang-tools
|
||||
lldb
|
||||
gdb
|
||||
codespell
|
||||
doxygen
|
||||
gtest
|
||||
cppcheck
|
||||
inotify-tools
|
||||
|
||||
pkg-config
|
||||
wayland
|
||||
wayland-protocols
|
||||
wlr-protocols
|
||||
wayland-scanner
|
||||
libGL
|
||||
libportal
|
||||
glib
|
||||
libxkbcommon
|
||||
fontconfig
|
||||
harfbuzz
|
||||
]
|
||||
++ buildInputs
|
||||
++ nativeBuildInputs
|
||||
|
||||
35
src/App.cpp
35
src/App.cpp
@@ -5,6 +5,9 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
@@ -300,7 +303,37 @@ auto App::init_egl() -> void
|
||||
m_tr = TextRenderer();
|
||||
auto const font = find_font_path();
|
||||
assert(font && "Could not find font");
|
||||
auto const font_handle = m_tr->load_font(*font);
|
||||
std::vector<std::filesystem::path> fallback_paths;
|
||||
std::unordered_set<std::string> seen_paths;
|
||||
auto const primary_path_str = font->string();
|
||||
|
||||
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",
|
||||
"sans-serif:lang=ja",
|
||||
"sans-serif:lang=ko",
|
||||
"sans-serif:lang=zh-cn",
|
||||
"sans-serif:lang=zh-tw",
|
||||
"sans-serif:lang=zh-hk",
|
||||
};
|
||||
for (auto const *name : fallback_candidates) {
|
||||
if (auto fallback = find_font_path(name)) {
|
||||
auto const path_str = fallback->string();
|
||||
if (path_str == primary_path_str)
|
||||
continue;
|
||||
if (!seen_paths.emplace(path_str).second)
|
||||
continue;
|
||||
fallback_paths.push_back(*fallback);
|
||||
}
|
||||
}
|
||||
if (fallback_paths.empty()) {
|
||||
TraceLog(LOG_WARNING,
|
||||
"No fallback fonts found; some glyphs may render as missing");
|
||||
}
|
||||
auto const font_handle
|
||||
= m_tr->load_font(*font, std::span(fallback_paths));
|
||||
assert(font_handle && "Could not load font");
|
||||
m_font = *font_handle;
|
||||
}
|
||||
|
||||
38
src/FreetypeHooks.cpp
Normal file
38
src/FreetypeHooks.cpp
Normal 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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "TextRenderer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -14,6 +13,7 @@
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace {
|
||||
|
||||
constexpr int kAtlasDimension = 1024;
|
||||
constexpr int kAtlasPadding = 2;
|
||||
constexpr float kDefaultPxRange = 4.0f;
|
||||
constexpr float kDefaultEmScale = 48.0f;
|
||||
|
||||
constexpr float hb_to_em(hb_position_t value, unsigned upem)
|
||||
@@ -66,6 +65,78 @@ auto ft_library() -> FT_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
|
||||
|
||||
auto TextRenderer::flush_font(FontRuntime &rt, FontData &fd) -> void
|
||||
@@ -253,50 +324,57 @@ TextRenderer::TextRenderer()
|
||||
|
||||
TextRenderer::~TextRenderer()
|
||||
{
|
||||
for (usize i = 0; i < m_font_runtime.size(); ++i) {
|
||||
if (m_font_runtime[i]) {
|
||||
FontHandle handle;
|
||||
handle.id = i;
|
||||
unload_font(handle);
|
||||
}
|
||||
}
|
||||
UnloadShader(m_msdf_shader);
|
||||
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 font_id = font();
|
||||
if (font_id >= m_font_runtime.size() || !m_font_runtime[font_id]
|
||||
|| !m_font_runtime[font_id]->hb_font)
|
||||
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 &rt = *m_font_runtime[font_id];
|
||||
auto &fd = m_font_data[font_id];
|
||||
auto placements = shape_text(font, text);
|
||||
|
||||
hb_buffer_t *buffer = hb_buffer_create();
|
||||
hb_buffer_add_utf8(buffer, text.data(), static_cast<int>(text.size()), 0,
|
||||
static_cast<int>(text.size()));
|
||||
hb_buffer_guess_segment_properties(buffer);
|
||||
hb_shape(rt.hb_font, buffer, nullptr, 0);
|
||||
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<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 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 (unsigned i = 0; i < length; ++i) {
|
||||
u32 glyph_index = infos[i].codepoint;
|
||||
if (glyph_index == 0)
|
||||
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 *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)
|
||||
continue;
|
||||
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
|
||||
= advance_em + x_offset_em + entry->glyph.plane_bounds.left;
|
||||
float const right
|
||||
@@ -309,17 +387,36 @@ auto TextRenderer::measure_text(FontHandle const font,
|
||||
min_x_em = std::min(min_x_em, left);
|
||||
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)
|
||||
return Vector2 { 0.0f,
|
||||
(rt.ascent - rt.descent) * static_cast<float>(size) };
|
||||
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<float>(size) };
|
||||
}
|
||||
|
||||
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),
|
||||
height_em * static_cast<float>(size) };
|
||||
}
|
||||
@@ -330,51 +427,47 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
||||
auto const draw_start = std::chrono::steady_clock::now();
|
||||
int const pos_x = pos.x;
|
||||
int const pos_y = pos.y;
|
||||
// Don't use pos from here on out!
|
||||
usize const font_id = font();
|
||||
if (font_id >= m_font_runtime.size() || !m_font_runtime[font_id]
|
||||
|| !m_font_runtime[font_id]->hb_font)
|
||||
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 &rt = *m_font_runtime[font_id];
|
||||
auto &fd = m_font_data[font_id];
|
||||
|
||||
hb_buffer_t *buffer = hb_buffer_create();
|
||||
hb_buffer_add_utf8(buffer, text.data(), static_cast<int>(text.size()), 0,
|
||||
static_cast<int>(text.size()));
|
||||
hb_buffer_guess_segment_properties(buffer);
|
||||
hb_shape(rt.hb_font, buffer, nullptr, 0);
|
||||
auto placements = shape_text(font, text);
|
||||
if (placements.empty())
|
||||
return;
|
||||
|
||||
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 pen_x_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
|
||||
// BeginShaderMode(m_msdf_shader);
|
||||
// if (m_px_range_uniform >= 0) {
|
||||
// float shader_px_range = rt.px_range;
|
||||
// SetShaderValue(m_msdf_shader, m_px_range_uniform, &shader_px_range,
|
||||
// SHADER_UNIFORM_FLOAT);
|
||||
// }
|
||||
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);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < length; ++i) {
|
||||
u32 glyph_index = infos[i].codepoint;
|
||||
if (glyph_index == 0)
|
||||
continue;
|
||||
auto *entry = ensure_glyph(rt, fd, glyph_index, true);
|
||||
if (!entry)
|
||||
continue;
|
||||
if (entry->width == 0 || entry->height == 0)
|
||||
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(positions[i].x_advance, rt.units_per_em);
|
||||
= hb_to_em(placement.x_advance, rt.units_per_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
|
||||
= 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 y_base_em = pen_y_em + y_offset_em;
|
||||
float const scale_px = size_f / static_cast<float>(rt.em_scale);
|
||||
@@ -397,23 +490,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);
|
||||
|
||||
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_ms
|
||||
= std::chrono::duration<double, std::milli>(draw_end - draw_start)
|
||||
.count();
|
||||
if (draw_ms > 5.0)
|
||||
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)
|
||||
-> std::optional<FontHandle>
|
||||
auto TextRenderer::load_single_font(std::filesystem::path const &path)
|
||||
-> std::optional<usize>
|
||||
{
|
||||
FT_Library const ft = ft_library();
|
||||
if (!ft)
|
||||
@@ -422,7 +512,10 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
|
||||
FT_Face face = nullptr;
|
||||
if (FT_New_Face(ft, path.string().c_str(), 0, &face) != 0)
|
||||
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>();
|
||||
runtime->face = face;
|
||||
@@ -446,10 +539,13 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
|
||||
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) {
|
||||
if (runtime->hb_face)
|
||||
hb_face_destroy(runtime->hb_face);
|
||||
hb_face_destroy(runtime->hb_face);
|
||||
FT_Done_Face(face);
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -472,18 +568,18 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
|
||||
= 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);
|
||||
FT_Done_Face(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);
|
||||
FT_Done_Face(face);
|
||||
return std::nullopt;
|
||||
}
|
||||
SetTextureFilter(font_data.atlas, TEXTURE_FILTER_BILINEAR);
|
||||
@@ -492,40 +588,175 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
|
||||
|
||||
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<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;
|
||||
handle.id = m_font_data.size() - 1;
|
||||
handle.id = m_font_sets.size() - 1;
|
||||
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;
|
||||
|
||||
constexpr usize kNoFont = std::numeric_limits<usize>::max();
|
||||
std::vector<usize> selections(codepoints.size(), kNoFont);
|
||||
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] = kNoFont;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
while (idx < codepoints.size()) {
|
||||
size_t 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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
usize const font_id = font();
|
||||
if (font_id >= m_font_runtime.size())
|
||||
usize const handle_id = font();
|
||||
if (handle_id >= m_font_sets.size())
|
||||
return;
|
||||
|
||||
if (m_font_runtime[font_id]) {
|
||||
auto &rt = *m_font_runtime[font_id];
|
||||
rt.glyph_cache.clear();
|
||||
if (rt.msdf_font)
|
||||
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();
|
||||
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 (font_id < m_font_data.size()) {
|
||||
auto &fd = m_font_data[font_id];
|
||||
if (fd.atlas.id != 0)
|
||||
UnloadTexture(fd.atlas);
|
||||
if (fd.atlas_img.data)
|
||||
UnloadImage(fd.atlas_img);
|
||||
fd.glyphs.clear();
|
||||
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)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -44,7 +45,8 @@ struct TextRenderer {
|
||||
Vector2 const pos, int const size = 16, Color const color = WHITE)
|
||||
-> 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>;
|
||||
auto unload_font(FontHandle const font) -> void;
|
||||
|
||||
@@ -103,6 +105,19 @@ private:
|
||||
};
|
||||
|
||||
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 allocate_region(
|
||||
@@ -114,6 +129,10 @@ private:
|
||||
-> std::optional<GlyphCacheEntry>;
|
||||
static auto ensure_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index,
|
||||
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")
|
||||
|
||||
@@ -30,7 +30,7 @@ auto App::tick() -> void
|
||||
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(BLANK);
|
||||
ClearBackground(theme().window.background);
|
||||
|
||||
DrawFPS(10, 10);
|
||||
if (m_tr) {
|
||||
|
||||
13
src/main.cpp
13
src/main.cpp
@@ -10,12 +10,12 @@
|
||||
|
||||
#include "App.hpp"
|
||||
|
||||
bool check_or_signal_running();
|
||||
bool signal_running();
|
||||
|
||||
std::optional<App> g_app{};
|
||||
|
||||
auto main() -> int {
|
||||
if (check_or_signal_running()) {
|
||||
if (signal_running()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ auto main() -> int {
|
||||
g_app->run();
|
||||
}
|
||||
|
||||
bool check_or_signal_running() {
|
||||
bool signal_running() {
|
||||
const char *lock_path = "/tmp/waylight.lock";
|
||||
int fd = open(lock_path, O_CREAT | O_RDWR, 0666);
|
||||
if (fd == -1)
|
||||
@@ -46,7 +46,12 @@ bool check_or_signal_running() {
|
||||
return true;
|
||||
}
|
||||
|
||||
ftruncate(fd, 0);
|
||||
if (ftruncate(fd, 0) == -1) {
|
||||
close(fd);
|
||||
unlink(lock_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
dprintf(fd, "%d\n", getpid());
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user