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)
docking
omar 6 years ago
parent 9a9712807e
commit 21828b08a0

@ -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. Missing calls to End(), past the assert, should not lead to crashes or to the fallback Debug window appearing on screen.
Those changes makes it easier to integrate dear imgui with a scripting language allowing, given asserts are redirected Those changes makes it easier to integrate dear imgui with a scripting language allowing, given asserts are redirected
into e.g. an error log and stopping the script execution. into e.g. an error log and stopping the script execution.
- ImFontAtlas: Stb: Atlas width is now properly based on total surface rather than glyph count (unless overridden with TexDesiredWidth). - ImFontAtlas: Stb and FreeType: 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 and FreeType: 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: 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. - ImDrawList: Optimized some of the functions for performance of debug builds where non-inline function call cost are non-negligible.
(Our test UI scene on VS2015 Debug Win64 with /RTC1 went ~5.9 ms -> ~4.9 ms. In Release same scene stays at ~0.3 ms.) (Our test UI scene on VS2015 Debug Win64 with /RTC1 went ~5.9 ms -> ~4.9 ms. In Release same scene stays at ~0.3 ms.)
- IO: Added BackendPlatformUserData, BackendRendererUserData, BackendLanguageUserData void* for storage use by back-ends. - IO: Added BackendPlatformUserData, BackendRendererUserData, BackendLanguageUserData void* for storage use by back-ends.

@ -1,14 +1,14 @@
# imgui_freetype # imgui_freetype
This is an attempt to replace stb_truetype (the default imgui's font rasterizer) with FreeType. Build font atlases using FreeType instead of stb_truetype (the default imgui's font rasterizer).
Currently not optimal and probably has some limitations or bugs. <br>by @vuhdo, @mikesart, @ocornut.
By [Vuhdo](https://github.com/Vuhdo) (Aleksei Skriabin). Improvements by @mikesart. Maintained by @ocornut.
**Usage** ### Usage
1. Get latest FreeType binaries or build yourself.
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. 2. Add imgui_freetype.h/cpp alongside your imgui sources.
3. Include imgui_freetype.h after imgui.h. 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 ```cpp
// See ImGuiFreeType::RasterizationFlags // See ImGuiFreeType::RasterizationFlags
@ -17,13 +17,14 @@ ImGuiFreeType::BuildFontAtlas(io.Fonts, flags);
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
``` ```
**Gamma Correct Blending** ### Gamma Correct Blending
FreeType assumes blending in linear space rather than gamma space. 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). 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. 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). The default imgui styles will be impacted by this change (alpha values will need tweaking).
**Test code Usage** ### Test code Usage
```cpp ```cpp
#include "misc/freetype/imgui_freetype.h" #include "misc/freetype/imgui_freetype.h"
#include "misc/freetype/imgui_freetype.cpp" #include "misc/freetype/imgui_freetype.cpp"
@ -42,16 +43,15 @@ while (true)
if (freetype_test.UpdateRebuild()) if (freetype_test.UpdateRebuild())
{ {
// REUPLOAD FONT TEXTURE TO GPU // REUPLOAD FONT TEXTURE TO GPU
// e.g ImGui_ImplGlfwGL3_InvalidateDeviceObjects() + ImGui_ImplGlfwGL3_CreateDeviceObjects() // e.g ImGui_ImplOpenGL3_DestroyDeviceObjects() + ImGui_ImplOpenGL3_CreateDeviceObjects()
} }
ImGui::NewFrame(); ImGui::NewFrame();
freetype_test.ShowFreetypeOptionsWindow(); freetype_test.ShowFreetypeOptionsWindow();
... ...
}
} }
``` ```
**Test code** ### Test code
```cpp ```cpp
#include "misc/freetype/imgui_freetype.h" #include "misc/freetype/imgui_freetype.h"
#include "misc/freetype/imgui_freetype.cpp" #include "misc/freetype/imgui_freetype.cpp"
@ -61,7 +61,7 @@ struct FreeTypeTest
enum FontBuildMode enum FontBuildMode
{ {
FontBuildMode_FreeType, FontBuildMode_FreeType,
FontBuildMode_Stb, FontBuildMode_Stb
}; };
FontBuildMode BuildMode; FontBuildMode BuildMode;
@ -120,14 +120,7 @@ struct FreeTypeTest
}; };
``` ```
**Known issues** ### Known issues
- Output texture has excessive resolution (lots of vertical waste).
- FreeType's memory allocator is not overridden. - FreeType's memory allocator is not overridden.
- `cfg.OversampleH`, `OversampleV` are ignored (but perhaps not so necessary with this rasterizer). - `cfg.OversampleH`, `OversampleV` are ignored (but perhaps not so necessary with this rasterizer).
**Obligatory comparison screenshots**
Using Windows built-in segoeui.ttf font. Open in new browser tabs, view at 1080p+.
![freetype rasterizer](https://raw.githubusercontent.com/wiki/ocornut/imgui_club/images/freetype_20170817.png)

@ -1,6 +1,6 @@
// Wrapper to use FreeType (instead of stb_truetype) for Dear ImGui // Wrapper to use FreeType (instead of stb_truetype) for Dear ImGui
// Get latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype // 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: // 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. // - 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.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.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.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: // Gamma Correct Blending:
// FreeType assumes blending in linear space rather than gamma space. // 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. // 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). // The default imgui styles will be impacted by this change (alpha values will need tweaking).
// TODO: // FIXME: FreeType's memory allocator is not overridden.
// - Output texture has excessive resolution (lots of vertical waste). // FIXME: cfg.OversampleH, OversampleV are not supported (but perhaps not so necessary with this rasterizer).
// - FreeType's memory allocator is not overridden.
// - cfg.OversampleH, OversampleV are not supported (but perhaps not so necessary with this rasterizer).
#include "imgui_freetype.h" #include "imgui_freetype.h"
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*, #include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
#include <stdint.h> #include <stdint.h>
#include <ft2build.h> #include <ft2build.h>
#include FT_FREETYPE_H // <freetype/freetype.h> #include FT_FREETYPE_H // <freetype/freetype.h>
@ -74,11 +73,11 @@ namespace
/// A structure that describe a glyph. /// A structure that describe a glyph.
struct GlyphInfo struct GlyphInfo
{ {
float Width; // Glyph's width in pixels. int Width; // Glyph's width in pixels.
float Height; // Glyph's height in pixels. int Height; // Glyph's height in pixels.
float OffsetX; // The distance from the origin ("pen position") to the left of the glyph. FT_Int 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. 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. float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0.
}; };
// Font parameters and metrics. // Font parameters and metrics.
@ -96,34 +95,31 @@ namespace
// NB: No ctor/dtor, explicitly call Init()/Shutdown() // NB: No ctor/dtor, explicitly call Init()/Shutdown()
struct FreeTypeFont 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. 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 Destroy(); void CloseFont();
void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size 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);
bool CalcGlyphInfo(uint32_t codepoint, GlyphInfo& glyph_info, FT_Glyph& ft_glyph, FT_BitmapGlyph& ft_bitmap); const FT_Bitmap* RenderGlyphAndGetInfo(uint32_t in_glyph_index, GlyphInfo* out_glyph_info);
void BlitGlyph(FT_BitmapGlyph ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = NULL); void BlitGlyph(const FT_Bitmap* ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = NULL);
~FreeTypeFont() { CloseFont(); }
// [Internals] // [Internals]
FontInfo Info; // Font descriptor of the current font. FontInfo Info; // Font descriptor of the current font.
FT_Face Face;
unsigned int UserFlags; // = ImFontConfig::RasterizerFlags unsigned int UserFlags; // = ImFontConfig::RasterizerFlags
FT_Library FreetypeLibrary; FT_Int32 LoadFlags;
FT_Face FreetypeFace;
FT_Int32 FreetypeLoadFlags;
}; };
// From SDL_ttf: Handy routines for converting from fixed point // From SDL_ttf: Handy routines for converting from fixed point
#define FT_CEIL(X) (((X + 63) & -64) / 64) #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 // FIXME: substitute allocator
FT_Error error = FT_Init_FreeType(&FreetypeLibrary); FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &Face);
if (error != 0)
return false;
error = FT_New_Memory_Face(FreetypeLibrary, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &FreetypeFace);
if (error != 0) if (error != 0)
return false; return false;
error = FT_Select_Charmap(FreetypeFace, FT_ENCODING_UNICODE); error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE);
if (error != 0) if (error != 0)
return false; return false;
@ -132,47 +128,47 @@ namespace
// 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; UserFlags = cfg.RasterizerFlags | extra_user_flags;
FreetypeLoadFlags = FT_LOAD_NO_BITMAP; LoadFlags = FT_LOAD_NO_BITMAP;
if (UserFlags & ImGuiFreeType::NoHinting) FreetypeLoadFlags |= FT_LOAD_NO_HINTING; if (UserFlags & ImGuiFreeType::NoHinting)
if (UserFlags & ImGuiFreeType::NoAutoHint) FreetypeLoadFlags |= FT_LOAD_NO_AUTOHINT; LoadFlags |= FT_LOAD_NO_HINTING;
if (UserFlags & ImGuiFreeType::ForceAutoHint) FreetypeLoadFlags |= FT_LOAD_FORCE_AUTOHINT; if (UserFlags & ImGuiFreeType::NoAutoHint)
LoadFlags |= FT_LOAD_NO_AUTOHINT;
if (UserFlags & ImGuiFreeType::ForceAutoHint)
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
if (UserFlags & ImGuiFreeType::LightHinting) if (UserFlags & ImGuiFreeType::LightHinting)
FreetypeLoadFlags |= FT_LOAD_TARGET_LIGHT; LoadFlags |= FT_LOAD_TARGET_LIGHT;
else if (UserFlags & ImGuiFreeType::MonoHinting) else if (UserFlags & ImGuiFreeType::MonoHinting)
FreetypeLoadFlags |= FT_LOAD_TARGET_MONO; LoadFlags |= FT_LOAD_TARGET_MONO;
else else
FreetypeLoadFlags |= FT_LOAD_TARGET_NORMAL; LoadFlags |= FT_LOAD_TARGET_NORMAL;
return true; return true;
} }
void FreeTypeFont::Destroy() void FreeTypeFont::CloseFont()
{ {
if (FreetypeFace) if (Face)
{ {
FT_Done_Face(FreetypeFace); FT_Done_Face(Face);
FreetypeFace = NULL; Face = NULL;
FT_Done_FreeType(FreetypeLibrary);
FreetypeLibrary = NULL;
} }
} }
void FreeTypeFont::SetPixelHeight(int pixel_height) void FreeTypeFont::SetPixelHeight(int pixel_height)
{ {
// I'm not sure how to deal with font sizes properly. // Vuhdo: I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
// As far as I understand, currently ImGui assumes that the 'pixel_height' is a maximum height of an any given glyph, // 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.
// 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_Set_Pixel_Sizes() doesn't seem to get us the same result.
FT_Size_RequestRec req; FT_Size_RequestRec req;
req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM; req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
req.width = 0; req.width = 0;
req.height = (uint32_t)pixel_height * 64; req.height = (uint32_t)pixel_height * 64;
req.horiResolution = 0; req.horiResolution = 0;
req.vertResolution = 0; req.vertResolution = 0;
FT_Request_Size(FreetypeFace, &req); FT_Request_Size(Face, &req);
// update font info // Update font info
FT_Size_Metrics metrics = FreetypeFace->size->metrics; FT_Size_Metrics metrics = Face->size->metrics;
Info.PixelHeight = (uint32_t)pixel_height; Info.PixelHeight = (uint32_t)pixel_height;
Info.Ascender = (float)FT_CEIL(metrics.ascender); Info.Ascender = (float)FT_CEIL(metrics.ascender);
Info.Descender = (float)FT_CEIL(metrics.descender); Info.Descender = (float)FT_CEIL(metrics.descender);
@ -181,52 +177,77 @@ namespace
Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance); 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) if (glyph_index == 0)
return false; return NULL;
FT_Error error = FT_Load_Glyph(FreetypeFace, glyph_index, FreetypeLoadFlags); FT_Error error = FT_Load_Glyph(Face, glyph_index, LoadFlags);
if (error) if (error)
return false; return NULL;
if (out_glyph_index)
*out_glyph_index = glyph_index;
// Need an outline for this to work // 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); 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) if (UserFlags & ImGuiFreeType::Bold)
FT_GlyphSlot_Embolden(slot); FT_GlyphSlot_Embolden(slot);
if (UserFlags & ImGuiFreeType::Oblique) if (UserFlags & ImGuiFreeType::Oblique)
{
FT_GlyphSlot_Oblique(slot); 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 return &slot->metrics;
error = FT_Get_Glyph(slot, &ft_glyph); }
if (error != 0)
return false; 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_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
error = FT_Glyph_To_Bitmap(&ft_glyph, FT_RENDER_MODE_NORMAL, NULL, true);
if (error != 0) if (error != 0)
return false; return false;
ft_bitmap = (FT_BitmapGlyph)ft_glyph; FT_Bitmap* ft_bitmap = &Face->glyph->bitmap;
glyph_info.AdvanceX = (float)FT_CEIL(slot->advance.x); out_glyph_info->Width = (int)ft_bitmap->width;
glyph_info.OffsetX = (float)ft_bitmap->left; out_glyph_info->Height = (int)ft_bitmap->rows;
glyph_info.OffsetY = -(float)ft_bitmap->top; out_glyph_info->OffsetX = Face->glyph->bitmap_left;
glyph_info.Width = (float)ft_bitmap->bitmap.width; out_glyph_info->OffsetY = -Face->glyph->bitmap_top;
glyph_info.Height = (float)ft_bitmap->bitmap.rows; 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); IM_ASSERT(ft_bitmap != NULL);
const uint32_t w = ft_bitmap->width;
const uint32_t w = ft_bitmap->bitmap.width; const uint32_t h = ft_bitmap->rows;
const uint32_t h = ft_bitmap->bitmap.rows; const uint8_t* src = ft_bitmap->buffer;
const uint8_t* src = ft_bitmap->bitmap.buffer; const uint32_t src_pitch = ft_bitmap->pitch;
const uint32_t src_pitch = ft_bitmap->bitmap.pitch;
if (multiply_table == NULL) if (multiply_table == NULL)
{ {
@ -247,10 +268,38 @@ namespace
#define STB_RECT_PACK_IMPLEMENTATION #define STB_RECT_PACK_IMPLEMENTATION
#include "imstb_rectpack.h" #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<ImFontBuildSrcGlyphFT> GlyphsList;
};
// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont)
struct ImFontBuildDstDataFT
{
int SrcCount; // Number of source fonts targeting this destination font.
int GlyphsHighest;
int GlyphsCount;
ImBoolVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font.
};
bool ImFontAtlasBuildWithFreeType(FT_Library ft_library, ImFontAtlas* atlas, unsigned int extra_flags)
{ {
IM_ASSERT(atlas->ConfigData.Size > 0); IM_ASSERT(atlas->ConfigData.Size > 0);
IM_ASSERT(atlas->TexGlyphPadding == 1); // Not supported
ImFontAtlasBuildRegisterDefaultCustomRects(atlas); ImFontAtlasBuildRegisterDefaultCustomRects(atlas);
@ -261,130 +310,291 @@ bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags)
atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
atlas->ClearTexData(); atlas->ClearTexData();
ImVector<FreeTypeFont> fonts; // Temporary storage for building
fonts.resize(atlas->ConfigData.Size); ImVector<ImFontBuildSrcDataFT> src_tmp_array;
ImVector<ImFontBuildDstDataFT> dst_tmp_array;
ImVec2 max_glyph_size(1.0f, 1.0f); 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 // 1. Initialize font loading structure, check font data validity
int total_glyphs_count = 0; for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++)
int total_ranges_count = 0;
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
{ {
ImFontConfig& cfg = atlas->ConfigData[input_i]; ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
FreeTypeFont& font_face = fonts[input_i]; ImFontConfig& cfg = atlas->ConfigData[src_i];
FreeTypeFont& font_face = src_tmp.Font;
IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); 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; return false;
max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth); // Measure highest codepoints
max_glyph_size.y = ImMax(max_glyph_size.y, font_face.Info.Ascender - font_face.Info.Descender); 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<stbrp_rect> buf_rects;
buf_rects.resize(total_glyphs_count);
memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());
// Allocate temporary rasterization data buffers.
// We could not find a way to retrieve accurate glyph size without rendering them.
// (e.g. slot->metrics->width not always matching bitmap->width, especially considering the Oblique transform)
// We allocate in chunks of 256 KB to not waste too much extra memory ahead. Hopefully users of FreeType won't find the temporary allocations.
const int BITMAP_BUFFERS_CHUNK_SIZE = 256 * 1024;
int buf_bitmap_current_used_bytes = 0;
ImVector<unsigned char*> buf_bitmap_buffers;
buf_bitmap_buffers.push_back((unsigned char*)ImGui::MemAlloc(BITMAP_BUFFERS_CHUNK_SIZE));
// 4. Gather glyphs sizes so we can pack them in our virtual canvas.
// 8. Render/rasterize font characters into the texture
int total_surface = 0;
int buf_rects_out_n = 0;
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
{
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
ImFontConfig& cfg = atlas->ConfigData[src_i];
if (src_tmp.GlyphsCount == 0)
continue;
src_tmp.Rects = &buf_rects[buf_rects_out_n];
buf_rects_out_n += src_tmp.GlyphsCount;
// Compute multiply table if requested
const bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
unsigned char multiply_table[256];
if (multiply_enabled)
ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
// Gather the sizes of all rectangles we will need to pack
const int padding = atlas->TexGlyphPadding;
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
{
ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
const FT_Glyph_Metrics* metrics = src_tmp.Font.LoadGlyph(src_glyph.Codepoint, &src_glyph.GlyphIndex);
IM_ASSERT(metrics != NULL);
if (metrics == NULL)
continue;
if (!cfg.GlyphRanges) // Render glyph into a bitmap (currently held by FreeType)
cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); const FT_Bitmap* ft_bitmap = src_tmp.Font.RenderGlyphAndGetInfo(src_glyph.GlyphIndex, &src_glyph.Info);
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[ 1 ]; in_range += 2, total_ranges_count++) IM_ASSERT(ft_bitmap);
total_glyphs_count += (in_range[1] - in_range[0]) + 1;
// 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. // We need a width for the skyline algorithm, any width!
// Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. // The exact 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; // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface.
const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1;
atlas->TexHeight = 0;
if (atlas->TexDesiredWidth > 0)
atlas->TexWidth = atlas->TexDesiredWidth;
else
atlas->TexWidth = (surface_sqrt >= 4096*0.7f) ? 4096 : (surface_sqrt >= 2048*0.7f) ? 2048 : (surface_sqrt >= 1024*0.7f) ? 1024 : 512;
// 5. Start packing
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
const int TEX_HEIGHT_MAX = 1024 * 32;
const int num_nodes_for_packing_algorithm = atlas->TexWidth - atlas->TexGlyphPadding;
ImVector<stbrp_node> pack_nodes;
pack_nodes.resize(num_nodes_for_packing_algorithm);
stbrp_context pack_context;
stbrp_init_target(&pack_context, atlas->TexWidth, TEX_HEIGHT_MAX, pack_nodes.Data, pack_nodes.Size);
ImFontAtlasBuildPackCustomRects(atlas, &pack_context);
// We don't do the original first pass to determine texture height, but just rough estimate. // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point.
// Looks ugly inaccurate and excessive, but AFAIK with FreeType we actually need to render glyphs to get exact sizes. for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
// 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.) ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
const int total_rects = total_glyphs_count + atlas->CustomRects.size(); if (src_tmp.GlyphsCount == 0)
float min_rects_per_row = ceilf((atlas->TexWidth / (max_glyph_size.x + 1.0f))); continue;
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));
// 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->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);
atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight);
memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight);
// Start packing // 8. Copy rasterized font characters back into the main texture
ImVector<stbrp_node> pack_nodes; // 9. Setup ImFont and glyphs for runtime
pack_nodes.resize(total_rects); for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
stbrp_context context; {
stbrp_init_target(&context, atlas->TexWidth, atlas->TexHeight, pack_nodes.Data, total_rects); 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). ImFontConfig& cfg = atlas->ConfigData[src_i];
ImFontAtlasBuildPackCustomRects(atlas, &context); 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 const float ascent = src_tmp.Font.Info.Ascender;
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) const float descent = src_tmp.Font.Info.Descender;
{
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;
ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
const float font_off_x = cfg.GlyphOffset.x; const float font_off_x = cfg.GlyphOffset.x;
const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f);
bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f); const int padding = atlas->TexGlyphPadding;
unsigned char multiply_table[256]; for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
if (multiply_enabled)
ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2)
{ {
for (uint32_t codepoint = in_range[0]; codepoint <= in_range[1]; ++codepoint) ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
{ stbrp_rect& pack_rect = src_tmp.Rects[glyph_i];
if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint)) IM_ASSERT(pack_rect.was_packed);
continue;
GlyphInfo& info = src_glyph.Info;
FT_Glyph ft_glyph = NULL; IM_ASSERT(info.Width + padding <= pack_rect.w);
FT_BitmapGlyph ft_glyph_bitmap = NULL; // NB: will point to bitmap within FT_Glyph IM_ASSERT(info.Height + padding <= pack_rect.h);
GlyphInfo glyph_info; const int tx = pack_rect.x + padding;
if (!font_face.CalcGlyphInfo(codepoint, glyph_info, ft_glyph, ft_glyph_bitmap)) const int ty = pack_rect.y + padding;
continue;
// Blit from temporary buffer to final texture
// Pack rectangle size_t blit_src_stride = (size_t)src_glyph.Info.Width;
stbrp_rect rect; size_t blit_dst_stride = (size_t)atlas->TexWidth;
rect.w = (uint16_t)glyph_info.Width + 1; // Account for texture filtering unsigned char* blit_src = src_glyph.BitmapData;
rect.h = (uint16_t)glyph_info.Height + 1; unsigned char* blit_dst = atlas->TexPixelsAlpha8 + (ty * blit_dst_stride) + tx;
stbrp_pack_rects(&context, &rect, 1); for (int y = info.Height; y > 0; y--, blit_dst += blit_dst_stride, blit_src += blit_src_stride)
memcpy(blit_dst, blit_src, blit_src_stride);
// Copy rasterized pixels to main texture
uint8_t* blit_dst = atlas->TexPixelsAlpha8 + rect.y * atlas->TexWidth + rect.x; float char_advance_x_org = info.AdvanceX;
font_face.BlitGlyph(ft_glyph_bitmap, blit_dst, atlas->TexWidth, multiply_enabled ? multiply_table : NULL); float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
FT_Done_Glyph(ft_glyph); float char_off_x = font_off_x;
if (char_advance_x_org != char_advance_x_mod)
float char_advance_x_org = glyph_info.AdvanceX; 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;
float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
float char_off_x = font_off_x; // Register glyph
if (char_advance_x_org != char_advance_x_mod) float x0 = info.OffsetX + char_off_x;
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; float y0 = info.OffsetY + font_off_y;
float x1 = x0 + info.Width;
// Register glyph float y1 = y0 + info.Height;
dst_font->AddGlyph((ImWchar)codepoint, float u0 = (tx) / (float)atlas->TexWidth;
glyph_info.OffsetX + char_off_x, float v0 = (ty) / (float)atlas->TexHeight;
glyph_info.OffsetY + font_off_y, float u1 = (tx + info.Width) / (float)atlas->TexWidth;
glyph_info.OffsetX + char_off_x + glyph_info.Width, float v1 = (ty + info.Height) / (float)atlas->TexHeight;
glyph_info.OffsetY + font_off_y + glyph_info.Height, dst_font->AddGlyph((ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, char_advance_x_mod);
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);
}
} }
src_tmp.Rects = NULL;
} }
// Cleanup // Cleanup
for (int n = 0; n < fonts.Size; n++) for (int buf_i = 0; buf_i < buf_bitmap_buffers.Size; buf_i++)
fonts[n].Destroy(); 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); ImFontAtlasBuildFinish(atlas);
return true; 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;
}

Loading…
Cancel
Save