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 Convenience: look up by Unicode codepoint.
88 *
89 * Calls FT_Get_Char_Index internally. Prefer the glyph_index overload
90 * when integrating with HarfBuzz output.
91 *
92 * @param codepoint Unicode codepoint (e.g. U+0041 for 'A').
93 * @return Pointer to cached GlyphMetrics, or nullptr on failure.
94 */
95 const GlyphMetrics* get_or_rasterize(FT_ULong codepoint);
96
97 /**
98 * @brief The atlas texture as a TextureContainer (R8, atlas_size x atlas_size).
99 *
100 * The container is valid after construction. Its pixel data is updated
101 * in-place as new glyphs are rasterized; callers must re-upload to GPU
102 * after any rasterization call that returns non-null.
103 */
104 [[nodiscard]] const Kakshya::TextureContainer& texture() const { return *m_texture; }
105 [[nodiscard]] Kakshya::TextureContainer& texture() { return *m_texture; }
106
107 /**
108 * @brief Returns true if at least one glyph was rasterized since the last
109 * call to clear_dirty(). Use to decide whether to re-upload to GPU.
110 */
111 [[nodiscard]] bool is_dirty() const { return m_dirty; }
112
113 /**
114 * @brief Clear the dirty flag after re-uploading the atlas to GPU.
115 */
116 void clear_dirty() { m_dirty = false; }
117
118 /**
119 * @brief Pixel size passed at construction.
120 */
121 [[nodiscard]] uint32_t pixel_size() const { return m_pixel_size; }
122
123 /**
124 * @brief Atlas texture dimension (width == height == atlas_size).
125 */
126 [[nodiscard]] uint32_t atlas_size() const { return m_atlas_size; }
127
128 /**
129 * @brief Line advance in pixels for this atlas's pixel_size.
130 *
131 * Derived from FT_Face metrics: (ascender - descender) in 26.6 fixed-point,
132 * shifted right by 6. Returns pixel_size as a safe fallback if the face has
133 * not yet been sized (i.e. before the first get_or_rasterize() call).
134 */
135 [[nodiscard]] uint32_t line_height() const;
136
137private:
138 bool rasterize(FT_UInt glyph_index);
139
141 uint32_t m_pixel_size;
142 uint32_t m_atlas_size;
143
144 std::unique_ptr<Kakshya::TextureContainer> m_texture;
145
146 std::unordered_map<FT_UInt, GlyphMetrics> m_cache;
147
148 uint32_t m_cursor_x { 0 };
149 uint32_t m_cursor_y { 0 };
150 uint32_t m_shelf_height { 0 };
151
152 bool m_dirty { false };
153};
154
155} // 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:26
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.
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.