diff --git a/examples/example_win32_directx11_freetype/build_win32.bat b/examples/example_win32_directx11_freetype/build_win32.bat new file mode 100644 index 000000000000..55dcaf97bd5f --- /dev/null +++ b/examples/example_win32_directx11_freetype/build_win32.bat @@ -0,0 +1,10 @@ +@REM Build for Visual Studio compiler. Run your copy of vcvars32.bat or vcvarsall.bat to setup command-line compiler. +@REM Requires FreeType library. Set FREETYPE_DIR to your FreeType installation path. +@set OUT_DIR=Debug +@set OUT_EXE=example_win32_directx11_freetype +@set FREETYPE_DIR=path\to\freetype +@set INCLUDES=/I..\.. /I..\..\backends /I..\..\misc\freetype /I "%FREETYPE_DIR%\include" /I "%WindowsSdkDir%Include\um" /I "%WindowsSdkDir%Include\shared" +@set SOURCES=main.cpp ..\..\backends\imgui_impl_dx11.cpp ..\..\backends\imgui_impl_win32.cpp ..\..\misc\freetype\imgui_freetype.cpp ..\..\imgui*.cpp +@set LIBS=/LIBPATH:"%FREETYPE_DIR%\lib" freetype.lib d3d11.lib d3dcompiler.lib +mkdir %OUT_DIR% +cl /nologo /Zi /MD /utf-8 %INCLUDES% /D UNICODE /D _UNICODE /D IMGUI_ENABLE_FREETYPE %SOURCES% /Fe%OUT_DIR%/%OUT_EXE%.exe /Fo%OUT_DIR%/ /link %LIBS% diff --git a/examples/example_win32_directx11_freetype/example_win32_directx11_freetype.vcxproj b/examples/example_win32_directx11_freetype/example_win32_directx11_freetype.vcxproj new file mode 100644 index 000000000000..be379d27e2b3 --- /dev/null +++ b/examples/example_win32_directx11_freetype/example_win32_directx11_freetype.vcxproj @@ -0,0 +1,112 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3} + example_win32_directx11_freetype + 10.0 + + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + + + + + + + + + + + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + + + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + + + + Level4 + Disabled + ..\..;..\..\backends;..\..\misc\freetype;..\..\..\vcpkg_installed\x64-windows\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;IMGUI_ENABLE_FREETYPE;%(PreprocessorDefinitions) + /utf-8 %(AdditionalOptions) + + + true + freetype.lib;d3d11.lib;d3dcompiler.lib;dxgi.lib;%(AdditionalDependencies) + ..\..\..\vcpkg_installed\x64-windows\lib;%(AdditionalLibraryDirectories) + Console + + + + + Level4 + MaxSpeed + true + true + ..\..;..\..\backends;..\..\misc\freetype;..\..\..\vcpkg_installed\x64-windows\include;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;IMGUI_ENABLE_FREETYPE;%(PreprocessorDefinitions) + false + /utf-8 %(AdditionalOptions) + + + true + true + true + freetype.lib;d3d11.lib;d3dcompiler.lib;dxgi.lib;%(AdditionalDependencies) + ..\..\..\vcpkg_installed\x64-windows\lib;%(AdditionalLibraryDirectories) + Console + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example_win32_directx11_freetype/example_win32_directx11_freetype.vcxproj.filters b/examples/example_win32_directx11_freetype/example_win32_directx11_freetype.vcxproj.filters new file mode 100644 index 000000000000..26b8491dc184 --- /dev/null +++ b/examples/example_win32_directx11_freetype/example_win32_directx11_freetype.vcxproj.filters @@ -0,0 +1,68 @@ + + + + + {20b90ce4-7fcb-4731-b9a0-075f875de82d} + + + {f18ab499-84e1-499f-8eff-9754361e0e52} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + imgui + + + imgui + + + imgui + + + backends + + + backends + + + imgui + + + + + imgui + + + imgui + + + imgui + + + imgui + + + imgui + + + backends + + + backends + + + imgui + + + + + + imgui + + + imgui + + + + diff --git a/examples/example_win32_directx11_freetype/main.cpp b/examples/example_win32_directx11_freetype/main.cpp new file mode 100644 index 000000000000..abc59132262b --- /dev/null +++ b/examples/example_win32_directx11_freetype/main.cpp @@ -0,0 +1,773 @@ +// Dear ImGui: standalone example application for FreeType variable fonts +// This example demonstrates variable font axis support (weight, width, slant, grade, italic). +// Requires: IMGUI_ENABLE_FREETYPE defined, FreeType library linked. + +#include "imgui.h" +#include "imgui_impl_win32.h" +#include "imgui_impl_dx11.h" +#include "imgui_freetype.h" +#include +#include +#include +#include + +// Data +static ID3D11Device* g_pd3dDevice = nullptr; +static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr; +static IDXGISwapChain* g_pSwapChain = nullptr; +static bool g_SwapChainOccluded = false; +static UINT g_ResizeWidth = 0, g_ResizeHeight = 0; +static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr; + +// Forward declarations +bool CreateDeviceD3D(HWND hWnd); +void CleanupDeviceD3D(); +void CreateRenderTarget(); +void CleanupRenderTarget(); +LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// Known variable fonts to search for in Windows fonts directory +// Prioritize fonts with weight axis (wght) for better demo experience +static const char* g_KnownVariableFonts[] = { + "SegoeUI-VF.ttf", // Windows 11 (wght, wdth, slnt, ital) + "bahnschrift.ttf", // Windows 10+ (wght: 300-700) - best for testing! + "CascadiaCode.ttf", // Windows Terminal (wght: 200-700) + "CascadiaMono.ttf", // Windows Terminal mono (wght: 200-700) + "Inter-Variable.ttf", // Popular variable font (wght, wdth, slnt) + "RobotoFlex-VariableFont.ttf", // Google (wght, wdth, slnt, GRAD, opsz) + "SitkaVF.ttf", // Windows 10+ (opsz only - no wght) + nullptr +}; + +// Find first available variable font +static bool FindVariableFont(char* outPath, size_t outPathSize) +{ + char fontsDir[MAX_PATH]; + if (GetWindowsDirectoryA(fontsDir, MAX_PATH) == 0) + strcpy(fontsDir, "C:\\Windows"); + strcat(fontsDir, "\\Fonts\\"); + + for (int i = 0; g_KnownVariableFonts[i] != nullptr; i++) + { + char testPath[MAX_PATH]; + snprintf(testPath, sizeof(testPath), "%s%s", fontsDir, g_KnownVariableFonts[i]); + + FILE* f = fopen(testPath, "rb"); + if (f) + { + fclose(f); + strncpy(outPath, testPath, outPathSize - 1); + outPath[outPathSize - 1] = '\0'; + return true; + } + } + return false; +} + +// Maximum number of variable font axes we support +#define MAX_FONT_AXES 16 + +// Variable font state +struct VariableFontState +{ + char FontPath[512] = ""; + float SizePixels = 32.0f; + char SampleText[128] = "\xc3\x89\xc3\xa7\xc3\xbc\xc3\xb1 \xc3\x85gjpqy"; // "Éçüñ Ågjpqy" in UTF-8 + + // Dynamic axis data (queried from font) + ImFontVarAxisInfo AxisInfo[MAX_FONT_AXES]; // Axis metadata from font + float AxisValues[MAX_FONT_AXES]; // Current axis values + int AxisCount = 0; // Number of axes in font + + // FreeType loader flags + unsigned int FontLoaderFlags = ImGuiFreeTypeLoaderFlags_NoHinting | ImGuiFreeTypeLoaderFlags_Bitmap; + + ImFont* UiFont = nullptr; // Fixed-size font for UI controls + ImFont* PreviewFont = nullptr; // Variable-size font for preview + bool NeedsRebuild = true; + bool NeedsAxisQuery = true; // Query axes on next font load + char StatusMessage[256] = ""; + + void ResetAxesToDefaults() + { + for (int i = 0; i < AxisCount; i++) + AxisValues[i] = AxisInfo[i].Default; + } +}; + +static VariableFontState g_FontState; + +// Check if file exists +static bool FileExists(const char* path) +{ + FILE* f = fopen(path, "rb"); + if (f) { fclose(f); return true; } + return false; +} + +// Rebuild font with current settings +static void RebuildFont(VariableFontState& state) +{ + ImGuiIO& io = ImGui::GetIO(); + + // Check if font file exists + bool fileExists = FileExists(state.FontPath); + + // Query axes if needed (new font loaded) + if (state.NeedsAxisQuery) + { + if (fileExists) + { + state.AxisCount = ImGuiFreeType::GetFontAxes(state.FontPath, state.AxisInfo, MAX_FONT_AXES); + if (state.AxisCount < 0) + state.AxisCount = 0; + state.ResetAxesToDefaults(); + } + else + { + state.AxisCount = 0; + } + state.NeedsAxisQuery = false; + } + + // Clear existing fonts + io.Fonts->Clear(); + + // Always add a default UI font first (ensures we have something valid) + state.UiFont = io.Fonts->AddFontDefault(); + + // Try to load the requested font if file exists + if (fileExists) + { + // Build axis array from current values + static ImFontConfigVarAxis s_previewAxes[MAX_FONT_AXES]; + for (int i = 0; i < state.AxisCount; i++) + s_previewAxes[i] = ImFontConfigVarAxis(state.AxisInfo[i].Tag, state.AxisValues[i]); + + ImFontConfig previewConfig; + previewConfig.FontLoaderFlags = state.FontLoaderFlags; + previewConfig.VarAxes.Axes = s_previewAxes; + previewConfig.VarAxes.AxesCount = state.AxisCount; + + state.PreviewFont = io.Fonts->AddFontFromFileTTF(state.FontPath, state.SizePixels, &previewConfig); + + if (state.PreviewFont) + { + const char* filename = strrchr(state.FontPath, '\\'); + if (!filename) filename = strrchr(state.FontPath, '/'); + filename = filename ? filename + 1 : state.FontPath; + + snprintf(state.StatusMessage, sizeof(state.StatusMessage), + "Loaded: %s @ %.0fpx (%d axes)", filename, state.SizePixels, state.AxisCount); + } + else + { + state.PreviewFont = state.UiFont; + snprintf(state.StatusMessage, sizeof(state.StatusMessage), + "Invalid font file: %s", state.FontPath); + } + } + else + { + state.PreviewFont = state.UiFont; + snprintf(state.StatusMessage, sizeof(state.StatusMessage), + "File not found: %s", state.FontPath); + } + + // Rebuild font atlas + io.Fonts->Build(); + state.NeedsRebuild = false; +} + +int main(int, char**) +{ + // Find a suitable variable font + if (!FindVariableFont(g_FontState.FontPath, sizeof(g_FontState.FontPath))) + { + // Fallback if no variable font found + strcpy(g_FontState.FontPath, "C:\\Windows\\Fonts\\segoeui.ttf"); + } + + // Create application window + WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui FreeType Variable Font Example", nullptr }; + ::RegisterClassExW(&wc); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui FreeType Variable Font Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr); + + // Initialize Direct3D + if (!CreateDeviceD3D(hwnd)) + { + CleanupDeviceD3D(); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); + return 1; + } + + ::ShowWindow(hwnd, SW_SHOWDEFAULT); + ::UpdateWindow(hwnd); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + // Setup style + ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplWin32_Init(hwnd); + ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); + + // Main loop + bool done = false; + while (!done) + { + MSG msg; + while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + if (msg.message == WM_QUIT) + done = true; + } + if (done) + break; + + if (g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) + { + ::Sleep(10); + continue; + } + g_SwapChainOccluded = false; + + if (g_ResizeWidth != 0 && g_ResizeHeight != 0) + { + CleanupRenderTarget(); + g_pSwapChain->ResizeBuffers(0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0); + g_ResizeWidth = g_ResizeHeight = 0; + CreateRenderTarget(); + } + + // Rebuild font if needed (must be done before NewFrame) + if (g_FontState.NeedsRebuild) + { + RebuildFont(g_FontState); + ImGui_ImplDX11_InvalidateDeviceObjects(); + } + + // Start frame + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + // Get display size for window positioning + ImVec2 displaySize = io.DisplaySize; + float padding = 10.0f; + float halfWidth = (displaySize.x - padding * 3) / 2; + float windowHeight = displaySize.y - padding * 2; + + // Variable Font Control Panel (left half) + ImGui::SetNextWindowPos(ImVec2(padding, padding), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(halfWidth, windowHeight), ImGuiCond_Always); + ImGui::Begin("Variable Font Controls", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + { + ImGui::Text("FreeType Variable Font Demo"); + ImGui::Separator(); + + // Font path + ImGui::Text("Font Path:"); + ImGui::SetNextItemWidth(-60); + if (ImGui::InputText("##FontPath", g_FontState.FontPath, sizeof(g_FontState.FontPath), ImGuiInputTextFlags_EnterReturnsTrue)) + { + g_FontState.NeedsAxisQuery = true; + g_FontState.NeedsRebuild = true; + } + ImGui::SameLine(); + if (ImGui::Button("Load")) + { + g_FontState.NeedsAxisQuery = true; + g_FontState.NeedsRebuild = true; + } + + ImGui::Separator(); + + // Size (always available) + ImGui::SetNextItemWidth(200); + if (ImGui::SliderFloat("Size (px)", &g_FontState.SizePixels, 8.0f, 72.0f, "%.0f")) + g_FontState.NeedsRebuild = true; + + // Sample text for metrics visualization (under Size) + ImGui::AlignTextToFramePadding(); + ImGui::Text("Sample:"); ImGui::SameLine(); + ImGui::SetNextItemWidth(140); + ImGui::InputText("##SampleText", g_FontState.SampleText, sizeof(g_FontState.SampleText)); + + // FreeType loader flags (two-column layout) + ImGui::Separator(); + ImGui::Text("FreeType Loader Flags:"); + { + bool edited = false; + if (ImGui::BeginTable("##FontLoaderFlags", 2)) + { + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("NoHinting", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_NoHinting); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("Bold", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_Bold); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("NoAutoHint", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_NoAutoHint); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("Oblique", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_Oblique); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("ForceAutoHint", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_ForceAutoHint); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("Monochrome", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_Monochrome); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("LightHinting", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_LightHinting); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("LoadColor", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_LoadColor); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("MonoHinting", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_MonoHinting); + ImGui::TableNextColumn(); edited |= ImGui::CheckboxFlags("Bitmap", &g_FontState.FontLoaderFlags, ImGuiFreeTypeLoaderFlags_Bitmap); + ImGui::EndTable(); + } + if (edited) + g_FontState.NeedsRebuild = true; + } + + // Dynamic axis sliders + if (g_FontState.AxisCount > 0) + { + ImGui::Separator(); + ImGui::Text("Variable Font Axes (%d):", g_FontState.AxisCount); + + if (ImGui::BeginTable("##AxisSliders", 3, ImGuiTableFlags_SizingFixedFit)) + { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Slider", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Reset", ImGuiTableColumnFlags_WidthFixed, 25.0f); + + for (int i = 0; i < g_FontState.AxisCount; i++) + { + const ImFontVarAxisInfo& axis = g_FontState.AxisInfo[i]; + + // Format axis tag as 4 characters for display + char tagStr[8]; + tagStr[0] = (char)((axis.Tag >> 24) & 0xFF); + tagStr[1] = (char)((axis.Tag >> 16) & 0xFF); + tagStr[2] = (char)((axis.Tag >> 8) & 0xFF); + tagStr[3] = (char)(axis.Tag & 0xFF); + tagStr[4] = '\0'; + + ImGui::TableNextRow(); + + // Column 1: Name (tag) + ImGui::TableNextColumn(); + ImGui::Text("%s (%s)", axis.Name[0] ? axis.Name : "???", tagStr); + + // Column 2: Slider + ImGui::TableNextColumn(); + char sliderId[32]; + snprintf(sliderId, sizeof(sliderId), "##axis%d", i); + ImGui::SetNextItemWidth(-FLT_MIN); // Fill column width + if (ImGui::SliderFloat(sliderId, &g_FontState.AxisValues[i], axis.Minimum, axis.Maximum, "%.0f")) + g_FontState.NeedsRebuild = true; + + // Column 3: Reset button + ImGui::TableNextColumn(); + char resetLabel[32]; + snprintf(resetLabel, sizeof(resetLabel), "R##%d", i); + if (ImGui::SmallButton(resetLabel)) + { + g_FontState.AxisValues[i] = axis.Default; + g_FontState.NeedsRebuild = true; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Reset to default: %.0f", axis.Default); + } + ImGui::EndTable(); + } + } + else + { + ImGui::Separator(); + ImGui::TextDisabled("No variable font axes found."); + ImGui::TextDisabled("This font may not be a variable font."); + } + + ImGui::Separator(); + + // Reset button + if (ImGui::Button("Reset to Defaults", ImVec2(200, 0))) + { + g_FontState.SizePixels = 32.0f; + g_FontState.ResetAxesToDefaults(); + g_FontState.NeedsRebuild = true; + } + + // Status + ImGui::Separator(); + ImGui::TextWrapped("Status: %s", g_FontState.StatusMessage); + } + ImGui::End(); + + // Font Preview Window (right half) + ImGui::SetNextWindowPos(ImVec2(padding * 2 + halfWidth, padding), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(halfWidth, windowHeight), ImGuiCond_Always); + ImGui::Begin("Font Preview", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + { + // Show current axis values compactly (with wrapping) + ImGui::TextDisabled("%.0fpx", g_FontState.SizePixels); + for (int i = 0; i < g_FontState.AxisCount; i++) + { + char tagStr[8]; + tagStr[0] = (char)((g_FontState.AxisInfo[i].Tag >> 24) & 0xFF); + tagStr[1] = (char)((g_FontState.AxisInfo[i].Tag >> 16) & 0xFF); + tagStr[2] = (char)((g_FontState.AxisInfo[i].Tag >> 8) & 0xFF); + tagStr[3] = (char)(g_FontState.AxisInfo[i].Tag & 0xFF); + tagStr[4] = '\0'; + + // Calculate text width to check if it fits on current line + char axisText[32]; + snprintf(axisText, sizeof(axisText), "%s=%.0f", tagStr, g_FontState.AxisValues[i]); + float textWidth = ImGui::CalcTextSize(axisText).x; + + // Try to put on same line, wrap if doesn't fit + ImGui::SameLine(); + if (ImGui::GetContentRegionAvail().x < textWidth) + ImGui::NewLine(); + + ImGui::TextDisabled("%s", axisText); + } + + ImGui::Separator(); + + // Visual metrics diagram + if (g_FontState.PreviewFont) + { + ImFont* font = g_FontState.PreviewFont; + ImFontBaked* baked = font->GetFontBaked(g_FontState.SizePixels); + if (!baked) + { + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "Font not baked at this size"); + } + else + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 canvasPos = ImGui::GetCursorScreenPos(); + + // Get sample text and first character for metrics + const char* sampleText = g_FontState.SampleText; + if (sampleText[0] == '\0') sampleText = "A"; // Fallback + + // Decode first UTF-8 character (simple inline decoder) + ImWchar firstChar = 0; + unsigned char c = (unsigned char)sampleText[0]; + if (c < 0x80) { + firstChar = c; + } else if ((c & 0xE0) == 0xC0) { + firstChar = ((c & 0x1F) << 6) | (sampleText[1] & 0x3F); + } else if ((c & 0xF0) == 0xE0) { + firstChar = ((c & 0x0F) << 12) | ((sampleText[1] & 0x3F) << 6) | (sampleText[2] & 0x3F); + } else { + firstChar = '?'; // Fallback for 4-byte sequences or invalid + } + + // Font metrics + // In ImGui/FreeType: Ascent is positive, Descent is negative + float fontSize = baked->Size; // Em height (requested size) + float ascent = baked->Ascent; // Positive distance above baseline + float descent = baked->Descent; // Negative distance below baseline + float cellHeight = ascent - descent; // Total cell height (ascent + |descent|) + float lineHeight = fontSize; // Em height = line height in ImGui + float internalLeading = cellHeight - lineHeight; // Positive = cell extends beyond em square + float leadingTop = internalLeading / 2.0f; + float leadingBottom = internalLeading - leadingTop; + float baselineFromTop = leadingTop + ascent; // Where baseline sits from top of line + + // Layout - left margin for vertical measurements + float leftMargin = 50.0f; // Space for 3 vertical measurements on left + float topPadding = 20.0f; + float bottomPadding = 44.0f; // Space for horizontal metrics (24px) + 20px gap to legend + + // Visual height is the max of cell height and line height + float visualHeight = (cellHeight > lineHeight) ? cellHeight : lineHeight; + float diagramHeight = visualHeight + topPadding + bottomPadding; + float diagramWidth = ImGui::GetContentRegionAvail().x; + + ImGui::Dummy(ImVec2(diagramWidth, diagramHeight)); + + // Y positions - anchor from visual top + // When internalLeading <= 0: line bounds contain text bounds (yLineTop is visual top) + // When internalLeading > 0: text bounds extend beyond line bounds (yAscent is visual top) + float yVisualTop = canvasPos.y + topPadding; + float yLineTop, yAscent, yBaseline, yDescent, yLineBottom, yVisualBottom; + if (internalLeading <= 0) { + // Normal case: text fits within line height + yLineTop = yVisualTop; + yAscent = yLineTop - leadingTop; // leadingTop is negative, so ascent is below line top + yBaseline = yAscent + ascent; + yDescent = yBaseline - descent; + yLineBottom = yDescent - leadingBottom; // leadingBottom is negative + yVisualBottom = yLineBottom; + } else { + // Text extends beyond em square - anchor from ascent + yAscent = yVisualTop; + yLineTop = yAscent + leadingTop; // leadingTop is positive, so line top is below ascent + yBaseline = yAscent + ascent; + yDescent = yBaseline - descent; + yLineBottom = yDescent - leadingBottom; // leadingBottom is positive, so line bottom is above descent + yVisualBottom = yDescent; + } + + // X position - text starts after left margin for measurements + float xTextStart = canvasPos.x + leftMargin; + + // Colors + ImU32 colBlue = IM_COL32(66, 135, 245, 255); // Ascent + ImU32 colGreen = IM_COL32(87, 200, 87, 255); // Baseline + ImU32 colOrange = IM_COL32(235, 160, 60, 255); // Descent + ImU32 colPurple = IM_COL32(180, 100, 220, 255); // Text width + ImU32 colCyan = IM_COL32(80, 200, 220, 255); // Character width + ImU32 colRed = IM_COL32(220, 80, 80, 255); // Line height + ImU32 colText = IM_COL32(255, 255, 255, 255); + + // Helper lambda to draw dashed horizontal line + auto drawDashedLineH = [&](float x1, float x2, float y, ImU32 col, float thickness = 1.0f) { + float dashLen = 6.0f, gapLen = 4.0f; + float x = x1; + while (x < x2) { + float endX = (x + dashLen < x2) ? x + dashLen : x2; + drawList->AddLine(ImVec2(x, y), ImVec2(endX, y), col, thickness); + x += dashLen + gapLen; + } + }; + + // Helper lambda to draw dashed vertical line + auto drawDashedLineV = [&](float x, float y1, float y2, ImU32 col, float thickness = 1.0f) { + float dashLen = 6.0f, gapLen = 4.0f; + float y = y1; + while (y < y2) { + float endY = (y + dashLen < y2) ? y + dashLen : y2; + drawList->AddLine(ImVec2(x, y), ImVec2(x, endY), col, thickness); + y += dashLen + gapLen; + } + }; + + // Calculate text width first (needed for line bounds) + ImVec2 textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, sampleText); + float textEndX = xTextStart + textSize.x; + + // Get first character glyph metrics + float firstCharAdvance = 0.0f; + if (ImFontGlyph* glyph = baked->FindGlyph(firstChar)) + firstCharAdvance = glyph->AdvanceX; + + // Draw background - only within text width + drawList->AddRectFilled(ImVec2(xTextStart, yAscent), ImVec2(textEndX, yDescent), IM_COL32(35, 35, 35, 255)); + // Leading areas (top and bottom) + drawList->AddRectFilled(ImVec2(xTextStart, yLineTop), ImVec2(textEndX, yAscent), IM_COL32(50, 35, 35, 255)); + drawList->AddRectFilled(ImVec2(xTextStart, yDescent), ImVec2(textEndX, yLineBottom), IM_COL32(50, 35, 35, 255)); + + // Draw horizontal metric lines (dashed, 2px) - only within text width + drawDashedLineH(xTextStart, textEndX, yAscent, colBlue, 2.0f); // Ascent + drawDashedLineH(xTextStart, textEndX, yBaseline, colGreen, 2.0f); // Baseline + drawDashedLineH(xTextStart, textEndX, yDescent, colOrange, 2.0f); // Descent + + // Draw sample text + font->RenderText(drawList, fontSize, ImVec2(xTextStart, yAscent), colText, + ImVec4(0, 0, 10000, 10000), sampleText, nullptr); + + // Draw leading/line height lines LAST (1px, on top of others) + drawDashedLineH(xTextStart, textEndX, yLineTop, colRed, 1.0f); // Line height top + drawDashedLineH(xTextStart, textEndX, yLineBottom, colRed, 1.0f); // Line height bottom + + // Vertical measurements to the LEFT of text (2px uniform thickness) + float xMeasure1 = canvasPos.x + 10.0f; // Line height + float xMeasure2 = canvasPos.x + 22.0f; // Cell height + float xMeasure3 = canvasPos.x + 34.0f; // Ascent/Descent + + ImU32 colYellow = IM_COL32(220, 200, 80, 255); // Cell height color + + // Line Height measurement (line top to line bottom) + drawList->AddLine(ImVec2(xMeasure1, yLineTop), ImVec2(xMeasure1, yLineBottom), colRed, 2.0f); + drawList->AddLine(ImVec2(xMeasure1 - 3, yLineTop), ImVec2(xMeasure1 + 3, yLineTop), colRed, 2.0f); + drawList->AddLine(ImVec2(xMeasure1 - 3, yLineBottom), ImVec2(xMeasure1 + 3, yLineBottom), colRed, 2.0f); + + // Cell Height measurement (ascent to descent) + drawList->AddLine(ImVec2(xMeasure2, yAscent), ImVec2(xMeasure2, yDescent), colYellow, 2.0f); + drawList->AddLine(ImVec2(xMeasure2 - 3, yAscent), ImVec2(xMeasure2 + 3, yAscent), colYellow, 2.0f); + drawList->AddLine(ImVec2(xMeasure2 - 3, yDescent), ImVec2(xMeasure2 + 3, yDescent), colYellow, 2.0f); + + // Ascent measurement (ascent line to baseline) + drawList->AddLine(ImVec2(xMeasure3, yAscent), ImVec2(xMeasure3, yBaseline), colBlue, 2.0f); + drawList->AddLine(ImVec2(xMeasure3 - 3, yAscent), ImVec2(xMeasure3 + 3, yAscent), colBlue, 2.0f); + drawList->AddLine(ImVec2(xMeasure3 - 3, yBaseline), ImVec2(xMeasure3 + 3, yBaseline), colBlue, 2.0f); + + // Descent measurement (baseline to descent line) + drawList->AddLine(ImVec2(xMeasure3, yBaseline), ImVec2(xMeasure3, yDescent), colOrange, 2.0f); + drawList->AddLine(ImVec2(xMeasure3 - 3, yDescent), ImVec2(xMeasure3 + 3, yDescent), colOrange, 2.0f); + + // Horizontal measurements below the text (use visual bottom to handle negative leading) + float yWidthLine = yVisualBottom + 12.0f; + float yCharLine = yWidthLine + 12.0f; + + // Text width line (purple, 2px) + drawList->AddLine(ImVec2(xTextStart, yWidthLine), ImVec2(textEndX, yWidthLine), colPurple, 2.0f); + drawList->AddLine(ImVec2(xTextStart, yWidthLine - 4), ImVec2(xTextStart, yWidthLine + 4), colPurple, 2.0f); + drawList->AddLine(ImVec2(textEndX, yWidthLine - 4), ImVec2(textEndX, yWidthLine + 4), colPurple, 2.0f); + + // First character AdvanceX line (cyan, 2px) + float charEndX = xTextStart + firstCharAdvance; + drawList->AddLine(ImVec2(xTextStart, yCharLine), ImVec2(charEndX, yCharLine), colCyan, 2.0f); + drawList->AddLine(ImVec2(xTextStart, yCharLine - 4), ImVec2(xTextStart, yCharLine + 4), colCyan, 2.0f); + drawList->AddLine(ImVec2(charEndX, yCharLine - 4), ImVec2(charEndX, yCharLine + 4), colCyan, 2.0f); + + // Legend with values (below the diagram) - 3 equal columns using table + + if (ImGui::BeginTable("##MetricsLegend", 3, ImGuiTableFlags_SizingStretchSame)) + { + // Row 1: Line Height | Ascent | Width + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.86f, 0.31f, 0.31f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Line Height %.0fpx", lineHeight); + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.26f, 0.53f, 0.96f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Ascent %.0fpx", ascent); + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.71f, 0.39f, 0.86f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Width %.0fpx", textSize.x); + + // Row 2: Leading | Baseline | Advance + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.86f, 0.31f, 0.31f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Leading %.1fpx", internalLeading); + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.34f, 0.78f, 0.34f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Baseline %.0fpx", baselineFromTop); + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.31f, 0.78f, 0.86f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Advance %.0fpx", firstCharAdvance); + + // Row 3: Cell Height | Descent | (empty) + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.86f, 0.78f, 0.31f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Cell Height %.0fpx", cellHeight); + ImGui::TableNextColumn(); + ImGui::TextColored(ImVec4(0.92f, 0.63f, 0.24f, 1.0f), "---"); ImGui::SameLine(); + ImGui::Text("Descent %.0fpx", -descent); + ImGui::TableNextColumn(); + // Empty cell + + ImGui::EndTable(); + } + } // end else (baked) + } + + ImGui::Separator(); + + // Push preview font for sample text + if (g_FontState.PreviewFont) + ImGui::PushFont(g_FontState.PreviewFont); + + ImGui::TextWrapped("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + ImGui::TextWrapped("abcdefghijklmnopqrstuvwxyz"); + ImGui::TextWrapped("0123456789"); + + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + + if (g_FontState.PreviewFont) + ImGui::PopFont(); + } + ImGui::End(); + + // Rendering + ImGui::Render(); + const float clear_color[4] = { 0.1f, 0.1f, 0.1f, 1.0f }; + g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr); + g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + + HRESULT hr = g_pSwapChain->Present(1, 0); + g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); + } + + // Cleanup + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + CleanupDeviceD3D(); + ::DestroyWindow(hwnd); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); + + return 0; +} + +// Helper functions +bool CreateDeviceD3D(HWND hWnd) +{ + DXGI_SWAP_CHAIN_DESC sd; + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = 2; + sd.BufferDesc.Width = 0; + sd.BufferDesc.Height = 0; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow = hWnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + UINT createDeviceFlags = 0; + D3D_FEATURE_LEVEL featureLevel; + const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, }; + HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext); + if (res == DXGI_ERROR_UNSUPPORTED) + res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext); + if (res != S_OK) + return false; + + CreateRenderTarget(); + return true; +} + +void CleanupDeviceD3D() +{ + CleanupRenderTarget(); + if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = nullptr; } + if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = nullptr; } + if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; } +} + +void CreateRenderTarget() +{ + ID3D11Texture2D* pBackBuffer; + g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView); + pBackBuffer->Release(); +} + +void CleanupRenderTarget() +{ + if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = nullptr; } +} + +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) + return true; + + switch (msg) + { + case WM_SIZE: + if (wParam == SIZE_MINIMIZED) + return 0; + g_ResizeWidth = (UINT)LOWORD(lParam); + g_ResizeHeight = (UINT)HIWORD(lParam); + return 0; + case WM_SYSCOMMAND: + if ((wParam & 0xfff0) == SC_KEYMENU) + return 0; + break; + case WM_DESTROY: + ::PostQuitMessage(0); + return 0; + } + return ::DefWindowProcW(hWnd, msg, wParam, lParam); +} diff --git a/imgui.h b/imgui.h index 936ce0add430..9e1ecbf5a74b 100644 --- a/imgui.h +++ b/imgui.h @@ -3527,6 +3527,26 @@ struct ImTextureData // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- +// Helper macro to create OpenType axis tags (e.g., IM_FONT_TAG('w','g','h','t') for weight) +#define IM_FONT_TAG(c1,c2,c3,c4) (((ImU32)(ImU8)(c1)<<24)|((ImU32)(ImU8)(c2)<<16)|((ImU32)(ImU8)(c3)<<8)|((ImU32)(ImU8)(c4))) + +// Variable font axis setting (for use with ImFontConfigVarAxes) +struct ImFontConfigVarAxis +{ + ImU32 Tag; // OpenType axis tag (use IM_FONT_TAG('w','g','h','t'), etc.) + float Value; // Axis value + ImFontConfigVarAxis() { memset(this, 0, sizeof(*this)); } + ImFontConfigVarAxis(ImU32 tag, float value) { Tag = tag; Value = value; } +}; + +// Container for variable font axis settings (pointer + count pattern) +struct ImFontConfigVarAxes +{ + const ImFontConfigVarAxis* Axes; // Pointer to axis array (data must persist as long as font is alive) + int AxesCount; // Number of entries in Axes array + ImFontConfigVarAxes() { memset(this, 0, sizeof(*this)); } +}; + // A font input/source (we may rename this to ImFontSource in the future) struct ImFontConfig { @@ -3557,6 +3577,9 @@ struct ImFontConfig float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. float ExtraSizeScale; // 1.0f // Extra rasterizer scale over SizePixels. + // Variable font settings (requires FreeType loader) + ImFontConfigVarAxes VarAxes; // {} // [Variable fonts] Generic axis settings for any OpenType axis. Use IM_FONT_TAG() macro for tags. + // [Internal] ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates) ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index c358d89dba8b..2d5cf727d761 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -48,6 +48,8 @@ #include FT_GLYPH_H // #include FT_SIZES_H // #include FT_SYNTHESIS_H // +#include FT_MULTIPLE_MASTERS_H // +#include FT_TRUETYPE_TABLES_H // // Handle LunaSVG and PlutoSVG #if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG) @@ -433,6 +435,38 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize); FT_Activate_Size(bd_baked_data->FtSize); + // Variable font axis support - configure design BEFORE size request + FT_MM_Var* mm_var = nullptr; + if (FT_Get_MM_Var(bd_font_data->FtFace, &mm_var) == 0 && mm_var != nullptr) + { + FT_Fixed* coords = (FT_Fixed*)IM_ALLOC(mm_var->num_axis * sizeof(FT_Fixed)); + // Initialize to font's default values (not current values which may be cached) + for (FT_UInt i = 0; i < mm_var->num_axis; i++) + coords[i] = mm_var->axis[i].def; + + // Helper to clamp and set axis value + auto SetAxis = [&](FT_UInt i, float value) { + float axis_min = (float)mm_var->axis[i].minimum / 65536.0f; + float axis_max = (float)mm_var->axis[i].maximum / 65536.0f; + if (value < axis_min) value = axis_min; + if (value > axis_max) value = axis_max; + coords[i] = (FT_Fixed)(value * 65536.0f); + }; + + // Apply axis settings from VarAxes (user-provided values override font defaults) + for (FT_UInt i = 0; i < mm_var->num_axis; i++) + { + FT_ULong tag = mm_var->axis[i].tag; + for (int j = 0; j < src->VarAxes.AxesCount; j++) + if (tag == (FT_ULong)src->VarAxes.Axes[j].Tag) + SetAxis(i, src->VarAxes.Axes[j].Value); + } + + FT_Set_Var_Design_Coordinates(bd_font_data->FtFace, mm_var->num_axis, coords); + IM_FREE(coords); + FT_Done_MM_Var(bd_font_data->FtFace->glyph->library, mm_var); + } + // Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height' // is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me. // FT_Set_Pixel_Sizes() doesn't seem to get us the same result." @@ -606,6 +640,66 @@ bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags) return edited; } +int ImGuiFreeType::GetFontAxes(const char* filename, ImFontVarAxisInfo* out_axes, int max_axes) +{ + // Initialize FreeType library (temporary, just for this query) + FT_Library ft_library; + if (FT_Init_FreeType(&ft_library) != 0) + return -1; + + // Load font face + FT_Face ft_face; + if (FT_New_Face(ft_library, filename, 0, &ft_face) != 0) + { + FT_Done_FreeType(ft_library); + return -1; + } + + // Query variable font axes + int result = 0; + FT_MM_Var* mm_var = nullptr; + if (FT_Get_MM_Var(ft_face, &mm_var) == 0 && mm_var != nullptr) + { + result = (int)mm_var->num_axis; + + // Fill output array if provided + if (out_axes != nullptr) + { + int count = (result < max_axes) ? result : max_axes; + for (int i = 0; i < count; i++) + { + const FT_Var_Axis& axis = mm_var->axis[i]; + out_axes[i].Tag = (ImU32)axis.tag; + out_axes[i].Minimum = (float)axis.minimum / 65536.0f; + out_axes[i].Default = (float)axis.def / 65536.0f; + out_axes[i].Maximum = (float)axis.maximum / 65536.0f; + + // Copy axis name (FreeType provides null-terminated string) + if (axis.name) + { + size_t len = strlen(axis.name); + if (len >= sizeof(out_axes[i].Name)) + len = sizeof(out_axes[i].Name) - 1; + memcpy(out_axes[i].Name, axis.name, len); + out_axes[i].Name[len] = '\0'; + } + else + { + out_axes[i].Name[0] = '\0'; + } + } + } + + FT_Done_MM_Var(ft_library, mm_var); + } + + // Cleanup + FT_Done_Face(ft_face); + FT_Done_FreeType(ft_library); + + return result; +} + #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG // For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c // The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT) diff --git a/misc/freetype/imgui_freetype.h b/misc/freetype/imgui_freetype.h index 85313699dd56..0440998d2b8b 100644 --- a/misc/freetype/imgui_freetype.h +++ b/misc/freetype/imgui_freetype.h @@ -58,6 +58,16 @@ enum ImGuiFreeTypeLoaderFlags_ typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_; #endif +// Variable font axis information (returned by GetFontAxes) +struct ImFontVarAxisInfo +{ + ImU32 Tag; // OpenType axis tag (use IM_FONT_TAG to compare, e.g., IM_FONT_TAG('w','g','h','t')) + char Name[64]; // Human-readable axis name (e.g., "Weight", "Width", "Optical Size") + float Minimum; // Minimum axis value + float Default; // Default axis value + float Maximum; // Maximum axis value +}; + namespace ImGuiFreeType { // This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'. @@ -73,6 +83,12 @@ namespace ImGuiFreeType // Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source) IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags); + // Query variable font axes from a font file (standalone utility, no atlas required) + // Returns number of axes found, or -1 on error (file not found, not a valid font, etc.) + // If out_axes is NULL, just returns the axis count without writing data. + // Axes array should have at least 'max_axes' elements. + IMGUI_API int GetFontAxes(const char* filename, ImFontVarAxisInfo* out_axes, int max_axes); + // Obsolete names (will be removed) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())' if you need runtime selection.