1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
[Bb]uild*
|
[Bb]uild*
|
||||||
|
CMakeLists.txt.*
|
||||||
result
|
result
|
||||||
.cache
|
.cache
|
||||||
.direnv
|
.direnv
|
||||||
|
|||||||
27
flake.nix
27
flake.nix
@@ -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
|
||||||
|
|||||||
16
src/App.cpp
16
src/App.cpp
@@ -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
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 "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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
13
src/main.cpp
13
src/main.cpp
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user