utility 2026.1.9
A comprehensive C++ utilities library tailored for the development of modern desktop and extended reality (XR) applications.
Loading...
Searching...
No Matches
font_sized.cpp
1/*
2** ETIB PROJECT, 2026
3** utility
4** File description:
5** font_sized
6*/
7
8#include <utility/graphic/text/font_sized.hpp>
9
10#include <algorithm>
11#include <cmath>
12#include <cstddef>
13#include <cstdint>
14#include <memory>
15#include <stdexcept>
16#include <vector>
17
18#include <ft2build.h>
19#include FT_FREETYPE_H
20
21namespace utility::graphic
22{
23 namespace
24 {
25 constexpr int GLYPH_PADDING = 2;
26 }
27
28 FontSized::FontSized(uint32_t fontSize, void *face)
29 : _fontSize(fontSize)
30 , _correspondingFace(face)
31 , _atlasWidth(1024)
32 , _atlasHeight(1024)
33 , _penX(0)
34 , _penY(0)
35 , _rowHeight(0)
36 {
37 FT_Face ftFace = static_cast<FT_Face>(_correspondingFace);
38 if (!ftFace) {
39 throw std::runtime_error("Invalid FreeType face");
40 }
41
42 if (FT_Set_Pixel_Sizes(ftFace, 0, _fontSize) != 0) {
43 throw std::runtime_error("FT_Set_Pixel_Sizes failed");
44 }
45
46 _ascender = ftFace->size->metrics.ascender / 64.0f;
47 _descender = std::abs(ftFace->size->metrics.descender / 64.0f);
48 _lineHeight = ftFace->size->metrics.height / 64.0f;
49
50 _generatedAtlas = std::make_shared<Texture>(
52
53 _generatedAtlas->pixels().resize(_atlasWidth * _atlasHeight, 0);
54 std::fill(_generatedAtlas->pixels().begin(),
55 _generatedAtlas->pixels().end(), 0);
56 }
57
58 Glyph FontSized::generateGlyph(uint32_t codePoint)
59 {
60 auto it = _generatedGlyphs.find(codePoint);
61 if (it != _generatedGlyphs.end()) {
62 return it->second;
63 }
64
65 FT_Face ftFace = static_cast<FT_Face>(_correspondingFace);
66
67 if (FT_Set_Pixel_Sizes(ftFace, 0, _fontSize) != 0) {
68 throw std::runtime_error("FT_Set_Pixel_Sizes failed");
69 }
70
71 if (FT_Load_Char(ftFace, codePoint, FT_LOAD_RENDER) != 0) {
72 throw std::runtime_error("Glyph not found");
73 }
74
75 FT_GlyphSlot g = ftFace->glyph;
76 if (!g) {
77 throw std::runtime_error("Invalid glyph slot");
78 }
79
80 if (g->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY
81 || g->bitmap.num_grays != 256) {
82 throw std::runtime_error("Unsupported glyph bitmap format");
83 }
84
85 const int glyphWidth = static_cast<int>(g->bitmap.width);
86 const int glyphHeight = static_cast<int>(g->bitmap.rows);
87
88 if (_penX + glyphWidth + GLYPH_PADDING
89 >= static_cast<int>(_atlasWidth)) {
90 _penX = 0;
91 _penY += _rowHeight + GLYPH_PADDING;
92 _rowHeight = 0;
93 }
94
95 if (_penY + glyphHeight + GLYPH_PADDING
96 >= static_cast<int>(_atlasHeight)) {
98 }
99
100 const int pitch = g->bitmap.pitch;
101 const int absPitch = std::abs(pitch);
102
103 for (int y = 0; y < glyphHeight; ++y) {
104 const uint8_t *srcRow = nullptr;
105
106 if (pitch >= 0) {
107 srcRow = reinterpret_cast<const uint8_t *>(g->bitmap.buffer)
108 + y * absPitch;
109 } else {
110 srcRow = reinterpret_cast<const uint8_t *>(g->bitmap.buffer)
111 + (glyphHeight - 1 - y) * absPitch;
112 }
113
114 for (int x = 0; x < glyphWidth; ++x) {
115 const int dstX = _penX + x;
116 const int dstY = _penY + y;
117 const int dstIndex =
118 dstY * static_cast<int>(_atlasWidth) + dstX;
119 _generatedAtlas->pixels()[dstIndex] = srcRow[x];
120 }
121 }
122
123 Glyph glyph;
124 glyph.size = { static_cast<float>(glyphWidth),
125 static_cast<float>(glyphHeight) };
126 glyph.bearing = { static_cast<float>(g->bitmap_left),
127 static_cast<float>(g->bitmap_top) };
128 glyph.advance = static_cast<float>(g->advance.x) / 64.0f;
129
130 glyph.uvMin = {
131 static_cast<float>(_penX) / static_cast<float>(_atlasWidth),
132 static_cast<float>(_penY) / static_cast<float>(_atlasHeight)
133 };
134
135 glyph.uvMax = { static_cast<float>(_penX + glyphWidth)
136 / static_cast<float>(_atlasWidth),
137 static_cast<float>(_penY + glyphHeight)
138 / static_cast<float>(_atlasHeight) };
139
140 _generatedGlyphs[codePoint] = glyph;
141
142 _penX += glyphWidth + GLYPH_PADDING;
143 _rowHeight = std::max(_rowHeight, glyphHeight);
144
145 return glyph;
146 }
147
148 std::vector<Glyph>
150 {
151 std::vector<Glyph> glyphs;
152 glyphs.reserve(codePoints.size());
153
154 for (auto codePoint: codePoints) {
155 glyphs.push_back(generateGlyph(codePoint));
156 }
157
158 return glyphs;
159 }
160
161 Glyph FontSized::measureGlyph(uint32_t codePoint) const
162 {
163 Glyph glyph;
164 FT_Face ftFace = static_cast<FT_Face>(_correspondingFace);
165 if (!ftFace) {
166 return glyph;
167 }
168
169 if (FT_Set_Pixel_Sizes(ftFace, 0, _fontSize) != 0) {
170 return glyph;
171 }
172
173 // Load metrics without rendering into a bitmap.
174 if (FT_Load_Char(ftFace, codePoint, FT_LOAD_NO_BITMAP) != 0) {
175 return glyph;
176 }
177
178 FT_GlyphSlot g = ftFace->glyph;
179 if (!g) {
180 return glyph;
181 }
182
183 glyph.size = { static_cast<float>(g->metrics.width) / 64.0f,
184 static_cast<float>(g->metrics.height) / 64.0f };
185 glyph.bearing = { static_cast<float>(g->metrics.horiBearingX) / 64.0f,
186 static_cast<float>(g->metrics.horiBearingY) / 64.0f };
187 glyph.advance = static_cast<float>(g->metrics.horiAdvance) / 64.0f;
188 return glyph;
189 }
190
191 std::shared_ptr<Texture> FontSized::getAtlas(bool shouldRegenerate)
192 {
193 if (!_generatedAtlas) {
194 _generatedAtlas = std::make_shared<Texture>(
196 _generatedAtlas->pixels().resize(_atlasWidth * _atlasHeight, 0);
197 shouldRegenerate = true;
198 }
199
200 if (shouldRegenerate) {
202 }
203
204 return _generatedAtlas;
205 }
206
207 bool FontSized::hasGlyph(uint32_t codePoint) const
208 {
209 return _generatedGlyphs.find(codePoint) != _generatedGlyphs.end();
210 }
211
213 {
214 if (!_generatedAtlas) {
215 throw std::runtime_error("Atlas texture is null");
216 }
217
218 std::fill(_generatedAtlas->pixels().begin(),
219 _generatedAtlas->pixels().end(), 0);
220
221 _generatedGlyphs.clear();
222 _penX = 0;
223 _penY = 0;
224 _rowHeight = 0;
225 }
226
227 void FontSized::resizeAtlas(int newHeight)
228 {
229 if (!_generatedAtlas) {
230 _atlasHeight = newHeight;
231 return;
232 }
233
234 if (newHeight <= _atlasHeight) {
235 return;
236 }
237
238 std::vector<uint8_t> resized(
239 static_cast<size_t>(_atlasWidth) * newHeight, 0);
240
241 const int oldWidth = static_cast<int>(_atlasWidth);
242 const int oldHeight = _atlasHeight;
243
244 for (int y = 0; y < oldHeight; ++y) {
245 const size_t srcRowStart = static_cast<size_t>(y) * oldWidth;
246 const size_t dstRowStart = static_cast<size_t>(y) * oldWidth;
247 auto &atlasPixels = _generatedAtlas->pixels();
248 std::copy(
249 atlasPixels.begin() + static_cast<std::ptrdiff_t>(srcRowStart),
250 atlasPixels.begin()
251 + static_cast<std::ptrdiff_t>(srcRowStart + oldWidth),
252 resized.begin() + static_cast<std::ptrdiff_t>(dstRowStart));
253 }
254
255 _generatedAtlas->pixels().swap(resized);
256 _atlasHeight = newHeight;
257 }
258} // namespace utility::graphic
std::map< uint32_t, Glyph > _generatedGlyphs
A map storing the generated glyphs for this font size.
uint32_t _fontSize
The font size in points for this FontSized object.
FontSized(uint32_t fontSize, void *face)
Constructs a FontSized object for a specific font size and corresponding FreeType face.
void resizeAtlas(int newHeight)
Grow the atlas to the given new height, preserving existing glyphs.
std::shared_ptr< Texture > getAtlas(bool shouldRegenerate=false)
Retrieves the texture atlas containing the rendered glyphs for this font size.
std::shared_ptr< Texture > _generatedAtlas
The font size in points for this FontSized object.
int _atlasWidth
The width of the texture atlas.
void generateAtlas()
Generates the texture atlas based on the currently generated glyphs for this font size.
int _penY
The y-coordinate of the pen for glyph placement.
void * _correspondingFace
The corresponding FreeType face associated with this FontSized object.
int _rowHeight
The height of each row in the texture atlas.
int _atlasHeight
The height of the texture atlas.
float _descender
The descender value for the font size, representing the depth of the lowest glyph below the baseline.
Glyph generateGlyph(uint32_t codePoint)
Generates a glyph for a specific Unicode code point and adds it to the texture atlas.
std::vector< Glyph > generateGlyphs(const codePointString &codePoints)
Generates glyphs for a string of Unicode code points and adds them to the texture atlas.
float _ascender
The ascender value for the font size, representing the height of the tallest glyph above the baseline...
Glyph measureGlyph(uint32_t codePoint) const
Compute glyph metrics for a code point without rasterizing or mutating the texture atlas.
bool hasGlyph(uint32_t codePoint) const
Checks whether a glyph has already been generated for this font size.
float _lineHeight
The line height value for the font size, representing the vertical distance between lines of text.
int _penX
The x-coordinate of the pen for glyph placement.
std::vector< uint32_t > codePointString
A type representing a string of Unicode code points.
The Glyph struct represents a single character's visual representation in a font atlas.
Definition glyph.hpp:29
math::Vector2F uvMax
Maximum UV coordinates for the glyph in the font atlas.
Definition glyph.hpp:55
math::Vector2F size
Size of the glyph in pixels (width and height).
Definition glyph.hpp:33
math::Vector2F uvMin
Minimum UV coordinates for the glyph in the font atlas.
Definition glyph.hpp:50
math::Vector2F bearing
Bearing of the glyph, representing the offset from the baseline to the top-left corner of the glyph.
Definition glyph.hpp:39
float advance
Advance value, specifying the horizontal distance to advance the cursor after rendering the glyph.
Definition glyph.hpp:45