This commit is contained in:
Peter Dimov 2026-09-25 22:01:47 +09:00 • committed by GitHub
commit 4bca0b7397
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1096 additions and 0 deletions

View file

@ -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%

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{B4CF9797-519D-4AFE-A8F4-5141A6B521D3}</ProjectGuid>
<RootNamespace>example_win32_directx11_freetype</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(ProjectDir)$(Configuration)\</OutDir>
<IntDir>$(ProjectDir)$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(ProjectDir)$(Configuration)\</OutDir>
<IntDir>$(ProjectDir)$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\..;..\..\backends;..\..\misc\freetype;..\..\..\vcpkg_installed\x64-windows\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;IMGUI_ENABLE_FREETYPE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>freetype.lib;d3d11.lib;d3dcompiler.lib;dxgi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\vcpkg_installed\x64-windows\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>..\..;..\..\backends;..\..\misc\freetype;..\..\..\vcpkg_installed\x64-windows\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;IMGUI_ENABLE_FREETYPE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BufferSecurityCheck>false</BufferSecurityCheck>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>freetype.lib;d3d11.lib;d3dcompiler.lib;dxgi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\vcpkg_installed\x64-windows\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\imconfig.h" />
<ClInclude Include="..\..\imgui.h" />
<ClInclude Include="..\..\imgui_internal.h" />
<ClInclude Include="..\..\backends\imgui_impl_dx11.h" />
<ClInclude Include="..\..\backends\imgui_impl_win32.h" />
<ClInclude Include="..\..\misc\freetype\imgui_freetype.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\imgui.cpp" />
<ClCompile Include="..\..\imgui_demo.cpp" />
<ClCompile Include="..\..\imgui_draw.cpp" />
<ClCompile Include="..\..\imgui_tables.cpp" />
<ClCompile Include="..\..\imgui_widgets.cpp" />
<ClCompile Include="..\..\backends\imgui_impl_dx11.cpp" />
<ClCompile Include="..\..\backends\imgui_impl_win32.cpp" />
<ClCompile Include="..\..\misc\freetype\imgui_freetype.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\misc\debuggers\imgui.natstepfilter" />
<None Include="..\..\misc\debuggers\imgui.natvis" />
<None Include="..\README.txt" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="imgui">
<UniqueIdentifier>{20b90ce4-7fcb-4731-b9a0-075f875de82d}</UniqueIdentifier>
</Filter>
<Filter Include="backends">
<UniqueIdentifier>{f18ab499-84e1-499f-8eff-9754361e0e52}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\imconfig.h">
<Filter>imgui</Filter>
</ClInclude>
<ClInclude Include="..\..\imgui.h">
<Filter>imgui</Filter>
</ClInclude>
<ClInclude Include="..\..\imgui_internal.h">
<Filter>imgui</Filter>
</ClInclude>
<ClInclude Include="..\..\backends\imgui_impl_dx11.h">
<Filter>backends</Filter>
</ClInclude>
<ClInclude Include="..\..\backends\imgui_impl_win32.h">
<Filter>backends</Filter>
</ClInclude>
<ClInclude Include="..\..\misc\freetype\imgui_freetype.h">
<Filter>imgui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\imgui.cpp">
<Filter>imgui</Filter>
</ClCompile>
<ClCompile Include="..\..\imgui_demo.cpp">
<Filter>imgui</Filter>
</ClCompile>
<ClCompile Include="..\..\imgui_draw.cpp">
<Filter>imgui</Filter>
</ClCompile>
<ClCompile Include="..\..\imgui_tables.cpp">
<Filter>imgui</Filter>
</ClCompile>
<ClCompile Include="..\..\imgui_widgets.cpp">
<Filter>imgui</Filter>
</ClCompile>
<ClCompile Include="..\..\backends\imgui_impl_dx11.cpp">
<Filter>backends</Filter>
</ClCompile>
<ClCompile Include="..\..\backends\imgui_impl_win32.cpp">
<Filter>backends</Filter>
</ClCompile>
<ClCompile Include="..\..\misc\freetype\imgui_freetype.cpp">
<Filter>imgui</Filter>
</ClCompile>
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\misc\debuggers\imgui.natstepfilter">
<Filter>imgui</Filter>
</None>
<None Include="..\..\misc\debuggers\imgui.natvis">
<Filter>imgui</Filter>
</None>
<None Include="..\README.txt" />
</ItemGroup>
</Project>

View file

@ -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 <d3d11.h>
#include <tchar.h>
#include <stdio.h>
#include <string.h>
// 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);
}

23
imgui.h
View file

@ -3629,6 +3629,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
{
@ -3659,6 +3679,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)

View file

@ -49,6 +49,8 @@
#include FT_GLYPH_H // <freetype/ftglyph.h>
#include FT_SIZES_H // <freetype/ftsizes.h>
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
#include FT_MULTIPLE_MASTERS_H // <freetype/ftmm.h>
#include FT_TRUETYPE_TABLES_H // <freetype/tttables.h>
// Handle LunaSVG and PlutoSVG
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
@ -445,6 +447,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."
@ -620,6 +654,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)

View file

@ -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.