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). Improvements by @mikesart. Maintained since 2019 by @ocornut.
6
7 // CHANGELOG
8 // (minor and older changes stripped away, please see git history for details)
9 // 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns NULL.
10 // 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
11 // 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a prefered texture format.
12 // 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
13 // 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'.
14 // renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
15 // 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
16 // 2019/02/09: added RasterizerFlags::Monochrome flag to disable font anti-aliasing (combine with ::MonoHinting for best results!)
17 // 2019/01/15: added support for imgui allocators + added FreeType only override function SetAllocatorFunctions().
18 // 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.
19 // 2018/06/08: added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX.
20 // 2018/02/04: moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
21 // 2018/01/22: fix for addition of ImFontAtlas::TexUvscale member.
22 // 2017/10/22: minor inconsequential change to match change in master (removed an unnecessary statement).
23 // 2017/09/26: fixes for imgui internal changes.
24 // 2017/08/26: cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
25 // 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.
26
27 // About Gamma Correct Blending:
28 // - FreeType assumes blending in linear space rather than gamma space.
29 // - See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
30 // - For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
31 // - The default dear imgui styles will be impacted by this change (alpha values will need tweaking).
32
33 // FIXME: cfg.OversampleH, OversampleV are not supported (but perhaps not so necessary with this rasterizer).
34
35 #include "imgui_freetype.h"
36 #include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
37 #include <stdint.h>
38 #include <ft2build.h>
39 #include FT_FREETYPE_H // <freetype/freetype.h>
40 #include FT_MODULE_H // <freetype/ftmodapi.h>
41 #include FT_GLYPH_H // <freetype/ftglyph.h>
42 #include FT_SYNTHESIS_H // <freetype/ftsynth.h>
43
44 #ifdef _MSC_VER
45 #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
46 #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
47 #endif
48
49 #if defined(__GNUC__)
50 #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
51 #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
52 #endif
53
54 //-------------------------------------------------------------------------
55 // Data
56 //-------------------------------------------------------------------------
57
58 // Default memory allocators
ImGuiFreeTypeDefaultAllocFunc(size_t size,void * user_data)59 static void* ImGuiFreeTypeDefaultAllocFunc(size_t size, void* user_data) { IM_UNUSED(user_data); return IM_ALLOC(size); }
ImGuiFreeTypeDefaultFreeFunc(void * ptr,void * user_data)60 static void ImGuiFreeTypeDefaultFreeFunc(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_FREE(ptr); }
61
62 // Current memory allocators
63 static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFreeTypeDefaultAllocFunc;
64 static void (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
65 static void* GImGuiFreeTypeAllocatorUserData = NULL;
66
67 //-------------------------------------------------------------------------
68 // Code
69 //-------------------------------------------------------------------------
70
71 namespace
72 {
73 // Glyph metrics:
74 // --------------
75 //
76 // xmin xmax
77 // | |
78 // |<-------- width -------->|
79 // | |
80 // | +-------------------------+----------------- ymax
81 // | | ggggggggg ggggg | ^ ^
82 // | | g:::::::::ggg::::g | | |
83 // | | g:::::::::::::::::g | | |
84 // | | g::::::ggggg::::::gg | | |
85 // | | g:::::g g:::::g | | |
86 // offsetX -|-------->| g:::::g g:::::g | offsetY |
87 // | | g:::::g g:::::g | | |
88 // | | g::::::g g:::::g | | |
89 // | | g:::::::ggggg:::::g | | |
90 // | | g::::::::::::::::g | | height
91 // | | gg::::::::::::::g | | |
92 // baseline ---*---------|---- gggggggg::::::g-----*-------- |
93 // / | | g:::::g | |
94 // origin | | gggggg g:::::g | |
95 // | | g:::::gg gg:::::g | |
96 // | | g::::::ggg:::::::g | |
97 // | | gg:::::::::::::g | |
98 // | | ggg::::::ggg | |
99 // | | gggggg | v
100 // | +-------------------------+----------------- ymin
101 // | |
102 // |------------- advanceX ----------->|
103
104 // A structure that describe a glyph.
105 struct GlyphInfo
106 {
107 int Width; // Glyph's width in pixels.
108 int Height; // Glyph's height in pixels.
109 FT_Int OffsetX; // The distance from the origin ("pen position") to the left of the glyph.
110 FT_Int OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0.
111 float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0.
112 bool IsColored; // The glyph is colored
113 };
114
115 // Font parameters and metrics.
116 struct FontInfo
117 {
118 uint32_t PixelHeight; // Size this font was generated with.
119 float Ascender; // The pixel extents above the baseline in pixels (typically positive).
120 float Descender; // The extents below the baseline in pixels (typically negative).
121 float LineSpacing; // 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.
122 float LineGap; // The spacing in pixels between one row's descent and the next row's ascent.
123 float MaxAdvanceWidth; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
124 };
125
126 // FreeType glyph rasterizer.
127 // NB: No ctor/dtor, explicitly call Init()/Shutdown()
128 struct FreeTypeFont
129 {
130 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.
131 void CloseFont();
132 void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size
133 const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint);
134 const FT_Bitmap* RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info);
135 void BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = NULL);
~FreeTypeFont__anoned4acadf0111::FreeTypeFont136 ~FreeTypeFont() { CloseFont(); }
137
138 // [Internals]
139 FontInfo Info; // Font descriptor of the current font.
140 FT_Face Face;
141 unsigned int UserFlags; // = ImFontConfig::RasterizerFlags
142 FT_Int32 LoadFlags;
143 FT_Render_Mode RenderMode;
144 };
145
146 // From SDL_ttf: Handy routines for converting from fixed point
147 #define FT_CEIL(X) (((X + 63) & -64) / 64)
148
InitFont(FT_Library ft_library,const ImFontConfig & cfg,unsigned int extra_font_builder_flags)149 bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& cfg, unsigned int extra_font_builder_flags)
150 {
151 FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &Face);
152 if (error != 0)
153 return false;
154 error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE);
155 if (error != 0)
156 return false;
157
158 // Convert to FreeType flags (NB: Bold and Oblique are processed separately)
159 UserFlags = cfg.FontBuilderFlags | extra_font_builder_flags;
160
161 LoadFlags = 0;
162 if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0)
163 LoadFlags |= FT_LOAD_NO_BITMAP;
164
165 if (UserFlags & ImGuiFreeTypeBuilderFlags_NoHinting)
166 LoadFlags |= FT_LOAD_NO_HINTING;
167 if (UserFlags & ImGuiFreeTypeBuilderFlags_NoAutoHint)
168 LoadFlags |= FT_LOAD_NO_AUTOHINT;
169 if (UserFlags & ImGuiFreeTypeBuilderFlags_ForceAutoHint)
170 LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
171 if (UserFlags & ImGuiFreeTypeBuilderFlags_LightHinting)
172 LoadFlags |= FT_LOAD_TARGET_LIGHT;
173 else if (UserFlags & ImGuiFreeTypeBuilderFlags_MonoHinting)
174 LoadFlags |= FT_LOAD_TARGET_MONO;
175 else
176 LoadFlags |= FT_LOAD_TARGET_NORMAL;
177
178 if (UserFlags & ImGuiFreeTypeBuilderFlags_Monochrome)
179 RenderMode = FT_RENDER_MODE_MONO;
180 else
181 RenderMode = FT_RENDER_MODE_NORMAL;
182
183 if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor)
184 LoadFlags |= FT_LOAD_COLOR;
185
186 memset(&Info, 0, sizeof(Info));
187 SetPixelHeight((uint32_t)cfg.SizePixels);
188
189 return true;
190 }
191
CloseFont()192 void FreeTypeFont::CloseFont()
193 {
194 if (Face)
195 {
196 FT_Done_Face(Face);
197 Face = NULL;
198 }
199 }
200
SetPixelHeight(int pixel_height)201 void FreeTypeFont::SetPixelHeight(int pixel_height)
202 {
203 // Vuhdo: I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
204 // 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.
205 // NB: FT_Set_Pixel_Sizes() doesn't seem to get us the same result.
206 FT_Size_RequestRec req;
207 req.type = (UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
208 req.width = 0;
209 req.height = (uint32_t)pixel_height * 64;
210 req.horiResolution = 0;
211 req.vertResolution = 0;
212 FT_Request_Size(Face, &req);
213
214 // Update font info
215 FT_Size_Metrics metrics = Face->size->metrics;
216 Info.PixelHeight = (uint32_t)pixel_height;
217 Info.Ascender = (float)FT_CEIL(metrics.ascender);
218 Info.Descender = (float)FT_CEIL(metrics.descender);
219 Info.LineSpacing = (float)FT_CEIL(metrics.height);
220 Info.LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender);
221 Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance);
222 }
223
LoadGlyph(uint32_t codepoint)224 const FT_Glyph_Metrics* FreeTypeFont::LoadGlyph(uint32_t codepoint)
225 {
226 uint32_t glyph_index = FT_Get_Char_Index(Face, codepoint);
227 if (glyph_index == 0)
228 return NULL;
229 FT_Error error = FT_Load_Glyph(Face, glyph_index, LoadFlags);
230 if (error)
231 return NULL;
232
233 // Need an outline for this to work
234 FT_GlyphSlot slot = Face->glyph;
235 IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
236
237 // Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
238 if (UserFlags & ImGuiFreeTypeBuilderFlags_Bold)
239 FT_GlyphSlot_Embolden(slot);
240 if (UserFlags & ImGuiFreeTypeBuilderFlags_Oblique)
241 {
242 FT_GlyphSlot_Oblique(slot);
243 //FT_BBox bbox;
244 //FT_Outline_Get_BBox(&slot->outline, &bbox);
245 //slot->metrics.width = bbox.xMax - bbox.xMin;
246 //slot->metrics.height = bbox.yMax - bbox.yMin;
247 }
248
249 return &slot->metrics;
250 }
251
RenderGlyphAndGetInfo(GlyphInfo * out_glyph_info)252 const FT_Bitmap* FreeTypeFont::RenderGlyphAndGetInfo(GlyphInfo* out_glyph_info)
253 {
254 FT_GlyphSlot slot = Face->glyph;
255 FT_Error error = FT_Render_Glyph(slot, RenderMode);
256 if (error != 0)
257 return NULL;
258
259 FT_Bitmap* ft_bitmap = &Face->glyph->bitmap;
260 out_glyph_info->Width = (int)ft_bitmap->width;
261 out_glyph_info->Height = (int)ft_bitmap->rows;
262 out_glyph_info->OffsetX = Face->glyph->bitmap_left;
263 out_glyph_info->OffsetY = -Face->glyph->bitmap_top;
264 out_glyph_info->AdvanceX = (float)FT_CEIL(slot->advance.x);
265 out_glyph_info->IsColored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
266
267 return ft_bitmap;
268 }
269
BlitGlyph(const FT_Bitmap * ft_bitmap,uint32_t * dst,uint32_t dst_pitch,unsigned char * multiply_table)270 void FreeTypeFont::BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch, unsigned char* multiply_table)
271 {
272 IM_ASSERT(ft_bitmap != NULL);
273 const uint32_t w = ft_bitmap->width;
274 const uint32_t h = ft_bitmap->rows;
275 const uint8_t* src = ft_bitmap->buffer;
276 const uint32_t src_pitch = ft_bitmap->pitch;
277
278 switch (ft_bitmap->pixel_mode)
279 {
280 case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel.
281 {
282 if (multiply_table == NULL)
283 {
284 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
285 for (uint32_t x = 0; x < w; x++)
286 dst[x] = IM_COL32(255, 255, 255, src[x]);
287 }
288 else
289 {
290 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
291 for (uint32_t x = 0; x < w; x++)
292 dst[x] = IM_COL32(255, 255, 255, multiply_table[src[x]]);
293 }
294 break;
295 }
296 case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
297 {
298 uint8_t color0 = multiply_table ? multiply_table[0] : 0;
299 uint8_t color1 = multiply_table ? multiply_table[255] : 255;
300 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
301 {
302 uint8_t bits = 0;
303 const uint8_t* bits_ptr = src;
304 for (uint32_t x = 0; x < w; x++, bits <<= 1)
305 {
306 if ((x & 7) == 0)
307 bits = *bits_ptr++;
308 dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? color1 : color0);
309 }
310 }
311 break;
312 }
313 case FT_PIXEL_MODE_BGRA:
314 {
315 // FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
316 #define DE_MULTIPLY(color, alpha) (ImU32)(255.0f * (float)color / (float)alpha + 0.5f)
317 if (multiply_table == NULL)
318 {
319 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
320 for (uint32_t x = 0; x < w; x++)
321 {
322 uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
323 dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
324 }
325 }
326 else
327 {
328 for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
329 {
330 for (uint32_t x = 0; x < w; x++)
331 {
332 uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
333 dst[x] = IM_COL32(multiply_table[DE_MULTIPLY(r, a)], multiply_table[DE_MULTIPLY(g, a)], multiply_table[DE_MULTIPLY(b, a)], multiply_table[a]);
334 }
335 }
336 }
337 #undef DE_MULTIPLY
338 break;
339 }
340 default:
341 IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!");
342 }
343 }
344 }
345
346 #ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds)
347 #ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
348 #define STBRP_ASSERT(x) do { IM_ASSERT(x); } while (0)
349 #define STBRP_STATIC
350 #define STB_RECT_PACK_IMPLEMENTATION
351 #endif
352 #ifdef IMGUI_STB_RECT_PACK_FILENAME
353 #include IMGUI_STB_RECT_PACK_FILENAME
354 #else
355 #include "imstb_rectpack.h"
356 #endif
357 #endif
358
359 struct ImFontBuildSrcGlyphFT
360 {
361 GlyphInfo Info;
362 uint32_t Codepoint;
363 unsigned int* BitmapData; // Point within one of the dst_tmp_bitmap_buffers[] array
364
ImFontBuildSrcGlyphFTImFontBuildSrcGlyphFT365 ImFontBuildSrcGlyphFT() { memset(this, 0, sizeof(*this)); }
366 };
367
368 struct ImFontBuildSrcDataFT
369 {
370 FreeTypeFont Font;
371 stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position.
372 const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF)
373 int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[]
374 int GlyphsHighest; // Highest requested codepoint
375 int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font)
376 ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB)
377 ImVector<ImFontBuildSrcGlyphFT> GlyphsList;
378 };
379
380 // Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont)
381 struct ImFontBuildDstDataFT
382 {
383 int SrcCount; // Number of source fonts targeting this destination font.
384 int GlyphsHighest;
385 int GlyphsCount;
386 ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font.
387 };
388
ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library,ImFontAtlas * atlas,unsigned int extra_flags)389 bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, unsigned int extra_flags)
390 {
391 IM_ASSERT(atlas->ConfigData.Size > 0);
392
393 ImFontAtlasBuildInit(atlas);
394
395 // Clear atlas
396 atlas->TexID = (ImTextureID)NULL;
397 atlas->TexWidth = atlas->TexHeight = 0;
398 atlas->TexUvScale = ImVec2(0.0f, 0.0f);
399 atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
400 atlas->ClearTexData();
401
402 // Temporary storage for building
403 bool src_load_color = false;
404 ImVector<ImFontBuildSrcDataFT> src_tmp_array;
405 ImVector<ImFontBuildDstDataFT> dst_tmp_array;
406 src_tmp_array.resize(atlas->ConfigData.Size);
407 dst_tmp_array.resize(atlas->Fonts.Size);
408 memset((void*)src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes());
409 memset((void*)dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes());
410
411 // 1. Initialize font loading structure, check font data validity
412 for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++)
413 {
414 ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
415 ImFontConfig& cfg = atlas->ConfigData[src_i];
416 FreeTypeFont& font_face = src_tmp.Font;
417 IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
418
419 // Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)
420 src_tmp.DstIndex = -1;
421 for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++)
422 if (cfg.DstFont == atlas->Fonts[output_i])
423 src_tmp.DstIndex = output_i;
424 IM_ASSERT(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array?
425 if (src_tmp.DstIndex == -1)
426 return false;
427
428 // Load font
429 if (!font_face.InitFont(ft_library, cfg, extra_flags))
430 return false;
431
432 // Measure highest codepoints
433 src_load_color |= (cfg.FontBuilderFlags & ImGuiFreeTypeBuilderFlags_LoadColor) != 0;
434 ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
435 src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
436 for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
437 src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]);
438 dst_tmp.SrcCount++;
439 dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest);
440 }
441
442 // 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.
443 int total_glyphs_count = 0;
444 for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
445 {
446 ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
447 ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
448 src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1);
449 if (dst_tmp.GlyphsSet.Storage.empty())
450 dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1);
451
452 for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
453 for (int codepoint = src_range[0]; codepoint <= (int)src_range[1]; codepoint++)
454 {
455 if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option (e.g. MergeOverwrite)
456 continue;
457 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..)
458 if (glyph_index == 0)
459 continue;
460
461 // Add to avail set/counters
462 src_tmp.GlyphsCount++;
463 dst_tmp.GlyphsCount++;
464 src_tmp.GlyphsSet.SetBit(codepoint);
465 dst_tmp.GlyphsSet.SetBit(codepoint);
466 total_glyphs_count++;
467 }
468 }
469
470 // 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)
471 for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
472 {
473 ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
474 src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount);
475
476 IM_ASSERT(sizeof(src_tmp.GlyphsSet.Storage.Data[0]) == sizeof(ImU32));
477 const ImU32* it_begin = src_tmp.GlyphsSet.Storage.begin();
478 const ImU32* it_end = src_tmp.GlyphsSet.Storage.end();
479 for (const ImU32* it = it_begin; it < it_end; it++)
480 if (ImU32 entries_32 = *it)
481 for (ImU32 bit_n = 0; bit_n < 32; bit_n++)
482 if (entries_32 & ((ImU32)1 << bit_n))
483 {
484 ImFontBuildSrcGlyphFT src_glyph;
485 src_glyph.Codepoint = (ImWchar)(((it - it_begin) << 5) + bit_n);
486 //src_glyph.GlyphIndex = 0; // FIXME-OPT: We had this info in the previous step and lost it..
487 src_tmp.GlyphsList.push_back(src_glyph);
488 }
489 src_tmp.GlyphsSet.Clear();
490 IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount);
491 }
492 for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++)
493 dst_tmp_array[dst_i].GlyphsSet.Clear();
494 dst_tmp_array.clear();
495
496 // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0)
497 // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity)
498 ImVector<stbrp_rect> buf_rects;
499 buf_rects.resize(total_glyphs_count);
500 memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());
501
502 // Allocate temporary rasterization data buffers.
503 // We could not find a way to retrieve accurate glyph size without rendering them.
504 // (e.g. slot->metrics->width not always matching bitmap->width, especially considering the Oblique transform)
505 // 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.
506 const int BITMAP_BUFFERS_CHUNK_SIZE = 256 * 1024;
507 int buf_bitmap_current_used_bytes = 0;
508 ImVector<unsigned char*> buf_bitmap_buffers;
509 buf_bitmap_buffers.push_back((unsigned char*)IM_ALLOC(BITMAP_BUFFERS_CHUNK_SIZE));
510
511 // 4. Gather glyphs sizes so we can pack them in our virtual canvas.
512 // 8. Render/rasterize font characters into the texture
513 int total_surface = 0;
514 int buf_rects_out_n = 0;
515 for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
516 {
517 ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
518 ImFontConfig& cfg = atlas->ConfigData[src_i];
519 if (src_tmp.GlyphsCount == 0)
520 continue;
521
522 src_tmp.Rects = &buf_rects[buf_rects_out_n];
523 buf_rects_out_n += src_tmp.GlyphsCount;
524
525 // Compute multiply table if requested
526 const bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
527 unsigned char multiply_table[256];
528 if (multiply_enabled)
529 ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
530
531 // Gather the sizes of all rectangles we will need to pack
532 const int padding = atlas->TexGlyphPadding;
533 for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
534 {
535 ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
536
537 const FT_Glyph_Metrics* metrics = src_tmp.Font.LoadGlyph(src_glyph.Codepoint);
538 if (metrics == NULL)
539 continue;
540
541 // Render glyph into a bitmap (currently held by FreeType)
542 const FT_Bitmap* ft_bitmap = src_tmp.Font.RenderGlyphAndGetInfo(&src_glyph.Info);
543 if (ft_bitmap == NULL)
544 continue;
545
546 // Allocate new temporary chunk if needed
547 const int bitmap_size_in_bytes = src_glyph.Info.Width * src_glyph.Info.Height * 4;
548 if (buf_bitmap_current_used_bytes + bitmap_size_in_bytes > BITMAP_BUFFERS_CHUNK_SIZE)
549 {
550 buf_bitmap_current_used_bytes = 0;
551 buf_bitmap_buffers.push_back((unsigned char*)IM_ALLOC(BITMAP_BUFFERS_CHUNK_SIZE));
552 }
553
554 // Blit rasterized pixels to our temporary buffer and keep a pointer to it.
555 src_glyph.BitmapData = (unsigned int*)(buf_bitmap_buffers.back() + buf_bitmap_current_used_bytes);
556 buf_bitmap_current_used_bytes += bitmap_size_in_bytes;
557 src_tmp.Font.BlitGlyph(ft_bitmap, src_glyph.BitmapData, src_glyph.Info.Width, multiply_enabled ? multiply_table : NULL);
558
559 src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + padding);
560 src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + padding);
561 total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;
562 }
563 }
564
565 // We need a width for the skyline algorithm, any width!
566 // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
567 // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface.
568 const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1;
569 atlas->TexHeight = 0;
570 if (atlas->TexDesiredWidth > 0)
571 atlas->TexWidth = atlas->TexDesiredWidth;
572 else
573 atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512;
574
575 // 5. Start packing
576 // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
577 const int TEX_HEIGHT_MAX = 1024 * 32;
578 const int num_nodes_for_packing_algorithm = atlas->TexWidth - atlas->TexGlyphPadding;
579 ImVector<stbrp_node> pack_nodes;
580 pack_nodes.resize(num_nodes_for_packing_algorithm);
581 stbrp_context pack_context;
582 stbrp_init_target(&pack_context, atlas->TexWidth, TEX_HEIGHT_MAX, pack_nodes.Data, pack_nodes.Size);
583 ImFontAtlasBuildPackCustomRects(atlas, &pack_context);
584
585 // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point.
586 for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
587 {
588 ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
589 if (src_tmp.GlyphsCount == 0)
590 continue;
591
592 stbrp_pack_rects(&pack_context, src_tmp.Rects, src_tmp.GlyphsCount);
593
594 // Extend texture height and mark missing glyphs as non-packed so we won't render them.
595 // 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?)
596 for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
597 if (src_tmp.Rects[glyph_i].was_packed)
598 atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h);
599 }
600
601 // 7. Allocate texture
602 atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);
603 atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
604 if (src_load_color)
605 {
606 size_t tex_size = (size_t)atlas->TexWidth * atlas->TexHeight * 4;
607 atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(tex_size);
608 memset(atlas->TexPixelsRGBA32, 0, tex_size);
609 }
610 else
611 {
612 size_t tex_size = (size_t)atlas->TexWidth * atlas->TexHeight * 1;
613 atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(tex_size);
614 memset(atlas->TexPixelsAlpha8, 0, tex_size);
615 }
616
617 // 8. Copy rasterized font characters back into the main texture
618 // 9. Setup ImFont and glyphs for runtime
619 bool tex_use_colors = false;
620 for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
621 {
622 ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
623 if (src_tmp.GlyphsCount == 0)
624 continue;
625
626 // When merging fonts with MergeMode=true:
627 // - We can have multiple input fonts writing into a same destination font.
628 // - dst_font->ConfigData is != from cfg which is our source configuration.
629 ImFontConfig& cfg = atlas->ConfigData[src_i];
630 ImFont* dst_font = cfg.DstFont;
631
632 const float ascent = src_tmp.Font.Info.Ascender;
633 const float descent = src_tmp.Font.Info.Descender;
634 ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
635 const float font_off_x = cfg.GlyphOffset.x;
636 const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent);
637
638 const int padding = atlas->TexGlyphPadding;
639 for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
640 {
641 ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
642 stbrp_rect& pack_rect = src_tmp.Rects[glyph_i];
643 IM_ASSERT(pack_rect.was_packed);
644 if (pack_rect.w == 0 && pack_rect.h == 0)
645 continue;
646
647 GlyphInfo& info = src_glyph.Info;
648 IM_ASSERT(info.Width + padding <= pack_rect.w);
649 IM_ASSERT(info.Height + padding <= pack_rect.h);
650 const int tx = pack_rect.x + padding;
651 const int ty = pack_rect.y + padding;
652
653 // Register glyph
654 float x0 = info.OffsetX + font_off_x;
655 float y0 = info.OffsetY + font_off_y;
656 float x1 = x0 + info.Width;
657 float y1 = y0 + info.Height;
658 float u0 = (tx) / (float)atlas->TexWidth;
659 float v0 = (ty) / (float)atlas->TexHeight;
660 float u1 = (tx + info.Width) / (float)atlas->TexWidth;
661 float v1 = (ty + info.Height) / (float)atlas->TexHeight;
662 dst_font->AddGlyph(&cfg, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX);
663
664 ImFontGlyph* dst_glyph = &dst_font->Glyphs.back();
665 IM_ASSERT(dst_glyph->Codepoint == src_glyph.Codepoint);
666 if (src_glyph.Info.IsColored)
667 dst_glyph->Colored = tex_use_colors = true;
668
669 // Blit from temporary buffer to final texture
670 size_t blit_src_stride = (size_t)src_glyph.Info.Width;
671 size_t blit_dst_stride = (size_t)atlas->TexWidth;
672 unsigned int* blit_src = src_glyph.BitmapData;
673 if (atlas->TexPixelsAlpha8 != NULL)
674 {
675 unsigned char* blit_dst = atlas->TexPixelsAlpha8 + (ty * blit_dst_stride) + tx;
676 for (int y = 0; y < info.Height; y++, blit_dst += blit_dst_stride, blit_src += blit_src_stride)
677 for (int x = 0; x < info.Width; x++)
678 blit_dst[x] = (unsigned char)((blit_src[x] >> IM_COL32_A_SHIFT) & 0xFF);
679 }
680 else
681 {
682 unsigned int* blit_dst = atlas->TexPixelsRGBA32 + (ty * blit_dst_stride) + tx;
683 for (int y = 0; y < info.Height; y++, blit_dst += blit_dst_stride, blit_src += blit_src_stride)
684 for (int x = 0; x < info.Width; x++)
685 blit_dst[x] = blit_src[x];
686 }
687 }
688
689 src_tmp.Rects = NULL;
690 }
691 atlas->TexPixelsUseColors = tex_use_colors;
692
693 // Cleanup
694 for (int buf_i = 0; buf_i < buf_bitmap_buffers.Size; buf_i++)
695 IM_FREE(buf_bitmap_buffers[buf_i]);
696 src_tmp_array.clear_destruct();
697
698 ImFontAtlasBuildFinish(atlas);
699
700 return true;
701 }
702
703 // FreeType memory allocation callbacks
FreeType_Alloc(FT_Memory,long size)704 static void* FreeType_Alloc(FT_Memory /*memory*/, long size)
705 {
706 return GImGuiFreeTypeAllocFunc((size_t)size, GImGuiFreeTypeAllocatorUserData);
707 }
708
FreeType_Free(FT_Memory,void * block)709 static void FreeType_Free(FT_Memory /*memory*/, void* block)
710 {
711 GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
712 }
713
FreeType_Realloc(FT_Memory,long cur_size,long new_size,void * block)714 static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size, void* block)
715 {
716 // Implement realloc() as we don't ask user to provide it.
717 if (block == NULL)
718 return GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
719
720 if (new_size == 0)
721 {
722 GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
723 return NULL;
724 }
725
726 if (new_size > cur_size)
727 {
728 void* new_block = GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
729 memcpy(new_block, block, (size_t)cur_size);
730 GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
731 return new_block;
732 }
733
734 return block;
735 }
736
ImFontAtlasBuildWithFreeType(ImFontAtlas * atlas)737 static bool ImFontAtlasBuildWithFreeType(ImFontAtlas* atlas)
738 {
739 // FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html
740 FT_MemoryRec_ memory_rec = {};
741 memory_rec.user = NULL;
742 memory_rec.alloc = &FreeType_Alloc;
743 memory_rec.free = &FreeType_Free;
744 memory_rec.realloc = &FreeType_Realloc;
745
746 // https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
747 FT_Library ft_library;
748 FT_Error error = FT_New_Library(&memory_rec, &ft_library);
749 if (error != 0)
750 return false;
751
752 // If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
753 FT_Add_Default_Modules(ft_library);
754
755 bool ret = ImFontAtlasBuildWithFreeTypeEx(ft_library, atlas, atlas->FontBuilderFlags);
756 FT_Done_Library(ft_library);
757
758 return ret;
759 }
760
GetBuilderForFreeType()761 const ImFontBuilderIO* ImGuiFreeType::GetBuilderForFreeType()
762 {
763 static ImFontBuilderIO io;
764 io.FontBuilder_Build = ImFontAtlasBuildWithFreeType;
765 return &io;
766 }
767
SetAllocatorFunctions(void * (* alloc_func)(size_t sz,void * user_data),void (* free_func)(void * ptr,void * user_data),void * user_data)768 void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
769 {
770 GImGuiFreeTypeAllocFunc = alloc_func;
771 GImGuiFreeTypeFreeFunc = free_func;
772 GImGuiFreeTypeAllocatorUserData = user_data;
773 }
774