MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GlyphAtlas.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "FontFace.hpp"
4
6
7#include <ft2build.h>
8#include FT_FREETYPE_H
9
10#include <cstdint>
11#include <memory>
12#include <unordered_map>
13
14namespace MayaFlux::Portal::Text {
15
16/**
17 * @struct GlyphMetrics
18 * @brief Per-glyph layout and UV data produced by GlyphAtlas.
19 *
20 * UV coordinates are in normalised [0, 1] atlas space.
21 * Bearing and size are in pixels at the atlas's declared pixel_size.
22 *
23 * When HarfBuzz is introduced it will supply glyph_index and position
24 * offsets directly; this struct remains the downstream currency.
25 */
27 float uv_x0 { 0.F }; ///< Left UV edge in atlas texture.
28 float uv_y0 { 0.F }; ///< Top UV edge in atlas texture.
29 float uv_x1 { 0.F }; ///< Right UV edge in atlas texture.
30 float uv_y1 { 0.F }; ///< Bottom UV edge in atlas texture.
31
32 int32_t bearing_x { 0 }; ///< Horizontal bearing in pixels (from FT_GlyphSlot).
33 int32_t bearing_y { 0 }; ///< Vertical bearing in pixels (from FT_GlyphSlot).
34 uint32_t width { 0 }; ///< Glyph bitmap width in pixels.
35 uint32_t height { 0 }; ///< Glyph bitmap height in pixels.
36 int32_t advance_x { 0 }; ///< Horizontal advance in pixels (26.6 fixed-point >> 6).
37};
38
39/**
40 * @class GlyphAtlas
41 * @brief Rasterizes and packs glyphs from a FontFace into a TextureContainer.
42 *
43 * One atlas corresponds to one (FontFace, pixel_size) pair. The atlas
44 * texture is R8 (single-channel coverage); colour is applied in the shader.
45 *
46 * Glyphs are rasterized on first request via get_or_rasterize() and packed
47 * into the atlas using a simple shelf packing algorithm. The atlas texture
48 * is rebuilt whenever a new glyph causes a shelf overflow.
49 *
50 * The atlas is keyed on FT_UInt glyph index, not Unicode codepoint.
51 * Callers obtain glyph indices via FT_Get_Char_Index on the FontFace.
52 * This keeps the path open for HarfBuzz, which outputs glyph indices directly.
53 *
54 * Thread safety: not thread-safe. Rasterization must occur on the thread
55 * that owns the FontFace.
56 */
57class MAYAFLUX_API GlyphAtlas {
58public:
59 /**
60 * @brief Construct an atlas for a specific face and pixel size.
61 * @param face Loaded FontFace. Must outlive this atlas.
62 * @param pixel_size Glyph height in pixels (width is derived by FreeType).
63 * @param atlas_size Width and height of the atlas texture in pixels.
64 * Must be a power of two. Default 512 is sufficient
65 * for ASCII + extended Latin at sizes up to ~48px.
66 */
67 explicit GlyphAtlas(
68 FontFace& face,
69 uint32_t pixel_size,
70 uint32_t atlas_size = 512);
71
72 ~GlyphAtlas() = default;
73
74 GlyphAtlas(const GlyphAtlas&) = delete;
75 GlyphAtlas& operator=(const GlyphAtlas&) = delete;
78
79 /**
80 * @brief Return metrics for a glyph, rasterizing it into the atlas if needed.
81 * @param glyph_index FT_UInt glyph index (from FT_Get_Char_Index).
82 * @return Pointer to cached GlyphMetrics, or nullptr if rasterization fails.
83 */
84 const GlyphMetrics* get_or_rasterize(FT_UInt glyph_index);
85
86 /**
87 * @brief Access the backing FontFace. Callers may query FT_Face properties
88 * or call FT_Get_Char_Index directly for HarfBuzz integration.
89 */
90 [[nodiscard]] FontFace& get_face() { return m_face; }
91 [[nodiscard]] const FontFace& get_face() const { return m_face; }
92
93 /**
94 * @brief Convenience: look up by Unicode codepoint.
95 *
96 * Calls FT_Get_Char_Index internally. Prefer the glyph_index overload
97 * when integrating with HarfBuzz output.
98 *
99 * @param codepoint Unicode codepoint (e.g. U+0041 for 'A').
100 * @return Pointer to cached GlyphMetrics, or nullptr on failure.
101 */
102 const GlyphMetrics* get_or_rasterize(FT_ULong codepoint);
103
104 /**
105 * @brief The atlas texture as a TextureContainer (R8, atlas_size x atlas_size).
106 *
107 * The container is valid after construction. Its pixel data is updated
108 * in-place as new glyphs are rasterized; callers must re-upload to GPU
109 * after any rasterization call that returns non-null.
110 */
111 [[nodiscard]] const Kakshya::TextureContainer& texture() const { return *m_texture; }
112 [[nodiscard]] Kakshya::TextureContainer& texture() { return *m_texture; }
113
114 /**
115 * @brief Returns true if at least one glyph was rasterized since the last
116 * call to clear_dirty(). Use to decide whether to re-upload to GPU.
117 */
118 [[nodiscard]] bool is_dirty() const { return m_dirty; }
119
120 /**
121 * @brief Clear the dirty flag after re-uploading the atlas to GPU.
122 */
123 void clear_dirty() { m_dirty = false; }
124
125 /**
126 * @brief Pixel size passed at construction.
127 */
128 [[nodiscard]] uint32_t pixel_size() const { return m_pixel_size; }
129
130 /**
131 * @brief Atlas texture dimension (width == height == atlas_size).
132 */
133 [[nodiscard]] uint32_t atlas_size() const { return m_atlas_size; }
134
135 /**
136 * @brief Line advance in pixels for this atlas's pixel_size.
137 *
138 * Derived from FT_Face metrics: (ascender - descender) in 26.6 fixed-point,
139 * shifted right by 6. Returns pixel_size as a safe fallback if the face has
140 * not yet been sized (i.e. before the first get_or_rasterize() call).
141 */
142 [[nodiscard]] uint32_t line_height() const;
143
144 /// @brief Ascender in pixels for this atlas's pixel_size.
145 [[nodiscard]] uint32_t ascender() const;
146
147private:
148 bool rasterize(FT_UInt glyph_index);
149
151 uint32_t m_pixel_size;
152 uint32_t m_atlas_size;
153
154 std::unique_ptr<Kakshya::TextureContainer> m_texture;
155
156 std::unordered_map<FT_UInt, GlyphMetrics> m_cache;
157
158 uint32_t m_cursor_x { 0 };
159 uint32_t m_cursor_y { 0 };
160 uint32_t m_shelf_height { 0 };
161
162 bool m_dirty { false };
163};
164
165} // namespace MayaFlux::Portal::Text
SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
Owns a single FT_Face loaded from a file path.
Definition FontFace.hpp:24
FontFace & get_face()
Access the backing FontFace.
GlyphAtlas(const GlyphAtlas &)=delete
GlyphAtlas(GlyphAtlas &&)=delete
GlyphAtlas & operator=(const GlyphAtlas &)=delete
std::unique_ptr< Kakshya::TextureContainer > m_texture
bool is_dirty() const
Returns true if at least one glyph was rasterized since the last call to clear_dirty().
void clear_dirty()
Clear the dirty flag after re-uploading the atlas to GPU.
GlyphAtlas & operator=(GlyphAtlas &&)=delete
Kakshya::TextureContainer & texture()
uint32_t pixel_size() const
Pixel size passed at construction.
const FontFace & get_face() const
std::unordered_map< FT_UInt, GlyphMetrics > m_cache
uint32_t atlas_size() const
Atlas texture dimension (width == height == atlas_size).
const Kakshya::TextureContainer & texture() const
The atlas texture as a TextureContainer (R8, atlas_size x atlas_size).
Rasterizes and packs glyphs from a FontFace into a TextureContainer.
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.
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.