ImFontAtlas: Rewrote stb_truetype based builder.

- Atlas width is now properly based on total surface rather than glyph count (unless overridden with TexDesiredWidth).
- Fixed atlas builder so missing glyphs won't influence the atlas texture width. (#2233)
- Fixed atlas builder so duplicate glyphs (when merging fonts) won't be included in the rasterized atlas.
This commit is contained in:
omar 2019-01-10 13:38:51 +01:00
parent e3ccc96789
commit 9a9712807e
5 changed files with 239 additions and 163 deletions

View File

@ -72,6 +72,9 @@ Other Changes:
Missing calls to End(), past the assert, should not lead to crashes or to the fallback Debug window appearing on screen. Missing calls to End(), past the assert, should not lead to crashes or to the fallback Debug window appearing on screen.
Those changes makes it easier to integrate dear imgui with a scripting language allowing, given asserts are redirected Those changes makes it easier to integrate dear imgui with a scripting language allowing, given asserts are redirected
into e.g. an error log and stopping the script execution. into e.g. an error log and stopping the script execution.
- ImFontAtlas: Stb: Atlas width is now properly based on total surface rather than glyph count (unless overridden with TexDesiredWidth).
- ImFontAtlas: Stb: Fixed atlas builder so missing glyphs won't influence the atlas texture width. (#2233)
- ImFontAtlas: Stb: Fixed atlas builder so duplicate glyphs (when merging fonts) won't be included in the rasterized atlas.
- ImDrawList: Optimized some of the functions for performance of debug builds where non-inline function call cost are non-negligible. - ImDrawList: Optimized some of the functions for performance of debug builds where non-inline function call cost are non-negligible.
(Our test UI scene on VS2015 Debug Win64 with /RTC1 went ~5.9 ms -> ~4.9 ms. In Release same scene stays at ~0.3 ms.) (Our test UI scene on VS2015 Debug Win64 with /RTC1 went ~5.9 ms -> ~4.9 ms. In Release same scene stays at ~0.3 ms.)
- IO: Added BackendPlatformUserData, BackendRendererUserData, BackendLanguageUserData void* for storage use by back-ends. - IO: Added BackendPlatformUserData, BackendRendererUserData, BackendLanguageUserData void* for storage use by back-ends.

View File

@ -235,9 +235,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i
- pie menus patterns (#434) - pie menus patterns (#434)
- markup: simple markup language for color change? (#902) - markup: simple markup language for color change? (#902)
!- font: need handling of missing glyphs by not packing/rasterizing glyph 0 of a given font. - font: MergeMode: flags to select overwriting or not (this is now very easy with refactored ImFontAtlasBuildWithStbTruetype)
- font: MergeMode: flags to select overwriting or not.
- font: MergeMode: duplicate glyphs are stored in the atlas texture which is suboptimal.
- font: free the Alpha buffer if user only requested RGBA. - font: free the Alpha buffer if user only requested RGBA.
!- font: better CalcTextSizeA() API, at least for simple use cases. current one is horrible (perhaps have simple vs extended versions). !- font: better CalcTextSizeA() API, at least for simple use cases. current one is horrible (perhaps have simple vs extended versions).
- font: a CalcTextHeight() helper could run faster than CalcTextSize().y - font: a CalcTextHeight() helper could run faster than CalcTextSize().y
@ -252,7 +250,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i
- font/draw: vertical and/or rotated text renderer (#705) - vertical is easier clipping wise - font/draw: vertical and/or rotated text renderer (#705) - vertical is easier clipping wise
- font/draw: need to be able to specify wrap start position. - font/draw: need to be able to specify wrap start position.
- font/draw: better reserve policy for large horizontal block of text (shouldn't reserve for all clipped lines) - font/draw: better reserve policy for large horizontal block of text (shouldn't reserve for all clipped lines)
- font: imgui_freetype.h alternative renderer (#618)
- font: optimization: for monospace font (like the default one) we can trim IndexXAdvance as long as trailing value is == FallbackXAdvance (need to make sure TAB is still correct). - font: optimization: for monospace font (like the default one) we can trim IndexXAdvance as long as trailing value is == FallbackXAdvance (need to make sure TAB is still correct).
- font: add support for kerning, probably optional. A) perhaps default to (32..128)^2 matrix ~ 9K entries = 36KB, then hash for non-ascii?. B) or sparse lookup into per-char list? - font: add support for kerning, probably optional. A) perhaps default to (32..128)^2 matrix ~ 9K entries = 36KB, then hash for non-ascii?. B) or sparse lookup into per-char list?
- font: add a simpler CalcTextSizeA() api? current one ok but not welcome if user needs to call it directly (without going through ImGui::CalcTextSize) - font: add a simpler CalcTextSizeA() api? current one ok but not welcome if user needs to call it directly (without going through ImGui::CalcTextSize)

View File

@ -2047,7 +2047,7 @@ struct ImFontAtlas
ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_)
ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure.
int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height.
int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0.
// [Internal] // [Internal]
// NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you.

View File

@ -1541,11 +1541,11 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
if (!font_cfg->MergeMode) if (!font_cfg->MergeMode)
Fonts.push_back(IM_NEW(ImFont)); Fonts.push_back(IM_NEW(ImFont));
else else
IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. IM_ASSERT(!Fonts.empty() && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font.
ConfigData.push_back(*font_cfg); ConfigData.push_back(*font_cfg);
ImFontConfig& new_font_cfg = ConfigData.back(); ImFontConfig& new_font_cfg = ConfigData.back();
if (!new_font_cfg.DstFont) if (new_font_cfg.DstFont == NULL)
new_font_cfg.DstFont = Fonts.back(); new_font_cfg.DstFont = Fonts.back();
if (!new_font_cfg.FontDataOwnedByAtlas) if (!new_font_cfg.FontDataOwnedByAtlas)
{ {
@ -1733,139 +1733,220 @@ void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsig
data[i] = table[data[i]]; data[i] = table[data[i]];
} }
// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont)
// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.)
struct ImFontBuildSrcData
{
stbtt_fontinfo FontInfo;
stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data)
stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position.
stbtt_packedchar* PackedChars; // Output glyphs
const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF)
int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[]
int GlyphsHighest; // Highest requested codepoint
int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font)
ImBoolVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB)
ImVector<int> GlyphsList; // Glyph codepoints list (flattened version of GlyphsMap)
};
// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont)
struct ImFontBuildDstData
{
int SrcCount; // Number of source fonts targeting this destination font.
int GlyphsHighest;
int GlyphsCount;
ImBoolVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font.
};
static void UnpackBoolVectorToFlatIndexList(const ImBoolVector* in, ImVector<int>* out)
{
IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int));
const int* it_begin = in->Storage.begin();
const int* it_end = in->Storage.end();
for (const int* it = it_begin; it < it_end; it++)
if (int entries_32 = *it)
for (int bit_n = 0; bit_n < 32; bit_n++)
if (entries_32 & (1 << bit_n))
out->push_back((int)((it - it_begin) << 5) + bit_n);
}
bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
{ {
IM_ASSERT(atlas->ConfigData.Size > 0); IM_ASSERT(atlas->ConfigData.Size > 0);
ImFontAtlasBuildRegisterDefaultCustomRects(atlas); ImFontAtlasBuildRegisterDefaultCustomRects(atlas);
// Clear atlas
atlas->TexID = (ImTextureID)NULL; atlas->TexID = (ImTextureID)NULL;
atlas->TexWidth = atlas->TexHeight = 0; atlas->TexWidth = atlas->TexHeight = 0;
atlas->TexUvScale = ImVec2(0.0f, 0.0f); atlas->TexUvScale = ImVec2(0.0f, 0.0f);
atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
atlas->ClearTexData(); atlas->ClearTexData();
// Count glyphs/ranges // Temporary storage for building
int total_glyphs_count = 0; ImVector<ImFontBuildSrcData> src_tmp_array;
int total_ranges_count = 0; ImVector<ImFontBuildDstData> dst_tmp_array;
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) src_tmp_array.resize(atlas->ConfigData.Size);
dst_tmp_array.resize(atlas->Fonts.Size);
memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes());
memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes());
// 1. Initialize font loading structure, check font data validity
for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++)
{ {
ImFontConfig& cfg = atlas->ConfigData[input_i]; ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
if (!cfg.GlyphRanges) ImFontConfig& cfg = atlas->ConfigData[src_i];
cfg.GlyphRanges = atlas->GetGlyphRangesDefault();
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++)
total_glyphs_count += (in_range[1] - in_range[0]) + 1;
}
// We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish.
// Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512;
atlas->TexHeight = 0;
// Start packing
const int max_tex_height = 1024*32;
stbtt_pack_context spc = {};
if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL))
return false;
stbtt_PackSetOversampling(&spc, 1, 1);
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info);
// Initialize font information (so we can error without any cleanup)
struct ImFontTempBuildData
{
stbtt_fontinfo FontInfo;
stbrp_rect* Rects;
int RectsCount;
stbtt_pack_range* Ranges;
int RangesCount;
};
ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData));
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
{
ImFontConfig& cfg = atlas->ConfigData[input_i];
ImFontTempBuildData& tmp = tmp_array[input_i];
IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
// Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)
src_tmp.DstIndex = -1;
for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++)
if (cfg.DstFont == atlas->Fonts[output_i])
src_tmp.DstIndex = output_i;
IM_ASSERT(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array?
if (src_tmp.DstIndex == -1)
return false;
// Initialize helper structure for font loading and verify that the TTF/OTF data is correct
const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo);
IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found.");
if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset))
{
atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure
ImGui::MemFree(tmp_array);
return false; return false;
// Measure highest codepoints
ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]);
dst_tmp.SrcCount++;
dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest);
}
// 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs.
int total_glyphs_count = 0;
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
ImFontConfig& cfg = atlas->ConfigData[src_i];
src_tmp.GlyphsSet.Resize(src_tmp.GlyphsHighest);
if (dst_tmp.SrcCount > 1 && dst_tmp.GlyphsSet.Storage.empty())
dst_tmp.GlyphsSet.Resize(dst_tmp.GlyphsHighest);
for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
for (int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++)
{
if (cfg.MergeMode && dst_tmp.GlyphsSet.GetBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option (e.g. MergeOverwrite)
continue;
if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font?
continue;
// Add to avail set/counters
src_tmp.GlyphsCount++;
dst_tmp.GlyphsCount++;
src_tmp.GlyphsSet.SetBit(codepoint, true);
if (dst_tmp.SrcCount > 1)
dst_tmp.GlyphsSet.SetBit(codepoint, true);
total_glyphs_count++;
} }
} }
// 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another)
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount);
UnpackBoolVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList);
src_tmp.GlyphsSet.Clear();
IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount);
}
for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++)
dst_tmp_array[dst_i].GlyphsSet.Clear();
dst_tmp_array.clear();
// Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0)
int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity)
stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); ImVector<stbrp_rect> buf_rects;
stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); ImVector<stbtt_packedchar> buf_packedchars;
stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); buf_rects.resize(total_glyphs_count);
memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); buf_packedchars.resize(total_glyphs_count);
memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());
memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes());
// First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) // 4. Gather glyphs sizes so we can pack them in our virtual canvas.
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) int total_surface = 0;
int buf_rects_out_n = 0;
int buf_packedchars_out_n = 0;
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{ {
ImFontConfig& cfg = atlas->ConfigData[input_i]; ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
ImFontTempBuildData& tmp = tmp_array[input_i]; if (src_tmp.GlyphsCount == 0)
continue;
// Setup ranges src_tmp.Rects = &buf_rects[buf_rects_out_n];
int font_glyphs_count = 0; src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n];
int font_ranges_count = 0; buf_rects_out_n += src_tmp.GlyphsCount;
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) buf_packedchars_out_n += src_tmp.GlyphsCount;
font_glyphs_count += (in_range[1] - in_range[0]) + 1;
tmp.Ranges = buf_ranges + buf_ranges_n; // Convert our ranges in the format stb_truetype wants
tmp.RangesCount = font_ranges_count; ImFontConfig& cfg = atlas->ConfigData[src_i];
buf_ranges_n += font_ranges_count; src_tmp.PackRange.font_size = cfg.SizePixels;
for (int i = 0; i < font_ranges_count; i++) src_tmp.PackRange.first_unicode_codepoint_in_range = 0;
src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data;
src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size;
src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars;
src_tmp.PackRange.h_oversample = (unsigned char)cfg.OversampleH;
src_tmp.PackRange.v_oversample = (unsigned char)cfg.OversampleV;
// Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects)
const float scale = (cfg.SizePixels > 0) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels);
const int padding = atlas->TexGlyphPadding;
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
{ {
const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; int x0, y0, x1, y1;
stbtt_pack_range& range = tmp.Ranges[i]; const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]);
range.font_size = cfg.SizePixels; IM_ASSERT(glyph_index_in_font != 0);
range.first_unicode_codepoint_in_range = in_range[0]; stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1);
range.num_chars = (in_range[1] - in_range[0]) + 1; src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1);
range.chardata_for_range = buf_packedchars + buf_packedchars_n; src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1);
buf_packedchars_n += range.num_chars; total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;
}
// Gather the sizes of all rectangle we need
tmp.Rects = buf_rects + buf_rects_n;
tmp.RectsCount = font_glyphs_count;
buf_rects_n += font_glyphs_count;
stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV);
int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
IM_ASSERT(n == font_glyphs_count);
// Detect missing glyphs and replace them with a zero-sized box instead of relying on the default glyphs
// This allows us merging overlapping icon fonts more easily.
int rect_i = 0;
for (int range_i = 0; range_i < tmp.RangesCount; range_i++)
for (int char_i = 0; char_i < tmp.Ranges[range_i].num_chars; char_i++, rect_i++)
if (stbtt_FindGlyphIndex(&tmp.FontInfo, tmp.Ranges[range_i].first_unicode_codepoint_in_range + char_i) == 0)
tmp.Rects[rect_i].w = tmp.Rects[rect_i].h = 0;
// Pack
stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n);
// Extend texture height
// Also mark missing glyphs as non-packed so we don't attempt to render into them
for (int i = 0; i < n; i++)
{
if (tmp.Rects[i].w == 0 && tmp.Rects[i].h == 0)
tmp.Rects[i].was_packed = 0;
if (tmp.Rects[i].was_packed)
atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h);
} }
} }
IM_ASSERT(buf_rects_n == total_glyphs_count);
IM_ASSERT(buf_packedchars_n == total_glyphs_count);
IM_ASSERT(buf_ranges_n == total_ranges_count);
// Create texture // We need a width for the skyline algorithm, any width!
// The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
// User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface.
const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1;
atlas->TexHeight = 0;
if (atlas->TexDesiredWidth > 0)
atlas->TexWidth = atlas->TexDesiredWidth;
else
atlas->TexWidth = (surface_sqrt >= 4096*0.7f) ? 4096 : (surface_sqrt >= 2048*0.7f) ? 2048 : (surface_sqrt >= 1024*0.7f) ? 1024 : 512;
// 5. Start packing
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
const int TEX_HEIGHT_MAX = 1024 * 32;
stbtt_pack_context spc = {};
stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, atlas->TexGlyphPadding, NULL);
ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info);
// 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point.
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
if (src_tmp.GlyphsCount == 0)
continue;
stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount);
// Extend texture height and mark missing glyphs as non-packed so we won't render them.
// FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?)
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
if (src_tmp.Rects[glyph_i].was_packed)
atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h);
}
// 7. Allocate texture
atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);
atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight);
@ -1873,41 +1954,46 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
spc.pixels = atlas->TexPixelsAlpha8; spc.pixels = atlas->TexPixelsAlpha8;
spc.height = atlas->TexHeight; spc.height = atlas->TexHeight;
// Second pass: render font characters // 8. Render/rasterize font characters into the texture
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{ {
ImFontConfig& cfg = atlas->ConfigData[input_i]; ImFontConfig& cfg = atlas->ConfigData[src_i];
ImFontTempBuildData& tmp = tmp_array[input_i]; ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); if (src_tmp.GlyphsCount == 0)
stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); continue;
stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects);
// Apply multiply operator
if (cfg.RasterizerMultiply != 1.0f) if (cfg.RasterizerMultiply != 1.0f)
{ {
unsigned char multiply_table[256]; unsigned char multiply_table[256];
ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) stbrp_rect* r = &src_tmp.Rects[0];
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++)
if (r->was_packed) if (r->was_packed)
ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1);
} }
tmp.Rects = NULL; src_tmp.Rects = NULL;
} }
// End packing // End packing
stbtt_PackEnd(&spc); stbtt_PackEnd(&spc);
ImGui::MemFree(buf_rects); buf_rects.clear();
buf_rects = NULL;
// Third pass: setup ImFont and glyphs for runtime // 9. Setup ImFont and glyphs for runtime
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{ {
ImFontConfig& cfg = atlas->ConfigData[input_i]; ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
ImFontTempBuildData& tmp = tmp_array[input_i]; if (src_tmp.GlyphsCount == 0)
ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) continue;
if (cfg.MergeMode)
dst_font->BuildLookupTable();
const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); ImFontConfig& cfg = atlas->ConfigData[src_i];
ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true)
const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels);
int unscaled_ascent, unscaled_descent, unscaled_line_gap; int unscaled_ascent, unscaled_descent, unscaled_line_gap;
stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap);
const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1));
const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1));
@ -1915,40 +2001,30 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
const float font_off_x = cfg.GlyphOffset.x; const float font_off_x = cfg.GlyphOffset.x;
const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f);
for (int i = 0; i < tmp.RangesCount; i++) for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
{ {
stbtt_pack_range& range = tmp.Ranges[i]; const int codepoint = src_tmp.GlyphsList[glyph_i];
for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i];
{
const stbtt_packedchar& pc = range.chardata_for_range[char_idx];
if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1)
continue;
const int codepoint = range.first_unicode_codepoint_in_range + char_idx; const float char_advance_x_org = pc.xadvance;
if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint)) const float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
continue;
float char_advance_x_org = pc.xadvance;
float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
float char_off_x = font_off_x; float char_off_x = font_off_x;
if (char_advance_x_org != char_advance_x_mod) if (char_advance_x_org != char_advance_x_mod)
char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f; char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f;
// Register glyph
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, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &dummy_x, &dummy_y, &q, 0);
dst_font->AddGlyph((ImWchar)codepoint, q.x0 + char_off_x, q.y0 + font_off_y, q.x1 + char_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, char_advance_x_mod); dst_font->AddGlyph((ImWchar)codepoint, q.x0 + char_off_x, q.y0 + font_off_y, q.x1 + char_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, char_advance_x_mod);
} }
} }
}
// Cleanup temporaries // Cleanup temporary (ImVector doesn't honor destructor)
ImGui::MemFree(buf_packedchars); for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
ImGui::MemFree(buf_ranges); src_tmp_array[src_i].~ImFontBuildSrcData();
ImGui::MemFree(tmp_array);
ImFontAtlasBuildFinish(atlas); ImFontAtlasBuildFinish(atlas);
return true; return true;
} }
@ -1976,16 +2052,16 @@ void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* f
font->ConfigDataCount++; font->ConfigDataCount++;
} }
void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque)
{ {
stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque;
ImVector<ImFontAtlas::CustomRect>& user_rects = atlas->CustomRects; ImVector<ImFontAtlas::CustomRect>& user_rects = atlas->CustomRects;
IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong.
ImVector<stbrp_rect> pack_rects; ImVector<stbrp_rect> pack_rects;
pack_rects.resize(user_rects.Size); pack_rects.resize(user_rects.Size);
memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes());
for (int i = 0; i < user_rects.Size; i++) for (int i = 0; i < user_rects.Size; i++)
{ {
pack_rects[i].w = user_rects[i].Width; pack_rects[i].w = user_rects[i].Width;

View File

@ -1452,7 +1452,7 @@ namespace ImGui
IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent);
IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);
IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor);
IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride);