Allow configuring variable axes in FreeType

This commit is contained in:
Peter Dimov 2026-01-24 09:29:25 -08:00
parent c91bcea7a8
commit 2c6218e324
3 changed files with 133 additions and 0 deletions

23
imgui.h
View File

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

View File

@ -48,6 +48,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)
@ -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)

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.