34.9 KB、755行 生 Blame 履歴
1// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
2// (code)
3
4// Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
5// Original code by @vuhdo (Aleksei Skriabin) in 2017, with improvements by @mikesart.
6// Maintained since 2019 by @ocornut.
7
8// CHANGELOG
9// (minor and older changes stripped away, please see git history for details)
10// 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support.
11// 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927)
12// 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics.
13// 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591)
14// 2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly.
15// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns nullptr.
16// 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
17// 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a preferred texture format.
18// 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
19// 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
20// 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
21// 2019/02/09: added RasterizerFlags::Monochrome flag to disable font anti-aliasing (combine with ::MonoHinting for best results!)
22// 2019/01/15: added support for imgui allocators + added FreeType only override function SetAllocatorFunctions().
23// 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.
24// 2018/06/08: added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX.
25// 2018/02/04: moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
26// 2018/01/22: fix for addition of ImFontAtlas::TexUvscale member.
27// 2017/10/22: minor inconsequential change to match change in master (removed an unnecessary statement).
28// 2017/09/26: fixes for imgui internal changes.
29// 2017/08/26: cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
30// 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.
31
32// About Gamma Correct Blending:
33// - FreeType assumes blending in linear space rather than gamma space.
34// - See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
35// - For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
36// - The default dear imgui styles will be impacted by this change (alpha values will need tweaking).
37
38// FIXME: cfg.OversampleH, OversampleV are not supported, but generally not necessary with this rasterizer because Hinting makes everything look better.
39
40#include "imgui.h"
41#ifndef IMGUI_DISABLE
42#include "imgui_freetype.h"
43#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
44#include <stdint.h>
45#include <ft2build.h>
46#include FT_FREETYPE_H // <freetype/freetype.h>
47#include FT_MODULE_H // <freetype/ftmodapi.h>
48#include FT_GLYPH_H // <freetype/ftglyph.h>
49#include FT_SIZES_H // <freetype/ftsizes.h>
50#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
51
52// Handle LunaSVG and PlutoSVG
53#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
54#error "Cannot enable both IMGUI_ENABLE_FREETYPE_LUNASVG and IMGUI_ENABLE_FREETYPE_PLUTOSVG"
55#endif
56#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
57#include FT_OTSVG_H // <freetype/otsvg.h>
58#include FT_BBOX_H // <freetype/ftbbox.h>
59#include <lunasvg.h>
60#endif
61#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
62#include <plutosvg.h>
63#endif
64#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined (IMGUI_ENABLE_FREETYPE_PLUTOSVG)
65#if !((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
66#error IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG requires FreeType version >= 2.12
67#endif
68#endif
69
70#ifdef _MSC_VER
71#pragma warning (push)
72#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
73#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
74#endif
75
76#ifdef __GNUC__
77#pragma GCC diagnostic push
78#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
79#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
80#ifndef __clang__
81#pragma GCC diagnostic ignored "-Wsubobject-linkage" // warning: 'xxxx' has a field 'xxxx' whose type uses the anonymous namespace
82#endif
83#endif
84
85//-------------------------------------------------------------------------
86// Data
87//-------------------------------------------------------------------------
88
89// Default memory allocators
90static void* ImGuiFreeTypeDefaultAllocFunc(size_t size, void* user_data) { IM_UNUSED(user_data); return IM_ALLOC(size); }
91static void ImGuiFreeTypeDefaultFreeFunc(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_FREE(ptr); }
92
93// Current memory allocators
94static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFreeTypeDefaultAllocFunc;
95static void (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
96static void* GImGuiFreeTypeAllocatorUserData = nullptr;
97
98// Load struct
99static ImFontLoader GImGuiFreeTypeLoader;
100static char GImGuiFreeTypeLoaderName[48] = "FreeType";
101
102// Lunasvg support
103#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
104static FT_Error ImGuiLunasvgPortInit(FT_Pointer* state);
105static void ImGuiLunasvgPortFree(FT_Pointer* state);
106static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state);
107static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state);
108#endif
109
110//-------------------------------------------------------------------------
111// Code
112//-------------------------------------------------------------------------
113
114#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point
115#define FT_SCALEFACTOR 64.0f
116
117// Glyph metrics:
118// --------------
119//
120// xmin xmax
121// | |
122// |<-------- width -------->|
123// | |
124// | +-------------------------+----------------- ymax
125// | | ggggggggg ggggg | ^ ^
126// | | g:::::::::ggg::::g | | |
127// | | g:::::::::::::::::g | | |
128// | | g::::::ggggg::::::gg | | |
129// | | g:::::g g:::::g | | |
130// offsetX -|-------->| g:::::g g:::::g | offsetY |
131// | | g:::::g g:::::g | | |
132// | | g::::::g g:::::g | | |
133// | | g:::::::ggggg:::::g | | |
134// | | g::::::::::::::::g | | height
135// | | gg::::::::::::::g | | |
136// baseline ---*---------|---- gggggggg::::::g-----*-------- |
137// / | | g:::::g | |
138// origin | | gggggg g:::::g | |
139// | | g:::::gg gg:::::g | |
140// | | g::::::ggg:::::::g | |
141// | | gg:::::::::::::g | |
142// | | ggg::::::ggg | |
143// | | gggggg | v
144// | +-------------------------+----------------- ymin
145// | |
146// |------------- advanceX ----------->|
147
148// Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US.
149struct ImGui_ImplFreeType_Data
150{
151 FT_Library Library;
152 FT_MemoryRec_ MemoryManager;
153 char BackendName[48];
154 ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); }
155};
156
157// Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US.
158struct ImGui_ImplFreeType_FontSrcData
159{
160 // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
161 bool InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_user_flags);
162 void CloseFont();
163 ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); }
164 ~ImGui_ImplFreeType_FontSrcData() { CloseFont(); }
165
166 // Members
167 FT_Face FtFace;
168 ImGuiFreeTypeLoaderFlags UserFlags; // = ImFontConfig::FontLoaderFlags
169 FT_Int32 LoadFlags;
170 ImFontBaked* BakedLastActivated;
171};
172
173// Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE.
174struct ImGui_ImplFreeType_FontSrcBakedData
175{
176 FT_Size FtSize; // This represent a FT_Face with a given size.
177 ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); }
178};
179
180bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags)
181{
182 FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
183 if (error != 0)
184 return false;
185 error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
186 if (error != 0)
187 return false;
188
189 // Convert to FreeType flags (NB: Bold and Oblique are processed separately)
190 UserFlags = (ImGuiFreeTypeLoaderFlags)(src->FontLoaderFlags | extra_font_loader_flags);
191
192 LoadFlags = 0;
193 if ((UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) == 0)
194 LoadFlags |= FT_LOAD_NO_BITMAP;
195 if (UserFlags & ImGuiFreeTypeLoaderFlags_NoHinting)
196 LoadFlags |= FT_LOAD_NO_HINTING;
197 if (UserFlags & ImGuiFreeTypeLoaderFlags_NoAutoHint)
198 LoadFlags |= FT_LOAD_NO_AUTOHINT;
199 if (UserFlags & ImGuiFreeTypeLoaderFlags_ForceAutoHint)
200 LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
201 if (UserFlags & ImGuiFreeTypeLoaderFlags_LightHinting)
202 LoadFlags |= FT_LOAD_TARGET_LIGHT;
203 else if (UserFlags & ImGuiFreeTypeLoaderFlags_MonoHinting)
204 LoadFlags |= FT_LOAD_TARGET_MONO;
205 else
206 LoadFlags |= FT_LOAD_TARGET_NORMAL;
207
208 if (UserFlags & ImGuiFreeTypeLoaderFlags_LoadColor)
209 LoadFlags |= FT_LOAD_COLOR;
210
211 return true;
212}
213
214void ImGui_ImplFreeType_FontSrcData::CloseFont()
215{
216 if (FtFace)
217 {
218 FT_Done_Face(FtFace);
219 FtFace = nullptr;
220 }
221}
222
223static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint)
224{
225 uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint);
226 if (glyph_index == 0)
227 return nullptr;
228
229 // If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
230 // - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
231 // - https://github.com/ocornut/imgui/issues/4567
232 // - https://github.com/ocornut/imgui/issues/4566
233 // You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
234 FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
235 if (error)
236 return nullptr;
237
238 // Need an outline for this to work
239 FT_GlyphSlot slot = src_data->FtFace->glyph;
240#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
241 IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG);
242#else
243#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
244 IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font");
245#endif
246 IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
247#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
248
249 // Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
250 if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bold)
251 FT_GlyphSlot_Embolden(slot);
252 if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Oblique)
253 {
254 FT_GlyphSlot_Oblique(slot);
255 //FT_BBox bbox;
256 //FT_Outline_Get_BBox(&slot->outline, &bbox);
257 //slot->metrics.width = bbox.xMax - bbox.xMin;
258 //slot->metrics.height = bbox.yMax - bbox.yMin;
259 }
260
261 return &slot->metrics;
262}
263
264static void ImGui_ImplFreeType_BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch)
265{
266 IM_ASSERT(ft_bitmap != nullptr);
267 const uint32_t w = ft_bitmap->width;
268 const uint32_t h = ft_bitmap->rows;
269 const uint8_t* src = ft_bitmap->buffer;
270 const uint32_t src_pitch = ft_bitmap->pitch;
271
272 switch (ft_bitmap->pixel_mode)
273 {
274 case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel.
275 {
276 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
277 for (uint32_t x = 0; x < w; x++)
278 dst[x] = IM_COL32(255, 255, 255, src[x]);
279 break;
280 }
281 case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
282 {
283 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
284 {
285 uint8_t bits = 0;
286 const uint8_t* bits_ptr = src;
287 for (uint32_t x = 0; x < w; x++, bits <<= 1)
288 {
289 if ((x & 7) == 0)
290 bits = *bits_ptr++;
291 dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0);
292 }
293 }
294 break;
295 }
296 case FT_PIXEL_MODE_BGRA:
297 {
298 // FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
299 #define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u)
300 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
301 for (uint32_t x = 0; x < w; x++)
302 {
303 uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
304 dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
305 }
306 #undef DE_MULTIPLY
307 break;
308 }
309 default:
310 IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!");
311 }
312}
313
314// FreeType memory allocation callbacks
315static void* FreeType_Alloc(FT_Memory /*memory*/, long size)
316{
317 return GImGuiFreeTypeAllocFunc((size_t)size, GImGuiFreeTypeAllocatorUserData);
318}
319
320static void FreeType_Free(FT_Memory /*memory*/, void* block)
321{
322 GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
323}
324
325static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size, void* block)
326{
327 // Implement realloc() as we don't ask user to provide it.
328 if (block == nullptr)
329 return GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
330
331 if (new_size == 0)
332 {
333 GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
334 return nullptr;
335 }
336
337 if (new_size > cur_size)
338 {
339 void* new_block = GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
340 memcpy(new_block, block, (size_t)cur_size);
341 GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
342 return new_block;
343 }
344
345 return block;
346}
347
348static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
349{
350 IM_ASSERT(atlas->FontLoaderData == nullptr);
351 ImGui_ImplFreeType_Data* bd = IM_NEW(ImGui_ImplFreeType_Data)();
352
353 // FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html
354 bd->MemoryManager.user = nullptr;
355 bd->MemoryManager.alloc = &FreeType_Alloc;
356 bd->MemoryManager.free = &FreeType_Free;
357 bd->MemoryManager.realloc = &FreeType_Realloc;
358
359 // https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
360 FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library);
361 if (error != 0)
362 {
363 IM_DELETE(bd);
364 return false;
365 }
366
367 // Update ImFontLoader::Name field with linked version
368 FT_Int ver_linked_major, ver_linked_minor, ver_linked_patch;
369 FT_Library_Version(bd->Library, &ver_linked_major, &ver_linked_minor, &ver_linked_patch);
370 snprintf(GImGuiFreeTypeLoaderName, sizeof(GImGuiFreeTypeLoaderName), "FreeType (%d.%d.%d; %d.%d.%d)", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH, ver_linked_major, ver_linked_minor, ver_linked_patch);
371
372 // If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
373 FT_Add_Default_Modules(bd->Library);
374
375#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
376 // Install svg hooks for FreeType
377 // https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks
378 // https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts
379 SVG_RendererHooks hooks = { ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot };
380 FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", &hooks);
381#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
382#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
383 // With plutosvg, use provided hooks
384 FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
385#endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG
386
387 // Store our data
388 atlas->FontLoaderData = (void*)bd;
389
390 return true;
391}
392
393static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
394{
395 ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
396 IM_ASSERT(bd != nullptr);
397 FT_Done_Library(bd->Library);
398 IM_DELETE(bd);
399 atlas->FontLoaderData = nullptr;
400}
401
402static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src)
403{
404 ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
405 ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData);
406 IM_ASSERT(src->FontLoaderData == nullptr);
407 src->FontLoaderData = bd_font_data;
408
409 if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeLoaderFlags)atlas->FontLoaderFlags))
410 {
411 IM_DELETE(bd_font_data);
412 src->FontLoaderData = nullptr;
413 return false;
414 }
415
416 return true;
417}
418
419static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src)
420{
421 IM_UNUSED(atlas);
422 ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
423 IM_DELETE(bd_font_data);
424 src->FontLoaderData = nullptr;
425}
426
427static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
428{
429 IM_UNUSED(atlas);
430 float size = baked->Size;
431 const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
432 if (src->MergeMode && src->SizePixels != 0.0f)
433 size *= (src->SizePixels / ref_size);
434 size *= src->ExtraSizeScale;
435
436 ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
437 bd_font_data->BakedLastActivated = baked;
438
439 // We use one FT_Size per (source + baked) combination.
440 ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
441 IM_ASSERT(bd_baked_data != nullptr);
442 IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData();
443
444 FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
445 FT_Activate_Size(bd_baked_data->FtSize);
446
447 // Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
448 // 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.
449 // FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
450 // (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
451 const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
452 FT_Size_RequestRec req;
453 req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
454 req.width = 0;
455 req.height = (uint32_t)(size * 64 * rasterizer_density);
456 req.horiResolution = 0;
457 req.vertResolution = 0;
458 FT_Request_Size(bd_font_data->FtFace, &req);
459
460 // Output
461 if (src->MergeMode == false)
462 {
463 // Read metrics
464 FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics;
465 const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale);
466 baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive).
467 baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative).
468 //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate.
469 //LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent.
470 //MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
471 }
472 return true;
473}
474
475static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
476{
477 IM_UNUSED(atlas);
478 IM_UNUSED(baked);
479 IM_UNUSED(src);
480 ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
481 IM_ASSERT(bd_baked_data != nullptr);
482 FT_Done_Size(bd_baked_data->FtSize);
483 bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE()
484}
485
486static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
487{
488 ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
489 uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
490 if (glyph_index == 0)
491 return false;
492
493 if (bd_font_data->BakedLastActivated != baked) // <-- could use id
494 {
495 // Activate current size
496 ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
497 FT_Activate_Size(bd_baked_data->FtSize);
498 bd_font_data->BakedLastActivated = baked;
499 }
500
501 const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint);
502 if (metrics == nullptr)
503 return false;
504
505 FT_Face face = bd_font_data->FtFace;
506 FT_GlyphSlot slot = face->glyph;
507 const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
508
509 // Load metrics only mode
510 const float advance_x = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density;
511 if (out_advance_x != NULL)
512 {
513 IM_ASSERT(out_glyph == NULL);
514 *out_advance_x = advance_x;
515 return true;
516 }
517
518 // Render glyph into a bitmap (currently held by FreeType)
519 FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL;
520 FT_Error error = FT_Render_Glyph(slot, render_mode);
521 const FT_Bitmap* ft_bitmap = &slot->bitmap;
522 if (error != 0 || ft_bitmap == nullptr)
523 return false;
524
525 const int w = (int)ft_bitmap->width;
526 const int h = (int)ft_bitmap->rows;
527 const bool is_visible = (w != 0 && h != 0);
528
529 // Prepare glyph
530 out_glyph->Codepoint = codepoint;
531 out_glyph->AdvanceX = advance_x;
532
533 // Pack and retrieve position inside texture atlas
534 if (is_visible)
535 {
536 ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h);
537 if (pack_id == ImFontAtlasRectId_Invalid)
538 {
539 // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?)
540 IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory.");
541 return false;
542 }
543 ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id);
544
545 // Render pixels to our temporary buffer
546 atlas->Builder->TempBuffer.resize(w * h * 4);
547 uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data;
548 ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, w);
549
550 const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
551 const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
552 float font_off_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f); // Snap scaled offset.
553 float font_off_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f) + baked->Ascent;
554 float recip_h = 1.0f / rasterizer_density;
555 float recip_v = 1.0f / rasterizer_density;
556
557 // Register glyph
558 float glyph_off_x = (float)face->glyph->bitmap_left;
559 float glyph_off_y = (float)-face->glyph->bitmap_top;
560 out_glyph->X0 = glyph_off_x * recip_h + font_off_x;
561 out_glyph->Y0 = glyph_off_y * recip_v + font_off_y;
562 out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x;
563 out_glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y;
564 out_glyph->Visible = true;
565 out_glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
566 out_glyph->PackId = pack_id;
567 ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4);
568 }
569
570 return true;
571}
572
573static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint)
574{
575 IM_UNUSED(atlas);
576 ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
577 int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
578 return glyph_index != 0;
579}
580
581const ImFontLoader* ImGuiFreeType::GetFontLoader()
582{
583 ImFontLoader* loader = &GImGuiFreeTypeLoader;
584 loader->Name = GImGuiFreeTypeLoaderName; // Initially "FreeType" then updated during the call to ImGui_ImplFreeType_LoaderInit()
585 loader->LoaderInit = ImGui_ImplFreeType_LoaderInit;
586 loader->LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown;
587 loader->FontSrcInit = ImGui_ImplFreeType_FontSrcInit;
588 loader->FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy;
589 loader->FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph;
590 loader->FontBakedInit = ImGui_ImplFreeType_FontBakedInit;
591 loader->FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy;
592 loader->FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph;
593 loader->FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData);
594 return loader;
595}
596
597void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
598{
599 GImGuiFreeTypeAllocFunc = alloc_func;
600 GImGuiFreeTypeFreeFunc = free_func;
601 GImGuiFreeTypeAllocatorUserData = user_data;
602}
603
604bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags)
605{
606 bool edited = false;
607 edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoHinting);
608 edited |= ImGui::CheckboxFlags("NoAutoHint", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoAutoHint);
609 edited |= ImGui::CheckboxFlags("ForceAutoHint",p_font_loader_flags, ImGuiFreeTypeLoaderFlags_ForceAutoHint);
610 edited |= ImGui::CheckboxFlags("LightHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LightHinting);
611 edited |= ImGui::CheckboxFlags("MonoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_MonoHinting);
612 edited |= ImGui::CheckboxFlags("Bold", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bold);
613 edited |= ImGui::CheckboxFlags("Oblique", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Oblique);
614 edited |= ImGui::CheckboxFlags("Monochrome", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Monochrome);
615 edited |= ImGui::CheckboxFlags("LoadColor", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LoadColor);
616 edited |= ImGui::CheckboxFlags("Bitmap", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bitmap);
617 return edited;
618}
619
620#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
621// For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
622// The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT)
623struct LunasvgPortState
624{
625 FT_Error err = FT_Err_Ok;
626 lunasvg::Matrix matrix;
627 std::unique_ptr<lunasvg::Document> svg = nullptr;
628};
629
630static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state)
631{
632 *_state = IM_NEW(LunasvgPortState)();
633 return FT_Err_Ok;
634}
635
636static void ImGuiLunasvgPortFree(FT_Pointer* _state)
637{
638 IM_DELETE(*(LunasvgPortState**)_state);
639}
640
641static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state)
642{
643 LunasvgPortState* state = *(LunasvgPortState**)_state;
644
645 // If there was an error while loading the svg in ImGuiLunasvgPortPresetSlot(), the renderer hook still get called, so just returns the error.
646 if (state->err != FT_Err_Ok)
647 return state->err;
648
649 // rows is height, pitch (or stride) equals to width * sizeof(int32)
650 lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
651#if LUNASVG_VERSION_MAJOR >= 3
652 state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
653#else
654 state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value
655 state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
656#endif
657 state->err = FT_Err_Ok;
658 return state->err;
659}
660
661static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state)
662{
663 FT_SVG_Document document = (FT_SVG_Document)slot->other;
664 LunasvgPortState* state = *(LunasvgPortState**)_state;
665 FT_Size_Metrics& metrics = document->metrics;
666
667 // This function is called twice, once in the FT_Load_Glyph() and another right before ImGuiLunasvgPortRender().
668 // If it's the latter, don't do anything because it's // already done in the former.
669 if (cache)
670 return state->err;
671
672 state->svg = lunasvg::Document::loadFromData((const char*)document->svg_document, document->svg_document_length);
673 if (state->svg == nullptr)
674 {
675 state->err = FT_Err_Invalid_SVG_Document;
676 return state->err;
677 }
678
679#if LUNASVG_VERSION_MAJOR >= 3
680 lunasvg::Box box = state->svg->boundingBox();
681#else
682 lunasvg::Box box = state->svg->box();
683#endif
684 double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h);
685 double xx = (double)document->transform.xx / (1 << 16);
686 double xy = -(double)document->transform.xy / (1 << 16);
687 double yx = -(double)document->transform.yx / (1 << 16);
688 double yy = (double)document->transform.yy / (1 << 16);
689 double x0 = (double)document->delta.x / 64 * box.w / metrics.x_ppem;
690 double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem;
691
692#if LUNASVG_VERSION_MAJOR >= 3
693 // Scale, transform and pre-translate the matrix for the rendering step
694 state->matrix = lunasvg::Matrix::translated(-box.x, -box.y);
695 state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0));
696 state->matrix.scale(scale, scale);
697
698 // Apply updated transformation to the bounding box
699 box.transform(state->matrix);
700#else
701 // Scale and transform, we don't translate the svg yet
702 state->matrix.identity();
703 state->matrix.scale(scale, scale);
704 state->matrix.transform(xx, xy, yx, yy, x0, y0);
705 state->svg->setMatrix(state->matrix);
706
707 // Pre-translate the matrix for the rendering step
708 state->matrix.translate(-box.x, -box.y);
709
710 // Get the box again after the transformation
711 box = state->svg->box();
712#endif
713
714 // Calculate the bitmap size
715 slot->bitmap_left = FT_Int(box.x);
716 slot->bitmap_top = FT_Int(-box.y);
717 slot->bitmap.rows = (unsigned int)(ImCeil((float)box.h));
718 slot->bitmap.width = (unsigned int)(ImCeil((float)box.w));
719 slot->bitmap.pitch = slot->bitmap.width * 4;
720 slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
721
722 // Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box.
723 double metrics_width = box.w;
724 double metrics_height = box.h;
725 double horiBearingX = box.x;
726 double horiBearingY = -box.y;
727 double vertBearingX = slot->metrics.horiBearingX / 64.0 - slot->metrics.horiAdvance / 64.0 / 2.0;
728 double vertBearingY = (slot->metrics.vertAdvance / 64.0 - slot->metrics.height / 64.0) / 2.0;
729 slot->metrics.width = FT_Pos(IM_ROUND(metrics_width * 64.0)); // Using IM_ROUND() assume width and height are positive
730 slot->metrics.height = FT_Pos(IM_ROUND(metrics_height * 64.0));
731 slot->metrics.horiBearingX = FT_Pos(horiBearingX * 64);
732 slot->metrics.horiBearingY = FT_Pos(horiBearingY * 64);
733 slot->metrics.vertBearingX = FT_Pos(vertBearingX * 64);
734 slot->metrics.vertBearingY = FT_Pos(vertBearingY * 64);
735
736 if (slot->metrics.vertAdvance == 0)
737 slot->metrics.vertAdvance = FT_Pos(metrics_height * 1.2 * 64.0);
738
739 state->err = FT_Err_Ok;
740 return state->err;
741}
742
743#endif // #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
744
745//-----------------------------------------------------------------------------
746
747#ifdef __GNUC__
748#pragma GCC diagnostic pop
749#endif
750
751#ifdef _MSC_VER
752#pragma warning (pop)
753#endif
754
755#endif // #ifndef IMGUI_DISABLE