This commit is contained in:
Leonard Hecker 2026-09-25 22:01:47 +09:00 • committed by GitHub
commit 377b679935
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 606 additions and 7 deletions

View file

@ -99,8 +99,12 @@
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
//#define IMGUI_ENABLE_FREETYPE_LUNASVG
//---- Use DirectWrite to rasterize fonts (Windows only).
// Requires misc/dwrite/imgui_dwrite.cpp. Takes precedence over IMGUI_ENABLE_FREETYPE.
//#define IMGUI_ENABLE_DWRITE
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType or DirectWrite backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.

View file

@ -16686,6 +16686,9 @@ static const char* FormatTextureRefForDebugDisplay(char* buf, int buf_size, ImTe
return buf;
}
#ifdef IMGUI_ENABLE_DWRITE
namespace ImGuiDWrite { IMGUI_API const ImFontLoader* GetFontLoader(); }
#endif
#ifdef IMGUI_ENABLE_FREETYPE
namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontLoaderFlags(unsigned int* p_font_builder_flags); }
#endif
@ -16734,7 +16737,12 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas)
if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL"))
{
const ImFontLoader* loader_current = atlas->FontLoader;
bool has_font_family = false;
for (const ImFontConfig& src : atlas->Sources)
if (src.FontLoader == NULL)
has_font_family |= src.FontData == NULL;
BeginDisabled(!atlas->RendererHasTextures);
BeginDisabled(has_font_family);
#ifdef IMGUI_ENABLE_STB_TRUETYPE
const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype();
if (RadioButton("stb_truetype", loader_current == loader_stbtruetype))
@ -16769,6 +16777,18 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas)
SetItemTooltip("Requires #define IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp.");
EndDisabled();
#endif
EndDisabled();
SameLine();
#ifdef IMGUI_ENABLE_DWRITE
const ImFontLoader* loader_dwrite = ImGuiDWrite::GetFontLoader();
if (RadioButton("DirectWrite", loader_current == loader_dwrite))
atlas->SetFontLoader(loader_dwrite);
#else
BeginDisabled();
RadioButton("DirectWrite", false);
SetItemTooltip("Requires #define IMGUI_ENABLE_DWRITE + imgui_dwrite.cpp.");
EndDisabled();
#endif
EndDisabled();
TreePop();
}

View file

@ -3633,7 +3633,7 @@ struct ImTextureData
struct ImFontConfig
{
// Data Source
char Name[40]; // <auto> // Name (strictly to ease debugging, hence limited size buffer)
char Name[64]; // <auto> // Debug name, or UTF-8 font family name when FontData is NULL.
void* FontData; // // TTF/OTF data
int FontDataSize; // // TTF/OTF data size
bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the owner ImFontAtlas (will delete memory itself). SINCE 1.92, THE DATA NEEDS TO PERSIST FOR WHOLE DURATION OF ATLAS.
@ -3756,6 +3756,7 @@ struct ImFontAtlas
IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); // Selects between AddFontDefaultVector() and AddFontDefaultBitmap().
IMGUI_API ImFont* AddFontDefaultVector(const ImFontConfig* font_cfg = NULL); // Embedded scalable font. Recommended at any higher size.
IMGUI_API ImFont* AddFontDefaultBitmap(const ImFontConfig* font_cfg = NULL); // Embedded classic pixel-clean font. Recommended at Size 13px with no scaling.
IMGUI_API ImFont* AddFontFromFamily(const char* family, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL); // Requires a loader supporting font families (e.g. DirectWrite).
IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);
IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed.
IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp.

View file

@ -36,6 +36,9 @@ Index of this file:
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_internal.h"
#ifdef IMGUI_ENABLE_DWRITE
#include "misc/dwrite/imgui_dwrite.h"
#endif
#ifdef IMGUI_ENABLE_FREETYPE
#include "misc/freetype/imgui_freetype.h"
#endif
@ -3074,7 +3077,6 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in)
{
// Sanity Checks
IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!");
IM_ASSERT((font_cfg_in->FontData != NULL && font_cfg_in->FontDataSize > 0) || (font_cfg_in->FontLoader != NULL));
//IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?");
IM_ASSERT(font_cfg_in->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?");
if (font_cfg_in->GlyphOffset.x != 0.0f || font_cfg_in->GlyphOffset.y != 0.0f || font_cfg_in->GlyphMinAdvanceX != 0.0f || font_cfg_in->GlyphMaxAdvanceX != FLT_MAX)
@ -3291,6 +3293,21 @@ ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, f
return AddFont(&font_cfg);
}
ImFont* ImFontAtlas::AddFontFromFamily(const char* family, float size_pixels, const ImFontConfig* font_cfg_template)
{
IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!");
ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
IM_ASSERT(font_cfg.FontData == NULL && font_cfg.FontDataSize == 0);
if (family == NULL || family[0] == '\0' || ImStrlen(family) >= IM_COUNTOF(font_cfg.Name))
{
IM_ASSERT_USER_ERROR(0, "Invalid font family name.");
return NULL;
}
ImStrncpy(font_cfg.Name, family, IM_COUNTOF(font_cfg.Name));
font_cfg.SizePixels = size_pixels > 0.0f ? size_pixels : font_cfg.SizePixels;
return AddFont(&font_cfg);
}
ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
{
const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data);
@ -4334,7 +4351,9 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas)
// and point to it instead of pointing directly to return value of the GetFontLoaderXXX functions.
if (atlas->FontLoader == NULL)
{
#ifdef IMGUI_ENABLE_FREETYPE
#ifdef IMGUI_ENABLE_DWRITE
atlas->SetFontLoader(ImGuiDWrite::GetFontLoader());
#elif defined(IMGUI_ENABLE_FREETYPE)
atlas->SetFontLoader(ImGuiFreeType::GetFontLoader());
#elif defined(IMGUI_ENABLE_STB_TRUETYPE)
atlas->SetFontLoader(ImFontAtlasGetFontLoaderForStbTruetype());
@ -4712,6 +4731,12 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig*
{
IM_UNUSED(atlas);
if (src->FontData == NULL || src->FontDataSize <= 0)
{
IM_ASSERT_USER_ERROR(0, "stb_truetype requires font data; font families are not supported.");
return false;
}
ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData);
IM_ASSERT(src->FontLoaderData == NULL);

View file

@ -124,9 +124,9 @@ Index of this file:
#error Use IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS
#endif
// Enable stb_truetype by default unless FreeType is enabled.
// You can compile with both by defining both IMGUI_ENABLE_FREETYPE and IMGUI_ENABLE_STB_TRUETYPE together.
#ifndef IMGUI_ENABLE_FREETYPE
// Enable stb_truetype by default unless FreeType or DirectWrite are enabled.
// You can re-enable stb_truetype by explicitly defining IMGUI_ENABLE_STB_TRUETYPE before including imgui.h.
#if !defined(IMGUI_ENABLE_FREETYPE) && !defined(IMGUI_ENABLE_DWRITE)
#define IMGUI_ENABLE_STB_TRUETYPE
#endif

View file

@ -7,6 +7,10 @@ misc/debuggers/
Helper files for popular debuggers (Visual Studio, GDB, LLDB).
e.g. With the .natvis file, types like ImVector<> will be displayed nicely in Visual Studio debugger.
misc/dwrite/
Font atlas builder/rasterizer using DirectWrite on Windows.
It features an improved rasterization quality over stb_truetype, the ability to load system font families, and system font fallback.
misc/fonts/
Fonts loading/merging instructions (e.g. How to handle glyph ranges, how to merge icons fonts).
Command line tool "binary_to_compressed_c" to create compressed arrays to embed data in source code.

34
misc/dwrite/README.md Normal file
View file

@ -0,0 +1,34 @@
# imgui_dwrite
This implements font rasterization using DirectWrite instead of stb_truetype (otherwise the default).
<br>by @lhecker.
The minimum requirement is Windows 10 1709 (build 16299).
## Usage
1. Add imgui_dwrite.h/cpp alongside your project files.
2. Either set `#define IMGUI_ENABLE_DWRITE` in your [imconfig.h](https://github.com/ocornut/imgui/blob/master/imconfig.h) file or call `ImGui::GetIO().Fonts->SetFontLoader(ImGuiDWrite::GetFontLoader())`.
3. Add fonts using `ImGui::GetIO().Fonts->AddFontFromFamily("Segoe UI")`.
## The Good
- An improved rasterization over stb_truetype (particularly at small font sizes).
- You can use system fonts.
- It supports font fallback.
## The Bad
- Loads font families using regular weight, normal stretch, and normal style only.
- Not all `ImFontConfig` rasterizer options are implemented.
- `IMGUI_USE_WCHAR32` is required to render codepoints above U+FFFF, such as emoji.
Multi-codepoint glyphs are not supported.
- `ProggyForever.ttf` will not look good.
If you're curious: This is because the font does not set `USE_TYPO_METRICS`, which causes DirectWrite to correctly use
the `OS/2` `usWinAscent`/`Descent` fields instead of `sTypoAscender`/`Descender`. stb_truetype on the other hand uses
the `hhea` table (OpenType is an old standard and has competing metrics tables) whose values coincide with the latter.
This causes the glyphs to be nice and large with stb_truetype and small and squished with DirectWrite.
...and most importantly: **Gamma correct text blending is not supported**.
DirectWrite relies on gamma correction being applied by a shader during composition.
A variant of this can be found at https://github.com/lhecker/dwrite-hlsl but this cannot be trivially integrated into ImGui.

View file

@ -0,0 +1,486 @@
#include "imgui_dwrite.h"
#ifndef IMGUI_DISABLE
#include "imgui_internal.h"
#include <assert.h>
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <dwrite_3.h>
#ifdef _MSC_VER
#pragma comment(lib, "dwrite")
#pragma comment(lib, "user32")
#endif
#define RETURN_FALSE_IF(cond) \
do { \
if (cond) { \
IM_ASSERT(false); \
return false; \
} \
} while (0)
#define RETURN_FALSE_IF_FAILED(hr) \
RETURN_FALSE_IF(FAILED(hr))
template<typename T>
struct com_ptr
{
~com_ptr()
{
if (m_ptr)
{
m_ptr->Release();
}
}
T* get() const
{
return m_ptr;
}
T* operator->() const
{
return m_ptr;
}
T** addressof()
{
return &m_ptr;
}
T* detach()
{
T* ptr = m_ptr;
m_ptr = nullptr;
return ptr;
}
private:
T* m_ptr = nullptr;
};
template<typename T>
struct unique_im
{
unique_im(T* ptr) : m_ptr(ptr)
{}
~unique_im()
{
IM_DELETE(m_ptr);
}
T* operator->()
{
return m_ptr;
}
T* detach()
{
T* ptr = m_ptr;
m_ptr = nullptr;
return ptr;
}
private:
T* m_ptr;
};
struct TextAnalysisSource final : IDWriteTextAnalysisSource
{
TextAnalysisSource(const wchar_t* locale, const wchar_t* text, UINT32 text_length) : m_locale(locale), m_text(text), m_text_length(text_length) {}
ULONG STDMETHODCALLTYPE AddRef() override
{
return 1;
}
ULONG STDMETHODCALLTYPE Release() override
{
return 1;
}
HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject) override
{
if (IsEqualGUID(riid, __uuidof(IDWriteTextAnalysisSource)))
{
*ppvObject = this;
return S_OK;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition, const WCHAR** textString, UINT32* textLength) override
{
textPosition = ImMin(textPosition, m_text_length);
*textString = m_text + textPosition;
*textLength = m_text_length - textPosition;
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition, const WCHAR** textString, UINT32* textLength) override
{
textPosition = ImMin(textPosition, m_text_length);
*textString = m_text;
*textLength = textPosition;
return S_OK;
}
DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() override
{
return DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
}
HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition, UINT32* textLength, const WCHAR** localeName) override
{
*textLength = m_text_length - textPosition;
*localeName = m_locale;
return S_OK;
}
HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32, UINT32*, IDWriteNumberSubstitution**) override
{
return E_NOTIMPL;
}
private:
const wchar_t* m_locale;
const wchar_t* m_text;
UINT32 m_text_length;
};
struct DWriteFontSource
{
com_ptr<IDWriteFactory5> factory;
com_ptr<IDWriteInMemoryFontFileLoader> memory_loader;
com_ptr<IDWriteRenderingParams> rendering_params;
com_ptr<IDWriteFontFace3> font_face;
com_ptr<IDWriteFontCollection> font_collection;
com_ptr<IDWriteFontFallback1> font_fallback;
wchar_t family_name[IM_COUNTOF(ImFontConfig::Name)];
float em_per_unit;
float ascent;
float descent;
~DWriteFontSource()
{
if (memory_loader.get())
factory->UnregisterFontFileLoader(memory_loader.get());
}
};
static bool font_src_init(ImFontAtlas*, ImFontConfig* src)
{
unique_im<DWriteFontSource> data(IM_NEW(DWriteFontSource));
// Create "global" objects. System font collection, font fallback handler, etc.
com_ptr<IDWriteFontFallback> font_fallback;
RETURN_FALSE_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory5), (IUnknown**)data->factory.addressof()));
RETURN_FALSE_IF_FAILED(data->factory->CreateRenderingParams(data->rendering_params.addressof()));
RETURN_FALSE_IF_FAILED(data->factory->GetSystemFontCollection(data->font_collection.addressof(), FALSE));
RETURN_FALSE_IF_FAILED(data->factory->GetSystemFontFallback(font_fallback.addressof()));
RETURN_FALSE_IF_FAILED(font_fallback->QueryInterface(data->font_fallback.addressof()));
// Create a `IDWriteFontFace` (a single stylistic variant) out of `src`.
if (src->FontData || src->FontDataSize)
{
// In-memory font? `IDWriteInMemoryFontFileLoader`!
IM_ASSERT_USER_ERROR(src->FontData != nullptr && src->FontDataSize > 0, "Invalid font data");
// DWriteFontSource assumes that memory_loader != null means it needs to be unregistered.
// As such, we create and register the loader first and then transfer ownership into `data`.
com_ptr<IDWriteInMemoryFontFileLoader> memory_loader;
RETURN_FALSE_IF_FAILED(data->factory->CreateInMemoryFontFileLoader(memory_loader.addressof()));
RETURN_FALSE_IF_FAILED(data->factory->RegisterFontFileLoader(memory_loader.get()));
*data->memory_loader.addressof() = memory_loader.detach();
com_ptr<IDWriteFontFile> font_file;
com_ptr<IDWriteFontFaceReference> font_reference;
RETURN_FALSE_IF_FAILED(data->memory_loader->CreateInMemoryFontFileReference(data->factory.get(), src->FontData, (UINT32)src->FontDataSize, nullptr, font_file.addressof()));
RETURN_FALSE_IF_FAILED(data->factory->CreateFontFaceReference(font_file.get(), src->FontNo, DWRITE_FONT_SIMULATIONS_NONE, font_reference.addressof()));
RETURN_FALSE_IF_FAILED(font_reference->CreateFontFace(data->font_face.addressof()));
}
else
{
// If no font data is present, we assume the Name field contains the family name of the font.
// Name -> Name, but wchar -> Find font family -> Get any variant out of it.
const int name_utf8_len = (int)strnlen(src->Name, IM_COUNTOF(src->Name));
const int name_utf16_len = MultiByteToWideChar(CP_UTF8, 0, src->Name, name_utf8_len, &data->family_name[0], IM_COUNTOF(data->family_name) - 1);
RETURN_FALSE_IF(name_utf16_len <= 0);
data->family_name[name_utf16_len] = L'\0';
UINT32 family_index;
BOOL family_exists;
RETURN_FALSE_IF_FAILED(data->font_collection->FindFamilyName(&data->family_name[0], &family_index, &family_exists));
RETURN_FALSE_IF(!family_exists);
com_ptr<IDWriteFontFamily> font_family;
com_ptr<IDWriteFont> font;
com_ptr<IDWriteFontFace> font_face;
RETURN_FALSE_IF_FAILED(data->font_collection->GetFontFamily(family_index, font_family.addressof()));
RETURN_FALSE_IF_FAILED(font_family->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof()));
RETURN_FALSE_IF_FAILED(font->CreateFontFace(font_face.addressof()));
RETURN_FALSE_IF_FAILED(font_face->QueryInterface(data->font_face.addressof()));
}
DWRITE_FONT_METRICS metrics;
data->font_face->GetMetrics(&metrics);
// Calculate the ascent/descent of the font in "em".
// 1.1em (in total) means that a 20px font has a 22px line height.
//
// Modern typographical metrics feature a line gap, which indicates the spacing between lines.
// Since ImGui has no concept of a line gap, we add 50% onto the ascent/descent respectively.
const float ascent = metrics.ascent;
const float descent = metrics.descent;
const float half_line_gap = (float)metrics.lineGap * 0.5f;
const float em_per_unit = 1.0f / (float)metrics.designUnitsPerEm;
data->em_per_unit = em_per_unit;
data->ascent = (ascent + half_line_gap) * em_per_unit;
data->descent = (descent + half_line_gap) * em_per_unit;
src->FontLoaderData = data.detach();
return true;
}
static void font_src_destroy(ImFontAtlas*, ImFontConfig* src)
{
IM_DELETE((DWriteFontSource*)src->FontLoaderData);
src->FontLoaderData = nullptr;
}
static UINT16 map_codepoint_with_fallback(DWriteFontSource* data, ImWchar codepoint, IDWriteFontFace3** font_face)
{
const UINT32 codepoint32 = codepoint;
// "Fast" path: Try the primary font. DirectWrite implements GetGlyphIndices as O(1).
UINT16 glyph_index;
if (FAILED(data->font_face->GetGlyphIndices(&codepoint32, 1, &glyph_index)))
return 0;
if (glyph_index != 0)
{
data->font_face->AddRef();
*font_face = data->font_face.get();
return glyph_index;
}
#ifdef IMGUI_USE_WCHAR32
wchar_t text[2];
UINT32 text_length;
if (codepoint > 0xFFFF)
{
text[0] = (wchar_t)(0xD800 + ((codepoint - 0x10000) >> 10));
text[1] = (wchar_t)(0xDC00 + ((codepoint - 0x10000) & 0x3FF));
text_length = 2;
}
else
{
text[0] = (wchar_t)codepoint;
text_length = 1;
}
#else
const wchar_t* text = (const wchar_t*)&codepoint;
const UINT32 text_length = 1;
#endif
TextAnalysisSource analysis_source(ImGuiDWrite::GetUserDefaultLocaleName(), text, text_length);
UINT32 mapped_length;
float scale;
IDWriteFontFace5* font_face5;
if (FAILED(data->font_fallback->MapCharacters(
/* analysisSource */ &analysis_source,
/* textPosition */ 0,
/* textLength */ text_length,
/* baseFontCollection */ data->font_collection.get(),
/* baseFamilyName */ data->family_name,
/* fontAxisValues */ nullptr,
/* fontAxisValueCount */ 0,
/* mappedLength */ &mapped_length,
/* scale */ &scale,
/* mappedFontFace */ &font_face5
)))
return 0;
// NOTE: MapCharacters() can return S_OK and yet mappedFontFace can be null.
// This indicates that no font contains the requested codepoint.
if (!font_face5 || FAILED(font_face5->GetGlyphIndices(&codepoint32, 1, &glyph_index)))
glyph_index = 0;
*font_face = font_face5;
return glyph_index;
}
static bool font_src_contains_glyph(ImFontAtlas*, ImFontConfig* src, ImWchar codepoint)
{
DWriteFontSource* data = (DWriteFontSource*)src->FontLoaderData;
com_ptr<IDWriteFontFace3> font_face;
return map_codepoint_with_fallback(data, codepoint, font_face.addressof()) != 0;
}
static bool font_baked_init(ImFontAtlas*, ImFontConfig* src, ImFontBaked* baked, void*)
{
if (!src->MergeMode)
{
DWriteFontSource* data = (DWriteFontSource*)src->FontLoaderData;
const float rasterizer_scale = src->RasterizerDensity * baked->RasterizerDensity * src->ExtraSizeScale;
const float font_size = baked->Size * rasterizer_scale / (data->ascent + data->descent);
baked->Ascent = ImCeil(font_size * data->ascent) / rasterizer_scale;
baked->Descent = -ImCeil(font_size * data->descent) / rasterizer_scale;
}
return true;
}
static bool font_baked_load_glyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
{
DWriteFontSource* data = (DWriteFontSource*)src->FontLoaderData;
com_ptr<IDWriteFontFace3> font_face;
const UINT16 glyph_index = map_codepoint_with_fallback(data, codepoint, font_face.addressof());
if (glyph_index == 0)
return false;
// ImGui's font size is actually the line height. Here, we reverse-calculate the actual
// font size by simply dividing the height in em. The result is in 96 DPI pixels.
const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
float font_size = baked->Size * src->ExtraSizeScale / (data->ascent + data->descent);
if (src->MergeMode && src->SizePixels != 0.0f)
font_size *= src->SizePixels / ref_size;
// DirectWrite yields fractional advance widths, but simultaneously we don't implement oversampling.
// To prevent ugly smearing, we need to ensure to only return integer advances.
DWRITE_GLYPH_METRICS metrics;
RETURN_FALSE_IF_FAILED(font_face->GetDesignGlyphMetrics(&glyph_index, 1, &metrics, FALSE));
const float advance_x = IM_ROUND((float)metrics.advanceWidth * data->em_per_unit * font_size);
if (out_advance_x)
{
IM_ASSERT(out_glyph == nullptr);
*out_advance_x = advance_x;
return true;
}
const DWRITE_GLYPH_OFFSET zero_offset = {};
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
const DWRITE_GLYPH_RUN glyph_run = {
/* fontFace */ font_face.get(),
/* fontEmSize */ font_size * rasterizer_density,
/* glyphCount */ 1,
/* glyphIndices */ &glyph_index,
/* glyphAdvances */ nullptr,
/* glyphOffsets */ &zero_offset,
/* isSideways */ FALSE,
/* bidiLevel */ 0,
};
// If you read SDK headers you may notice the very enticing IDWriteBitmapRenderTarget3::DrawGlyphRunWithColorSupport
// function, but (and these words are written in anger) that one is exclusive to the Windows App SDK.
//
// In the future, it may be reasonable to call IDWriteFactory4::TranslateColorGlyphRun here,
// and manually compose COLRv0 layers into a single bitmap on the CPU side. Using Direct2D
// would not be a good idea as it does not interop well with various graphics debuggers.
// To properly support GASP, a call to GetRecommendedRenderingMode is required.
DWRITE_RENDERING_MODE1 rendering_mode;
DWRITE_GRID_FIT_MODE grid_fit_mode;
com_ptr<IDWriteGlyphRunAnalysis> analysis;
RECT bounds;
RETURN_FALSE_IF_FAILED(font_face->GetRecommendedRenderingMode(
/* fontEmSize */ glyph_run.fontEmSize,
/* dpiX */ 96.0f, // fontEmSize is already in display pixels
/* dpiY */ 96.0f,
/* transform */ nullptr,
/* isSideways */ FALSE,
/* outlineThreshold */ DWRITE_OUTLINE_THRESHOLD_ANTIALIASED,
/* measuringMode */ DWRITE_MEASURING_MODE_NATURAL,
/* renderingParams */ data->rendering_params.get(),
/* renderingMode */ &rendering_mode,
/* gridFitMode */ &grid_fit_mode
));
RETURN_FALSE_IF_FAILED(data->factory->CreateGlyphRunAnalysis(
/* glyphRun */ &glyph_run,
/* transform */ nullptr,
/* renderingMode */ rendering_mode,
/* measuringMode */ DWRITE_MEASURING_MODE_NATURAL,
/* gridFitMode */ grid_fit_mode,
/* antialiasMode */ DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE,
/* baselineOriginX */ 0,
/* baselineOriginY */ 0,
/* glyphRunAnalysis */ analysis.addressof()
));
// Even if it says "aliased", it actually produces an antialiased 8-bit grayscale texture.
// I don't get it either. I don't think anyone at Microsoft does.
RETURN_FALSE_IF_FAILED(analysis->GetAlphaTextureBounds(DWRITE_TEXTURE_ALIASED_1x1, &bounds));
out_glyph->Codepoint = codepoint;
out_glyph->AdvanceX = advance_x;
// Invisible glyphs (whitespace) produce empty bounds.
// out_glyph is initialized with Visible = false, so we can just return here.
if (IsRectEmpty(&bounds))
return true;
const int width = bounds.right - bounds.left;
const int height = bounds.bottom - bounds.top;
ImVector<ImU8> pixels;
pixels.resize(width * height);
RETURN_FALSE_IF_FAILED(analysis->CreateAlphaTexture(DWRITE_TEXTURE_ALIASED_1x1, &bounds, pixels.Data, pixels.Size));
const ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, width, height);
RETURN_FALSE_IF(pack_id == ImFontAtlasRectId_Invalid);
const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
const float offset_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f);
const float offset_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f) + baked->Ascent;
const float inv_density = 1.0f / rasterizer_density;
out_glyph->X0 = (float)bounds.left * inv_density + offset_x;
out_glyph->Y0 = (float)bounds.top * inv_density + offset_y;
out_glyph->X1 = (float)bounds.right * inv_density + offset_x;
out_glyph->Y1 = (float)bounds.bottom * inv_density + offset_y;
out_glyph->Visible = true;
out_glyph->PackId = pack_id;
ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, pack_id);
ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, rect, pixels.Data, ImTextureFormat_Alpha8, width);
return true;
}
const wchar_t* ImGuiDWrite::GetUserDefaultLocaleName()
{
static wchar_t locale_name[LOCALE_NAME_MAX_LENGTH];
if (locale_name[0] == L'\0')
{
if (!::GetUserDefaultLocaleName(locale_name, LOCALE_NAME_MAX_LENGTH))
{
static const wchar_t fallback_locale[] = L"en-US";
memcpy(&locale_name[0], &fallback_locale[0], sizeof(fallback_locale));
}
}
return &locale_name[0];
}
const ImFontLoader* ImGuiDWrite::GetFontLoader()
{
static ImFontLoader loader;
loader.Name = "DirectWrite";
loader.FontSrcInit = font_src_init;
loader.FontSrcDestroy = font_src_destroy;
loader.FontSrcContainsGlyph = font_src_contains_glyph;
loader.FontBakedInit = font_baked_init;
loader.FontBakedDestroy = nullptr;
loader.FontBakedLoadGlyph = font_baked_load_glyph;
return &loader;
}
#endif

View file

@ -0,0 +1,13 @@
#pragma once
#include "imgui.h"
#ifndef IMGUI_DISABLE
struct ImFontLoader;
namespace ImGuiDWrite
{
IMGUI_API const wchar_t* GetUserDefaultLocaleName();
IMGUI_API const ImFontLoader* GetFontLoader();
}
#endif

View file

@ -402,6 +402,12 @@ static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src)
{
if (src->FontData == NULL || src->FontDataSize <= 0)
{
IM_ASSERT_USER_ERROR(0, "FreeType requires font data; font families are not supported.");
return false;
}
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData);
IM_ASSERT(src->FontLoaderData == nullptr);

View file

@ -13,6 +13,9 @@
#endif
#include "../../imgui.h"
#ifdef IMGUI_ENABLE_DWRITE
#include "../../misc/dwrite/imgui_dwrite.h"
#endif
#ifdef IMGUI_ENABLE_FREETYPE
#include "../../misc/freetype/imgui_freetype.h"
#endif
@ -23,6 +26,9 @@
#include "../../imgui_draw.cpp"
#include "../../imgui_tables.cpp"
#include "../../imgui_widgets.cpp"
#ifdef IMGUI_ENABLE_DWRITE
#include "../../misc/dwrite/imgui_dwrite.cpp"
#endif
#ifdef IMGUI_ENABLE_FREETYPE
#include "../../misc/freetype/imgui_freetype.cpp"
#endif