mirror of
https://github.com/Drezil/imgui.git
synced 2025-03-31 16:32:45 +00:00
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)
This commit is contained in:
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+.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
@ -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,15 +73,15 @@ 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.
|
||||||
struct FontInfo
|
struct FontInfo
|
||||||
{
|
{
|
||||||
uint32_t PixelHeight; // Size this font was generated with.
|
uint32_t PixelHeight; // Size this font was generated with.
|
||||||
float Ascender; // The pixel extents above the baseline in pixels (typically positive).
|
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()
|
// 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)
|
if (error != 0)
|
||||||
return false;
|
return false;
|
||||||
error = FT_New_Memory_Face(FreetypeLibrary, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &FreetypeFace);
|
error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE);
|
||||||
if (error != 0)
|
|
||||||
return false;
|
|
||||||
error = FT_Select_Charmap(FreetypeFace, 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)
|
||||||
if (UserFlags & ImGuiFreeType::LightHinting)
|
LoadFlags |= FT_LOAD_NO_AUTOHINT;
|
||||||
FreetypeLoadFlags |= FT_LOAD_TARGET_LIGHT;
|
if (UserFlags & ImGuiFreeType::ForceAutoHint)
|
||||||
else if (UserFlags & ImGuiFreeType::MonoHinting)
|
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
|
||||||
FreetypeLoadFlags |= FT_LOAD_TARGET_MONO;
|
if (UserFlags & ImGuiFreeType::LightHinting)
|
||||||
|
LoadFlags |= FT_LOAD_TARGET_LIGHT;
|
||||||
|
else if (UserFlags & ImGuiFreeType::MonoHinting)
|
||||||
|
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,17 +177,49 @@ 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);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE);
|
||||||
|
|
||||||
if (UserFlags & ImGuiFreeType::Bold)
|
if (UserFlags & ImGuiFreeType::Bold)
|
||||||
@ -199,34 +227,27 @@ namespace
|
|||||||
if (UserFlags & ImGuiFreeType::Oblique)
|
if (UserFlags & ImGuiFreeType::Oblique)
|
||||||
FT_GlyphSlot_Oblique(slot);
|
FT_GlyphSlot_Oblique(slot);
|
||||||
|
|
||||||
// Retrieve the glyph
|
error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
|
||||||
error = FT_Get_Glyph(slot, &ft_glyph);
|
|
||||||
if (error != 0)
|
if (error != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Rasterize
|
FT_Bitmap* ft_bitmap = &Face->glyph->bitmap;
|
||||||
error = FT_Glyph_To_Bitmap(&ft_glyph, FT_RENDER_MODE_NORMAL, NULL, true);
|
out_glyph_info->Width = (int)ft_bitmap->width;
|
||||||
if (error != 0)
|
out_glyph_info->Height = (int)ft_bitmap->rows;
|
||||||
return false;
|
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);
|
||||||
|
|
||||||
ft_bitmap = (FT_BitmapGlyph)ft_glyph;
|
return ft_bitmap;
|
||||||
glyph_info.AdvanceX = (float)FT_CEIL(slot->advance.x);
|
|
||||||
glyph_info.OffsetX = (float)ft_bitmap->left;
|
|
||||||
glyph_info.OffsetY = -(float)ft_bitmap->top;
|
|
||||||
glyph_info.Width = (float)ft_bitmap->bitmap.width;
|
|
||||||
glyph_info.Height = (float)ft_bitmap->bitmap.rows;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
src_tmp_array.resize(atlas->ConfigData.Size);
|
||||||
|
dst_tmp_array.resize(atlas->Fonts.Size);
|
||||||
|
memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes());
|
||||||
|
memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes());
|
||||||
|
|
||||||
ImVec2 max_glyph_size(1.0f, 1.0f);
|
// 1. Initialize font loading structure, check font data validity
|
||||||
|
for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++)
|
||||||
// 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++)
|
|
||||||
{
|
{
|
||||||
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;
|
return false;
|
||||||
|
|
||||||
max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth);
|
// Load font
|
||||||
max_glyph_size.y = ImMax(max_glyph_size.y, font_face.Info.Ascender - font_face.Info.Descender);
|
if (!font_face.InitFont(ft_library, cfg, extra_flags))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!cfg.GlyphRanges)
|
// Measure highest codepoints
|
||||||
cfg.GlyphRanges = atlas->GetGlyphRangesDefault();
|
ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
|
||||||
for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[ 1 ]; in_range += 2, total_ranges_count++)
|
src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
|
||||||
total_glyphs_count += (in_range[1] - in_range[0]) + 1;
|
for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
|
||||||
|
src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]);
|
||||||
|
dst_tmp.SrcCount++;
|
||||||
|
dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish.
|
// 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.
|
||||||
// Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
|
int total_glyphs_count = 0;
|
||||||
atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512;
|
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);
|
||||||
|
|
||||||
// We don't do the original first pass to determine texture height, but just rough estimate.
|
for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
|
||||||
// Looks ugly inaccurate and excessive, but AFAIK with FreeType we actually need to render glyphs to get exact sizes.
|
for (int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++)
|
||||||
// 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.)
|
if (cfg.MergeMode && dst_tmp.GlyphsSet.GetBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option (e.g. MergeOverwrite)
|
||||||
const int total_rects = total_glyphs_count + atlas->CustomRects.size();
|
continue;
|
||||||
float min_rects_per_row = ceilf((atlas->TexWidth / (max_glyph_size.x + 1.0f)));
|
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..)
|
||||||
float min_rects_per_column = ceilf(total_rects / min_rects_per_row);
|
if (glyph_index == 0)
|
||||||
atlas->TexHeight = (int)(min_rects_per_column * (max_glyph_size.y + 1.0f));
|
continue;
|
||||||
|
|
||||||
// Create texture
|
// Add to avail set/counters
|
||||||
|
src_tmp.GlyphsCount++;
|
||||||
|
dst_tmp.GlyphsCount++;
|
||||||
|
src_tmp.GlyphsSet.SetBit(codepoint, true);
|
||||||
|
if (dst_tmp.SrcCount > 1)
|
||||||
|
dst_tmp.GlyphsSet.SetBit(codepoint, true);
|
||||||
|
total_glyphs_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another)
|
||||||
|
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||||
|
{
|
||||||
|
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||||
|
src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount);
|
||||||
|
|
||||||
|
IM_ASSERT(sizeof(src_tmp.GlyphsSet.Storage.Data[0]) == sizeof(int));
|
||||||
|
const int* it_begin = src_tmp.GlyphsSet.Storage.begin();
|
||||||
|
const int* it_end = src_tmp.GlyphsSet.Storage.end();
|
||||||
|
for (const int* it = it_begin; it < it_end; it++)
|
||||||
|
if (int entries_32 = *it)
|
||||||
|
for (int bit_n = 0; bit_n < 32; bit_n++)
|
||||||
|
if (entries_32 & (1 << bit_n))
|
||||||
|
{
|
||||||
|
ImFontBuildSrcGlyphFT src_glyph;
|
||||||
|
memset(&src_glyph, 0, sizeof(src_glyph));
|
||||||
|
src_glyph.Codepoint = (ImWchar)(((it - it_begin) << 5) + bit_n);
|
||||||
|
//src_glyph.GlyphIndex = 0; // FIXME-OPT: We had this info in the previous step and lost it..
|
||||||
|
src_tmp.GlyphsList.push_back(src_glyph);
|
||||||
|
}
|
||||||
|
src_tmp.GlyphsSet.Clear();
|
||||||
|
IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount);
|
||||||
|
}
|
||||||
|
for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++)
|
||||||
|
dst_tmp_array[dst_i].GlyphsSet.Clear();
|
||||||
|
dst_tmp_array.clear();
|
||||||
|
|
||||||
|
// Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0)
|
||||||
|
// (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity)
|
||||||
|
ImVector<stbrp_rect> buf_rects;
|
||||||
|
buf_rects.resize(total_glyphs_count);
|
||||||
|
memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());
|
||||||
|
|
||||||
|
// Allocate temporary rasterization data buffers.
|
||||||
|
// We could not find a way to retrieve accurate glyph size without rendering them.
|
||||||
|
// (e.g. slot->metrics->width not always matching bitmap->width, especially considering the Oblique transform)
|
||||||
|
// We allocate in chunks of 256 KB to not waste too much extra memory ahead. Hopefully users of FreeType won't find the temporary allocations.
|
||||||
|
const int BITMAP_BUFFERS_CHUNK_SIZE = 256 * 1024;
|
||||||
|
int buf_bitmap_current_used_bytes = 0;
|
||||||
|
ImVector<unsigned char*> buf_bitmap_buffers;
|
||||||
|
buf_bitmap_buffers.push_back((unsigned char*)ImGui::MemAlloc(BITMAP_BUFFERS_CHUNK_SIZE));
|
||||||
|
|
||||||
|
// 4. Gather glyphs sizes so we can pack them in our virtual canvas.
|
||||||
|
// 8. Render/rasterize font characters into the texture
|
||||||
|
int total_surface = 0;
|
||||||
|
int buf_rects_out_n = 0;
|
||||||
|
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||||
|
{
|
||||||
|
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||||
|
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||||
|
if (src_tmp.GlyphsCount == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
src_tmp.Rects = &buf_rects[buf_rects_out_n];
|
||||||
|
buf_rects_out_n += src_tmp.GlyphsCount;
|
||||||
|
|
||||||
|
// Compute multiply table if requested
|
||||||
|
const bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
|
||||||
|
unsigned char multiply_table[256];
|
||||||
|
if (multiply_enabled)
|
||||||
|
ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
|
||||||
|
|
||||||
|
// Gather the sizes of all rectangles we will need to pack
|
||||||
|
const int padding = atlas->TexGlyphPadding;
|
||||||
|
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
|
||||||
|
{
|
||||||
|
ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
|
||||||
|
|
||||||
|
const FT_Glyph_Metrics* metrics = src_tmp.Font.LoadGlyph(src_glyph.Codepoint, &src_glyph.GlyphIndex);
|
||||||
|
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);
|
||||||
|
IM_ASSERT(ft_bitmap);
|
||||||
|
|
||||||
|
// Allocate new temporary chunk if needed
|
||||||
|
const int bitmap_size_in_bytes = src_glyph.Info.Width * src_glyph.Info.Height;
|
||||||
|
if (buf_bitmap_current_used_bytes + bitmap_size_in_bytes > BITMAP_BUFFERS_CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
buf_bitmap_current_used_bytes = 0;
|
||||||
|
buf_bitmap_buffers.push_back((unsigned char*)ImGui::MemAlloc(BITMAP_BUFFERS_CHUNK_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blit rasterized pixels to our temporary buffer and keep a pointer to it.
|
||||||
|
src_glyph.BitmapData = buf_bitmap_buffers.back() + buf_bitmap_current_used_bytes;
|
||||||
|
buf_bitmap_current_used_bytes += bitmap_size_in_bytes;
|
||||||
|
src_tmp.Font.BlitGlyph(ft_bitmap, src_glyph.BitmapData, src_glyph.Info.Width * 1, multiply_enabled ? multiply_table : NULL);
|
||||||
|
|
||||||
|
src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + padding);
|
||||||
|
src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + padding);
|
||||||
|
total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need a width for the skyline algorithm, any width!
|
||||||
|
// The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
|
||||||
|
// User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface.
|
||||||
|
const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1;
|
||||||
|
atlas->TexHeight = 0;
|
||||||
|
if (atlas->TexDesiredWidth > 0)
|
||||||
|
atlas->TexWidth = atlas->TexDesiredWidth;
|
||||||
|
else
|
||||||
|
atlas->TexWidth = (surface_sqrt >= 4096*0.7f) ? 4096 : (surface_sqrt >= 2048*0.7f) ? 2048 : (surface_sqrt >= 1024*0.7f) ? 1024 : 512;
|
||||||
|
|
||||||
|
// 5. Start packing
|
||||||
|
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
|
||||||
|
const int TEX_HEIGHT_MAX = 1024 * 32;
|
||||||
|
const int num_nodes_for_packing_algorithm = atlas->TexWidth - atlas->TexGlyphPadding;
|
||||||
|
ImVector<stbrp_node> pack_nodes;
|
||||||
|
pack_nodes.resize(num_nodes_for_packing_algorithm);
|
||||||
|
stbrp_context pack_context;
|
||||||
|
stbrp_init_target(&pack_context, atlas->TexWidth, TEX_HEIGHT_MAX, pack_nodes.Data, pack_nodes.Size);
|
||||||
|
ImFontAtlasBuildPackCustomRects(atlas, &pack_context);
|
||||||
|
|
||||||
|
// 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point.
|
||||||
|
for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
|
||||||
|
{
|
||||||
|
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||||
|
if (src_tmp.GlyphsCount == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
stbrp_pack_rects(&pack_context, src_tmp.Rects, src_tmp.GlyphsCount);
|
||||||
|
|
||||||
|
// Extend texture height and mark missing glyphs as non-packed so we won't render them.
|
||||||
|
// FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?)
|
||||||
|
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
|
||||||
|
if (src_tmp.Rects[glyph_i].was_packed)
|
||||||
|
atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Allocate texture
|
||||||
atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);
|
atlas->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);
|
|
||||||
|
|
||||||
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
|
|
||||||
ImFontAtlasBuildPackCustomRects(atlas, &context);
|
|
||||||
|
|
||||||
// Render characters, setup ImFont and glyphs for runtime
|
|
||||||
for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
|
|
||||||
{
|
{
|
||||||
ImFontConfig& cfg = atlas->ConfigData[input_i];
|
ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
|
||||||
FreeTypeFont& font_face = fonts[input_i];
|
if (src_tmp.GlyphsCount == 0)
|
||||||
ImFont* dst_font = cfg.DstFont;
|
continue;
|
||||||
if (cfg.MergeMode)
|
|
||||||
dst_font->BuildLookupTable();
|
|
||||||
|
|
||||||
const float ascent = font_face.Info.Ascender;
|
ImFontConfig& cfg = atlas->ConfigData[src_i];
|
||||||
const float descent = font_face.Info.Descender;
|
ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true)
|
||||||
|
|
||||||
|
const float ascent = src_tmp.Font.Info.Ascender;
|
||||||
|
const float descent = src_tmp.Font.Info.Descender;
|
||||||
ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
|
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;
|
|
||||||
|
|
||||||
FT_Glyph ft_glyph = NULL;
|
GlyphInfo& info = src_glyph.Info;
|
||||||
FT_BitmapGlyph ft_glyph_bitmap = NULL; // NB: will point to bitmap within FT_Glyph
|
IM_ASSERT(info.Width + padding <= pack_rect.w);
|
||||||
GlyphInfo glyph_info;
|
IM_ASSERT(info.Height + padding <= pack_rect.h);
|
||||||
if (!font_face.CalcGlyphInfo(codepoint, glyph_info, ft_glyph, ft_glyph_bitmap))
|
const int tx = pack_rect.x + padding;
|
||||||
continue;
|
const int ty = pack_rect.y + padding;
|
||||||
|
|
||||||
// Pack rectangle
|
// Blit from temporary buffer to final texture
|
||||||
stbrp_rect rect;
|
size_t blit_src_stride = (size_t)src_glyph.Info.Width;
|
||||||
rect.w = (uint16_t)glyph_info.Width + 1; // Account for texture filtering
|
size_t blit_dst_stride = (size_t)atlas->TexWidth;
|
||||||
rect.h = (uint16_t)glyph_info.Height + 1;
|
unsigned char* blit_src = src_glyph.BitmapData;
|
||||||
stbrp_pack_rects(&context, &rect, 1);
|
unsigned char* blit_dst = atlas->TexPixelsAlpha8 + (ty * blit_dst_stride) + tx;
|
||||||
|
for (int y = info.Height; y > 0; y--, blit_dst += blit_dst_stride, blit_src += blit_src_stride)
|
||||||
|
memcpy(blit_dst, blit_src, blit_src_stride);
|
||||||
|
|
||||||
// Copy rasterized pixels to main texture
|
float char_advance_x_org = info.AdvanceX;
|
||||||
uint8_t* blit_dst = atlas->TexPixelsAlpha8 + rect.y * atlas->TexWidth + rect.x;
|
float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
|
||||||
font_face.BlitGlyph(ft_glyph_bitmap, blit_dst, atlas->TexWidth, multiply_enabled ? multiply_table : NULL);
|
float char_off_x = font_off_x;
|
||||||
FT_Done_Glyph(ft_glyph);
|
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;
|
||||||
float char_advance_x_org = glyph_info.AdvanceX;
|
|
||||||
float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
|
// Register glyph
|
||||||
float char_off_x = font_off_x;
|
float x0 = info.OffsetX + char_off_x;
|
||||||
if (char_advance_x_org != char_advance_x_mod)
|
float y0 = info.OffsetY + font_off_y;
|
||||||
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 x1 = x0 + info.Width;
|
||||||
|
float y1 = y0 + info.Height;
|
||||||
// Register glyph
|
float u0 = (tx) / (float)atlas->TexWidth;
|
||||||
dst_font->AddGlyph((ImWchar)codepoint,
|
float v0 = (ty) / (float)atlas->TexHeight;
|
||||||
glyph_info.OffsetX + char_off_x,
|
float u1 = (tx + info.Width) / (float)atlas->TexWidth;
|
||||||
glyph_info.OffsetY + font_off_y,
|
float v1 = (ty + info.Height) / (float)atlas->TexHeight;
|
||||||
glyph_info.OffsetX + char_off_x + glyph_info.Width,
|
dst_font->AddGlyph((ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, char_advance_x_mod);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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…
x
Reference in New Issue
Block a user