Skip to content

Commit d4dce8d

Browse files
committed
FreeType: Add font rasterization
1 parent 7945745 commit d4dce8d

File tree

6 files changed

+303
-9
lines changed

6 files changed

+303
-9
lines changed

.vscode/settings.example.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
"editor.insertSpaces": false,
44
"editor.tabSize": 4
55
},
6+
"[c]": {
7+
"editor.insertSpaces": false,
8+
"editor.tabSize": 4
9+
},
10+
"[cpp]": {
11+
"editor.insertSpaces": false,
12+
"editor.tabSize": 4
13+
},
614
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
715
"clangd.arguments": [
816
"-header-insertion=never",

src/vgui/fonts_freetype/src/font.cpp

Lines changed: 235 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,245 @@
1+
//
2+
// This code is based on Dear ImGui FreeType intergration
3+
// https://github.com/ocornut/imgui/blob/master/misc/freetype/imgui_freetype.cpp
4+
// Copyright (c) 2014-2025 Omar Cornut
5+
//
6+
7+
#include <algorithm>
8+
#include <fstream>
9+
#include <tier0/dbg.h>
10+
#include "font_manager.h"
111
#include "font.h"
212

3-
CFont::CFont(CFontManager *pFontManager, const FontSettings &settings)
13+
// Glyph metrics:
14+
// --------------
15+
//
16+
// xmin xmax
17+
// | |
18+
// |<-------- width -------->|
19+
// | |
20+
// | +-------------------------+----------------- ymax
21+
// | | ggggggggg ggggg | ^ ^
22+
// | | g:::::::::ggg::::g | | |
23+
// | | g:::::::::::::::::g | | |
24+
// | | g::::::ggggg::::::gg | | |
25+
// | | g:::::g g:::::g | | |
26+
// offsetX -|-------->| g:::::g g:::::g | offsetY |
27+
// | | g:::::g g:::::g | | |
28+
// | | g::::::g g:::::g | | |
29+
// | | g:::::::ggggg:::::g | | |
30+
// | | g::::::::::::::::g | | height
31+
// | | gg::::::::::::::g | | |
32+
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
33+
// / | | g:::::g | |
34+
// origin | | gggggg g:::::g | |
35+
// | | g:::::gg gg:::::g | |
36+
// | | g::::::ggg:::::::g | |
37+
// | | gg:::::::::::::g | |
38+
// | | ggg::::::ggg | |
39+
// | | gggggg | v
40+
// | +-------------------------+----------------- ymin
41+
// | |
42+
// |------------- advanceX ----------->|
43+
44+
constexpr int FT_SCALEFACTOR = 64;
45+
46+
CFont::CFont(CFontManager *pFontManager, ILogger *pLogger, const FontSettings &settings)
47+
: m_Logger(pLogger, settings.fontPath)
448
{
549
m_pFontManager = pFontManager;
650
m_Settings = settings;
751
}
852

953
void CFont::LoadFont()
1054
{
55+
ReadFontFile();
56+
57+
FT_Error error = FT_New_Memory_Face(
58+
m_pFontManager->GetFreeType(),
59+
(uint8_t *)m_FontFileData.data(),
60+
(uint32_t)m_FontFileData.size(),
61+
(uint32_t)0,
62+
&m_Face);
63+
64+
CheckFreeTypeError(error);
65+
66+
error = FT_Select_Charmap(m_Face, FT_ENCODING_UNICODE);
67+
CheckFreeTypeError(error);
68+
69+
m_LoadFlags = 0;
70+
71+
if (m_Settings.antialias)
72+
m_LoadFlags |= FT_LOAD_TARGET_NORMAL;
73+
else
74+
m_LoadFlags |= FT_LOAD_TARGET_MONO;
75+
76+
FT_New_Size(m_Face, &m_Size);
77+
FT_Activate_Size(m_Size);
78+
79+
FT_Size_RequestRec req;
80+
req.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
81+
req.width = 0;
82+
req.height = (uint32_t)(m_Settings.tall * FT_SCALEFACTOR);
83+
req.horiResolution = 0;
84+
req.vertResolution = 0;
85+
FT_Request_Size(m_Face, &req);
86+
}
87+
88+
void CFont::BlitGlyph(const FT_Bitmap *ft_bitmap, std::vector<uint8_t> dstBuffer)
89+
{
90+
const uint32_t w = ft_bitmap->width;
91+
const uint32_t h = ft_bitmap->rows;
92+
const uint8_t *src = ft_bitmap->buffer;
93+
const uint32_t src_pitch = ft_bitmap->pitch;
94+
95+
dstBuffer.resize(4 * w * h);
96+
97+
const uint32_t dst_pitch = 4 * w;
98+
uint8_t* dst = dstBuffer.data();
99+
100+
switch (ft_bitmap->pixel_mode)
101+
{
102+
case FT_PIXEL_MODE_GRAY:
103+
{
104+
// Grayscale image, 1 byte per pixel.
105+
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
106+
{
107+
for (uint32_t x = 0; x < w; x++)
108+
{
109+
dst[4 * x + 0] = 255;
110+
dst[4 * x + 1] = 255;
111+
dst[4 * x + 2] = 255;
112+
dst[4 * x + 3] = src[x];
113+
}
114+
}
115+
116+
break;
117+
}
118+
case FT_PIXEL_MODE_MONO:
119+
{
120+
// Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
121+
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
122+
{
123+
uint8_t bits = 0;
124+
const uint8_t *bits_ptr = src;
125+
for (uint32_t x = 0; x < w; x++, bits <<= 1)
126+
{
127+
if ((x & 7) == 0)
128+
bits = *bits_ptr++;
129+
130+
dst[4 * x + 0] = 255;
131+
dst[4 * x + 1] = 255;
132+
dst[4 * x + 2] = 255;
133+
dst[4 * x + 3] = (bits & 0x80) ? 255 : 0;
134+
}
135+
}
136+
137+
break;
138+
}
139+
case FT_PIXEL_MODE_BGRA:
140+
{
141+
// FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
142+
#define DE_MULTIPLY(color, alpha) std::min((uint32_t)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u)
143+
144+
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
145+
{
146+
for (uint32_t x = 0; x < w; x++)
147+
{
148+
uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
149+
dst[4 * x + 0] = DE_MULTIPLY(r, a);
150+
dst[4 * x + 1] = DE_MULTIPLY(g, a);
151+
dst[4 * x + 2] = DE_MULTIPLY(b, a);
152+
dst[4 * x + 3] = a;
153+
}
154+
}
155+
156+
#undef DE_MULTIPLY
157+
break;
158+
}
159+
default:
160+
Assert(!("CFont::BlitGlyph(): Unknown bitmap pixel mode!"));
161+
}
162+
}
163+
164+
void CFont::ReadFontFile()
165+
{
166+
if (m_Settings.custom)
167+
{
168+
// TODO: Read using IFileSystem
169+
}
170+
else
171+
{
172+
// Read from the FS
173+
std::ifstream fs;
174+
fs.exceptions(std::ios::failbit | std::ios::badbit);
175+
fs.open(m_Settings.fontPath, std::ios::binary);
176+
177+
fs.seekg(0, std::ios::end);
178+
uint64_t fileSize = fs.tellg();
179+
180+
if (fileSize > std::numeric_limits<ssize_t>::max())
181+
throw std::runtime_error("Font file too large");
182+
183+
fs.seekg(0, std::ios::beg);
184+
m_FontFileData.resize((size_t)fileSize);
185+
fs.read((char*)m_FontFileData.data(), fileSize);
186+
}
187+
}
188+
189+
const GlyphBitmap &CFont::RasterizeGlyph(uint32_t codepoint)
190+
{
191+
auto it = m_GlyphCache.find(codepoint);
192+
193+
if (it != m_GlyphCache.end())
194+
return it->second;
195+
196+
try
197+
{
198+
GlyphBitmap glyph = RasterizeGlyphInternal(codepoint);
199+
return m_GlyphCache.emplace(codepoint, std::move(glyph)).first->second;
200+
}
201+
catch (const std::exception &e)
202+
{
203+
m_Logger.LogError("Failed to rasterize codepoint {}: {}", codepoint, e.what());
204+
205+
// Cache empty glyph
206+
return m_GlyphCache.emplace(codepoint, GlyphBitmap()).first->second;
207+
}
208+
}
209+
210+
GlyphBitmap CFont::RasterizeGlyphInternal(uint32_t codepoint)
211+
{
212+
uint32_t glyphIndex = FT_Get_Char_Index(m_Face, codepoint);
213+
214+
if (glyphIndex == 0)
215+
throw std::runtime_error("Codepoint not found in font");
216+
217+
218+
FT_Error error = FT_Load_Glyph(m_Face, glyphIndex, m_LoadFlags);
219+
CheckFreeTypeError(error);
220+
221+
// Need an outline for this to work
222+
FT_GlyphSlot slot = m_Face->glyph;
223+
224+
if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
225+
throw std::runtime_error(fmt::format("Unknown glyph format {}", (int)slot->format));
226+
227+
// Activate current size
228+
FT_Activate_Size(m_Size);
229+
230+
// Render glyph into a bitmap (currently held by FreeType)
231+
FT_Render_Mode renderMode = m_Settings.antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO;
232+
error = FT_Render_Glyph(slot, renderMode);
233+
CheckFreeTypeError(error);
234+
235+
const FT_Bitmap* ftBitmap = &slot->bitmap;
236+
237+
GlyphBitmap bitmap;
238+
bitmap.wide = ftBitmap->width;
239+
bitmap.tall = ftBitmap->rows;
240+
bitmap.advance = std::ceil((float)slot->advance.x / FT_SCALEFACTOR);
241+
bitmap.metrics = slot->metrics;
242+
243+
BlitGlyph(ftBitmap, bitmap.data);
244+
return bitmap;
11245
}

src/vgui/fonts_freetype/src/font.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#pragma once
22
#include <string>
3+
#include <vector>
4+
#include <unordered_map>
5+
#include <bhl/logging/ILogger.h>
6+
#include <bhl/logging/prefix_logger.h>
37

48
class CFontManager;
59

@@ -36,18 +40,42 @@ struct FontSettings
3640
bool operator!=(const FontSettings &other) const { return !(*this == other); }
3741
};
3842

43+
struct GlyphBitmap
44+
{
45+
int wide = 0;
46+
int tall = 0;
47+
int advance = 0;
48+
std::vector<uint8_t> data;
49+
FT_Glyph_Metrics metrics;
50+
};
51+
3952
class CFont
4053
{
4154
public:
42-
CFont(CFontManager* pFontManager, const FontSettings& settings);
55+
CFont(CFontManager* pFontManager, ILogger* pLogger, const FontSettings& settings);
4356

4457
//! @returns The font settings that were used to create the font.
4558
const FontSettings &GetSettings() const { return m_Settings; }
4659

4760
//! Loads the font.
4861
void LoadFont();
4962

63+
//! Rasterizes a glyph. Results are cached.
64+
const GlyphBitmap &RasterizeGlyph(uint32_t codepoint);
65+
5066
private:
67+
CPrefixLogger m_Logger;
5168
CFontManager *m_pFontManager = nullptr;
5269
FontSettings m_Settings;
70+
std::vector<uint8_t> m_FontFileData;
71+
FT_Face m_Face = {};
72+
FT_Int32 m_LoadFlags = 0;
73+
FT_Size m_Size = {};
74+
75+
std::unordered_map<uint32_t, GlyphBitmap> m_GlyphCache;
76+
77+
static void BlitGlyph(const FT_Bitmap *ft_bitmap, std::vector<uint8_t> dstBuffer);
78+
79+
void ReadFontFile();
80+
GlyphBitmap RasterizeGlyphInternal(uint32_t codepoint);
5381
};

src/vgui/fonts_freetype/src/font_manager.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ CFont *CFontManager::FindOrCreateFont(const FontSettings &settings)
112112
// Create a new font
113113
try
114114
{
115-
std::unique_ptr<CFont> pFont = std::make_unique<CFont>(this, settings);
115+
std::unique_ptr<CFont> pFont = std::make_unique<CFont>(this, m_pLogger, settings);
116116
pFont->LoadFont();
117117
return m_Fonts.emplace_back(std::move(pFont)).get();
118118
}
@@ -129,15 +129,13 @@ std::string CFontManager::FindSystemFontPath(const char *pszFontName)
129129

130130
// https://stackoverflow.com/a/14634033
131131
if (!m_hFontConfig)
132-
{
133132
m_hFontConfig = FcInitLoadConfigAndFonts();
134-
}
135133

136134
// configure the search pattern,
137135
// assume "name" is a std::string with the desired font name in it
138136
FcPattern* pat = FcNameParse((const FcChar8*)pszFontName);
139137
FcConfigSubstitute(m_hFontConfig, pat, FcMatchPattern);
140-
FcDefaultSubstitute(pat);
138+
FcDefaultSubstitute(pat);
141139

142140
// Find the font
143141
FcResult res;
@@ -158,3 +156,9 @@ std::string CFontManager::FindSystemFontPath(const char *pszFontName)
158156
FcPatternDestroy(pat);
159157
return fontFile;
160158
}
159+
160+
void CFontManager::InitFreeType()
161+
{
162+
FT_Error error = FT_Init_FreeType(&m_hFTLib);
163+
CheckFreeTypeError(error);
164+
}

src/vgui/fonts_freetype/src/font_manager.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ class CFont;
1313
class CFontManager
1414
{
1515
public:
16-
CFontManager(ILogger *pLogger, vgui2::ISurface* pVGuiSurface, vgui2::ISchemeManager* pVGuiSchemeManager);
16+
CFontManager(ILogger *pLogger, vgui2::ISurface *pVGuiSurface, vgui2::ISchemeManager *pVGuiSchemeManager);
17+
18+
//! @returns The FreeType library instance.
19+
FT_Library GetFreeType() const { return m_hFTLib; }
1720

1821
//! Parses font settings from the KeyValues. Automcatically selects first compatible font.
1922
FontSettings ParseFontSettings(KeyValues *kv, bool isProportional);
@@ -26,8 +29,11 @@ class CFontManager
2629
vgui2::ISurface* m_pVGuiSurface = nullptr;
2730
vgui2::ISchemeManager *m_pVGuiSchemeManager = nullptr;
2831

29-
FcConfig* m_hFontConfig = nullptr;
30-
std::vector<std::unique_ptr<CFont>> m_Fonts;
32+
FcConfig *m_hFontConfig = nullptr;
33+
FT_Library m_hFTLib = nullptr;
3134

35+
std::vector<std::unique_ptr<CFont>> m_Fonts;
36+
37+
void InitFreeType();
3238
std::string FindSystemFontPath(const char *pszFontName);
3339
};

src/vgui/fonts_freetype/src/pch.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
11
#pragma once
2+
#include <stdexcept>
3+
#include <fmt/format.h>
4+
#include <ft2build.h>
5+
#include FT_FREETYPE_H // <freetype/freetype.h>
6+
#include FT_MODULE_H // <freetype/ftmodapi.h>
7+
#include FT_GLYPH_H // <freetype/ftglyph.h>
8+
#include FT_SIZES_H // <freetype/ftsizes.h>
9+
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
10+
11+
inline void CheckFreeTypeError(FT_Error error)
12+
{
13+
if (error != 0)
14+
throw std::runtime_error(fmt::format("FT error: {}", FT_Error_String(error)));
15+
}

0 commit comments

Comments
 (0)