How to Load and Use Multiple Fonts with ImFontAtlas in Dear ImGui
TLDR: Call ImGuiIO::Fonts->AddFontFromFileTTF() or AddFontFromMemoryTTF() for each font before the first frame, configure glyph ranges and oversampling via ImFontConfig, then invoke Build() to generate the texture atlas that GPU backends upload with SetTexID().
The ImFontAtlas class in the ocornut/imgui repository aggregates multiple font sources into a single GPU texture, managing rasterization, packing, and UV coordinate generation. Understanding how to load and use multiple fonts with ImFontAtlas allows you to implement multilingual support, icon fonts, and typographic hierarchy. This guide references the actual source implementation to demonstrate the complete workflow from file loading to texture upload.
Accessing the Font Atlas
Every Dear ImGui context exposes its font atlas through the ImGuiIO structure. Retrieve the atlas before the first rendering frame to ensure the texture is ready for GPU upload.
ImGuiIO& io = ImGui::GetIO();
ImFontAtlas* atlas = io.Fonts;
The io.Fonts pointer provides access to all loading and configuration methods defined in imgui.h around line 3713.
Loading Fonts from Files and Memory
The atlas provides distinct methods for loading fonts from disk versus memory buffers. Each returns an ImFont* pointer that you store for later activation with PushFont() and PopFont().
Loading TrueType Fonts from Disk
The AddFontFromFileTTF() function loads and rasterizes a font file according to the specified pixel size:
ImFont* bodyFont = io.Fonts->AddFontFromFileTTF("Roboto-Regular.ttf", 16.0f);
ImFont* headerFont = io.Fonts->AddFontFromFileTTF("Roboto-Bold.ttf", 24.0f);
ImFont* iconFont = io.Fonts->AddFontFromFileTTF("material-icons.ttf", 20.0f);
Loading from Memory Buffers
For embedded resources or dynamically loaded files, use AddFontFromMemoryTTF():
ImFont* font = io.Fonts->AddFontFromMemoryTTF(ttfData, ttfSize, 18.0f);
According to the source code at imgui.h line 3714, this function takes ownership of the memory buffer by default. Set FontDataOwnedByAtlas = false in the config struct to retain ownership and prevent the atlas from freeing your buffer.
Configuring Fonts with ImFontConfig
The ImFontConfig struct, defined in imgui.h around line 3600, controls rasterization quality, glyph subsets, and memory ownership flags.
Oversampling and Sizing
Increase oversampling for sharper text at scaled resolutions:
ImFontConfig cfg;
cfg.OversampleH = 3;
cfg.OversampleV = 3;
cfg.SizePixels = 20.0f;
ImFont* crispFont = io.Fonts->AddFontFromFileTTF("font.ttf", 20.0f, &cfg);
Restricting Glyph Ranges
Limiting Unicode ranges reduces texture memory and build time. The GlyphRanges field expects a zero-terminated array of ImWchar pairs defining inclusive ranges:
ImFontConfig cfg;
static const ImWchar ranges[] = { 0x0020, 0x00FF, 0x0400, 0x044F, 0 }; // Latin + Cyrillic
cfg.GlyphRanges = ranges;
ImFont* localizedFont = io.Fonts->AddFontFromFileTTF("arial.ttf", 16.0f, &cfg);
Critical: As warned in imgui.h line 3699, the glyph range array must remain valid until Build() is called because the atlas stores only a pointer, not a copy.
Managing Multiple Fonts and Custom Glyphs
When using several fonts simultaneously, you can also reserve space for non-font graphics such as colored icons.
Adding Custom Rectangles for Icons
Reserve texture space using AddCustomRect(), declared in imgui.h lines 3889-3892:
ImFontAtlasRect rect;
ImFontAtlasRectId iconId = io.Fonts->AddCustomRect(64, 64, &rect);
After building the atlas, retrieve the UV coordinates:
io.Fonts->Build();
io.Fonts->GetCustomRect(iconId, &rect);
// rect.uv0 and rect.uv1 now contain normalized coordinates for ImDrawList::AddImage()
Handling Font Data Ownership
When using AddFontFromMemoryTTF(), the atlas assumes ownership of the buffer unless configured otherwise:
ImFontConfig cfg;
cfg.FontDataOwnedByAtlas = false; // Keep the buffer valid yourself
ImFont* font = io.Fonts->AddFontFromMemoryTTF(myBuffer, bufferSize, 16.0f, &cfg);
Building and Uploading the Atlas Texture
The atlas remains virtual until explicitly built. Any call that modifies the atlas—adding fonts, changing configs, or adding custom rects—requires a rebuild.
Generating the Pixel Buffer
Call Build() to pack all glyphs and rectangles, then retrieve the raw pixels:
io.Fonts->Build();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
For memory-constrained backends, use GetTexDataAsAlpha8() to generate a single-channel texture instead.
GPU Integration
Upload the buffer to your renderer, then inform ImGui of the texture identifier:
GLuint textureID = create_gpu_texture(pixels, width, height, GL_RGBA);
io.Fonts->SetTexID((ImTextureID)(intptr_t)textureID);
The atlas also exposes TexDesiredFormat if you need to force a specific pixel format before building.
Enabling FreeType for Advanced Rasterization
For superior hinting, subpixel rendering, or advanced typographic features, enable the optional FreeType backend located in misc/freetype/imgui_freetype.cpp:
#define IMGUI_ENABLE_FREETYPE
#include "imgui.h"
// After ImGui::CreateContext():
io.Fonts->SetFontLoader(ImGui::GetFreeTypeFontLoader());
This redirects all AddFont* calls through FreeType instead of the default stb_truetype rasterizer.
Summary
- Access the global atlas via
ImGui::GetIO().Fontsbefore the first frame. - Load multiple fonts using
AddFontFromFileTTF()for disk files orAddFontFromMemoryTTF()for embedded data. - Configure rasterization quality, glyph subsets, and memory ownership through
ImFontConfig(defined inimgui.haround line 3600). - Ensure glyph range arrays and font data buffers remain valid until
Build()completes (as noted inimgui.hline 3699). - Reserve custom rectangles with
AddCustomRect()for icons or user graphics, retrieving UVs after the build. - Generate the texture with
Build()orGetTexDataAsRGBA32(), upload to GPU, and register the handle viaSetTexID(). - Optionally enable FreeType via
SetFontLoader()for higher-quality rasterization.
Frequently Asked Questions
Can I add fonts after the first frame has rendered?
No. Adding fonts or modifying configurations invalidates the texture coordinates and pixel buffer. You must call Build() again and re-upload the resulting texture to the GPU, which typically requires recreating dependent GPU resources. Perform all font loading during application initialization.
Why are my Cyrillic or CJK characters rendering as blank squares?
The default glyph range only includes Basic Latin. Specify the appropriate range in ImFontConfig::GlyphRanges using built-in helpers like GetGlyphRangesCyrillic(), GetGlyphRangesJapanese(), or GetGlyphRangesChineseFull(), or define a custom range array. Verify that your TTF/OTF file actually contains glyphs for the target Unicode blocks.
How do I switch between multiple fonts during UI rendering?
Store the ImFont* pointers returned by AddFont* calls. Activate a specific font by calling ImGui::PushFont(myFont) before your widget code, then ImGui::PopFont() after. Never call PushFont with a null pointer, and always ensure proper stack balancing.
Does AddFontFromMemoryTTF copy the font data?
By default, yes—the atlas takes ownership and will free the memory using IM_FREE(). To retain ownership of the buffer, set cfg.FontDataOwnedByAtlas = false in the ImFontConfig struct passed to the function, as documented at imgui.h line 3714.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →