diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f2b0e2..cf39324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(LIBPORTAL REQUIRED IMPORTED_TARGET libportal) pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig) +pkg_check_modules(HARFBUZZ REQUIRED IMPORTED_TARGET harfbuzz) pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) pkg_check_modules(WLR_PROTOCOLS REQUIRED wlr-protocols) @@ -30,6 +31,18 @@ set(PLATFORM DRM) set(BUILD_EXAMPLES OFF) FetchContent_MakeAvailable(raylib) +FetchContent_Declare( + msdfgen + GIT_REPOSITORY https://github.com/Chlumsky/msdfgen.git + GIT_SHALLOW 1 +) +set(MSDFGEN_BUILD_STANDALONE OFF) +set(MSDFGEN_USE_VCPKG OFF) +set(MSDFGEN_USE_SKIA OFF) +set(MSDFGEN_DISABLE_SVG ON) +set(MSDFGEN_DISABLE_PNG ON) +FetchContent_MakeAvailable(msdfgen) + find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) @@ -118,6 +131,7 @@ add_executable(waylight ${GEN_C_PRIVATES} ${CMAKE_CURRENT_SOURCE_DIR}/src/App.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/TextRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Tick.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ) @@ -136,8 +150,10 @@ target_link_libraries(waylight PRIVATE PkgConfig::LIBPORTAL PkgConfig::XKBCOMMON PkgConfig::FONTCONFIG + PkgConfig::HARFBUZZ raylib + msdfgen::msdfgen-core m dl diff --git a/flake.nix b/flake.nix index 1295607..5d21304 100644 --- a/flake.nix +++ b/flake.nix @@ -47,6 +47,7 @@ glib libxkbcommon fontconfig + harfbuzz ] ++ buildInputs ++ nativeBuildInputs diff --git a/src/App.cpp b/src/App.cpp index 378462e..14d3c13 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -1,11 +1,11 @@ #include "App.hpp" +#include #include #include #include #include #include -#include #include #include #include @@ -27,35 +27,6 @@ #include "blur-client-protocol.h" #include "ext-background-effect-v1-client-protocol.h" -auto find_font_path(std::string_view path = "sans-serif:style=Regular") - -> std::optional -{ - if (!FcInit()) - return std::nullopt; - - std::string query(path); - FcPattern *pattern - = FcNameParse(reinterpret_cast(query.c_str())); - 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); - FcFini(); - return final_path; -} - auto TypingBuffer::push_utf8(char const *s) -> void { for (unsigned char const *p = reinterpret_cast(s); @@ -83,11 +54,6 @@ auto TypingBuffer::push_utf8(char const *s) -> void App::App() { - { - auto const path = find_font_path(); - if (path) - std::println("font path = {}", *path); - } init_wayland(); init_egl(); init_signal(); @@ -330,6 +296,13 @@ auto App::init_egl() -> void ensure_egl_surface(); InitWindow(m_win_w, m_win_h, ""); + + m_tr = TextRenderer(); + auto const font = find_font_path(); + assert(font && "Could not find font"); + auto const font_handle = m_tr->load_font(*font); + assert(font_handle && "Could not load font"); + m_font = *font_handle; } auto App::init_signal() -> void diff --git a/src/App.hpp b/src/App.hpp index e3995b8..69f615b 100644 --- a/src/App.hpp +++ b/src/App.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -18,6 +17,7 @@ extern "C" { #include #include +#include "TextRenderer.hpp" #include "Theme.hpp" #include "common.hpp" @@ -139,6 +139,9 @@ private: } } m_kbd; + std::optional m_tr { std::nullopt }; + FontHandle m_font; + enum_array m_themes { make_default_themes() }; Theme m_active_theme { Theme::Light }; diff --git a/src/TextRenderer.cpp b/src/TextRenderer.cpp new file mode 100644 index 0000000..edc1871 --- /dev/null +++ b/src/TextRenderer.cpp @@ -0,0 +1,104 @@ +#include "TextRenderer.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +TextRenderer::TextRenderer() +{ + static char const msdf_fs_data[] { +#embed "msdf.fs" + , 0 + }; + m_msdf_shader = LoadShaderFromMemory(nullptr, msdf_fs_data); + assert(IsShaderValid(m_msdf_shader)); +} + +TextRenderer::~TextRenderer() { UnloadShader(m_msdf_shader); } + +auto TextRenderer::measure_text(FontHandle const font, + std::string_view const text, int const size) -> Vector2 +{ + // FIXME: Implement. + return {}; +} + +auto TextRenderer::draw_text(FontHandle const font, std::string_view const text, + Vector2 const pos, int const size, Color const color) -> void +{ + int const pos_x = pos.x; + int const pos_y = pos.y; + // Don't use pos from here on out! + // FIXME: Implement. + (void)pos_x, (void)pos_y; +} + +auto TextRenderer::load_font(std::filesystem::path const &path) + -> std::optional +{ + // FIXME: Implement. + return std::nullopt; +} + +auto TextRenderer::unload_font(FontHandle const font) +{ + // FIXME: Implement. +} + +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; +} diff --git a/src/TextRenderer.hpp b/src/TextRenderer.hpp new file mode 100644 index 0000000..17e3a12 --- /dev/null +++ b/src/TextRenderer.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +#include + +#include "common.hpp" + +struct FontHandle { + auto operator()() const -> auto const & { return id; } + +private: + usize id; +}; + +struct TextRenderer { + TextRenderer(); // Requires raylib to be initialized! + ~TextRenderer(); + + auto measure_text(FontHandle const font, std::string_view const text, + int const size = 16) -> Vector2; + auto draw_text(FontHandle const font, std::string_view const text, + Vector2 const pos, int const size = 16, Color const color = WHITE) + -> void; + + auto load_font(std::filesystem::path const &path) + -> std::optional; + auto unload_font(FontHandle const font); + +private: + struct FontData { + struct Glyph { + struct Rect { + float top, left, right, bottom; + }; + + float advance; + Rect plane_bounds; + Rect glyph_bounds; + }; + + Texture2D atlas; + Image atlas_img; + std::filesystem::path font_path; + std::unordered_map glyphs; + }; + + Shader m_msdf_shader; + + std::vector m_font_data; +}; + +auto find_font_path(std::string_view path = "sans-serif:style=Regular") + -> std::optional; diff --git a/src/common.hpp b/src/common.hpp index 5f26516..2f4e3cc 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,3 +1,5 @@ +#pragma once + #include using u8 = std::uint8_t; @@ -11,7 +13,7 @@ using i64 = std::int64_t; using usize = std::uintptr_t; using isize = std::intptr_t; -inline auto rune_to_string(uint32_t cp) -> char const * +[[maybe_unused]] static inline auto rune_to_string(uint32_t cp) -> char const * { static char utf8[5] = { 0 }; for (auto &c : utf8) diff --git a/src/msdf.fs b/src/msdf.fs new file mode 100644 index 0000000..4646729 --- /dev/null +++ b/src/msdf.fs @@ -0,0 +1,34 @@ +#version 100 +#extension GL_OES_standard_derivatives : enable + +#ifdef GL_ES +precision mediump float; +#endif + +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform sampler2D texture0; +uniform vec4 colDiffuse; +uniform float pxRange; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +float screenPxRange(vec2 uv) { + vec2 duv_dx = dFdx(uv); + vec2 duv_dy = dFdy(uv); + float scale = 0.5 * (length(duv_dx) + length(duv_dy)); + return max(scale * pxRange, 1.0); +} + +void main() { + vec3 msd = texture2D(texture0, fragTexCoord).rgb; + float sd = median(msd.r, msd.g, msd.b); + float spx = screenPxRange(fragTexCoord); + float dist = spx * (sd - 0.5); + float opacity = clamp(dist + 0.5, 0.0, 1.0); + + gl_FragColor = vec4(fragColor.rgb, fragColor.a * opacity) * colDiffuse; +}