imgui_freetype.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // Wrapper to use Freetype (instead of stb_truetype) for Dear ImGui
  2. // Get latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
  3. // Original code by @Vuhdo (Aleksei Skriabin). Improvements by @mikesart. Maintained by @ocornut
  4. // Changelog:
  5. // - v0.50: (2017/08/16) imported from https://github.com/Vuhdo/imgui_freetype into http://www.github.com/ocornut/imgui_club, updated for latest changes in ImFontAtlas, minor tweaks.
  6. // - v0.51: (2017/08/26) cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
  7. // - v0.52: (2017/09/26) fixes for imgui internal changes
  8. // - v0.53: (2017/10/22) minor inconsequential change to match change in master (removed an unnecessary statement)
  9. // - v0.54: (2018/01/22) fix for addition of ImFontAtlas::TexUvscale member
  10. // - v0.55: (2018/02/04) moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
  11. // Gamma Correct Blending:
  12. // FreeType assumes blending in linear space rather than gamma space.
  13. // See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
  14. // For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
  15. // The default imgui styles will be impacted by this change (alpha values will need tweaking).
  16. // TODO:
  17. // - Output texture has excessive resolution (lots of vertical waste)
  18. // - FreeType's memory allocator is not overridden.
  19. #include "imgui_freetype.h"
  20. #include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
  21. #include <stdint.h>
  22. #include <ft2build.h>
  23. #include FT_FREETYPE_H
  24. #include FT_GLYPH_H
  25. #include FT_SYNTHESIS_H
  26. #ifdef _MSC_VER
  27. #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
  28. #endif
  29. #if defined(__GNUC__)
  30. #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
  31. #endif
  32. namespace
  33. {
  34. // Glyph metrics:
  35. // --------------
  36. //
  37. // xmin xmax
  38. // | |
  39. // |<-------- width -------->|
  40. // | |
  41. // | +-------------------------+----------------- ymax
  42. // | | ggggggggg ggggg | ^ ^
  43. // | | g:::::::::ggg::::g | | |
  44. // | | g:::::::::::::::::g | | |
  45. // | | g::::::ggggg::::::gg | | |
  46. // | | g:::::g g:::::g | | |
  47. // offsetX -|-------->| g:::::g g:::::g | offsetY |
  48. // | | g:::::g g:::::g | | |
  49. // | | g::::::g g:::::g | | |
  50. // | | g:::::::ggggg:::::g | | |
  51. // | | g::::::::::::::::g | | height
  52. // | | gg::::::::::::::g | | |
  53. // baseline ---*---------|---- gggggggg::::::g-----*-------- |
  54. // / | | g:::::g | |
  55. // origin | | gggggg g:::::g | |
  56. // | | g:::::gg gg:::::g | |
  57. // | | g::::::ggg:::::::g | |
  58. // | | gg:::::::::::::g | |
  59. // | | ggg::::::ggg | |
  60. // | | gggggg | v
  61. // | +-------------------------+----------------- ymin
  62. // | |
  63. // |------------- advanceX ----------->|
  64. /// A structure that describe a glyph.
  65. struct GlyphInfo
  66. {
  67. float Width; // Glyph's width in pixels.
  68. float Height; // Glyph's height in pixels.
  69. float OffsetX; // The distance from the origin ("pen position") to the left of the glyph.
  70. float OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0.
  71. float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0.
  72. };
  73. // Font parameters and metrics.
  74. struct FontInfo
  75. {
  76. uint32_t PixelHeight; // Size this font was generated with.
  77. float Ascender; // The pixel extents above the baseline in pixels (typically positive).
  78. float Descender; // The extents below the baseline in pixels (typically negative).
  79. 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.
  80. float LineGap; // The spacing in pixels between one row's descent and the next row's ascent.
  81. float MaxAdvanceWidth; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
  82. };
  83. // FreeType glyph rasterizer.
  84. // NB: No ctor/dtor, explicitly call Init()/Shutdown()
  85. struct FreeTypeFont
  86. {
  87. bool Init(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.
  88. void Shutdown();
  89. void SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size
  90. bool CalcGlyphInfo(uint32_t codepoint, GlyphInfo& glyph_info, FT_Glyph& ft_glyph, FT_BitmapGlyph& ft_bitmap);
  91. void BlitGlyph(FT_BitmapGlyph ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table = NULL);
  92. // [Internals]
  93. FontInfo Info; // Font descriptor of the current font.
  94. unsigned int UserFlags; // = ImFontConfig::RasterizerFlags
  95. FT_Library FreetypeLibrary;
  96. FT_Face FreetypeFace;
  97. FT_Int32 FreetypeLoadFlags;
  98. };
  99. // From SDL_ttf: Handy routines for converting from fixed point
  100. #define FT_CEIL(X) (((X + 63) & -64) / 64)
  101. bool FreeTypeFont::Init(const ImFontConfig& cfg, unsigned int extra_user_flags)
  102. {
  103. // FIXME: substitute allocator
  104. FT_Error error = FT_Init_FreeType(&FreetypeLibrary);
  105. if (error != 0)
  106. return false;
  107. error = FT_New_Memory_Face(FreetypeLibrary, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &FreetypeFace);
  108. if (error != 0)
  109. return false;
  110. error = FT_Select_Charmap(FreetypeFace, FT_ENCODING_UNICODE);
  111. if (error != 0)
  112. return false;
  113. memset(&Info, 0, sizeof(Info));
  114. SetPixelHeight((uint32_t)cfg.SizePixels);
  115. // Convert to freetype flags (nb: Bold and Oblique are processed separately)
  116. UserFlags = cfg.RasterizerFlags | extra_user_flags;
  117. FreetypeLoadFlags = FT_LOAD_NO_BITMAP;
  118. if (UserFlags & ImGuiFreeType::NoHinting) FreetypeLoadFlags |= FT_LOAD_NO_HINTING;
  119. if (UserFlags & ImGuiFreeType::NoAutoHint) FreetypeLoadFlags |= FT_LOAD_NO_AUTOHINT;
  120. if (UserFlags & ImGuiFreeType::ForceAutoHint) FreetypeLoadFlags |= FT_LOAD_FORCE_AUTOHINT;
  121. if (UserFlags & ImGuiFreeType::LightHinting)
  122. FreetypeLoadFlags |= FT_LOAD_TARGET_LIGHT;
  123. else if (UserFlags & ImGuiFreeType::MonoHinting)
  124. FreetypeLoadFlags |= FT_LOAD_TARGET_MONO;
  125. else
  126. FreetypeLoadFlags |= FT_LOAD_TARGET_NORMAL;
  127. return true;
  128. }
  129. void FreeTypeFont::Shutdown()
  130. {
  131. if (FreetypeFace)
  132. {
  133. FT_Done_Face(FreetypeFace);
  134. FreetypeFace = NULL;
  135. FT_Done_FreeType(FreetypeLibrary);
  136. FreetypeLibrary = NULL;
  137. }
  138. }
  139. void FreeTypeFont::SetPixelHeight(int pixel_height)
  140. {
  141. // I'm not sure how to deal with font sizes properly.
  142. // As far as I understand, currently ImGui assumes that the 'pixel_height' is a maximum height of an any given glyph,
  143. // i.e. it's the sum of font's ascender and descender. Seems strange to me.
  144. FT_Size_RequestRec req;
  145. req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
  146. req.width = 0;
  147. req.height = (uint32_t)pixel_height * 64;
  148. req.horiResolution = 0;
  149. req.vertResolution = 0;
  150. FT_Request_Size(FreetypeFace, &req);
  151. // update font info
  152. FT_Size_Metrics metrics = FreetypeFace->size->metrics;
  153. Info.PixelHeight = (uint32_t)pixel_height;
  154. Info.Ascender = (float)FT_CEIL(metrics.ascender);
  155. Info.Descender = (float)FT_CEIL(metrics.descender);
  156. Info.LineSpacing = (float)FT_CEIL(metrics.height);
  157. Info.LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender);
  158. Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance);
  159. }
  160. bool FreeTypeFont::CalcGlyphInfo(uint32_t codepoint, GlyphInfo &glyph_info, FT_Glyph& ft_glyph, FT_BitmapGlyph& ft_bitmap)
  161. {
  162. uint32_t glyph_index = FT_Get_Char_Index(FreetypeFace, codepoint);
  163. FT_Error error = FT_Load_Glyph(FreetypeFace, glyph_index, FreetypeLoadFlags);
  164. if (error)
  165. return false;
  166. // Need an outline for this to work
  167. FT_GlyphSlot slot = FreetypeFace->glyph;
  168. IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE);
  169. if (UserFlags & ImGuiFreeType::Bold)
  170. FT_GlyphSlot_Embolden(slot);
  171. if (UserFlags & ImGuiFreeType::Oblique)
  172. FT_GlyphSlot_Oblique(slot);
  173. // Retrieve the glyph
  174. error = FT_Get_Glyph(slot, &ft_glyph);
  175. if (error != 0)
  176. return false;
  177. // Rasterize
  178. error = FT_Glyph_To_Bitmap(&ft_glyph, FT_RENDER_MODE_NORMAL, NULL, true);
  179. if (error != 0)
  180. return false;
  181. ft_bitmap = (FT_BitmapGlyph)ft_glyph;
  182. glyph_info.AdvanceX = (float)FT_CEIL(slot->advance.x);
  183. glyph_info.OffsetX = (float)ft_bitmap->left;
  184. glyph_info.OffsetY = -(float)ft_bitmap->top;
  185. glyph_info.Width = (float)ft_bitmap->bitmap.width;
  186. glyph_info.Height = (float)ft_bitmap->bitmap.rows;
  187. return true;
  188. }
  189. void FreeTypeFont::BlitGlyph(FT_BitmapGlyph ft_bitmap, uint8_t* dst, uint32_t dst_pitch, unsigned char* multiply_table)
  190. {
  191. IM_ASSERT(ft_bitmap != NULL);
  192. const uint32_t w = ft_bitmap->bitmap.width;
  193. const uint32_t h = ft_bitmap->bitmap.rows;
  194. const uint8_t* src = ft_bitmap->bitmap.buffer;
  195. const uint32_t src_pitch = ft_bitmap->bitmap.pitch;
  196. if (multiply_table == NULL)
  197. {
  198. for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
  199. memcpy(dst, src, w);
  200. }
  201. else
  202. {
  203. for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
  204. for (uint32_t x = 0; x < w; x++)
  205. dst[x] = multiply_table[src[x]];
  206. }
  207. }
  208. }
  209. #define STBRP_ASSERT(x) IM_ASSERT(x)
  210. #define STBRP_STATIC
  211. #define STB_RECT_PACK_IMPLEMENTATION
  212. #include "stb_rect_pack.h"
  213. bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags)
  214. {
  215. IM_ASSERT(atlas->ConfigData.Size > 0);
  216. IM_ASSERT(atlas->TexGlyphPadding == 1); // Not supported
  217. ImFontAtlasBuildRegisterDefaultCustomRects(atlas);
  218. atlas->TexID = NULL;
  219. atlas->TexWidth = atlas->TexHeight = 0;
  220. atlas->TexUvScale = ImVec2(0.0f, 0.0f);
  221. atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
  222. atlas->ClearTexData();
  223. ImVector<FreeTypeFont> fonts;
  224. fonts.resize(atlas->ConfigData.Size);
  225. ImVec2 max_glyph_size(1.0f, 1.0f);
  226. // Count glyphs/ranges, initialize font
  227. int total_glyphs_count = 0;
  228. int total_ranges_count = 0;
  229. for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
  230. {
  231. ImFontConfig& cfg = atlas->ConfigData[input_i];
  232. FreeTypeFont& font_face = fonts[input_i];
  233. IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
  234. if (!font_face.Init(cfg, extra_flags))
  235. return false;
  236. max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth);
  237. max_glyph_size.y = ImMax(max_glyph_size.y, font_face.Info.Ascender - font_face.Info.Descender);
  238. if (!cfg.GlyphRanges)
  239. cfg.GlyphRanges = atlas->GetGlyphRangesDefault();
  240. for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[ 1 ]; in_range += 2, total_ranges_count++)
  241. total_glyphs_count += (in_range[1] - in_range[0]) + 1;
  242. }
  243. // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish.
  244. // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
  245. atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512;
  246. // We don't do the original first pass to determine texture height, but just rough estimate.
  247. // Looks ugly inaccurate and excessive, but AFAIK with FreeType we actually need to render glyphs to get exact sizes.
  248. // Alternatively, we could just render all glyphs into a big shadow buffer, get their sizes, do the rectangle packing and just copy back from the
  249. // shadow buffer to the texture buffer. Will give us an accurate texture height, but eat a lot of temp memory. Probably no one will notice.)
  250. const int total_rects = total_glyphs_count + atlas->CustomRects.size();
  251. float min_rects_per_row = ceilf((atlas->TexWidth / (max_glyph_size.x + 1.0f)));
  252. float min_rects_per_column = ceilf(total_rects / min_rects_per_row);
  253. atlas->TexHeight = (int)(min_rects_per_column * (max_glyph_size.y + 1.0f));
  254. // Create texture
  255. atlas->TexHeight = ImUpperPowerOfTwo(atlas->TexHeight);
  256. atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
  257. atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight);
  258. memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight);
  259. // Start packing
  260. ImVector<stbrp_node> pack_nodes;
  261. pack_nodes.resize(total_rects);
  262. stbrp_context context;
  263. stbrp_init_target(&context, atlas->TexWidth, atlas->TexHeight, pack_nodes.Data, total_rects);
  264. // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
  265. ImFontAtlasBuildPackCustomRects(atlas, &context);
  266. // Render characters, setup ImFont and glyphs for runtime
  267. for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
  268. {
  269. ImFontConfig& cfg = atlas->ConfigData[input_i];
  270. FreeTypeFont& font_face = fonts[input_i];
  271. ImFont* dst_font = cfg.DstFont;
  272. const float ascent = font_face.Info.Ascender;
  273. const float descent = font_face.Info.Descender;
  274. ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
  275. const float off_x = cfg.GlyphOffset.x;
  276. const float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f);
  277. bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
  278. unsigned char multiply_table[256];
  279. if (multiply_enabled)
  280. ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
  281. for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2)
  282. {
  283. for (uint32_t codepoint = in_range[0]; codepoint <= in_range[1]; ++codepoint)
  284. {
  285. if (cfg.MergeMode && dst_font->FindGlyph((unsigned short)codepoint))
  286. continue;
  287. FT_Glyph ft_glyph = NULL;
  288. FT_BitmapGlyph ft_glyph_bitmap = NULL; // NB: will point to bitmap within FT_Glyph
  289. GlyphInfo glyph_info;
  290. if (!font_face.CalcGlyphInfo(codepoint, glyph_info, ft_glyph, ft_glyph_bitmap))
  291. continue;
  292. // Pack rectangle
  293. stbrp_rect rect;
  294. rect.w = (uint16_t)glyph_info.Width + 1; // Account for texture filtering
  295. rect.h = (uint16_t)glyph_info.Height + 1;
  296. stbrp_pack_rects(&context, &rect, 1);
  297. // Copy rasterized pixels to main texture
  298. uint8_t* blit_dst = atlas->TexPixelsAlpha8 + rect.y * atlas->TexWidth + rect.x;
  299. font_face.BlitGlyph(ft_glyph_bitmap, blit_dst, atlas->TexWidth, multiply_enabled ? multiply_table : NULL);
  300. FT_Done_Glyph(ft_glyph);
  301. // Register glyph
  302. dst_font->AddGlyph((ImWchar)codepoint,
  303. glyph_info.OffsetX + off_x,
  304. glyph_info.OffsetY + off_y,
  305. glyph_info.OffsetX + off_x + glyph_info.Width,
  306. glyph_info.OffsetY + off_y + glyph_info.Height,
  307. rect.x / (float)atlas->TexWidth,
  308. rect.y / (float)atlas->TexHeight,
  309. (rect.x + glyph_info.Width) / (float)atlas->TexWidth,
  310. (rect.y + glyph_info.Height) / (float)atlas->TexHeight,
  311. glyph_info.AdvanceX);
  312. }
  313. }
  314. }
  315. // Cleanup
  316. for (int n = 0; n < fonts.Size; n++)
  317. fonts[n].Shutdown();
  318. ImFontAtlasBuildFinish(atlas);
  319. return true;
  320. }