ImFontAtlas: new AddFont() API, oversampling, subpositiong, merging fonts, etc. (#182, #220, #232, #242)

This commit is contained in:
ocornut 2015-07-15 07:01:21 -06:00
parent 6ae8062ca0
commit 815168c7ef
3 changed files with 241 additions and 117 deletions

View File

@ -53,16 +53,41 @@
LOADING INSTRUCTIONS LOADING INSTRUCTIONS
--------------------------------- ---------------------------------
Load default font with:
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontDefault();
Load .TTF file with: Load .TTF file with:
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels);
Add a third parameter to bake specific font ranges: Detailed options:
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesDefault()); // Basic Latin, Extended Latin ImFontConfig config;
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs config.OversampleH = 3;
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesChinese()); // Include full set of about 21000 CJK Unified Ideographs config.OversampleV = 3;
config.GlyphExtraSpacing.x = 1.0f;
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
Merge two fonts:
// Load main font
io.Fonts->AddFontDefault();
// Add character ranges and merge into main font
ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
ImFontConfig config;
config.MergeMode = true;
io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges);
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese());
Add a fourth parameter to bake specific font ranges only:
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, io.Fonts->GetGlyphRangesDefault()); // Basic Latin, Extended Latin
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, io.Fonts->GetGlyphRangesChinese()); // Include full set of about 21000 CJK Unified Ideographs
Offset font vertically by altering the io.Font->DisplayOffset value: Offset font vertically by altering the io.Font->DisplayOffset value:

286
imgui.cpp
View File

@ -138,6 +138,7 @@
Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix.
Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code.
- 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure.
- 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost.
this necessary change will break your rendering function! the fix should be very easy. sorry for that :( this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
- if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest.
@ -333,12 +334,26 @@
// the first loaded font gets used by default // the first loaded font gets used by default
// use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime
// Options
ImFontConfig config;
config.OversampleH = 3;
config.OversampleV = 3;
config.GlyphExtraSpacing.x = 1.0f;
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
// Merging input from different fonts into one
ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
ImFontConfig config;
config.MergeMode = true;
io.Fonts->AddFontDefault();
io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges);
io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese());
Q: How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? Q: How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. ImGui will support UTF-8 encoding across the board. A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. ImGui will support UTF-8 encoding across the board.
Character input depends on you passing the right character code to io.AddInputCharacter(). The example applications do that. Character input depends on you passing the right character code to io.AddInputCharacter(). The example applications do that.
io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, io.Fonts->GetGlyphRangesJapanese()); // Load Japanese characters io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); // Load Japanese characters
io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
io.ImeWindowHandle = MY_HWND; // To input using Microsoft IME, give ImGui the hwnd of your application io.ImeWindowHandle = MY_HWND; // To input using Microsoft IME, give ImGui the hwnd of your application
@ -9645,23 +9660,23 @@ void ImDrawData::DeIndexAllBuffers()
// ImFontAtlias // ImFontAtlias
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
struct ImFontAtlas::ImFontAtlasData ImFontConfig::ImFontConfig()
{ {
// Input FontData = NULL;
ImFont* OutFont; // Load into this font FontDataSize = 0;
void* TTFData; // TTF data, we own the memory FontDataOwnedByAtlas = true;
int TTFDataSize; // TTF data size, in bytes FontNo = 0;
float SizePixels; // Desired output size, in pixels SizePixels = 0.0f;
const ImWchar* GlyphRanges; // List of Unicode range (2 value per range, values are inclusive, zero-terminated list) OversampleH = 3;
int FontNo; // Index of font within .TTF file (0) OversampleV = 1;
PixelSnapH = false;
// Temporary Build Data GlyphExtraSpacing = ImVec2(0.0f, 0.0f);
stbtt_fontinfo FontInfo; GlyphRanges = NULL;
stbrp_rect* Rects; MergeMode = false;
stbtt_pack_range* Ranges; MergeGlyphCenterV = false;
int RangesCount; DstFont = NULL;
int OversampleH, OversampleV; memset(Name, 0, sizeof(Name));
}; }
ImFontAtlas::ImFontAtlas() ImFontAtlas::ImFontAtlas()
{ {
@ -9679,13 +9694,12 @@ ImFontAtlas::~ImFontAtlas()
void ImFontAtlas::ClearInputData() void ImFontAtlas::ClearInputData()
{ {
for (int i = 0; i < InputData.Size; i++) for (int i = 0; i < ConfigData.Size; i++)
{ if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas)
if (InputData[i]->TTFData) {
ImGui::MemFree(InputData[i]->TTFData); ImGui::MemFree(ConfigData[i].FontData);
ImGui::MemFree(InputData[i]); ConfigData[i].FontData = NULL;
} }
InputData.clear();
} }
void ImFontAtlas::ClearTexData() void ImFontAtlas::ClearTexData()
@ -9720,7 +9734,7 @@ void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_wid
// Lazily build // Lazily build
if (TexPixelsAlpha8 == NULL) if (TexPixelsAlpha8 == NULL)
{ {
if (InputData.empty()) if (ConfigData.empty())
AddFontDefault(); AddFontDefault();
Build(); Build();
} }
@ -9752,6 +9766,27 @@ void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_wid
if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; if (out_bytes_per_pixel) *out_bytes_per_pixel = 4;
} }
ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
{
IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0);
IM_ASSERT(font_cfg->SizePixels > 0.0f);
// Create new font
if (!font_cfg->MergeMode)
{
ImFont* font = (ImFont*)ImGui::MemAlloc(sizeof(ImFont));
new (font) ImFont();
Fonts.push_back(font);
}
ConfigData.push_back(*font_cfg);
ConfigData.back().DstFont = Fonts.back();
// Invalidate texture
ClearTexData();
return Fonts.back();
}
static void GetDefaultCompressedFontDataTTF(const void** ttf_compressed_data, unsigned int* ttf_compressed_size); static void GetDefaultCompressedFontDataTTF(const void** ttf_compressed_data, unsigned int* ttf_compressed_size);
static unsigned int stb_decompress_length(unsigned char *input); static unsigned int stb_decompress_length(unsigned char *input);
static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length);
@ -9762,10 +9797,14 @@ ImFont* ImFontAtlas::AddFontDefault()
unsigned int ttf_compressed_size; unsigned int ttf_compressed_size;
const void* ttf_compressed; const void* ttf_compressed;
GetDefaultCompressedFontDataTTF(&ttf_compressed, &ttf_compressed_size); GetDefaultCompressedFontDataTTF(&ttf_compressed, &ttf_compressed_size);
return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, 13.0f, GetGlyphRangesDefault(), 0); ImFontConfig font_cfg;
font_cfg.OversampleH = font_cfg.OversampleV = 1;
font_cfg.PixelSnapH = true;
strcpy(font_cfg.Name, "<default>");
return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, 13.0f, &font_cfg, GetGlyphRangesDefault());
} }
ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges, int font_no) ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
{ {
void* data = NULL; void* data = NULL;
int data_size = 0; int data_size = 0;
@ -9774,67 +9813,82 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels,
IM_ASSERT(0); // Could not load file. IM_ASSERT(0); // Could not load file.
return NULL; return NULL;
} }
return AddFontFromMemoryTTF(data, data_size, size_pixels, glyph_ranges, font_no); ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
if (font_cfg.Name[0] == 0)
{
const char* p;
for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {}
ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s", p);
}
return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges);
} }
// Transfer ownership of 'ttf_data' to ImFontAtlas, will be deleted after Build() // Transfer ownership of 'ttf_data' to ImFontAtlas, will be deleted after Build()
ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImWchar* glyph_ranges, int font_no) ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
{ {
// Create new font ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
ImFont* font = (ImFont*)ImGui::MemAlloc(sizeof(ImFont)); IM_ASSERT(font_cfg.FontData == NULL);
new (font) ImFont(); font_cfg.FontData = ttf_data;
Fonts.push_back(font); font_cfg.FontDataSize = ttf_size;
font_cfg.SizePixels = size_pixels;
// Add to build list if (glyph_ranges)
ImFontAtlasData* data = (ImFontAtlasData*)ImGui::MemAlloc(sizeof(ImFontAtlasData)); font_cfg.GlyphRanges = glyph_ranges;
memset(data, 0, sizeof(ImFontAtlasData)); if (!font_cfg.FontDataOwnedByAtlas)
data->OutFont = font; {
data->TTFData = ttf_data; font_cfg.FontData = ImGui::MemAlloc(ttf_size);
data->TTFDataSize = ttf_size; font_cfg.FontDataOwnedByAtlas = true;
data->SizePixels = size_pixels; memcpy(font_cfg.FontData, ttf_data, (size_t)ttf_size);
data->OversampleH = data->OversampleV = 1; }
data->GlyphRanges = glyph_ranges; return AddFont(&font_cfg);
data->FontNo = font_no;
InputData.push_back(data);
// Invalidate texture
ClearTexData();
return font;
} }
ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImWchar* glyph_ranges, int font_no) ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
{ {
const unsigned int buf_decompressed_size = stb_decompress_length((unsigned char*)compressed_ttf_data); const unsigned int buf_decompressed_size = stb_decompress_length((unsigned char*)compressed_ttf_data);
unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size);
stb_decompress(buf_decompressed_data, (unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); stb_decompress(buf_decompressed_data, (unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size);
return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, glyph_ranges, font_no);
ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
IM_ASSERT(font_cfg.FontData == NULL);
font_cfg.FontDataOwnedByAtlas = true;
return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, font_cfg_template, glyph_ranges);
} }
bool ImFontAtlas::Build() bool ImFontAtlas::Build()
{ {
IM_ASSERT(InputData.Size > 0); IM_ASSERT(ConfigData.Size > 0);
TexID = NULL; TexID = NULL;
TexWidth = TexHeight = 0; TexWidth = TexHeight = 0;
TexUvWhitePixel = ImVec2(0, 0); TexUvWhitePixel = ImVec2(0, 0);
ClearTexData(); ClearTexData();
struct ImFontTempBuildData
{
stbtt_fontinfo FontInfo;
stbrp_rect* Rects;
stbtt_pack_range* Ranges;
int RangesCount;
};
ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)ConfigData.Size * sizeof(ImFontTempBuildData));
// Initialize font information early (so we can error without any cleanup) + count glyphs // Initialize font information early (so we can error without any cleanup) + count glyphs
int total_glyph_count = 0; int total_glyph_count = 0;
int total_glyph_range_count = 0; int total_glyph_range_count = 0;
for (int input_i = 0; input_i < InputData.Size; input_i++) for (int input_i = 0; input_i < ConfigData.Size; input_i++)
{ {
ImFontAtlasData& data = *InputData[input_i]; ImFontConfig& cfg = ConfigData[input_i];
IM_ASSERT(data.OutFont && (!data.OutFont->IsLoaded() || data.OutFont->ContainerAtlas == this)); ImFontTempBuildData& tmp = tmp_array[input_i];
const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)data.TTFData, data.FontNo);
IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == this));
const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo);
IM_ASSERT(font_offset >= 0); IM_ASSERT(font_offset >= 0);
if (!stbtt_InitFont(&data.FontInfo, (unsigned char*)data.TTFData, font_offset)) if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset))
return false; return false;
if (!data.GlyphRanges) if (!cfg.GlyphRanges)
data.GlyphRanges = GetGlyphRangesDefault(); cfg.GlyphRanges = GetGlyphRangesDefault();
for (const ImWchar* in_range = data.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2)
{ {
total_glyph_count += (in_range[1] - in_range[0]) + 1; total_glyph_count += (in_range[1] - in_range[0]) + 1;
total_glyph_range_count++; total_glyph_range_count++;
@ -9846,8 +9900,7 @@ bool ImFontAtlas::Build()
TexHeight = 0; TexHeight = 0;
const int max_tex_height = 1024*32; const int max_tex_height = 1024*32;
stbtt_pack_context spc; stbtt_pack_context spc;
int ret = stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL); stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL);
IM_ASSERT(ret);
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
ImVector<stbrp_rect> extra_rects; ImVector<stbrp_rect> extra_rects;
@ -9868,26 +9921,27 @@ bool ImFontAtlas::Build()
memset(buf_ranges, 0, total_glyph_range_count * sizeof(stbtt_pack_range)); memset(buf_ranges, 0, total_glyph_range_count * sizeof(stbtt_pack_range));
// First font pass: pack all glyphs (no rendering at this point, we are working with glyph sizes only) // First font pass: pack all glyphs (no rendering at this point, we are working with glyph sizes only)
for (int input_i = 0; input_i < InputData.Size; input_i++) for (int input_i = 0; input_i < ConfigData.Size; input_i++)
{ {
ImFontAtlasData& data = *InputData[input_i]; ImFontConfig& cfg = ConfigData[input_i];
ImFontTempBuildData& tmp = tmp_array[input_i];
// Setup ranges // Setup ranges
int glyph_count = 0; int glyph_count = 0;
int glyph_ranges_count = 0; int glyph_ranges_count = 0;
for (const ImWchar* in_range = data.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2)
{ {
glyph_count += (in_range[1] - in_range[0]) + 1; glyph_count += (in_range[1] - in_range[0]) + 1;
glyph_ranges_count++; glyph_ranges_count++;
} }
data.Ranges = buf_ranges + buf_ranges_n; tmp.Ranges = buf_ranges + buf_ranges_n;
data.RangesCount = glyph_ranges_count; tmp.RangesCount = glyph_ranges_count;
buf_ranges_n += glyph_ranges_count; buf_ranges_n += glyph_ranges_count;
for (int i = 0; i < glyph_ranges_count; i++) for (int i = 0; i < glyph_ranges_count; i++)
{ {
const ImWchar* in_range = &data.GlyphRanges[i * 2]; const ImWchar* in_range = &cfg.GlyphRanges[i * 2];
stbtt_pack_range& range = data.Ranges[i]; stbtt_pack_range& range = tmp.Ranges[i];
range.font_size = data.SizePixels; range.font_size = cfg.SizePixels;
range.first_unicode_char_in_range = in_range[0]; range.first_unicode_char_in_range = in_range[0];
range.num_chars_in_range = (in_range[1] - in_range[0]) + 1; range.num_chars_in_range = (in_range[1] - in_range[0]) + 1;
range.chardata_for_range = buf_packedchars + buf_packedchars_n; range.chardata_for_range = buf_packedchars + buf_packedchars_n;
@ -9895,16 +9949,16 @@ bool ImFontAtlas::Build()
} }
// Pack // Pack
data.Rects = buf_rects + buf_rects_n; tmp.Rects = buf_rects + buf_rects_n;
buf_rects_n += glyph_count; buf_rects_n += glyph_count;
stbtt_PackSetOversampling(&spc, data.OversampleH, data.OversampleV); stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV);
const int n = stbtt_PackFontRangesGatherRects(&spc, &data.FontInfo, data.Ranges, data.RangesCount, data.Rects); int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
stbrp_pack_rects((stbrp_context*)spc.pack_info, data.Rects, n); stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n);
// Extend texture height // Extend texture height
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
if (data.Rects[i].was_packed) if (tmp.Rects[i].was_packed)
TexHeight = ImMax(TexHeight, data.Rects[i].y + data.Rects[i].h); TexHeight = ImMax(TexHeight, tmp.Rects[i].y + tmp.Rects[i].h);
} }
IM_ASSERT(buf_rects_n == total_glyph_count); IM_ASSERT(buf_rects_n == total_glyph_count);
IM_ASSERT(buf_packedchars_n == total_glyph_count); IM_ASSERT(buf_packedchars_n == total_glyph_count);
@ -9918,12 +9972,13 @@ bool ImFontAtlas::Build()
spc.height = TexHeight; spc.height = TexHeight;
// Second pass: render characters // Second pass: render characters
for (int input_i = 0; input_i < InputData.Size; input_i++) for (int input_i = 0; input_i < ConfigData.Size; input_i++)
{ {
ImFontAtlasData& data = *InputData[input_i]; ImFontConfig& cfg = ConfigData[input_i];
stbtt_PackSetOversampling(&spc, data.OversampleH, data.OversampleV); ImFontTempBuildData& tmp = tmp_array[input_i];
ret = stbtt_PackFontRangesRenderIntoRects(&spc, &data.FontInfo, data.Ranges, data.RangesCount, data.Rects); stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV);
data.Rects = NULL; stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
tmp.Rects = NULL;
} }
// End packing // End packing
@ -9932,50 +9987,68 @@ bool ImFontAtlas::Build()
buf_rects = NULL; buf_rects = NULL;
// Third pass: setup ImFont and glyphs for runtime // Third pass: setup ImFont and glyphs for runtime
for (int input_i = 0; input_i < InputData.Size; input_i++) for (int input_i = 0; input_i < ConfigData.Size; input_i++)
{ {
ImFontAtlasData& data = *InputData[input_i]; ImFontConfig& cfg = ConfigData[input_i];
data.OutFont->ContainerAtlas = this; ImFontTempBuildData& tmp = tmp_array[input_i];
data.OutFont->FontSize = data.SizePixels; ImFont* dst_font = cfg.DstFont;
const float font_scale = stbtt_ScaleForPixelHeight(&data.FontInfo, data.SizePixels); float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels);
int font_ascent, font_descent, font_line_gap; int unscaled_ascent, unscaled_descent, unscaled_line_gap;
stbtt_GetFontVMetrics(&data.FontInfo, &font_ascent, &font_descent, &font_line_gap); stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap);
data.OutFont->Ascent = (font_ascent * font_scale);
data.OutFont->Descent = (font_descent * font_scale);
data.OutFont->Glyphs.resize(0);
const int character_spacing_x = 1; float ascent = unscaled_ascent * font_scale;
for (int i = 0; i < data.RangesCount; i++) float descent = unscaled_descent * font_scale;
if (!cfg.MergeMode)
{ {
stbtt_pack_range& range = data.Ranges[i]; dst_font->ContainerAtlas = this;
dst_font->ConfigData = &cfg;
dst_font->ConfigDataCount = 0;
dst_font->FontSize = cfg.SizePixels;
dst_font->Ascent = ascent;
dst_font->Descent = descent;
dst_font->Glyphs.resize(0);
}
dst_font->ConfigDataCount++;
float off_y = (cfg.MergeMode && cfg.MergeGlyphCenterV) ? (ascent - dst_font->Ascent) * 0.5f : 0.0f;
dst_font->FallbackGlyph = NULL; // Always clear fallback so FindGlyph can return NULL. It will be set again in BuildLookupTable()
for (int i = 0; i < tmp.RangesCount; i++)
{
stbtt_pack_range& range = tmp.Ranges[i];
for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1) for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1)
{ {
const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; const stbtt_packedchar& pc = range.chardata_for_range[char_idx];
if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1)
continue; continue;
const int codepoint = range.first_unicode_char_in_range + char_idx;
if (cfg.MergeMode && dst_font->FindGlyph((unsigned short)codepoint))
continue;
stbtt_aligned_quad q; stbtt_aligned_quad q;
float dummy_x = 0.0f, dummy_y = 0.0f; float dummy_x = 0.0f, dummy_y = 0.0f;
stbtt_GetPackedQuad(range.chardata_for_range, TexWidth, TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); stbtt_GetPackedQuad(range.chardata_for_range, TexWidth, TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0);
data.OutFont->Glyphs.resize(data.OutFont->Glyphs.Size + 1); dst_font->Glyphs.resize(dst_font->Glyphs.Size + 1);
ImFont::Glyph& glyph = data.OutFont->Glyphs.back(); ImFont::Glyph& glyph = dst_font->Glyphs.back();
glyph.Codepoint = (ImWchar)(range.first_unicode_char_in_range + char_idx); glyph.Codepoint = (ImWchar)codepoint;
glyph.X0 = q.x0; glyph.Y0 = q.y0; glyph.X1 = q.x1; glyph.Y1 = q.y1; glyph.X0 = q.x0; glyph.Y0 = q.y0; glyph.X1 = q.x1; glyph.Y1 = q.y1;
glyph.U0 = q.s0; glyph.V0 = q.t0; glyph.U1 = q.s1; glyph.V1 = q.t1; glyph.U0 = q.s0; glyph.V0 = q.t0; glyph.U1 = q.s1; glyph.V1 = q.t1;
glyph.Y0 += (float)(int)(data.OutFont->Ascent + 0.5f); glyph.Y0 += (float)(int)(dst_font->Ascent + off_y + 0.5f);
glyph.Y1 += (float)(int)(data.OutFont->Ascent + 0.5f); glyph.Y1 += (float)(int)(dst_font->Ascent + off_y + 0.5f);
glyph.XAdvance = (float)(int)(pc.xadvance + character_spacing_x + 0.5f); // Bake spacing into XAdvance glyph.XAdvance = (pc.xadvance + cfg.GlyphExtraSpacing.x); // Bake spacing into XAdvance
if (cfg.PixelSnapH)
glyph.XAdvance = (float)(int)(glyph.XAdvance + 0.5f);
} }
} }
cfg.DstFont->BuildLookupTable();
data.OutFont->BuildLookupTable();
} }
// Cleanup temporaries // Cleanup temporaries
ImGui::MemFree(buf_packedchars); ImGui::MemFree(buf_packedchars);
ImGui::MemFree(buf_ranges); ImGui::MemFree(buf_ranges);
ImGui::MemFree(tmp_array);
// Render into our custom data block // Render into our custom data block
RenderCustomTexData(1, &extra_rects); RenderCustomTexData(1, &extra_rects);
@ -10195,6 +10268,8 @@ void ImFont::Clear()
{ {
FontSize = 0.0f; FontSize = 0.0f;
DisplayOffset = ImVec2(0.0f, 1.0f); DisplayOffset = ImVec2(0.0f, 1.0f);
ConfigData = NULL;
ConfigDataCount = 0;
Ascent = Descent = 0.0f; Ascent = Descent = 0.0f;
ContainerAtlas = NULL; ContainerAtlas = NULL;
Glyphs.clear(); Glyphs.clear();
@ -11160,6 +11235,7 @@ void ImGui::ShowTestWindow(bool* opened)
ImFontAtlas* atlas = ImGui::GetIO().Fonts; ImFontAtlas* atlas = ImGui::GetIO().Fonts;
if (ImGui::TreeNode("Atlas texture")) if (ImGui::TreeNode("Atlas texture"))
{ {
ImGui::Text("%dx%d pixels", atlas->TexWidth, atlas->TexHeight);
ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128));
ImGui::TreePop(); ImGui::TreePop();
} }
@ -11167,7 +11243,7 @@ void ImGui::ShowTestWindow(bool* opened)
for (int i = 0; i < atlas->Fonts.Size; i++) for (int i = 0; i < atlas->Fonts.Size; i++)
{ {
ImFont* font = atlas->Fonts[i]; ImFont* font = atlas->Fonts[i];
ImGui::BulletText("Font %d: %.2f pixels, %d glyphs", i, font->FontSize, font->Glyphs.Size); ImGui::BulletText("Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData[0].Name, font->FontSize, font->Glyphs.Size);
ImGui::TreePush((void*)i); ImGui::TreePush((void*)i);
if (i > 0) { ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) { atlas->Fonts[i] = atlas->Fonts[0]; atlas->Fonts[0] = font; } } if (i > 0) { ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) { atlas->Fonts[i] = atlas->Fonts[0]; atlas->Fonts[0] = font; } }
ImGui::PushFont(font); ImGui::PushFont(font);
@ -11178,6 +11254,8 @@ void ImGui::ShowTestWindow(bool* opened)
ImGui::DragFloat("font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this font ImGui::DragFloat("font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this font
ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent);
ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar);
for (int i = 0; i < font->ConfigDataCount; i++)
ImGui::BulletText("Input %d: \'%s\'", i, font->ConfigData[i].Name);
ImGui::TreePop(); ImGui::TreePop();
} }
ImGui::TreePop(); ImGui::TreePop();

39
imgui.h
View File

@ -1115,6 +1115,27 @@ struct ImDrawData
void DeIndexAllBuffers(); // For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! void DeIndexAllBuffers(); // For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering!
}; };
struct ImFontConfig
{
void* FontData; // // TTF data
int FontDataSize; // // TTF data size
bool FontDataOwnedByAtlas; // true // TTF data ownership taken by the container ImFontAtlas (will delete memory itself). Set to true
int FontNo; // 0 // Index of font within TTF file
float SizePixels; // // Size in pixels for rasterizer
int OversampleH, OversampleV; // 3, 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis.
bool PixelSnapH; // false // Align every character to pixel boundary (if enabled, set OversampleH/V to 1)
ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs
const ImWchar* GlyphRanges; // // List of Unicode range (2 value per range, values are inclusive, zero-terminated list)
bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs).
bool MergeGlyphCenterV; // false // When merging (multiple ImFontInput for one ImFont), vertically center new glyphs instead of aligning their baseline
// [Internal]
char Name[32]; // Name (strictly for debugging)
ImFont* DstFont;
IMGUI_API ImFontConfig();
};
// Load and rasterize multiple TTF fonts into a same texture. // Load and rasterize multiple TTF fonts into a same texture.
// Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. // Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering.
// We also add custom graphic data into the texture that serves for ImGui. // We also add custom graphic data into the texture that serves for ImGui.
@ -1127,10 +1148,11 @@ struct ImFontAtlas
{ {
IMGUI_API ImFontAtlas(); IMGUI_API ImFontAtlas();
IMGUI_API ~ImFontAtlas(); IMGUI_API ~ImFontAtlas();
IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg);
IMGUI_API ImFont* AddFontDefault(); IMGUI_API ImFont* AddFontDefault();
IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);
IMGUI_API ImFont* AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); // Transfer ownership of 'ttf_data' to ImFontAtlas, will be deleted after Build() IMGUI_API ImFont* AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Transfer ownership of 'ttf_data' to ImFontAtlas, will be deleted after Build()
IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); // 'compressed_ttf_data' untouched and still owned by caller. Compress with binary_to_compressed_c.cpp IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_ttf_data' untouched and still owned by caller. Compress with binary_to_compressed_c.cpp
IMGUI_API void ClearTexData(); // Clear the CPU-side texture data. Saves RAM once the texture has been copied to graphics memory. IMGUI_API void ClearTexData(); // Clear the CPU-side texture data. Saves RAM once the texture has been copied to graphics memory.
IMGUI_API void ClearInputData(); // Clear the input TTF data (inc sizes, glyph ranges) IMGUI_API void ClearInputData(); // Clear the input TTF data (inc sizes, glyph ranges)
IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates) IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates)
@ -1163,15 +1185,13 @@ struct ImFontAtlas
ImVector<ImFont*> Fonts; ImVector<ImFont*> Fonts;
// Private // Private
struct ImFontAtlasData; ImVector<ImFontConfig> ConfigData; // Internal data
ImVector<ImFontAtlasData*> InputData; // Internal data
IMGUI_API bool Build(); // Build pixels data. This is automatically for you by the GetTexData*** functions. IMGUI_API bool Build(); // Build pixels data. This is automatically for you by the GetTexData*** functions.
IMGUI_API void RenderCustomTexData(int pass, void* rects); IMGUI_API void RenderCustomTexData(int pass, void* rects);
}; };
// TTF font loading and rendering // Font runtime data and rendering
// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). // ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32().
// Kerning isn't supported. At the moment some ImGui code does per-character CalcTextSize calls, need something more state-ful.
struct ImFont struct ImFont
{ {
// Members: Settings // Members: Settings
@ -1179,6 +1199,8 @@ struct ImFont
float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale()
ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels
ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar()
ImFontConfig* ConfigData; // // Pointer within ImFontAtlas->ConfigData
int ConfigDataCount; //
// Members: Runtime data // Members: Runtime data
struct Glyph struct Glyph
@ -1188,8 +1210,7 @@ struct ImFont
float X0, Y0, X1, Y1; float X0, Y0, X1, Y1;
float U0, V0, U1, V1; // Texture coordinates float U0, V0, U1, V1; // Texture coordinates
}; };
float Ascent; // Distance from top to bottom of e.g. 'A' [0..FontSize] float Ascent, Descent; // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize]
float Descent; //
ImFontAtlas* ContainerAtlas; // What we has been loaded into ImFontAtlas* ContainerAtlas; // What we has been loaded into
ImVector<Glyph> Glyphs; ImVector<Glyph> Glyphs;
const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar)