From 7ed8e55fc75da2c0ba81bcb5e3c2f6528641a671 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 9 Jan 2019 16:59:48 +0100 Subject: [PATCH 1/9] ImVector: Added size_in_bytes() helper. --- imgui.h | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui.h b/imgui.h index dad97c6f..9da2e9f2 100644 --- a/imgui.h +++ b/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]; } From 56caf7da29d3de46981b5d6b6bc7b0e91c66fba3 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 10 Jan 2019 13:16:30 +0100 Subject: [PATCH 2/9] imgui_freetype: Minor tweaks and comments. --- misc/freetype/imgui_freetype.cpp | 30 ++++++++++++++++-------------- misc/freetype/imgui_freetype.h | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 33bdb140..70039491 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -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). Improvements by @mikesart. Maintained by @ocornut @@ -20,15 +20,15 @@ // 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). +// - 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 #include -#include FT_FREETYPE_H -#include FT_GLYPH_H -#include FT_SYNTHESIS_H +#include FT_FREETYPE_H // +#include FT_GLYPH_H // +#include FT_SYNTHESIS_H // #ifdef _MSC_VER #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) @@ -96,8 +96,8 @@ 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(); + bool Create(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 Destroy(); 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); @@ -114,7 +114,7 @@ namespace // 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::Create(const ImFontConfig& cfg, unsigned int extra_user_flags) { // FIXME: substitute allocator FT_Error error = FT_Init_FreeType(&FreetypeLibrary); @@ -130,7 +130,7 @@ namespace 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; @@ -146,7 +146,7 @@ namespace return true; } - void FreeTypeFont::Shutdown() + void FreeTypeFont::Destroy() { if (FreetypeFace) { @@ -162,6 +162,7 @@ namespace // I'm not sure how to deal with font sizes properly. // As far as I understand, currently ImGui assumes that the 'pixel_height' is a maximum height of an any given glyph, // i.e. it's the sum of font's ascender and descender. Seems strange to me. + // FT_Set_Pixel_Sizes() doesn't seem to get us the same result. FT_Size_RequestRec req; req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM; req.width = 0; @@ -180,7 +181,7 @@ 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) + bool FreeTypeFont::CalcGlyphInfo(uint32_t codepoint, GlyphInfo& glyph_info, FT_Glyph& ft_glyph, FT_BitmapGlyph& ft_bitmap) { uint32_t glyph_index = FT_Get_Char_Index(FreetypeFace, codepoint); if (glyph_index == 0) @@ -253,7 +254,8 @@ bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) 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); @@ -273,7 +275,7 @@ bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) FreeTypeFont& font_face = fonts[input_i]; IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); - if (!font_face.Init(cfg, extra_flags)) + if (!font_face.Create(cfg, extra_flags)) return false; max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth); @@ -380,7 +382,7 @@ bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) // Cleanup for (int n = 0; n < fonts.Size; n++) - fonts[n].Shutdown(); + fonts[n].Destroy(); ImFontAtlasBuildFinish(atlas); diff --git a/misc/freetype/imgui_freetype.h b/misc/freetype/imgui_freetype.h index b8062bf7..aa0aba63 100644 --- a/misc/freetype/imgui_freetype.h +++ b/misc/freetype/imgui_freetype.h @@ -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 From e3ccc967895e9bf3986d133224d1e359054849db Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 10 Jan 2019 13:21:33 +0100 Subject: [PATCH 3/9] Internals: Added ImBoolVector helper. --- imgui_internal.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/imgui_internal.h b/imgui_internal.h index 51f3a3f7..46a37ee2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -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 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; From 9a9712807efb4c197d94e9f9a7d2b02159f029d1 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 10 Jan 2019 13:38:51 +0100 Subject: [PATCH 4/9] 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. --- docs/CHANGELOG.txt | 3 + docs/TODO.txt | 5 +- imgui.h | 2 +- imgui_draw.cpp | 392 +++++++++++++++++++++++++++------------------ imgui_internal.h | 2 +- 5 files changed, 240 insertions(+), 164 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 66ed9545..bd176914 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -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. 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: 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. (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. diff --git a/docs/TODO.txt b/docs/TODO.txt index 5e3f73ff..a21bc804 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -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) diff --git a/imgui.h b/imgui.h index 9da2e9f2..acc33f2c 100644 --- a/imgui.h +++ b/imgui.h @@ -2047,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. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 9833590f..9987b1ab 100644 --- a/imgui_draw.cpp +++ b/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 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* 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++) - { - 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); + // Temporary storage for building + ImVector src_tmp_array; + ImVector 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()); - // Initialize font information (so we can error without any cleanup) - struct ImFontTempBuildData + // 1. Initialize font loading structure, check font data validity + for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++) { - 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); + 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) - 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)); - - // 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++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - ImFontTempBuildData& tmp = tmp_array[input_i]; - - // 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++) - { - 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; - } + // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) + ImVector buf_rects; + ImVector 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()); + + // 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++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; - // 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++) + 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++) { - 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]; + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; + + 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) - if (cfg.MergeMode) - dst_font->BuildLookupTable(); - const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + 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 = 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 int codepoint = src_tmp.GlyphsList[glyph_i]; + const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; + + 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& 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 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; diff --git a/imgui_internal.h b/imgui_internal.h index 46a37ee2..8b8a299a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1452,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); From 21828b08a0f5b4ac70bc66beaf93dcea0279bace Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 10 Jan 2019 22:11:27 +0100 Subject: [PATCH 5/9] ImFontAtlas: Rewrote FreeType based builder. - Fixed abnormally high atlas height. (#618) - Fixed support for any values of TexGlyphPadding (not just only 1). (#618) - Atlas width is now properly based on total surface rather than glyph count (unless overridden with TexDesiredWidth). (#618) - Fixed atlas builder so missing glyphs won't influence the atlas texture width. (#2233, #618) - Fixed atlas builder so duplicate glyphs (when merging fonts) won't be included in the rasterized atlas. (#618) --- docs/CHANGELOG.txt | 8 +- misc/freetype/README.md | 33 +- misc/freetype/imgui_freetype.cpp | 552 +++++++++++++++++++++---------- 3 files changed, 399 insertions(+), 194 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index bd176914..048f176f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -72,9 +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: 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. +- 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. diff --git a/misc/freetype/README.md b/misc/freetype/README.md index ba633f69..885451a4 100644 --- a/misc/freetype/README.md +++ b/misc/freetype/README.md @@ -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). +
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) - diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 70039491..263dd938 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -1,6 +1,6 @@ // 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,13 +18,11 @@ // 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 not supported (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 #include #include FT_FREETYPE_H // @@ -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,34 +95,31 @@ namespace // NB: No ctor/dtor, explicitly call Init()/Shutdown() struct FreeTypeFont { - bool Create(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 Destroy(); - 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, uint32_t* out_glyph_index); + const FT_Bitmap* RenderGlyphAndGetInfo(uint32_t in_glyph_index, 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::Create(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); - if (error != 0) - return false; - error = FT_New_Memory_Face(FreetypeLibrary, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &FreetypeFace); + 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_Select_Charmap(FreetypeFace, FT_ENCODING_UNICODE); + error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE); if (error != 0) return false; @@ -132,47 +128,47 @@ namespace // 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::Destroy() + 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. - // FT_Set_Pixel_Sizes() doesn't seem to get us the same result. + // 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); @@ -181,52 +177,77 @@ 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* out_glyph_index) { - uint32_t glyph_index = FT_Get_Char_Index(FreetypeFace, codepoint); + if (out_glyph_index) + *out_glyph_index = 0; + + 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; + + if (out_glyph_index) + *out_glyph_index = glyph_index; // 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; + return &slot->metrics; + } + + const FT_Bitmap* FreeTypeFont::RenderGlyphAndGetInfo(uint32_t in_glyph_index, GlyphInfo* out_glyph_info) + { + IM_ASSERT(in_glyph_index != 0); + FT_Error error = FT_Load_Glyph(Face, in_glyph_index, LoadFlags); + if (error) + return NULL; + + // Need an outline for this to work + FT_GlyphSlot slot = Face->glyph; + IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE); + + if (UserFlags & ImGuiFreeType::Bold) + FT_GlyphSlot_Embolden(slot); + if (UserFlags & ImGuiFreeType::Oblique) + FT_GlyphSlot_Oblique(slot); - // Rasterize - error = FT_Glyph_To_Bitmap(&ft_glyph, FT_RENDER_MODE_NORMAL, NULL, true); + error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); 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; + 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 true; + return ft_bitmap; } - void FreeTypeFont::BlitGlyph(FT_BitmapGlyph ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table) + 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) { @@ -247,10 +268,38 @@ 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; + uint32_t GlyphIndex; // Index in font (to avoid calling FT_Get_Char_Index multiple times) + 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 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); @@ -261,130 +310,291 @@ bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); atlas->ClearTexData(); - ImVector fonts; - fonts.resize(atlas->ConfigData.Size); - - ImVec2 max_glyph_size(1.0f, 1.0f); + // Temporary storage for building + ImVector src_tmp_array; + ImVector 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()); - // 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.Create(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; + + // Load font + if (!font_face.InitFont(ft_library, cfg, extra_flags)) 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); + // 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); + } + + // 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); + 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; + 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; + + // 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 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 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, &src_glyph.GlyphIndex); + IM_ASSERT(metrics != NULL); + if (metrics == NULL) + continue; - 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; + // Render glyph into a bitmap (currently held by FreeType) + const FT_Bitmap* ft_bitmap = src_tmp.Font.RenderGlyphAndGetInfo(src_glyph.GlyphIndex, &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. 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; + // 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 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); - // 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)); + // 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; - // Create texture + 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 pack_nodes; - pack_nodes.resize(total_rects); - stbrp_context context; - stbrp_init_target(&context, atlas->TexWidth, atlas->TexHeight, pack_nodes.Data, total_rects); + // 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++) + { + ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; - // 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); + 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) - // Render characters, setup ImFont and glyphs for runtime - for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - FreeTypeFont& font_face = fonts[input_i]; - ImFont* dst_font = cfg.DstFont; - if (cfg.MergeMode) - dst_font->BuildLookupTable(); - - const float ascent = font_face.Info.Ascender; - const float descent = font_face.Info.Descender; + 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; - - 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; - - // 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); - - // 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); - } + ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i]; + stbrp_rect& pack_rect = src_tmp.Rects[glyph_i]; + IM_ASSERT(pack_rect.was_packed); + + 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; + + // 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); + + 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].Destroy(); + 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; +} From 651130002f4fc13c24f1bddb3e007fb789c5a0a2 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 11 Jan 2019 15:25:43 +0100 Subject: [PATCH 6/9] ImFontAtlas: Fixed allocating for last bit (would only affect is that last codepoint is a multiple of 32). (#2270) --- imgui_draw.cpp | 4 ++-- misc/freetype/imgui_freetype.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 9987b1ab..4ea37353 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1829,9 +1829,9 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) 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); + 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); + 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++) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 263dd938..33a5a63b 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -355,9 +355,9 @@ bool ImFontAtlasBuildWithFreeType(FT_Library ft_library, ImFontAtlas* atlas, uns 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); + 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); + 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++) From 8df8482ef4011b8ead449dfe65db0632e759f9f3 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 12 Jan 2019 11:45:58 +0100 Subject: [PATCH 7/9] imgui_freetype: Fixed redundant FT_Load_Glyph() calls, unused parameters, and compilation warning/error. (#2270) --- misc/freetype/imgui_freetype.cpp | 36 +++++++------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 33a5a63b..7c5403b6 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -98,8 +98,8 @@ namespace 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, uint32_t* out_glyph_index); - const FT_Bitmap* RenderGlyphAndGetInfo(uint32_t in_glyph_index, GlyphInfo* out_glyph_info); + 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(); } @@ -177,11 +177,8 @@ namespace Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance); } - const FT_Glyph_Metrics* FreeTypeFont::LoadGlyph(uint32_t codepoint, uint32_t* out_glyph_index) + const FT_Glyph_Metrics* FreeTypeFont::LoadGlyph(uint32_t codepoint) { - if (out_glyph_index) - *out_glyph_index = 0; - uint32_t glyph_index = FT_Get_Char_Index(Face, codepoint); if (glyph_index == 0) return NULL; @@ -189,9 +186,6 @@ namespace if (error) return NULL; - if (out_glyph_index) - *out_glyph_index = glyph_index; - // Need an outline for this to work FT_GlyphSlot slot = Face->glyph; IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE); @@ -211,25 +205,12 @@ namespace return &slot->metrics; } - const FT_Bitmap* FreeTypeFont::RenderGlyphAndGetInfo(uint32_t in_glyph_index, GlyphInfo* out_glyph_info) + const FT_Bitmap* FreeTypeFont::RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info) { - IM_ASSERT(in_glyph_index != 0); - FT_Error error = FT_Load_Glyph(Face, in_glyph_index, LoadFlags); - if (error) - return NULL; - - // Need an outline for this to work FT_GlyphSlot slot = Face->glyph; - IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE); - - if (UserFlags & ImGuiFreeType::Bold) - FT_GlyphSlot_Embolden(slot); - if (UserFlags & ImGuiFreeType::Oblique) - FT_GlyphSlot_Oblique(slot); - - error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); + FT_Error error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); if (error != 0) - return false; + return NULL; FT_Bitmap* ft_bitmap = &Face->glyph->bitmap; out_glyph_info->Width = (int)ft_bitmap->width; @@ -272,7 +253,6 @@ struct ImFontBuildSrcGlyphFT { GlyphInfo Info; uint32_t Codepoint; - uint32_t GlyphIndex; // Index in font (to avoid calling FT_Get_Char_Index multiple times) unsigned char* BitmapData; // Point within one of the dst_tmp_bitmap_buffers[] array }; @@ -446,13 +426,13 @@ bool ImFontAtlasBuildWithFreeType(FT_Library ft_library, ImFontAtlas* atlas, uns { ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i]; - const FT_Glyph_Metrics* metrics = src_tmp.Font.LoadGlyph(src_glyph.Codepoint, &src_glyph.GlyphIndex); + 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.GlyphIndex, &src_glyph.Info); + const FT_Bitmap* ft_bitmap = src_tmp.Font.RenderGlyphAndGetInfo(&src_glyph.Info); IM_ASSERT(ft_bitmap); // Allocate new temporary chunk if needed From 49994ceb6edee01a25a25c4c304038a60cf0eb74 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 11 Jan 2019 17:38:14 +0100 Subject: [PATCH 8/9] FAQ entry, moved ImTextureId, Gallery links. --- docs/README.md | 8 +++++--- docs/TODO.txt | 1 + imgui.cpp | 23 +++++++++++++++++++++++ imgui.h | 6 +++--- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index 5f9a188c..b1e10d53 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,7 +20,7 @@ Dear ImGui is designed to enable fast iterations and to empower programmers to c Dear ImGui is particularly suited to integration in games engine (for tooling), real-time 3D applications, fullscreen applications, embedded applications, or any applications on consoles platforms where operating system features are non-standard. -See [Software using dear imgui](https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui), [Quotes](https://github.com/ocornut/imgui/wiki/Quotes) and [Gallery](https://github.com/ocornut/imgui/issues/1902) pages to get an idea of its use cases. +See [Software using dear imgui](https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui), [Quotes](https://github.com/ocornut/imgui/wiki/Quotes) and [Gallery](https://github.com/ocornut/imgui/issues/2265) pages to get an idea of its use cases. Dear ImGui is self-contained within a few files that you can easily copy and compile into your application/engine: - imgui.cpp @@ -175,7 +175,8 @@ User screenshots:
[Gallery Part 4](https://github.com/ocornut/imgui/issues/973) (Jan 2017 to Aug 2017)
[Gallery Part 5](https://github.com/ocornut/imgui/issues/1269) (Aug 2017 to Feb 2018)
[Gallery Part 6](https://github.com/ocornut/imgui/issues/1607) (Feb 2018 to June 2018) -
[Gallery Part 7](https://github.com/ocornut/imgui/issues/1902) (June 2018 onward) +
[Gallery Part 7](https://github.com/ocornut/imgui/issues/1902) (June 2018 to January 2018) +
[Gallery Part 8](https://github.com/ocornut/imgui/issues/2265) (January 2018 onward)
Also see the [Mega screenshots](https://github.com/ocornut/imgui/issues/1273) for an idea of the available features. Custom engine @@ -243,6 +244,7 @@ The library started its life and is best known as "ImGui" only due to the fact t
**How can I easily use icons in my application?**
**How can I load multiple fonts?**
**How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?** ([example](https://github.com/ocornut/imgui/wiki/Loading-Font-Example)) +
**How can I interact with standard C++ types (such as std::string and std::vector)?**
**How can I use the drawing facilities without an Dear ImGui window? (using ImDrawList API)**
**How can I use this without a mouse, without a keyboard or without a screen? (gamepad, input share, remote display)**
**I integrated Dear ImGui in my engine and the text or lines are blurry..** @@ -298,7 +300,7 @@ Ongoing dear imgui development is financially supported by users and private spo - **Blizzard Entertainment**. **Double-chocolate sponsors** -- Media Molecule, Mobigame, Insomniac Games, Aras Pranckevičius, Lizardcube, Greggman, DotEmu, Nadeo, Supercell, Runner, Aiden Koss. +- Media Molecule, Mobigame, Insomniac Games, Aras Pranckevičius, Lizardcube, Greggman, DotEmu, Nadeo, Supercell, Runner, Aiden Koss, Kylotonn. **Salty caramel supporters** - Jetha Chan, Wild Sheep Studio, Pastagames, Mārtiņš Možeiko, Daniel Collin, Recognition Robotics, Chris Genova, ikrima, Glenn Fiedler, Geoffrey Evans, Dakko Dakko, Mercury Labs, Singularity Demo Group, Mischa Alff, Sebastien Ronsse, Lionel Landwerlin, Nikolay Ivanov, Ron Gilbert, Brandon Townsend, Nikhil Deshpande, Cort Stratton, drudru, Harfang 3D, Jeff Roberts, Rainway inc. diff --git a/docs/TODO.txt b/docs/TODO.txt index 5e3f73ff..9c5f3140 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -257,6 +257,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - 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: fix AddRemapChar() to work before font has been built. + - font: what would it take to support codepoint higher than 0xFFFF? (smileys, etc.) - font: (api breaking) remove "TTF" from symbol names. also because it now supports OTF. - font/opt: Considering storing standalone AdvanceX table as 16-bit fixed point integer? - font/opt: Glyph currently 40 bytes (2+9*4). Consider storing UV as 16 bits integer? (->32 bytes). X0/Y0/X1/Y1 as 16 fixed-point integers? Or X0/Y0 as float and X1/Y1 as fixed8_8? diff --git a/imgui.cpp b/imgui.cpp index c9b4188b..8c118918 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -45,6 +45,7 @@ DOCUMENTATION - How can I easily use icons in my application? - How can I load multiple fonts? - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? + - How can I interact with standard C++ types (such as std::string and std::vector)? - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) - How can I use Dear ImGui on a platform that doesn't have a mouse or a keyboard? (input share, remoting, gamepad) - I integrated Dear ImGui in my engine and the text or lines are blurry.. @@ -843,6 +844,28 @@ CODE Windows: if your language is relying on an Input Method Editor (IME), you copy the HWND of your window to io.ImeWindowHandle in order for the default implementation of io.ImeSetInputScreenPosFn() to set your Microsoft IME position correctly. + Q: How can I interact with standard C++ types (such as std::string and std::vector)? + A: - Being highly portable (bindings for several languages, frameworks, programming style, obscure or older platforms/compilers), + and aiming for compatibility & performance suitable for every modern real-time game engines, dear imgui does not use + any of std C++ types. We use raw types (e.g. char* instead of std::string) because they adapt to more use cases. + - To use ImGui::InputText() with a std::string or any resizable string class, see misc/cpp/imgui_stdlib.h. + - To use combo boxes and list boxes with std::vector or any other data structure: the BeginCombo()/EndCombo() API + lets you iterate and submit items yourself, so does the ListBoxHeader()/ListBoxFooter() API. + Prefer using them over the old and awkward Combo()/ListBox() api. + - Generally for most high-level types you should be able to access the underlying data type. + You may write your own one-liner wrappers to facilitate user code (tip: add new functions in ImGui:: namespace from your code). + - Dear ImGui applications often need to make intensive use of strings. It is expected that many of the strings you will pass + to the API are raw literals (free in C/C++) or allocated in a manner that won't incur a large cost on your application. + Please bear in mind that using std::string on applications with large amount of UI may incur unsatisfactory performances. + Modern implementations of std::string often include small-string optimization (which is often a local buffer) but those + are not configurable and not the same across implementations. + - If you are finding your UI traversal cost to be too large, make sure your string usage is not leading to excessive amount + of heap allocations. Consider using literals, statically sized buffers and your own helper functions. A common pattern + is that you will need to build lots of strings on the fly, and their maximum length can be easily be scoped ahead. + One possible implementation of a helper to facilitate printf-style building of strings: https://github.com/ocornut/Str + This is a small helper where you can instance strings with configurable local buffers length. Many game engines will + provide similar or better string helpers. + Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) A: - You can create a dummy window. Call Begin() with the NoBackground | NoDecoration | NoSavedSettings | NoInputs flags. (The ImGuiWindowFlags_NoDecoration flag itself is a shortcut for NoTitleBar | NoResize | NoScrollbar | NoCollapse) diff --git a/imgui.h b/imgui.h index dad97c6f..3ac1f0e9 100644 --- a/imgui.h +++ b/imgui.h @@ -98,9 +98,6 @@ struct ImFontConfig; // Configuration data when adding a font or struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) -#ifndef ImTextureID -typedef void* ImTextureID; // User data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) -#endif struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) struct ImGuiIO; // Main configuration and I/O between your application and ImGui struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) @@ -115,6 +112,9 @@ struct ImGuiTextFilter; // Helper to parse and apply text filters (e // Typedefs and Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute the top of this file) // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. +#ifndef ImTextureID +typedef void* ImTextureID; // User data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) +#endif typedef unsigned int ImGuiID; // Unique ID used by widgets (typically hashed from a stack of string) typedef unsigned short ImWchar; // A single U16 character for keyboard input/display. We encode them as multi bytes UTF-8 when used in strings. typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling From 7e78865613cc0159d4705997a0d19967eb2767d0 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 12 Jan 2019 19:46:35 +0100 Subject: [PATCH 9/9] Demo: Fixed bounds of DragFloat in Clipping section to avoid passing zero-sized to InvisibleButton(). --- imgui_demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index dcaa3660..cf1d4b38 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1988,7 +1988,7 @@ static void ShowDemoWindowLayout() { static ImVec2 size(100, 100), offset(50, 20); ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); - ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 1.0f, 200.0f, "%.0f"); ImGui::TextWrapped("(Click and drag)"); ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec4 clip_rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y);