diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 970b97ea6..7c99e9687 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -40,6 +40,7 @@ set(LIBIMHEX_SOURCES source/helpers/imgui_hooks.cpp source/helpers/semantic_version.cpp source/helpers/keys.cpp + source/helpers/freetype.cpp source/test/tests.cpp diff --git a/lib/libimhex/include/hex/api/imhex_api.hpp b/lib/libimhex/include/hex/api/imhex_api.hpp index 7db671601..613c26f3f 100644 --- a/lib/libimhex/include/hex/api/imhex_api.hpp +++ b/lib/libimhex/include/hex/api/imhex_api.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include "imgui_internal.h" #if !defined(HEX_MODULE_EXPORT) using ImGuiID = unsigned int; @@ -752,6 +754,7 @@ EXPORT_MODULE namespace hex { std::vector glyphRanges; Offset offset; u32 flags; + std::optional scalable; std::optional defaultSize; }; @@ -768,14 +771,18 @@ EXPORT_MODULE namespace hex { GlyphRange range(const char *glyphBegin, const char *glyphEnd); GlyphRange range(u32 codepointBegin, u32 codepointEnd); - void loadFont(const std::fs::path &path, const std::vector &glyphRanges = {}, Offset offset = {}, u32 flags = 0, std::optional defaultSize = std::nullopt); - void loadFont(const std::string &name, const std::span &data, const std::vector &glyphRanges = {}, Offset offset = {}, u32 flags = 0, std::optional defaultSize = std::nullopt); + void loadFont(const std::fs::path &path, const std::vector &glyphRanges = {}, Offset offset = {}, u32 flags = 0, std::optional scalable = std::nullopt, std::optional defaultSize = std::nullopt); + void loadFont(const std::string &name, const std::span &data, const std::vector &glyphRanges = {}, Offset offset = {}, u32 flags = 0, std::optional scalable = std::nullopt, std::optional defaultSize = std::nullopt); constexpr float DefaultFontSize = 13.0; void registerFont(const UnlocalizedString &fontName); ImFont* getFont(const UnlocalizedString &fontName); + float getDpi(); + float pixelsToPoints(float pixels); + float pointsToPixels(float points); + } } diff --git a/lib/libimhex/include/hex/helpers/freetype.hpp b/lib/libimhex/include/hex/helpers/freetype.hpp new file mode 100644 index 000000000..71eea3b18 --- /dev/null +++ b/lib/libimhex/include/hex/helpers/freetype.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "imgui.h" +#include + +namespace hex::ft { + + class Bitmap { + ImU32 m_width; + ImU32 m_height; + ImU32 m_pitch; + std::vector m_data; + public: + Bitmap(ImU32 width, ImU32 height, ImU32 pitch, ImU8 *data) : m_width(width), m_height(height), m_pitch(pitch), m_data(std::vector(data, data + pitch * height)) {} + + Bitmap(ImU32 width, ImU32 height, ImU32 pitch) : m_width(width), m_height(height), m_pitch(pitch) { m_data.resize(pitch * height); } + + void clear() { m_data.clear(); } + + ImU32 getWidth() const { return m_width; } + + ImU32 getHeight() const { return m_height; } + + ImU32 getPitch() const { return m_pitch; } + + const std::vector &getData() const { return m_data; } + + ImU8 *getData() { return m_data.data(); } + + void lcdFilter(); + }; + + class RGBA { + public: + + static ImU32 addAlpha(ImU8 r, ImU8 g, ImU8 b) { + color.rgbaVec[0] = r; + color.rgbaVec[1] = g; + color.rgbaVec[2] = b; + color.rgbaVec[3] = (r + g + b) / 3;//luminance + return color.rgba; + } + + RGBA(ImU8 r, ImU8 g, ImU8 b, ImU8 a) { + color.rgbaVec[0] = r; + color.rgbaVec[1] = b; + color.rgbaVec[2] = g; + color.rgbaVec[3] = a; + } + + explicit RGBA(ImU32 rgba) { + color.rgba = rgba; + } + + union RGBAU { + ImU8 rgbaVec[4]; + ImU32 rgba; + }; + inline static RGBAU color; + }; +} \ No newline at end of file diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 4f443658b..50b3a98de 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -1065,7 +1065,7 @@ namespace hex { }; } - void loadFont(const std::fs::path &path, const std::vector &glyphRanges, Offset offset, u32 flags, std::optional defaultSize) { + void loadFont(const std::fs::path &path, const std::vector &glyphRanges, Offset offset, u32 flags, std::optional scalable, std::optional defaultSize) { wolv::io::File fontFile(path, wolv::io::File::Mode::Read); if (!fontFile.isValid()) { log::error("Failed to load font from file '{}'", wolv::util::toUTF8String(path)); @@ -1078,17 +1078,19 @@ namespace hex { glyphRanges, offset, flags, + scalable, defaultSize }); } - void loadFont(const std::string &name, const std::span &data, const std::vector &glyphRanges, Offset offset, u32 flags, std::optional defaultSize) { + void loadFont(const std::string &name, const std::span &data, const std::vector &glyphRanges, Offset offset, u32 flags, std::optional scalable, std::optional defaultSize) { impl::s_fonts->emplace_back(Font { name, { data.begin(), data.end() }, glyphRanges, offset, flags, + scalable, defaultSize }); } @@ -1117,6 +1119,18 @@ namespace hex { return impl::s_italicFont; } + float getDpi() { + auto dpi = ImGui::GetCurrentContext()->CurrentDpiScale * 96.0F; + return dpi ? dpi : 96.0F; + } + + float pixelsToPoints(float pixels) { + return pixels * (72.0 / getDpi()); + } + + float pointsToPixels(float points) { + return points / (72.0 / getDpi()); + } } diff --git a/lib/libimhex/source/helpers/freetype.cpp b/lib/libimhex/source/helpers/freetype.cpp new file mode 100644 index 000000000..b65a855de --- /dev/null +++ b/lib/libimhex/source/helpers/freetype.cpp @@ -0,0 +1,60 @@ +#include "hex/helpers/freetype.hpp" +#include "imgui.h" + +namespace hex::ft { + + void Bitmap::lcdFilter() { + if (m_width * m_height == 0) + return; + std::vector h = {8, 0x4D, 0x55, 0x4D, 8}; + auto paddedWidth = m_width + 4; + auto fWidth = paddedWidth + ((3 - (paddedWidth % 3)) % 3); + auto fPitch = (fWidth + 3) & (-4); + Bitmap f(fWidth, m_height, fPitch); + //std::vector> fir(3, std::vector(3, 0)); + // first output: h[0]*input[0]/255.0f; + // 2nd output: (h[1]*input[0] + h[0]*input[1])/255.0f; + // 3rd output: (h[2]*input[0] + h[1]*input[1] + h[0]*input[2])/255.0f; + // 4th output: (h[1]*input[0] + h[2]*input[1] + h[1]*input[2] + h[0]*input[3])/255.0f; + // ith output: (h[0]*input[5+i] + h[1]*input[i+6] + h[2]*input[i+7] + h[1]*input[i+8] + h[0]*input[i+9])/255.0f; + // aap output: (h[0]*input[N-4] + h[1]*input[N-3] + h[2]*input[N-2] + h[1]*input[N-1])/255.0f; + // ap output: (h[0]*input[N-3] + h[1]*input[N-2] + h[2]*input[N-1])/255.0f; + // p output: (h[0]*input[N-2] + h[1]*input[N-1] )/255.0f; + // last output: h[0]*input[N-1]/255.0f; + for (ImU32 y = 0; y < m_height; y++) { + auto fp = y * fPitch; + auto yp = y * m_pitch; + bool done = false; + for (ImU32 x = 0; x < 4; x++) { + ImU32 head = 0; + ImU32 tail = 0; + if (m_width >= x + 1) { + for (ImU32 i = 0; i < x + 1; i++) { + head += h[x - i] * m_data[yp + i]; + tail += h[i] * m_data[yp + m_width - 1 - x + i]; + } + f.m_data[fp + x] = head >> 8; + f.m_data[fp + paddedWidth - 1 - x] = tail >> 8; + } else { + done = true; + break; + } + } + if (done) + continue; + for (ImU32 x = 4; x < paddedWidth - 4; x++) { + ImU32 head = 0; + for (ImS32 i = 0; i < 5; i++) { + head += h[i] * m_data[yp + i + x - 4]; + } + f.m_data[fp + x] = head >> 8; + } + } + clear(); + m_width = f.m_width; + m_height = f.m_height; + m_pitch = f.m_pitch; + m_data = std::move(f.m_data); + } + +} \ No newline at end of file diff --git a/lib/third_party/imgui/backend/source/imgui_impl_opengl3.cpp b/lib/third_party/imgui/backend/source/imgui_impl_opengl3.cpp index 6e27f0e85..dadc57e03 100644 --- a/lib/third_party/imgui/backend/source/imgui_impl_opengl3.cpp +++ b/lib/third_party/imgui/backend/source/imgui_impl_opengl3.cpp @@ -419,6 +419,20 @@ void ImGui_ImplOpenGL3_NewFrame() ImGui_ImplOpenGL3_CreateDeviceObjects(); } +// IMHEX PATCH BEGIN +bool useFontShaders = false; + +void FontShadersOn(const ImDrawList *parent_list, const ImDrawCmd *cmd) { + useFontShaders = !useFontShaders; +} +void FontShadersOff(const ImDrawList *parent_list, const ImDrawCmd *cmd) { + useFontShaders = !useFontShaders; +} +ImDrawCallback ImGui_ImplOpenGL3_TurnFontShadersOn = &FontShadersOn; +ImDrawCallback ImGui_ImplOpenGL3_TurnFontShadersOff = &FontShadersOff; + +// IMHEX PATCH END + static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -426,7 +440,19 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); + // IMHEX PATCH BEGIN +#if !defined(__EMSCRIPTEN__) + if (useFontShaders) { + glBlendFuncSeparate(GL_SRC1_COLOR, GL_ONE_MINUS_SRC1_COLOR,GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } else { + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } +#else + // IMHEX PATCH END glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + // IMHEX PATCH BEGIN +#endif + // IMHEX PATCH END glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); @@ -461,9 +487,19 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid #if defined(GL_CLIP_ORIGIN) if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left #endif +// IMHEX PATCH BEGIN + float Gamma; + if (useFontShaders) + Gamma = 1.0f; + else + Gamma = 0.0f; +// IMHEX PATCH END const float ortho_projection[4][4] = { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + // IMHEX PATCH BEGIN + { 2.0f/(R-L), 0.0f, Gamma, 0.0f }, + // { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + // IMHEX PATCH END { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f, 0.0f }, { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, @@ -603,6 +639,14 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + // IMHEX PATCH BEGIN + else if (pcmd->UserCallback == ImGui_ImplOpenGL3_TurnFontShadersOn) { + ImGui_ImplOpenGL3_TurnFontShadersOn(cmd_list, pcmd); + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + } else if (pcmd->UserCallback == ImGui_ImplOpenGL3_TurnFontShadersOff) { + ImGui_ImplOpenGL3_TurnFontShadersOff(cmd_list, pcmd); + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + } // IMHEX PATCH END else pcmd->UserCallback(cmd_list, pcmd); } @@ -827,11 +871,21 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" + // IMHEX PATCH BEGIN + "out mat4 Frag_mat;\n" + // IMHEX PATCH END "void main()\n" "{\n" " Frag_UV = UV;\n" + // IMHEX PATCH BEGIN + " mat4 projMtx = ProjMtx;\n" + " projMtx[0][2] = 0.0;\n" + " Frag_mat = ProjMtx;\n" " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + " gl_Position = projMtx * vec4(Position.xy,0,1);\n" + //" Frag_Color = Color;\n" + // " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + // IMHEX PATCH END "}\n"; const GLchar* fragment_shader_glsl_120 = @@ -870,11 +924,27 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() const GLchar* fragment_shader_glsl_410_core = "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" + // IMHEX PATCH BEGIN + "in mat4 Frag_mat;\n" + // IMHEX PATCH END "uniform sampler2D Texture;\n" "layout (location = 0) out vec4 Out_Color;\n" + // IMHEX PATCH BEGIN + "layout (location = 0, index = 1) out vec4 SRC1_Color;\n" + // IMHEX PATCH END "void main()\n" "{\n" - " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + // IMHEX PATCH BEGIN + " float Gamma = Frag_mat[0][2];\n" + " if (Gamma == 0.0)\n" + // IMHEX PATCH END + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + // IMHEX PATCH BEGIN + " else {\n" + " Out_Color = Frag_Color;\n" + " SRC1_Color = vec4(texture(Texture, Frag_UV.st).rgb * Frag_Color.aaa,1.0);\n" + " }\n" + // IMHEX PATCH END "}\n"; // Select shaders matching our GLSL versions diff --git a/lib/third_party/imgui/imgui/include/misc/freetype/imgui_freetype.h b/lib/third_party/imgui/imgui/include/misc/freetype/imgui_freetype.h index 6572b1547..502fff5ac 100644 --- a/lib/third_party/imgui/imgui/include/misc/freetype/imgui_freetype.h +++ b/lib/third_party/imgui/imgui/include/misc/freetype/imgui_freetype.h @@ -34,7 +34,8 @@ enum ImGuiFreeTypeBuilderFlags ImGuiFreeTypeBuilderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? ImGuiFreeTypeBuilderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! ImGuiFreeTypeBuilderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs - ImGuiFreeTypeBuilderFlags_Bitmap = 1 << 9 // Enable FreeType bitmap glyphs + ImGuiFreeTypeBuilderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs + ImGuiFreeTypeBuilderFlags_SubPixel = 1 << 10 // Atlas was generated with sub-pixel rendering enabled }; namespace ImGuiFreeType diff --git a/lib/third_party/imgui/imgui/source/imgui_draw.cpp b/lib/third_party/imgui/imgui/source/imgui_draw.cpp index 93c9cefd1..2e0476fd7 100644 --- a/lib/third_party/imgui/imgui/source/imgui_draw.cpp +++ b/lib/third_party/imgui/imgui/source/imgui_draw.cpp @@ -1695,7 +1695,10 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathBezierQuadraticCurveTo(p2, p3, num_segments); PathStroke(col, 0, thickness); } - +// IMHEX PATCH BEGIN +extern ImDrawCallback ImGui_ImplOpenGL3_TurnFontShadersOn; +extern ImDrawCallback ImGui_ImplOpenGL3_TurnFontShadersOff; +// IMHEX PATCH END void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) { if ((col & IM_COL32_A_MASK) == 0) @@ -1713,7 +1716,16 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 font_size = _Data->FontSize; IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - + // IMHEX PATCH BEGIN + int flags; + bool is_subpixel = false; + if (font != nullptr && font->ContainerAtlas != nullptr) { + flags = font->ContainerAtlas->FontBuilderFlags; + is_subpixel = (flags & ImGuiFreeTypeBuilderFlags_SubPixel) != 0; + } + if (is_subpixel) + AddCallback(ImGui_ImplOpenGL3_TurnFontShadersOn, NULL); + // IMHEX PATCH END ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) { @@ -1723,6 +1735,10 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); } font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); + // IMHEX PATCH BEGIN + if (is_subpixel) + AddCallback(ImGui_ImplOpenGL3_TurnFontShadersOff, NULL); + // IMHEX PATCH END } void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) @@ -3313,8 +3329,12 @@ void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_wid TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); const unsigned char* src = pixels; unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + // IMHEX PATCH BEGIN + //for (int n = TexWidth * TexHeight; n > 0; n--) + // *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + for (int n = TexWidth * TexHeight; n > 0; n--,src++) + *dst++ = IM_COL32(*src, *src, *src, *src); + // IMHEX PATCH END } } diff --git a/main/gui/source/init/splash_window.cpp b/main/gui/source/init/splash_window.cpp index eb69fc945..d2a91a500 100644 --- a/main/gui/source/init/splash_window.cpp +++ b/main/gui/source/init/splash_window.cpp @@ -438,8 +438,8 @@ namespace hex::init { glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_TRUE); #else - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); #endif #if defined(OS_LINUX) @@ -520,7 +520,7 @@ namespace hex::init { ImGui_ImplOpenGL3_Init(); ImGui_ImplGlfw_InstallEmscriptenCallbacks(m_window, "#canvas"); #else - ImGui_ImplOpenGL3_Init("#version 130"); + ImGui_ImplOpenGL3_Init("#version 410"); #endif auto &io = ImGui::GetIO(); diff --git a/main/gui/source/window/win_window.cpp b/main/gui/source/window/win_window.cpp index 74e0959bb..0f91ed815 100644 --- a/main/gui/source/window/win_window.cpp +++ b/main/gui/source/window/win_window.cpp @@ -387,8 +387,8 @@ namespace hex { } void Window::configureGLFW() { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_DECORATED, ImHexApi::System::isBorderlessWindowModeEnabled() ? GL_FALSE : GL_TRUE); // Windows versions before Windows 10 have issues with transparent framebuffers diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 0e0c4c768..583b6aa5c 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -1335,7 +1335,7 @@ namespace hex { ImGui_ImplOpenGL3_Init(); ImGui_ImplGlfw_InstallEmscriptenCallbacks(m_window, "#canvas"); #else - ImGui_ImplOpenGL3_Init("#version 130"); + ImGui_ImplOpenGL3_Init("#version 410"); #endif ImGui_ImplGlfw_SetCallbacksChainForAllWindows(true); diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 46c37522b..8e0dd5cc5 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -474,10 +474,10 @@ "hex.builtin.setting.general.auto_backup_time.format.extended": "Every {0}m {1}s", "hex.builtin.setting.general.auto_load_patterns": "Auto-load supported pattern", "hex.builtin.setting.general.server_contact": "Enable update checks and usage statistics", - "hex.builtin.setting.general.max_mem_file_size": "Maximum size of file to load into memory", + "hex.builtin.setting.general.max_mem_file_size": "Max file size to load into RAM", "hex.builtin.setting.general.max_mem_file_size.desc": "Small files are loaded into memory to prevent them from being modified directly on disk.\n\nIncreasing this size allows larger files to be loaded into memory before ImHex resorts to streaming in data from disk.", "hex.builtin.setting.general.network_interface": "Enable network interface", - "hex.builtin.setting.general.pattern_data_max_filter_items": "Max number of items shown when filtering pattern data", + "hex.builtin.setting.general.pattern_data_max_filter_items": "Max filtered pattern items shown", "hex.builtin.setting.general.save_recent_providers": "Save recently used providers", "hex.builtin.setting.general.show_tips": "Show tips on startup", "hex.builtin.setting.general.sync_pattern_source": "Sync pattern source code between providers", diff --git a/plugins/fonts/include/font_atlas.hpp b/plugins/fonts/include/font_atlas.hpp index 72512aaac..61a0bb24e 100644 --- a/plugins/fonts/include/font_atlas.hpp +++ b/plugins/fonts/include/font_atlas.hpp @@ -3,6 +3,10 @@ #include #include #include +#include +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H +#include FT_BITMAP_H #include #include @@ -24,6 +28,34 @@ namespace hex::fonts { return m_font->Descent; } + float calculateFontDescend(FT_Library ft, float fontSize) const { + + if (ft == nullptr) { + log::fatal("FreeType not initialized"); + return 0.0f; + } + + FT_Face face; + if (FT_New_Memory_Face(ft, reinterpret_cast(m_font->ConfigData->FontData), m_font->ConfigData->FontDataSize, 0, &face) != 0) { + log::fatal("Failed to load face"); + return 0.0f; + } + + // Calculate the expected font size + auto size = fontSize; + if (m_font->FontSize > 0.0F) + size = m_font->FontSize * std::max(1.0F, std::floor(ImHexApi::System::getGlobalScale())); + else + size = std::max(1.0F, std::floor(size / ImHexApi::Fonts::DefaultFontSize)) * ImHexApi::Fonts::DefaultFontSize; + + if (FT_Set_Pixel_Sizes(face, size, size) != 0) { + log::fatal("Failed to set pixel size"); + return 0.0f; + } + + return face->size->metrics.descender / 64.0F; + } + ImFont* getFont() { return m_font; } private: @@ -109,6 +141,10 @@ namespace hex::fonts { Font addFontFromMemory(const std::vector &fontData, float fontSize, bool scalable, ImVec2 offset, const ImVector &glyphRange = {}) { auto &storedFontData = m_fontData.emplace_back(fontData); + if (storedFontData.empty()) { + log::fatal("Failed to load font data"); + return Font(); + } auto &config = m_fontConfigs.emplace_back(m_defaultConfig); config.FontDataOwnedByAtlas = false; @@ -152,7 +188,7 @@ namespace hex::fonts { void setAntiAliasing(bool enabled) { if (enabled) - m_defaultConfig.FontBuilderFlags &= ~ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting; + m_defaultConfig.FontBuilderFlags &= ~(ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting); else m_defaultConfig.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting; } @@ -197,9 +233,16 @@ namespace hex::fonts { return m_fontAtlas; } - float calculateFontDescend(const ImHexApi::Fonts::Font &font, float fontSize) const { - auto atlas = std::make_unique(); - auto cfg = m_defaultConfig; + float calculateFontDescend( FT_Library ft, const ImHexApi::Fonts::Font &font, float fontSize) const { + if (ft == nullptr) { + log::fatal("FreeType not initialized"); + return 0.0f; + } + FT_Face face; + if (FT_New_Memory_Face(ft, reinterpret_cast(font.fontData.data()), font.fontData.size(), 0, &face) != 0) { + log::fatal("Failed to load face"); + return 0.0f; + } // Calculate the expected font size auto size = fontSize; @@ -208,28 +251,20 @@ namespace hex::fonts { else size = std::max(1.0F, std::floor(size / ImHexApi::Fonts::DefaultFontSize)) * ImHexApi::Fonts::DefaultFontSize; - cfg.MergeMode = false; - cfg.SizePixels = size; - cfg.FontDataOwnedByAtlas = false; - - // Construct a range that only contains the first glyph of the font - ImVector queryRange; - { - auto firstGlyph = font.glyphRanges.empty() ? m_glyphRange.front() : font.glyphRanges.front().begin; - queryRange.push_back(firstGlyph); - queryRange.push_back(firstGlyph); + if (FT_Set_Pixel_Sizes(face, size, size) != 0) { + log::fatal("Failed to set pixel size"); + return false; } - queryRange.push_back(0x00); - // Build the font atlas with the query range - auto newFont = atlas->AddFontFromMemoryTTF(const_cast(font.fontData.data()), int(font.fontData.size()), 0, &cfg, queryRange.Data); - atlas->Build(); - - return newFont->Descent; + return face->size->metrics.descender / 64.0F; } void reset() { m_fontData.clear(); + m_glyphRange.clear(); + m_fontSizes.clear(); + m_fontConfigs.clear(); + m_fontAtlas->Clear(); m_defaultConfig.MergeMode = false; } @@ -255,5 +290,4 @@ namespace hex::fonts { std::list> m_fontData; }; - } \ No newline at end of file diff --git a/plugins/fonts/include/font_settings.hpp b/plugins/fonts/include/font_settings.hpp index 5201723e1..de6917760 100644 --- a/plugins/fonts/include/font_settings.hpp +++ b/plugins/fonts/include/font_settings.hpp @@ -1,8 +1,17 @@ #pragma once +#include #include namespace hex::fonts { + class AntialiasPicker : public ContentRegistry::Settings::Widgets::DropDown { + public: + AntialiasPicker() : DropDown( + std::vector({"hex.fonts.setting.font.antialias_none", "hex.fonts.setting.font.antialias_grayscale", "hex.fonts.setting.font.antialias_subpixel"}), + std::vector({"none" , "grayscale" , "subpixel"}), + nlohmann::json("subpixel") + ){} + }; class FontFilePicker : public ContentRegistry::Settings::Widgets::FilePicker { public: @@ -31,7 +40,7 @@ namespace hex::fonts { class FontSelector : public ContentRegistry::Settings::Widgets::Widget { public: - FontSelector() : m_fontSize(16, 2, 100), m_bold(false), m_italic(false), m_antiAliased(true) { } + FontSelector() : m_fontSize(ImHexApi::Fonts::pointsToPixels(10), 2, 100), m_antiAliased(), m_bold(false), m_italic(false) { } bool draw(const std::string &name) override; @@ -43,7 +52,7 @@ namespace hex::fonts { [[nodiscard]] float getFontSize() const; [[nodiscard]] bool isBold() const; [[nodiscard]] bool isItalic() const; - [[nodiscard]] bool isAntiAliased() const; + [[nodiscard]] const std::string antiAliasingType() const; private: bool drawPopup(); @@ -51,7 +60,8 @@ namespace hex::fonts { private: FontFilePicker m_fontFilePicker; SliderPoints m_fontSize; - ContentRegistry::Settings::Widgets::Checkbox m_bold, m_italic, m_antiAliased; + AntialiasPicker m_antiAliased; + ContentRegistry::Settings::Widgets::Checkbox m_bold, m_italic; bool m_applyEnabled = false; }; diff --git a/plugins/fonts/romfs/lang/en_US.json b/plugins/fonts/romfs/lang/en_US.json index f0586ae6f..951d3ff6c 100644 --- a/plugins/fonts/romfs/lang/en_US.json +++ b/plugins/fonts/romfs/lang/en_US.json @@ -16,6 +16,9 @@ "hex.fonts.setting.font.load_all_unicode_chars": "Load all unicode glyphs", "hex.fonts.font.default": "General UI Font", "hex.fonts.font.hex_editor": "Hex Editor Font", - "hex.fonts.font.code_editor": "Code Editor Font" + "hex.fonts.font.code_editor": "Code Editor Font", + "hex.fonts.setting.font.antialias_none": "None", + "hex.fonts.setting.font.antialias_grayscale": "Grayscale", + "hex.fonts.setting.font.antialias_subpixel": "Subpixel" } } diff --git a/plugins/fonts/source/font_loader.cpp b/plugins/fonts/source/font_loader.cpp index eb488c31d..c201d727d 100644 --- a/plugins/fonts/source/font_loader.cpp +++ b/plugins/fonts/source/font_loader.cpp @@ -1,3 +1,7 @@ +#if defined(_MSC_VER) +#include +#endif + #include #include #include @@ -8,25 +12,149 @@ #include #include #include +#include #include +#include +#include "imgui_impl_opengl3_loader.h" #include namespace hex::fonts { - bool buildFontAtlas(FontAtlas *fontAtlas, std::fs::path fontPath, bool pixelPerfectFont, float fontSize, bool loadUnicodeCharacters, bool bold, bool italic, bool antialias) { - if (fontAtlas == nullptr) { + bool BuildSubPixelAtlas(FontAtlas *fontAtlas, float fontSize) { + FT_Library ft = nullptr; + if (FT_Init_FreeType(&ft) != 0) { + log::fatal("Failed to initialize FreeType"); return false; } - fontAtlas->reset(); + FT_Face face; + auto io = ImGui::GetIO(); + io.Fonts = fontAtlas->getAtlas(); + if (io.Fonts->ConfigData.Size <= 0) { + log::fatal("No font data found"); + return false; + } else { + ImVector rect_ids; + std::map bitmapLCD; + ImU32 fontCount = io.Fonts->ConfigData.Size; + for (ImU32 i = 0; i < fontCount; i++) { + std::string fontName = io.Fonts->ConfigData[i].Name; + + std::ranges::transform(fontName.begin(), fontName.end(), fontName.begin(), [](unsigned char c) { return std::tolower(c); }); + if (fontName == "nonscalable") { + continue; + } + + if (FT_New_Memory_Face(ft, reinterpret_cast(io.Fonts->ConfigData[i].FontData), io.Fonts->ConfigData[i].FontDataSize, 0, &face) != 0) { + log::fatal("Failed to load face"); + return false; + } + + float actualFontSize; + if (fontName.find("icon") != std::string::npos) + actualFontSize = ImHexApi::Fonts::pointsToPixels(fontSize); + else + actualFontSize = fontSize; + + if (FT_Set_Pixel_Sizes(face, actualFontSize, actualFontSize) != 0) { + log::fatal("Failed to set pixel size"); + return false; + } + + FT_UInt gIndex; + FT_ULong charCode = FT_Get_First_Char(face, &gIndex); + + while (gIndex != 0) { + + + FT_UInt glyph_index = FT_Get_Char_Index(face, charCode); + if (FT_Load_Glyph(face, glyph_index, FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LIGHT | FT_LOAD_RENDER) != 0) { + IM_ASSERT(true && "Failed to load glyph"); + return false; + } + + ft::Bitmap bitmap_lcd = ft::Bitmap(face->glyph->bitmap.width, face->glyph->bitmap.rows, face->glyph->bitmap.pitch, face->glyph->bitmap.buffer); + if (face->glyph->bitmap.width * face->glyph->bitmap.rows == 0) { + charCode = FT_Get_Next_Char(face, charCode, &gIndex); + continue; + } + + auto width = bitmap_lcd.getWidth() / 3; + auto height = bitmap_lcd.getHeight(); + FT_GlyphSlot slot = face->glyph; + FT_Size size = face->size; + + ImVec2 offset = ImVec2((slot->metrics.horiBearingX / 64.0f), (size->metrics.ascender - slot->metrics.horiBearingY) / 64.0f); + if (fontName.find("codicon") != std::string::npos) + offset.x -= 1.0f; + ImS32 advance = (float) slot->advance.x / 64.0f; + if (offset.x+width > advance && advance >= (int) width) + offset.x = advance - width; + + ImS32 rect_id = io.Fonts->AddCustomRectFontGlyph(io.Fonts->Fonts[0], charCode, width, height, advance, offset); + rect_ids.push_back(rect_id); + bitmapLCD.insert(std::make_pair(rect_id, bitmap_lcd)); + charCode = FT_Get_Next_Char(face, charCode, &gIndex); + } + FT_Done_Face(face); + } + fontAtlas->getAtlas()->FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_SubPixel; + fontAtlas->build(); + ImU8 *tex_pixels_ch = nullptr; + ImS32 tex_width; + + ImS32 tex_height; + fontAtlas->getAtlas()->GetTexDataAsRGBA32(&tex_pixels_ch, &tex_width, &tex_height); + ImU32 *tex_pixels = reinterpret_cast(tex_pixels_ch); + for (auto rect_id: rect_ids) { + if (const ImFontAtlasCustomRect *rect = io.Fonts->GetCustomRectByIndex(rect_id)) { + if (rect->X == 0xFFFF || rect->Y == 0xFFFF || !bitmapLCD.contains(rect_id)) + continue; + + ft::Bitmap bitmapLCDSaved = bitmapLCD.at(rect_id); + ImU32 imageWidth = bitmapLCDSaved.getWidth() / 3; + ImU32 imageHeight = bitmapLCDSaved.getHeight(); + const ImU8 *bitmapBuffer = bitmapLCDSaved.getData(); + + for (ImU32 y = 0; y < imageHeight; y++) { + ImU32 *p = tex_pixels + (rect->Y + y) * tex_width + (rect->X); + for (ImU32 x = 0; x < imageWidth; x++) { + const ImU8 *bitmapPtrLCD = &bitmapBuffer[y * bitmapLCDSaved.getPitch() + 3 * x]; + *p++ = ft::RGBA::addAlpha(*bitmapPtrLCD, *(bitmapPtrLCD + 1), *(bitmapPtrLCD + 2)); + } + } + } + } + if (ft != nullptr) { + FT_Done_FreeType(ft); + ft = nullptr; + } + } + return true; + } + + bool buildFontAtlas(FontAtlas *fontAtlas, std::fs::path fontPath, bool pixelPerfectFont, float fontSize, bool loadUnicodeCharacters, bool bold, bool italic,const std::string &antiAliasType) { + if (fontAtlas == nullptr) { + return false; + } + auto realFontSize = ImHexApi::Fonts::pointsToPixels(fontSize); + bool antialias = antiAliasType == "grayscale"; + bool monochrome = antiAliasType == "none"; + FT_Library ft = nullptr; + if (FT_Init_FreeType(&ft) != 0) { + log::fatal("Failed to initialize FreeType"); + return false; + } + fontAtlas->reset(); + u32 fontIndex = 0; + auto io = ImGui::GetIO(); + io.Fonts = fontAtlas->getAtlas(); // Check if Unicode support is enabled in the settings and that the user doesn't use the No GPU version on Windows // The Mesa3D software renderer on Windows identifies itself as "VMware, Inc." - bool shouldLoadUnicode = - ContentRegistry::Settings::read("hex.fonts.setting.font", "hex.builtin.fonts.font.load_all_unicode_chars", false) && - ImHexApi::System::getGPUVendor() != "VMware, Inc."; + bool shouldLoadUnicode = ContentRegistry::Settings::read("hex.fonts.setting.font", "hex.builtin.fonts.font.load_all_unicode_chars", false) && ImHexApi::System::getGPUVendor() != "VMware, Inc."; if (!loadUnicodeCharacters) shouldLoadUnicode = false; @@ -37,7 +165,8 @@ namespace hex::fonts { if (!pixelPerfectFont) { fontAtlas->setBold(bold); fontAtlas->setItalic(italic); - fontAtlas->setAntiAliasing(antialias); + if (antialias || monochrome) + fontAtlas->setAntiAliasing(antialias); } else { fontPath.clear(); } @@ -45,8 +174,11 @@ namespace hex::fonts { // Try to load the custom font if one was set std::optional defaultFont; if (!fontPath.empty()) { - defaultFont = fontAtlas->addFontFromFile(fontPath, fontSize, true, ImVec2()); - if (!fontAtlas->build()) { + defaultFont = fontAtlas->addFontFromFile(fontPath, realFontSize, true, ImVec2()); + std::string defaultFontName = defaultFont.has_value() ? fontPath.filename().string() : "Custom Font"; + memcpy(fontAtlas->getAtlas()->ConfigData[fontIndex].Name, defaultFontName.c_str(), defaultFontName.size()); + fontIndex += 1; + if ((antialias || monochrome) && !fontAtlas->build()) { log::error("Failed to load custom font '{}'! Falling back to default font", wolv::util::toUTF8String(fontPath)); defaultFont.reset(); } @@ -57,12 +189,14 @@ namespace hex::fonts { if (pixelPerfectFont) { fontSize = std::max(1.0F, std::floor(ImHexApi::System::getGlobalScale() * ImHexApi::System::getBackingScaleFactor() * 13.0F)); defaultFont = fontAtlas->addDefaultFont(); - } else - defaultFont = fontAtlas->addFontFromRomFs("fonts/JetBrainsMono.ttf", fontSize, true, ImVec2()); - - if (!fontAtlas->build()) { - log::fatal("Failed to load default font!"); - return false; + std::string defaultFontName = "Proggy Clean"; + memcpy(fontAtlas->getAtlas()->ConfigData[fontIndex].Name, defaultFontName.c_str(), defaultFontName.size()); + fontIndex += 1; + } else { + defaultFont = fontAtlas->addFontFromRomFs("fonts/JetBrainsMono.ttf", realFontSize, true, ImVec2()); + std::string defaultFontName = "JetBrains Mono"; + memcpy(fontAtlas->getAtlas()->ConfigData[fontIndex].Name, defaultFontName.c_str(), defaultFontName.size()); + fontIndex += 1; } } @@ -85,29 +219,37 @@ namespace hex::fonts { glyphRanges.push_back(glyphRange); // Calculate the glyph offset for the font - const ImVec2 offset = { font.offset.x, font.offset.y - (defaultFont->getDescent() - fontAtlas->calculateFontDescend(font, fontSize)) }; + const ImVec2 offset = { font.offset.x, font.offset.y - (defaultFont->calculateFontDescend(ft, realFontSize) - fontAtlas->calculateFontDescend(ft, font, realFontSize)) }; // Load the font - float size = fontSize; + float size = realFontSize; if (font.defaultSize.has_value()) size = font.defaultSize.value() * ImHexApi::System::getBackingScaleFactor(); fontAtlas->addFontFromMemory(font.fontData, size, !font.defaultSize.has_value(), offset, glyphRanges.back()); + if (!font.scalable.value_or(true)) { + std::string fontName = "NonScalable"; + auto nameSize = fontName.size(); + memcpy(fontAtlas->getAtlas()->ConfigData[fontIndex].Name, fontName.c_str(), nameSize); + } else { + auto nameSize = font.name.size(); + memcpy(fontAtlas->getAtlas()->ConfigData[fontIndex].Name, font.name.c_str(), nameSize); + } + fontIndex += 1; } } - // Build the font atlas - if (fontAtlas->build()) { - fontAtlas->reset(); + if (ft != nullptr) { + FT_Done_FreeType(ft); + ft = nullptr; + } + + if (antialias || monochrome) { + if (!fontAtlas->build()) { + log::fatal("Failed to load font!"); + return false; + } return true; - } - - // If the build wasn't successful and Unicode characters are enabled, try again without them - // If they were disabled already, something went wrong, and we can't recover from it - if (!shouldLoadUnicode) { - return false; - } else { - return buildFontAtlas(fontAtlas, fontPath, pixelPerfectFont, fontSize, false, bold, italic, antialias); - } + } else + return BuildSubPixelAtlas(fontAtlas,fontSize); } - } \ No newline at end of file diff --git a/plugins/fonts/source/font_settings.cpp b/plugins/fonts/source/font_settings.cpp index 2e0668548..dcbe12bcd 100644 --- a/plugins/fonts/source/font_settings.cpp +++ b/plugins/fonts/source/font_settings.cpp @@ -5,6 +5,7 @@ #include #include +#include "hex/api/imhex_api.hpp" namespace hex::fonts { constexpr static auto PixelPerfectName = "Pixel-Perfect Default Font (Proggy Clean)"; @@ -99,22 +100,14 @@ namespace hex::fonts { return customFont; } - static float pixelsToPoints(float pixels) { - return pixels * (72_scaled / 96.0F); - } - - static float pointsToPixels(float points) { - return points / (72_scaled / 96.0F); - } - bool SliderPoints::draw(const std::string &name) { - float value = pixelsToPoints(m_value); - float min = pixelsToPoints(m_min); - float max = pixelsToPoints(m_max); + float value = ImHexApi::Fonts::pixelsToPoints(m_value); + float min = ImHexApi::Fonts::pixelsToPoints(m_min); + float max = ImHexApi::Fonts::pixelsToPoints(m_max); auto changed = ImGui::SliderFloat(name.c_str(), &value, min, max, "%.0f pt"); - m_value = pointsToPixels(value); + m_value = ImHexApi::Fonts::pointsToPixels(value); return changed; } @@ -204,8 +197,10 @@ namespace hex::fonts { return m_italic.isChecked(); } - [[nodiscard]] bool FontSelector::isAntiAliased() const { - return m_antiAliased.isChecked(); + [[nodiscard]] const std::string FontSelector::antiAliasingType() const { + if (isPixelPerfectFont()) + return "none"; + return m_antiAliased.getValue(); } diff --git a/plugins/fonts/source/fonts.cpp b/plugins/fonts/source/fonts.cpp index a626756a4..ded8c2886 100644 --- a/plugins/fonts/source/fonts.cpp +++ b/plugins/fonts/source/fonts.cpp @@ -26,7 +26,7 @@ namespace hex::fonts { }, { -1_scaled, -1_scaled }); - ImHexApi::Fonts::loadFont("Unifont", romfs::get("fonts/unifont.otf").span(), { }, {}, 0, 16); + ImHexApi::Fonts::loadFont("Unifont", romfs::get("fonts/unifont.otf").span(), { }, {}, 0, false, 16); } } \ No newline at end of file diff --git a/plugins/fonts/source/library_fonts.cpp b/plugins/fonts/source/library_fonts.cpp index 2315eca22..13d08a68f 100644 --- a/plugins/fonts/source/library_fonts.cpp +++ b/plugins/fonts/source/library_fonts.cpp @@ -14,7 +14,7 @@ namespace hex::fonts { void registerFonts(); - bool buildFontAtlas(FontAtlas *fontAtlas, std::fs::path fontPath, bool pixelPerfectFont, float fontSize, bool loadUnicodeCharacters, bool bold, bool italic, bool antialias); + bool buildFontAtlas(FontAtlas *fontAtlas, std::fs::path fontPath, bool pixelPerfectFont, float fontSize, bool loadUnicodeCharacters, bool bold, bool italic,const std::string &antialias); static AutoReset>> s_fontAtlases; @@ -31,7 +31,7 @@ namespace hex::fonts { true, settings.isBold(), settings.isItalic(), - settings.isAntiAliased() + settings.antiAliasingType() ); if (!atlasBuilt) { @@ -43,7 +43,7 @@ namespace hex::fonts { false, settings.isBold(), settings.isItalic(), - settings.isAntiAliased() + settings.antiAliasingType() ); log::error("Failed to load font {}! Reverting back to default font!", name.get()); @@ -78,7 +78,6 @@ namespace hex::fonts { return true; } - } IMHEX_LIBRARY_SETUP("Fonts") {