MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GlyphAtlas.cpp
Go to the documentation of this file.
1#include "GlyphAtlas.hpp"
2
4
6
7GlyphAtlas::GlyphAtlas(FontFace& face, uint32_t pixel_size, uint32_t atlas_size)
8 : m_face(face)
9 , m_pixel_size(pixel_size)
10 , m_atlas_size(atlas_size)
11{
12 m_texture = std::make_unique<Kakshya::TextureContainer>(
13 atlas_size, atlas_size, Portal::Graphics::ImageFormat::R8);
14
15 FT_Set_Pixel_Sizes(m_face.get_face(), 0, m_pixel_size);
16}
17
18const GlyphMetrics* GlyphAtlas::get_or_rasterize(FT_UInt glyph_index)
19{
20 return get_or_rasterize_from(m_face, glyph_index);
21}
22
24{
25 if (!m_face.is_loaded()) {
27 "GlyphAtlas::get_or_rasterize: FontFace not loaded");
28 return nullptr;
29 }
30
31 if (const FT_UInt idx = FT_Get_Char_Index(m_face.get_face(), codepoint); idx != 0) {
32 return get_or_rasterize_from(m_face, idx);
33 }
34
35 for (FontFace* fallback : m_fallbacks) {
36 if (!fallback || !fallback->is_loaded()) {
37 continue;
38 }
39 if (const FT_UInt idx = FT_Get_Char_Index(fallback->get_face(), codepoint); idx != 0) {
40 return get_or_rasterize_from(*fallback, idx);
41 }
42 }
43
45 "GlyphAtlas: no glyph for codepoint U+{:04X} in primary or {} fallback face(s)",
46 static_cast<uint32_t>(codepoint), m_fallbacks.size());
47 return nullptr;
48}
49
51{
52 auto& face_cache = m_cache[&face];
53 if (auto it = face_cache.find(glyph_index); it != face_cache.end()) {
54 return &it->second;
55 }
56
57 if (!rasterize(face, glyph_index)) {
58 return nullptr;
59 }
60
61 return &m_cache.at(&face).at(glyph_index);
62}
63
65{
66 m_fallbacks.push_back(&face);
67}
68
69[[nodiscard]] uint32_t GlyphAtlas::line_height() const
70{
71 FT_Face face = m_face.get_face();
72 if (!face || face->size == nullptr) {
73 return m_pixel_size;
74 }
75 const int32_t h = (face->size->metrics.ascender - face->size->metrics.descender) >> 6;
76 return h > 0 ? static_cast<uint32_t>(h) : m_pixel_size;
77}
78
79[[nodiscard]] uint32_t GlyphAtlas::ascender() const
80{
81 FT_Face face = m_face.get_face();
82 if (!face || face->size == nullptr)
83 return m_pixel_size;
84 const int32_t a = face->size->metrics.ascender >> 6;
85 return a > 0 ? static_cast<uint32_t>(a) : m_pixel_size;
86}
87
89{
90 const uint32_t new_size = m_atlas_size * 2;
91
93 "GlyphAtlas: growing {}x{} -> {}x{} (pixel_size={})",
94 m_atlas_size, m_atlas_size, new_size, new_size, m_pixel_size);
95
96 struct CachedGlyph {
97 FontFace* face;
98 FT_UInt glyph_index;
99 };
100
101 std::vector<CachedGlyph> cached;
102 for (const auto& [face, glyphs] : m_cache) {
103 for (const auto& glyph_entry : glyphs) {
104 cached.push_back({ face, glyph_entry.first });
105 }
106 }
107
108 m_atlas_size = new_size;
109 m_texture = std::make_unique<Kakshya::TextureContainer>(
110 new_size, new_size, Portal::Graphics::ImageFormat::R8);
111
112 m_cursor_x = 0;
113 m_cursor_y = 0;
114 m_shelf_height = 0;
115 m_cache.clear();
116
117 for (const auto& c : cached) {
118 rasterize(*c.face, c.glyph_index);
119 }
120
121 m_dirty = true;
122}
123
124bool GlyphAtlas::rasterize(FontFace& face_ref, FT_UInt glyph_index)
125{
126 FT_Face face = face_ref.get_face();
127
128 if (const FT_Error err = FT_Set_Pixel_Sizes(face, 0, m_pixel_size); err != 0) {
130 "FT_Set_Pixel_Sizes({}) failed: {}", m_pixel_size, static_cast<int>(err));
131 return false;
132 }
133
134 if (const FT_Error err = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER); err != 0) {
136 "FT_Load_Glyph({}) failed: {}", glyph_index, static_cast<int>(err));
137 return false;
138 }
139
140 const FT_Bitmap* bmp = &face->glyph->bitmap;
141 uint32_t gw = bmp->width;
142 uint32_t gh = bmp->rows;
143
144 constexpr uint32_t k_pad = 1;
145
146 if (m_cursor_x + gw + k_pad > m_atlas_size) {
147 m_cursor_x = 0;
148 m_cursor_y += m_shelf_height + k_pad;
149 m_shelf_height = 0;
150 }
151
152 if (m_cursor_y + gh + k_pad > m_atlas_size) {
153 grow();
154
155 // grow() re-rasterized every previously cached glyph, possibly from
156 // other faces, which overwrites face->glyph in between; reload
157 // this glyph before placing it in the now-larger atlas.
158 if (const FT_Error err = FT_Set_Pixel_Sizes(face, 0, m_pixel_size); err != 0) {
160 "FT_Set_Pixel_Sizes({}) failed after growth: {}", m_pixel_size, static_cast<int>(err));
161 return false;
162 }
163 if (const FT_Error err = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER); err != 0) {
165 "FT_Load_Glyph({}) failed after growth: {}", glyph_index, static_cast<int>(err));
166 return false;
167 }
168
169 bmp = &face->glyph->bitmap;
170 gw = bmp->width;
171 gh = bmp->rows;
172
173 if (m_cursor_x + gw + k_pad > m_atlas_size) {
174 m_cursor_x = 0;
175 m_cursor_y += m_shelf_height + k_pad;
176 m_shelf_height = 0;
177 }
178
179 if (m_cursor_y + gh + k_pad > m_atlas_size) {
181 "GlyphAtlas still full after growth to {}x{} at pixel_size={}",
183 return false;
184 }
185 }
186
187 const std::span<uint8_t> pixels = m_texture->pixel_bytes(0);
188 if (pixels.empty()) {
190 "GlyphAtlas: TextureContainer pixel_bytes returned empty span");
191 return false;
192 }
193
194 const uint32_t stride = m_atlas_size;
195
196 for (uint32_t row = 0; row < gh; ++row) {
197 const uint8_t* src = bmp->buffer + static_cast<size_t>(row * static_cast<uint32_t>(std::abs(bmp->pitch)));
198 uint8_t* dst = pixels.data() + static_cast<size_t>((m_cursor_y + row) * stride) + m_cursor_x;
199 std::memcpy(dst, src, gw);
200 }
201
202 const float inv = 1.F / static_cast<float>(m_atlas_size);
203
204 GlyphMetrics m;
205 m.uv_x0 = static_cast<float>(m_cursor_x) * inv;
206 m.uv_y0 = static_cast<float>(m_cursor_y) * inv;
207 m.uv_x1 = static_cast<float>(m_cursor_x + gw) * inv;
208 m.uv_y1 = static_cast<float>(m_cursor_y + gh) * inv;
209 m.bearing_x = face->glyph->bitmap_left;
210 m.bearing_y = face->glyph->bitmap_top;
211 m.width = gw;
212 m.height = gh;
213 m.advance_x = static_cast<int32_t>(face->glyph->advance.x >> 6);
214 m.face = &face_ref;
215 m.glyph_index = glyph_index;
216
217 m_cache[&face_ref].emplace(glyph_index, m);
218
219 m_cursor_x += gw + k_pad;
220 if (gh > m_shelf_height) {
221 m_shelf_height = gh;
222 }
223
224 m_dirty = true;
225 return true;
226}
227
228} // namespace MayaFlux::Portal::Text
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const std::vector< float > * pixels
Definition Decoder.cpp:48
uint32_t h
Definition InkPress.cpp:29
uint32_t * dst
const uint32_t * src
size_t a
FT_Face get_face() const
Raw FT_Face handle for use by GlyphAtlas.
Definition FontFace.hpp:57
bool is_loaded() const
Returns true after a successful load() call.
Definition FontFace.hpp:51
Owns a single FT_Face loaded from a file path.
Definition FontFace.hpp:24
std::unordered_map< FontFace *, std::unordered_map< FT_UInt, GlyphMetrics > > m_cache
Per-face glyph cache.
std::unique_ptr< Kakshya::TextureContainer > m_texture
bool rasterize(FontFace &face, FT_UInt glyph_index)
std::vector< FontFace * > m_fallbacks
void add_fallback(FontFace &face)
Register a fallback face, tried in order after the primary on a miss.
uint32_t line_height() const
Line advance in pixels for this atlas's pixel_size.
const GlyphMetrics * get_or_rasterize(FT_UInt glyph_index)
Return metrics for a glyph, rasterizing it into the atlas if needed.
void grow()
Double the atlas texture and re-rasterize every cached glyph into it.
const GlyphMetrics * get_or_rasterize_from(FontFace &face, FT_UInt glyph_index)
uint32_t ascender() const
Ascender in pixels for this atlas's pixel_size.
uint32_t atlas_size() const
Atlas texture dimension (width == height == atlas_size).
GlyphAtlas(FontFace &face, uint32_t pixel_size, uint32_t atlas_size=512)
Construct an atlas for a specific face and pixel size.
Definition GlyphAtlas.cpp:7
@ API
API calls from external code.
@ Portal
High-level user-facing API layer.
FT_UInt glyph_index
Glyph index within face (from FT_Get_Char_Index).
int32_t bearing_x
Horizontal bearing in pixels (from FT_GlyphSlot).
float uv_x0
Left UV edge in atlas texture.
float uv_x1
Right UV edge in atlas texture.
const FontFace * face
Face that produced this glyph (primary or fallback).
uint32_t height
Glyph bitmap height in pixels.
int32_t advance_x
Horizontal advance in pixels (26.6 fixed-point >> 6).
uint32_t width
Glyph bitmap width in pixels.
float uv_y0
Top UV edge in atlas texture.
int32_t bearing_y
Vertical bearing in pixels (from FT_GlyphSlot).
float uv_y1
Bottom UV edge in atlas texture.
Per-glyph layout and UV data produced by GlyphAtlas.