Merge branch 'atlas_fixes'
This commit is contained in:
commit
abdd39b700
@ -72,6 +72,11 @@ Other Changes:
|
||||
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
|
||||
into e.g. an error log and stopping the script execution.
|
||||
- ImFontAtlas: Stb and FreeType: Atlas width is now properly based on total surface rather than glyph count (unless overridden with TexDesiredWidth).
|
||||
- ImFontAtlas: Stb and FreeType: Fixed atlas builder so missing glyphs won't influence the atlas texture width. (#2233)
|
||||
- ImFontAtlas: Stb and FreeType: Fixed atlas builder so duplicate glyphs (when merging fonts) won't be included in the rasterized atlas.
|
||||
- ImFontAtlas: FreeType: Fixed abnormally high atlas height.
|
||||
- ImFontAtlas: FreeType: Fixed support for any values of TexGlyphPadding (not just only 1).
|
||||
- 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.)
|
||||
- IO: Added BackendPlatformUserData, BackendRendererUserData, BackendLanguageUserData void* for storage use by back-ends.
|
||||
|
@ -235,9 +235,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i
|
||||
- pie menus patterns (#434)
|
||||
- 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.
|
||||
- font: MergeMode: duplicate glyphs are stored in the atlas texture which is suboptimal.
|
||||
- font: MergeMode: flags to select overwriting or not (this is now very easy with refactored ImFontAtlasBuildWithStbTruetype)
|
||||
- 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: 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: 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: 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: 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)
|
||||
|
3
imgui.h
3
imgui.h
@ -1182,6 +1182,7 @@ struct ImVector
|
||||
|
||||
inline bool empty() const { return Size == 0; }
|
||||
inline int size() const { return Size; }
|
||||
inline int size_in_bytes() const { return Size * (int)sizeof(T); }
|
||||
inline int capacity() const { return Capacity; }
|
||||
inline T& operator[](int i) { IM_ASSERT(i < Size); return Data[i]; }
|
||||
inline const T& operator[](int i) const { IM_ASSERT(i < Size); return Data[i]; }
|
||||
@ -2046,7 +2047,7 @@ struct ImFontAtlas
|
||||
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.
|
||||
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]
|
||||
// NB: Access texture data via GetTexData*() calls! Which will setup a default font for you.
|
||||
|
390
imgui_draw.cpp
390
imgui_draw.cpp
@ -1541,11 +1541,11 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
|
||||
if (!font_cfg->MergeMode)
|
||||
Fonts.push_back(IM_NEW(ImFont));
|
||||
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);
|
||||
ImFontConfig& new_font_cfg = ConfigData.back();
|
||||
if (!new_font_cfg.DstFont)
|
||||
if (new_font_cfg.DstFont == NULL)
|
||||
new_font_cfg.DstFont = Fonts.back();
|
||||
if (!new_font_cfg.FontDataOwnedByAtlas)
|
||||
{
|
||||
@ -1733,139 +1733,220 @@ void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsig
|
||||
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)
|
||||
{
|
||||
IM_ASSERT(atlas->ConfigData.Size > 0);
|
||||
|
||||
ImFontAtlasBuildRegisterDefaultCustomRects(atlas);
|
||||
|
||||
// Clear atlas
|
||||
atlas->TexID = (ImTextureID)NULL;
|
||||
atlas->TexWidth = atlas->TexHeight = 0;
|
||||
atlas->TexUvScale = ImVec2(0.0f, 0.0f);
|
||||
atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
|
||||
atlas->ClearTexData();
|
||||
|
||||
// Count glyphs/ranges
|
||||
int total_glyphs_count = 0;
|
||||
int total_ranges_count = 0;
|
||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
||||
// Temporary storage for building
|
||||
ImVector<ImFontBuildSrcData> src_tmp_array;
|
||||
ImVector<ImFontBuildDstData> dst_tmp_array;
|
||||
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];
|
||||
if (!cfg.GlyphRanges)
|
||||
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];
|
||||
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
|
||||
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||
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);
|
||||
IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found.");
|
||||
if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset))
|
||||
{
|
||||
atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure
|
||||
ImGui::MemFree(tmp_array);
|
||||
if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset))
|
||||
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 + 1);
|
||||
if (dst_tmp.SrcCount > 1 && dst_tmp.GlyphsSet.Storage.empty())
|
||||
dst_tmp.GlyphsSet.Resize(dst_tmp.GlyphsHighest + 1);
|
||||
|
||||
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)
|
||||
int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0;
|
||||
stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar));
|
||||
stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect));
|
||||
stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range));
|
||||
memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar));
|
||||
memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity.
|
||||
memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range));
|
||||
// (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity)
|
||||
ImVector<stbrp_rect> buf_rects;
|
||||
ImVector<stbtt_packedchar> buf_packedchars;
|
||||
buf_rects.resize(total_glyphs_count);
|
||||
buf_packedchars.resize(total_glyphs_count);
|
||||
memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());
|
||||
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)
|
||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
||||
// 4. Gather glyphs sizes so we can pack them in our virtual canvas.
|
||||
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];
|
||||
ImFontTempBuildData& tmp = tmp_array[input_i];
|
||||
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
|
||||
if (src_tmp.GlyphsCount == 0)
|
||||
continue;
|
||||
|
||||
// Setup ranges
|
||||
int font_glyphs_count = 0;
|
||||
int font_ranges_count = 0;
|
||||
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++)
|
||||
font_glyphs_count += (in_range[1] - in_range[0]) + 1;
|
||||
tmp.Ranges = buf_ranges + buf_ranges_n;
|
||||
tmp.RangesCount = font_ranges_count;
|
||||
buf_ranges_n += font_ranges_count;
|
||||
for (int i = 0; i < font_ranges_count; i++)
|
||||
src_tmp.Rects = &buf_rects[buf_rects_out_n];
|
||||
src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n];
|
||||
buf_rects_out_n += src_tmp.GlyphsCount;
|
||||
buf_packedchars_out_n += src_tmp.GlyphsCount;
|
||||
|
||||
// Convert our ranges in the format stb_truetype wants
|
||||
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||
src_tmp.PackRange.font_size = cfg.SizePixels;
|
||||
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];
|
||||
stbtt_pack_range& range = tmp.Ranges[i];
|
||||
range.font_size = cfg.SizePixels;
|
||||
range.first_unicode_codepoint_in_range = in_range[0];
|
||||
range.num_chars = (in_range[1] - in_range[0]) + 1;
|
||||
range.chardata_for_range = buf_packedchars + buf_packedchars_n;
|
||||
buf_packedchars_n += range.num_chars;
|
||||
}
|
||||
|
||||
// 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);
|
||||
int x0, y0, x1, y1;
|
||||
const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]);
|
||||
IM_ASSERT(glyph_index_in_font != 0);
|
||||
stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1);
|
||||
src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1);
|
||||
src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1);
|
||||
total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_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->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / 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.height = atlas->TexHeight;
|
||||
|
||||
// Second pass: render font characters
|
||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
||||
// 8. Render/rasterize font characters into the texture
|
||||
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||
{
|
||||
ImFontConfig& cfg = atlas->ConfigData[input_i];
|
||||
ImFontTempBuildData& tmp = tmp_array[input_i];
|
||||
stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV);
|
||||
stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
|
||||
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
|
||||
if (src_tmp.GlyphsCount == 0)
|
||||
continue;
|
||||
|
||||
stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects);
|
||||
|
||||
// Apply multiply operator
|
||||
if (cfg.RasterizerMultiply != 1.0f)
|
||||
{
|
||||
unsigned char multiply_table[256];
|
||||
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)
|
||||
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
|
||||
stbtt_PackEnd(&spc);
|
||||
ImGui::MemFree(buf_rects);
|
||||
buf_rects = NULL;
|
||||
buf_rects.clear();
|
||||
|
||||
// Third pass: setup ImFont and glyphs for runtime
|
||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
||||
// 9. Setup ImFont and glyphs for runtime
|
||||
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||
{
|
||||
ImFontConfig& cfg = atlas->ConfigData[input_i];
|
||||
ImFontTempBuildData& tmp = tmp_array[input_i];
|
||||
ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true)
|
||||
if (cfg.MergeMode)
|
||||
dst_font->BuildLookupTable();
|
||||
ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
|
||||
if (src_tmp.GlyphsCount == 0)
|
||||
continue;
|
||||
|
||||
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;
|
||||
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 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_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];
|
||||
for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1)
|
||||
{
|
||||
const stbtt_packedchar& pc = range.chardata_for_range[char_idx];
|
||||
if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1)
|
||||
continue;
|
||||
const int codepoint = src_tmp.GlyphsList[glyph_i];
|
||||
const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i];
|
||||
|
||||
const int codepoint = range.first_unicode_codepoint_in_range + char_idx;
|
||||
if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint))
|
||||
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;
|
||||
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;
|
||||
|
||||
stbtt_aligned_quad q;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
const float char_advance_x_org = pc.xadvance;
|
||||
const float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
|
||||
float char_off_x = font_off_x;
|
||||
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;
|
||||
|
||||
// Register glyph
|
||||
stbtt_aligned_quad q;
|
||||
float dummy_x = 0.0f, dummy_y = 0.0f;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup temporaries
|
||||
ImGui::MemFree(buf_packedchars);
|
||||
ImGui::MemFree(buf_ranges);
|
||||
ImGui::MemFree(tmp_array);
|
||||
// Cleanup temporary (ImVector doesn't honor destructor)
|
||||
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||
src_tmp_array[src_i].~ImFontBuildSrcData();
|
||||
|
||||
ImFontAtlasBuildFinish(atlas);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1976,16 +2052,16 @@ void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* f
|
||||
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;
|
||||
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;
|
||||
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++)
|
||||
{
|
||||
pack_rects[i].w = user_rects[i].Width;
|
||||
|
@ -236,6 +236,18 @@ static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a)
|
||||
static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; }
|
||||
static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); }
|
||||
|
||||
// Helper: ImBoolVector. Store 1-bit per value.
|
||||
// Note that Resize() currently clears the whole vector.
|
||||
struct ImBoolVector
|
||||
{
|
||||
ImVector<int> Storage;
|
||||
ImBoolVector() { }
|
||||
void Resize(int sz) { Storage.resize((sz + 31) >> 5); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); }
|
||||
void Clear() { Storage.clear(); }
|
||||
bool GetBit(int n) const { int off = (n >> 5); int mask = 1 << (n & 31); return (Storage[off] & mask) != 0; }
|
||||
void SetBit(int n, bool v) { int off = (n >> 5); int mask = 1 << (n & 31); if (v) Storage[off] |= mask; else Storage[off] &= ~mask; }
|
||||
};
|
||||
|
||||
// Helper: ImPool<>. Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer,
|
||||
// Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object.
|
||||
typedef int ImPoolIdx;
|
||||
@ -1440,7 +1452,7 @@ namespace ImGui
|
||||
IMGUI_API bool ImFontAtlasBuildWithStbTruetype(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 ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc);
|
||||
IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);
|
||||
IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas);
|
||||
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);
|
||||
|
@ -1,14 +1,14 @@
|
||||
# imgui_freetype
|
||||
|
||||
This is an attempt to replace stb_truetype (the default imgui's font rasterizer) with FreeType.
|
||||
Currently not optimal and probably has some limitations or bugs.
|
||||
By [Vuhdo](https://github.com/Vuhdo) (Aleksei Skriabin). Improvements by @mikesart. Maintained by @ocornut.
|
||||
Build font atlases using FreeType instead of stb_truetype (the default imgui's font rasterizer).
|
||||
<br>by @vuhdo, @mikesart, @ocornut.
|
||||
|
||||
**Usage**
|
||||
1. Get latest FreeType binaries or build yourself.
|
||||
### Usage
|
||||
|
||||
1. Get latest FreeType binaries or build yourself (under Windows you may use vcpkg with `vcpkg install freetype`).
|
||||
2. Add imgui_freetype.h/cpp alongside your imgui sources.
|
||||
3. Include imgui_freetype.h after imgui.h.
|
||||
4. Call ImGuiFreeType::BuildFontAtlas() *BEFORE* calling ImFontAtlas::GetTexDataAsRGBA32() or ImFontAtlas::Build() (so normal Build() won't be called):
|
||||
4. Call `ImGuiFreeType::BuildFontAtlas()` *BEFORE* calling `ImFontAtlas::GetTexDataAsRGBA32()` or `ImFontAtlas::Build()` (so normal Build() won't be called):
|
||||
|
||||
```cpp
|
||||
// See ImGuiFreeType::RasterizationFlags
|
||||
@ -17,13 +17,14 @@ ImGuiFreeType::BuildFontAtlas(io.Fonts, flags);
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
```
|
||||
|
||||
**Gamma Correct Blending**
|
||||
### Gamma Correct Blending
|
||||
|
||||
FreeType assumes blending in linear space rather than gamma space.
|
||||
See FreeType note for [FT_Render_Glyph](https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph).
|
||||
For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
|
||||
The default imgui styles will be impacted by this change (alpha values will need tweaking).
|
||||
|
||||
**Test code Usage**
|
||||
### Test code Usage
|
||||
```cpp
|
||||
#include "misc/freetype/imgui_freetype.h"
|
||||
#include "misc/freetype/imgui_freetype.cpp"
|
||||
@ -42,16 +43,15 @@ while (true)
|
||||
if (freetype_test.UpdateRebuild())
|
||||
{
|
||||
// REUPLOAD FONT TEXTURE TO GPU
|
||||
// e.g ImGui_ImplGlfwGL3_InvalidateDeviceObjects() + ImGui_ImplGlfwGL3_CreateDeviceObjects()
|
||||
// e.g ImGui_ImplOpenGL3_DestroyDeviceObjects() + ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
}
|
||||
ImGui::NewFrame();
|
||||
freetype_test.ShowFreetypeOptionsWindow();
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Test code**
|
||||
### Test code
|
||||
```cpp
|
||||
#include "misc/freetype/imgui_freetype.h"
|
||||
#include "misc/freetype/imgui_freetype.cpp"
|
||||
@ -61,7 +61,7 @@ struct FreeTypeTest
|
||||
enum FontBuildMode
|
||||
{
|
||||
FontBuildMode_FreeType,
|
||||
FontBuildMode_Stb,
|
||||
FontBuildMode_Stb
|
||||
};
|
||||
|
||||
FontBuildMode BuildMode;
|
||||
@ -120,14 +120,7 @@ struct FreeTypeTest
|
||||
};
|
||||
```
|
||||
|
||||
**Known issues**
|
||||
- Output texture has excessive resolution (lots of vertical waste).
|
||||
### Known issues
|
||||
- FreeType's memory allocator is not overridden.
|
||||
- `cfg.OversampleH`, `OversampleV` are ignored (but perhaps not so necessary with this rasterizer).
|
||||
|
||||
**Obligatory comparison screenshots**
|
||||
|
||||
Using Windows built-in segoeui.ttf font. Open in new browser tabs, view at 1080p+.
|
||||
|
||||
![freetype rasterizer](https://raw.githubusercontent.com/wiki/ocornut/imgui_club/images/freetype_20170817.png)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Wrapper to use Freetype (instead of stb_truetype) for Dear ImGui
|
||||
// Wrapper to use FreeType (instead of stb_truetype) for Dear ImGui
|
||||
// Get latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
|
||||
// Original code by @Vuhdo (Aleksei Skriabin). Improvements by @mikesart. Maintained by @ocornut
|
||||
// Original code by @vuhdo (Aleksei Skriabin). Improvements by @mikesart. Maintained and v0.60+ by @ocornut.
|
||||
|
||||
// Changelog:
|
||||
// - v0.50: (2017/08/16) imported from https://github.com/Vuhdo/imgui_freetype into http://www.github.com/ocornut/imgui_club, updated for latest changes in ImFontAtlas, minor tweaks.
|
||||
@ -10,6 +10,7 @@
|
||||
// - v0.54: (2018/01/22) fix for addition of ImFontAtlas::TexUvscale member
|
||||
// - v0.55: (2018/02/04) moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
|
||||
// - v0.56: (2018/06/08) added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX
|
||||
// - v0.60: (2019/01/10) re-factored to match big update in STB builder. fixed texture height waste. fixed redundant glyphs when merging. support for glyph padding.
|
||||
|
||||
// Gamma Correct Blending:
|
||||
// FreeType assumes blending in linear space rather than gamma space.
|
||||
@ -17,18 +18,16 @@
|
||||
// For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
|
||||
// The default imgui styles will be impacted by this change (alpha values will need tweaking).
|
||||
|
||||
// TODO:
|
||||
// - Output texture has excessive resolution (lots of vertical waste).
|
||||
// - FreeType's memory allocator is not overridden.
|
||||
// - cfg.OversampleH, OversampleV are ignored (but perhaps not so necessary with this rasterizer).
|
||||
// FIXME: FreeType's memory allocator is not overridden.
|
||||
// FIXME: cfg.OversampleH, OversampleV are not supported (but perhaps not so necessary with this rasterizer).
|
||||
|
||||
#include "imgui_freetype.h"
|
||||
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
|
||||
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
|
||||
#include <stdint.h>
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_GLYPH_H
|
||||
#include FT_SYNTHESIS_H
|
||||
#include FT_FREETYPE_H // <freetype/freetype.h>
|
||||
#include FT_GLYPH_H // <freetype/ftglyph.h>
|
||||
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
|
||||
@ -74,15 +73,15 @@ namespace
|
||||
/// A structure that describe a glyph.
|
||||
struct GlyphInfo
|
||||
{
|
||||
float Width; // Glyph's width in pixels.
|
||||
float Height; // Glyph's height in pixels.
|
||||
float OffsetX; // The distance from the origin ("pen position") to the left of the glyph.
|
||||
float OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0.
|
||||
float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0.
|
||||
int Width; // Glyph's width in pixels.
|
||||
int Height; // Glyph's height in pixels.
|
||||
FT_Int OffsetX; // The distance from the origin ("pen position") to the left of the glyph.
|
||||
FT_Int OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0.
|
||||
float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0.
|
||||
};
|
||||
|
||||
// Font parameters and metrics.
|
||||
struct FontInfo
|
||||
struct FontInfo
|
||||
{
|
||||
uint32_t PixelHeight; // Size this font was generated with.
|
||||
float Ascender; // The pixel extents above the baseline in pixels (typically positive).
|
||||
@ -96,82 +95,80 @@ namespace
|
||||
// NB: No ctor/dtor, explicitly call Init()/Shutdown()
|
||||
struct FreeTypeFont
|
||||
{
|
||||
bool Init(const ImFontConfig& cfg, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
|
||||
void Shutdown();
|
||||
void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size
|
||||
|
||||
bool CalcGlyphInfo(uint32_t codepoint, GlyphInfo& glyph_info, FT_Glyph& ft_glyph, FT_BitmapGlyph& ft_bitmap);
|
||||
void BlitGlyph(FT_BitmapGlyph ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = NULL);
|
||||
bool InitFont(FT_Library ft_library, const ImFontConfig& cfg, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
|
||||
void CloseFont();
|
||||
void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size
|
||||
const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint);
|
||||
const FT_Bitmap* RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info);
|
||||
void BlitGlyph(const FT_Bitmap* ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = NULL);
|
||||
~FreeTypeFont() { CloseFont(); }
|
||||
|
||||
// [Internals]
|
||||
FontInfo Info; // Font descriptor of the current font.
|
||||
FT_Face Face;
|
||||
unsigned int UserFlags; // = ImFontConfig::RasterizerFlags
|
||||
FT_Library FreetypeLibrary;
|
||||
FT_Face FreetypeFace;
|
||||
FT_Int32 FreetypeLoadFlags;
|
||||
FT_Int32 LoadFlags;
|
||||
};
|
||||
|
||||
// From SDL_ttf: Handy routines for converting from fixed point
|
||||
#define FT_CEIL(X) (((X + 63) & -64) / 64)
|
||||
|
||||
bool FreeTypeFont::Init(const ImFontConfig& cfg, unsigned int extra_user_flags)
|
||||
bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& cfg, unsigned int extra_user_flags)
|
||||
{
|
||||
// FIXME: substitute allocator
|
||||
FT_Error error = FT_Init_FreeType(&FreetypeLibrary);
|
||||
FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &Face);
|
||||
if (error != 0)
|
||||
return false;
|
||||
error = FT_New_Memory_Face(FreetypeLibrary, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &FreetypeFace);
|
||||
if (error != 0)
|
||||
return false;
|
||||
error = FT_Select_Charmap(FreetypeFace, FT_ENCODING_UNICODE);
|
||||
error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE);
|
||||
if (error != 0)
|
||||
return false;
|
||||
|
||||
memset(&Info, 0, sizeof(Info));
|
||||
SetPixelHeight((uint32_t)cfg.SizePixels);
|
||||
|
||||
// Convert to freetype flags (nb: Bold and Oblique are processed separately)
|
||||
// Convert to FreeType flags (NB: Bold and Oblique are processed separately)
|
||||
UserFlags = cfg.RasterizerFlags | extra_user_flags;
|
||||
FreetypeLoadFlags = FT_LOAD_NO_BITMAP;
|
||||
if (UserFlags & ImGuiFreeType::NoHinting) FreetypeLoadFlags |= FT_LOAD_NO_HINTING;
|
||||
if (UserFlags & ImGuiFreeType::NoAutoHint) FreetypeLoadFlags |= FT_LOAD_NO_AUTOHINT;
|
||||
if (UserFlags & ImGuiFreeType::ForceAutoHint) FreetypeLoadFlags |= FT_LOAD_FORCE_AUTOHINT;
|
||||
if (UserFlags & ImGuiFreeType::LightHinting)
|
||||
FreetypeLoadFlags |= FT_LOAD_TARGET_LIGHT;
|
||||
else if (UserFlags & ImGuiFreeType::MonoHinting)
|
||||
FreetypeLoadFlags |= FT_LOAD_TARGET_MONO;
|
||||
LoadFlags = FT_LOAD_NO_BITMAP;
|
||||
if (UserFlags & ImGuiFreeType::NoHinting)
|
||||
LoadFlags |= FT_LOAD_NO_HINTING;
|
||||
if (UserFlags & ImGuiFreeType::NoAutoHint)
|
||||
LoadFlags |= FT_LOAD_NO_AUTOHINT;
|
||||
if (UserFlags & ImGuiFreeType::ForceAutoHint)
|
||||
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
|
||||
if (UserFlags & ImGuiFreeType::LightHinting)
|
||||
LoadFlags |= FT_LOAD_TARGET_LIGHT;
|
||||
else if (UserFlags & ImGuiFreeType::MonoHinting)
|
||||
LoadFlags |= FT_LOAD_TARGET_MONO;
|
||||
else
|
||||
FreetypeLoadFlags |= FT_LOAD_TARGET_NORMAL;
|
||||
LoadFlags |= FT_LOAD_TARGET_NORMAL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FreeTypeFont::Shutdown()
|
||||
void FreeTypeFont::CloseFont()
|
||||
{
|
||||
if (FreetypeFace)
|
||||
if (Face)
|
||||
{
|
||||
FT_Done_Face(FreetypeFace);
|
||||
FreetypeFace = NULL;
|
||||
FT_Done_FreeType(FreetypeLibrary);
|
||||
FreetypeLibrary = NULL;
|
||||
FT_Done_Face(Face);
|
||||
Face = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeTypeFont::SetPixelHeight(int pixel_height)
|
||||
{
|
||||
// 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.
|
||||
// Vuhdo: 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.
|
||||
// NB: FT_Set_Pixel_Sizes() doesn't seem to get us the same result.
|
||||
FT_Size_RequestRec req;
|
||||
req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
|
||||
req.width = 0;
|
||||
req.height = (uint32_t)pixel_height * 64;
|
||||
req.horiResolution = 0;
|
||||
req.vertResolution = 0;
|
||||
FT_Request_Size(FreetypeFace, &req);
|
||||
FT_Request_Size(Face, &req);
|
||||
|
||||
// update font info
|
||||
FT_Size_Metrics metrics = FreetypeFace->size->metrics;
|
||||
// Update font info
|
||||
FT_Size_Metrics metrics = Face->size->metrics;
|
||||
Info.PixelHeight = (uint32_t)pixel_height;
|
||||
Info.Ascender = (float)FT_CEIL(metrics.ascender);
|
||||
Info.Descender = (float)FT_CEIL(metrics.descender);
|
||||
@ -180,52 +177,58 @@ namespace
|
||||
Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance);
|
||||
}
|
||||
|
||||
bool FreeTypeFont::CalcGlyphInfo(uint32_t codepoint, GlyphInfo &glyph_info, FT_Glyph& ft_glyph, FT_BitmapGlyph& ft_bitmap)
|
||||
const FT_Glyph_Metrics* FreeTypeFont::LoadGlyph(uint32_t codepoint)
|
||||
{
|
||||
uint32_t glyph_index = FT_Get_Char_Index(FreetypeFace, codepoint);
|
||||
uint32_t glyph_index = FT_Get_Char_Index(Face, codepoint);
|
||||
if (glyph_index == 0)
|
||||
return false;
|
||||
FT_Error error = FT_Load_Glyph(FreetypeFace, glyph_index, FreetypeLoadFlags);
|
||||
return NULL;
|
||||
FT_Error error = FT_Load_Glyph(Face, glyph_index, LoadFlags);
|
||||
if (error)
|
||||
return false;
|
||||
return NULL;
|
||||
|
||||
// Need an outline for this to work
|
||||
FT_GlyphSlot slot = FreetypeFace->glyph;
|
||||
FT_GlyphSlot slot = Face->glyph;
|
||||
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE);
|
||||
|
||||
// Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
|
||||
if (UserFlags & ImGuiFreeType::Bold)
|
||||
FT_GlyphSlot_Embolden(slot);
|
||||
if (UserFlags & ImGuiFreeType::Oblique)
|
||||
{
|
||||
FT_GlyphSlot_Oblique(slot);
|
||||
//FT_BBox bbox;
|
||||
//FT_Outline_Get_BBox(&slot->outline, &bbox);
|
||||
//slot->metrics.width = bbox.xMax - bbox.xMin;
|
||||
//slot->metrics.height = bbox.yMax - bbox.yMin;
|
||||
}
|
||||
|
||||
// Retrieve the glyph
|
||||
error = FT_Get_Glyph(slot, &ft_glyph);
|
||||
if (error != 0)
|
||||
return false;
|
||||
|
||||
// Rasterize
|
||||
error = FT_Glyph_To_Bitmap(&ft_glyph, FT_RENDER_MODE_NORMAL, NULL, true);
|
||||
if (error != 0)
|
||||
return false;
|
||||
|
||||
ft_bitmap = (FT_BitmapGlyph)ft_glyph;
|
||||
glyph_info.AdvanceX = (float)FT_CEIL(slot->advance.x);
|
||||
glyph_info.OffsetX = (float)ft_bitmap->left;
|
||||
glyph_info.OffsetY = -(float)ft_bitmap->top;
|
||||
glyph_info.Width = (float)ft_bitmap->bitmap.width;
|
||||
glyph_info.Height = (float)ft_bitmap->bitmap.rows;
|
||||
|
||||
return true;
|
||||
return &slot->metrics;
|
||||
}
|
||||
|
||||
void FreeTypeFont::BlitGlyph(FT_BitmapGlyph ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table)
|
||||
const FT_Bitmap* FreeTypeFont::RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info)
|
||||
{
|
||||
FT_GlyphSlot slot = Face->glyph;
|
||||
FT_Error error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
|
||||
if (error != 0)
|
||||
return NULL;
|
||||
|
||||
FT_Bitmap* ft_bitmap = &Face->glyph->bitmap;
|
||||
out_glyph_info->Width = (int)ft_bitmap->width;
|
||||
out_glyph_info->Height = (int)ft_bitmap->rows;
|
||||
out_glyph_info->OffsetX = Face->glyph->bitmap_left;
|
||||
out_glyph_info->OffsetY = -Face->glyph->bitmap_top;
|
||||
out_glyph_info->AdvanceX = (float)FT_CEIL(slot->advance.x);
|
||||
|
||||
return ft_bitmap;
|
||||
}
|
||||
|
||||
void FreeTypeFont::BlitGlyph(const FT_Bitmap* ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table)
|
||||
{
|
||||
IM_ASSERT(ft_bitmap != NULL);
|
||||
|
||||
const uint32_t w = ft_bitmap->bitmap.width;
|
||||
const uint32_t h = ft_bitmap->bitmap.rows;
|
||||
const uint8_t* src = ft_bitmap->bitmap.buffer;
|
||||
const uint32_t src_pitch = ft_bitmap->bitmap.pitch;
|
||||
const uint32_t w = ft_bitmap->width;
|
||||
const uint32_t h = ft_bitmap->rows;
|
||||
const uint8_t* src = ft_bitmap->buffer;
|
||||
const uint32_t src_pitch = ft_bitmap->pitch;
|
||||
|
||||
if (multiply_table == NULL)
|
||||
{
|
||||
@ -246,143 +249,332 @@ namespace
|
||||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#include "imstb_rectpack.h"
|
||||
|
||||
bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags)
|
||||
struct ImFontBuildSrcGlyphFT
|
||||
{
|
||||
GlyphInfo Info;
|
||||
uint32_t Codepoint;
|
||||
unsigned char* BitmapData; // Point within one of the dst_tmp_bitmap_buffers[] array
|
||||
};
|
||||
|
||||
struct ImFontBuildSrcDataFT
|
||||
{
|
||||
FreeTypeFont Font;
|
||||
stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position.
|
||||
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<ImFontBuildSrcGlyphFT> GlyphsList;
|
||||
};
|
||||
|
||||
// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont)
|
||||
struct ImFontBuildDstDataFT
|
||||
{
|
||||
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.
|
||||
};
|
||||
|
||||
bool ImFontAtlasBuildWithFreeType(FT_Library ft_library, ImFontAtlas* atlas, unsigned int extra_flags)
|
||||
{
|
||||
IM_ASSERT(atlas->ConfigData.Size > 0);
|
||||
IM_ASSERT(atlas->TexGlyphPadding == 1); // Not supported
|
||||
|
||||
ImFontAtlasBuildRegisterDefaultCustomRects(atlas);
|
||||
|
||||
atlas->TexID = NULL;
|
||||
// Clear atlas
|
||||
atlas->TexID = (ImTextureID)NULL;
|
||||
atlas->TexWidth = atlas->TexHeight = 0;
|
||||
atlas->TexUvScale = ImVec2(0.0f, 0.0f);
|
||||
atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
|
||||
atlas->ClearTexData();
|
||||
|
||||
ImVector<FreeTypeFont> fonts;
|
||||
fonts.resize(atlas->ConfigData.Size);
|
||||
// Temporary storage for building
|
||||
ImVector<ImFontBuildSrcDataFT> src_tmp_array;
|
||||
ImVector<ImFontBuildDstDataFT> dst_tmp_array;
|
||||
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());
|
||||
|
||||
ImVec2 max_glyph_size(1.0f, 1.0f);
|
||||
|
||||
// Count glyphs/ranges, initialize font
|
||||
int total_glyphs_count = 0;
|
||||
int total_ranges_count = 0;
|
||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
||||
// 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];
|
||||
FreeTypeFont& font_face = fonts[input_i];
|
||||
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||
FreeTypeFont& font_face = src_tmp.Font;
|
||||
IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
|
||||
|
||||
if (!font_face.Init(cfg, extra_flags))
|
||||
// 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;
|
||||
|
||||
max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth);
|
||||
max_glyph_size.y = ImMax(max_glyph_size.y, font_face.Info.Ascender - font_face.Info.Descender);
|
||||
// Load font
|
||||
if (!font_face.InitFont(ft_library, cfg, extra_flags))
|
||||
return false;
|
||||
|
||||
if (!cfg.GlyphRanges)
|
||||
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;
|
||||
// Measure highest codepoints
|
||||
ImFontBuildDstDataFT& 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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++)
|
||||
{
|
||||
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||
ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
|
||||
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||
src_tmp.GlyphsSet.Resize(src_tmp.GlyphsHighest + 1);
|
||||
if (dst_tmp.SrcCount > 1 && dst_tmp.GlyphsSet.Storage.empty())
|
||||
dst_tmp.GlyphsSet.Resize(dst_tmp.GlyphsHighest + 1);
|
||||
|
||||
// We don't do the original first pass to determine texture height, but just rough estimate.
|
||||
// Looks ugly inaccurate and excessive, but AFAIK with FreeType we actually need to render glyphs to get exact sizes.
|
||||
// Alternatively, we could just render all glyphs into a big shadow buffer, get their sizes, do the rectangle packing and just copy back from the
|
||||
// shadow buffer to the texture buffer. Will give us an accurate texture height, but eat a lot of temp memory. Probably no one will notice.)
|
||||
const int total_rects = total_glyphs_count + atlas->CustomRects.size();
|
||||
float min_rects_per_row = ceilf((atlas->TexWidth / (max_glyph_size.x + 1.0f)));
|
||||
float min_rects_per_column = ceilf(total_rects / min_rects_per_row);
|
||||
atlas->TexHeight = (int)(min_rects_per_column * (max_glyph_size.y + 1.0f));
|
||||
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;
|
||||
uint32_t glyph_index = FT_Get_Char_Index(src_tmp.Font.Face, codepoint); // It is actually in the font? (FIXME-OPT: We are not storing the glyph_index..)
|
||||
if (glyph_index == 0)
|
||||
continue;
|
||||
|
||||
// Create texture
|
||||
// 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++)
|
||||
{
|
||||
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||
src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount);
|
||||
|
||||
IM_ASSERT(sizeof(src_tmp.GlyphsSet.Storage.Data[0]) == sizeof(int));
|
||||
const int* it_begin = src_tmp.GlyphsSet.Storage.begin();
|
||||
const int* it_end = src_tmp.GlyphsSet.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))
|
||||
{
|
||||
ImFontBuildSrcGlyphFT src_glyph;
|
||||
memset(&src_glyph, 0, sizeof(src_glyph));
|
||||
src_glyph.Codepoint = (ImWchar)(((it - it_begin) << 5) + bit_n);
|
||||
//src_glyph.GlyphIndex = 0; // FIXME-OPT: We had this info in the previous step and lost it..
|
||||
src_tmp.GlyphsList.push_back(src_glyph);
|
||||
}
|
||||
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)
|
||||
// (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity)
|
||||
ImVector<stbrp_rect> buf_rects;
|
||||
buf_rects.resize(total_glyphs_count);
|
||||
memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());
|
||||
|
||||
// Allocate temporary rasterization data buffers.
|
||||
// We could not find a way to retrieve accurate glyph size without rendering them.
|
||||
// (e.g. slot->metrics->width not always matching bitmap->width, especially considering the Oblique transform)
|
||||
// We allocate in chunks of 256 KB to not waste too much extra memory ahead. Hopefully users of FreeType won't find the temporary allocations.
|
||||
const int BITMAP_BUFFERS_CHUNK_SIZE = 256 * 1024;
|
||||
int buf_bitmap_current_used_bytes = 0;
|
||||
ImVector<unsigned char*> buf_bitmap_buffers;
|
||||
buf_bitmap_buffers.push_back((unsigned char*)ImGui::MemAlloc(BITMAP_BUFFERS_CHUNK_SIZE));
|
||||
|
||||
// 4. Gather glyphs sizes so we can pack them in our virtual canvas.
|
||||
// 8. Render/rasterize font characters into the texture
|
||||
int total_surface = 0;
|
||||
int buf_rects_out_n = 0;
|
||||
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||
{
|
||||
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||
if (src_tmp.GlyphsCount == 0)
|
||||
continue;
|
||||
|
||||
src_tmp.Rects = &buf_rects[buf_rects_out_n];
|
||||
buf_rects_out_n += src_tmp.GlyphsCount;
|
||||
|
||||
// Compute multiply table if requested
|
||||
const bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
|
||||
unsigned char multiply_table[256];
|
||||
if (multiply_enabled)
|
||||
ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
|
||||
|
||||
// Gather the sizes of all rectangles we will need to pack
|
||||
const int padding = atlas->TexGlyphPadding;
|
||||
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
|
||||
{
|
||||
ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
|
||||
|
||||
const FT_Glyph_Metrics* metrics = src_tmp.Font.LoadGlyph(src_glyph.Codepoint);
|
||||
IM_ASSERT(metrics != NULL);
|
||||
if (metrics == NULL)
|
||||
continue;
|
||||
|
||||
// Render glyph into a bitmap (currently held by FreeType)
|
||||
const FT_Bitmap* ft_bitmap = src_tmp.Font.RenderGlyphAndGetInfo(&src_glyph.Info);
|
||||
IM_ASSERT(ft_bitmap);
|
||||
|
||||
// Allocate new temporary chunk if needed
|
||||
const int bitmap_size_in_bytes = src_glyph.Info.Width * src_glyph.Info.Height;
|
||||
if (buf_bitmap_current_used_bytes + bitmap_size_in_bytes > BITMAP_BUFFERS_CHUNK_SIZE)
|
||||
{
|
||||
buf_bitmap_current_used_bytes = 0;
|
||||
buf_bitmap_buffers.push_back((unsigned char*)ImGui::MemAlloc(BITMAP_BUFFERS_CHUNK_SIZE));
|
||||
}
|
||||
|
||||
// Blit rasterized pixels to our temporary buffer and keep a pointer to it.
|
||||
src_glyph.BitmapData = buf_bitmap_buffers.back() + buf_bitmap_current_used_bytes;
|
||||
buf_bitmap_current_used_bytes += bitmap_size_in_bytes;
|
||||
src_tmp.Font.BlitGlyph(ft_bitmap, src_glyph.BitmapData, src_glyph.Info.Width * 1, multiply_enabled ? multiply_table : NULL);
|
||||
|
||||
src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + padding);
|
||||
src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + padding);
|
||||
total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
const int num_nodes_for_packing_algorithm = atlas->TexWidth - atlas->TexGlyphPadding;
|
||||
ImVector<stbrp_node> pack_nodes;
|
||||
pack_nodes.resize(num_nodes_for_packing_algorithm);
|
||||
stbrp_context pack_context;
|
||||
stbrp_init_target(&pack_context, atlas->TexWidth, TEX_HEIGHT_MAX, pack_nodes.Data, pack_nodes.Size);
|
||||
ImFontAtlasBuildPackCustomRects(atlas, &pack_context);
|
||||
|
||||
// 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++)
|
||||
{
|
||||
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||
if (src_tmp.GlyphsCount == 0)
|
||||
continue;
|
||||
|
||||
stbrp_pack_rects(&pack_context, 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->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
|
||||
atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight);
|
||||
memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight);
|
||||
|
||||
// Start packing
|
||||
ImVector<stbrp_node> pack_nodes;
|
||||
pack_nodes.resize(total_rects);
|
||||
stbrp_context context;
|
||||
stbrp_init_target(&context, atlas->TexWidth, atlas->TexHeight, pack_nodes.Data, total_rects);
|
||||
|
||||
// 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, &context);
|
||||
|
||||
// Render characters, setup ImFont and glyphs for runtime
|
||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
||||
// 8. Copy rasterized font characters back into the main texture
|
||||
// 9. Setup ImFont and glyphs for runtime
|
||||
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||
{
|
||||
ImFontConfig& cfg = atlas->ConfigData[input_i];
|
||||
FreeTypeFont& font_face = fonts[input_i];
|
||||
ImFont* dst_font = cfg.DstFont;
|
||||
if (cfg.MergeMode)
|
||||
dst_font->BuildLookupTable();
|
||||
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||
if (src_tmp.GlyphsCount == 0)
|
||||
continue;
|
||||
|
||||
const float ascent = font_face.Info.Ascender;
|
||||
const float descent = font_face.Info.Descender;
|
||||
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 ascent = src_tmp.Font.Info.Ascender;
|
||||
const float descent = src_tmp.Font.Info.Descender;
|
||||
ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
|
||||
const float font_off_x = cfg.GlyphOffset.x;
|
||||
const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f);
|
||||
|
||||
bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
|
||||
unsigned char multiply_table[256];
|
||||
if (multiply_enabled)
|
||||
ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
|
||||
|
||||
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2)
|
||||
const int padding = atlas->TexGlyphPadding;
|
||||
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
|
||||
{
|
||||
for (uint32_t codepoint = in_range[0]; codepoint <= in_range[1]; ++codepoint)
|
||||
{
|
||||
if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint))
|
||||
continue;
|
||||
ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
|
||||
stbrp_rect& pack_rect = src_tmp.Rects[glyph_i];
|
||||
IM_ASSERT(pack_rect.was_packed);
|
||||
|
||||
FT_Glyph ft_glyph = NULL;
|
||||
FT_BitmapGlyph ft_glyph_bitmap = NULL; // NB: will point to bitmap within FT_Glyph
|
||||
GlyphInfo glyph_info;
|
||||
if (!font_face.CalcGlyphInfo(codepoint, glyph_info, ft_glyph, ft_glyph_bitmap))
|
||||
continue;
|
||||
GlyphInfo& info = src_glyph.Info;
|
||||
IM_ASSERT(info.Width + padding <= pack_rect.w);
|
||||
IM_ASSERT(info.Height + padding <= pack_rect.h);
|
||||
const int tx = pack_rect.x + padding;
|
||||
const int ty = pack_rect.y + padding;
|
||||
|
||||
// Pack rectangle
|
||||
stbrp_rect rect;
|
||||
rect.w = (uint16_t)glyph_info.Width + 1; // Account for texture filtering
|
||||
rect.h = (uint16_t)glyph_info.Height + 1;
|
||||
stbrp_pack_rects(&context, &rect, 1);
|
||||
// Blit from temporary buffer to final texture
|
||||
size_t blit_src_stride = (size_t)src_glyph.Info.Width;
|
||||
size_t blit_dst_stride = (size_t)atlas->TexWidth;
|
||||
unsigned char* blit_src = src_glyph.BitmapData;
|
||||
unsigned char* blit_dst = atlas->TexPixelsAlpha8 + (ty * blit_dst_stride) + tx;
|
||||
for (int y = info.Height; y > 0; y--, blit_dst += blit_dst_stride, blit_src += blit_src_stride)
|
||||
memcpy(blit_dst, blit_src, blit_src_stride);
|
||||
|
||||
// Copy rasterized pixels to main texture
|
||||
uint8_t* blit_dst = atlas->TexPixelsAlpha8 + rect.y * atlas->TexWidth + rect.x;
|
||||
font_face.BlitGlyph(ft_glyph_bitmap, blit_dst, atlas->TexWidth, multiply_enabled ? multiply_table : NULL);
|
||||
FT_Done_Glyph(ft_glyph);
|
||||
|
||||
float char_advance_x_org = glyph_info.AdvanceX;
|
||||
float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
|
||||
float char_off_x = font_off_x;
|
||||
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;
|
||||
|
||||
// Register glyph
|
||||
dst_font->AddGlyph((ImWchar)codepoint,
|
||||
glyph_info.OffsetX + char_off_x,
|
||||
glyph_info.OffsetY + font_off_y,
|
||||
glyph_info.OffsetX + char_off_x + glyph_info.Width,
|
||||
glyph_info.OffsetY + font_off_y + glyph_info.Height,
|
||||
rect.x / (float)atlas->TexWidth,
|
||||
rect.y / (float)atlas->TexHeight,
|
||||
(rect.x + glyph_info.Width) / (float)atlas->TexWidth,
|
||||
(rect.y + glyph_info.Height) / (float)atlas->TexHeight,
|
||||
char_advance_x_mod);
|
||||
}
|
||||
float char_advance_x_org = info.AdvanceX;
|
||||
float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
|
||||
float char_off_x = font_off_x;
|
||||
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;
|
||||
|
||||
// Register glyph
|
||||
float x0 = info.OffsetX + char_off_x;
|
||||
float y0 = info.OffsetY + font_off_y;
|
||||
float x1 = x0 + info.Width;
|
||||
float y1 = y0 + info.Height;
|
||||
float u0 = (tx) / (float)atlas->TexWidth;
|
||||
float v0 = (ty) / (float)atlas->TexHeight;
|
||||
float u1 = (tx + info.Width) / (float)atlas->TexWidth;
|
||||
float v1 = (ty + info.Height) / (float)atlas->TexHeight;
|
||||
dst_font->AddGlyph((ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, char_advance_x_mod);
|
||||
}
|
||||
|
||||
src_tmp.Rects = NULL;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
for (int n = 0; n < fonts.Size; n++)
|
||||
fonts[n].Shutdown();
|
||||
for (int buf_i = 0; buf_i < buf_bitmap_buffers.Size; buf_i++)
|
||||
ImGui::MemFree(buf_bitmap_buffers[buf_i]);
|
||||
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||
src_tmp_array[src_i].~ImFontBuildSrcDataFT();
|
||||
|
||||
ImFontAtlasBuildFinish(atlas);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags)
|
||||
{
|
||||
FT_Library ft_library;
|
||||
FT_Error error = FT_Init_FreeType(&ft_library);
|
||||
if (error != 0)
|
||||
return false;
|
||||
|
||||
bool ret = ImFontAtlasBuildWithFreeType(ft_library, atlas, extra_flags);
|
||||
FT_Done_FreeType(ft_library);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Wrapper to use Freetype (instead of stb_truetype) for Dear ImGui
|
||||
// Wrapper to use FreeType (instead of stb_truetype) for Dear ImGui
|
||||
// Get latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
|
||||
// Original code by @Vuhdo (Aleksei Skriabin), maintained by @ocornut
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user