Repository navigation
Expand file tree
/
Copy pathTextComponent.h
More file actions
261 lines (207 loc) · 9.59 KB
/
Copy pathTextComponent.h
File metadata and controls
261 lines (207 loc) · 9.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#pragma once
#include <deki/providers/Buffer.h>
#include <cstdint>
#include <string>
#include <vector>
#include "deki-rendering/RendererComponent.h"
#include "BitmapFont.h"
#include <deki/Color.h>
#include <deki/assets/AssetRef.h>
namespace Deki2D
{
enum class TextAlign : uint8_t
{
Left = 0,
Center = 1,
Right = 2
};
/// Vertical text alignment. Top, Middle and Bottom use the font's full
/// ascent and descent. The other anchors use font-wide typographic metrics,
/// so text centres the same whether or not it has ascenders or descenders.
enum class TextVerticalAlign : uint8_t
{
Top = 0,
Middle = 1, // centres on the whole font's visual bounds (ascent+descent)
Bottom = 2,
CapCenter = 3, // centres on cap height; best for uppercase and mixed UI labels
XCenter = 4, // centres on x-height; best for mostly lowercase body text
TypoCenter = 5, // Centers on typographic midline; same as Middle for single-line
Baseline = 6 // Baseline sits on the container center line
};
/// Draws text with a BitmapFont, with colour, alignment and word wrap.
DEKI_CATEGORY("2D")
DEKI_DESCRIPTION("Draws text with a bitmap font, alignment and word wrap.")
class TextComponent : public DekiRendering::RendererComponent
{
public:
TextComponent();
virtual ~TextComponent();
// ========================================================================
// Editor-visible properties
// ========================================================================
DEKI_EXPORT
DEKI_TOOLTIP(
"The string to draw. Newlines start a new line, and with a width set, lines wrap between words to fit it.")
std::string text;
// The AssetRef stores the GUID and loads the font.
DEKI_EXPORT
DEKI_TOOLTIP("A TrueType or BDF font asset. Font Size picks the size a TrueType font is drawn at.")
Deki::AssetRef<BitmapFont> font;
#ifdef DEKI_EDITOR
/// Font size in pixels; picks the baked variant of that size. The editor
/// bakes a size the first time a text uses it.
DEKI_EXPORT
DEKI_TOOLTIP("Font size in pixels. The editor bakes the font at this size the first time a text uses it, and a "
"build ships the sizes its texts use.")
DEKI_RANGE(6, 128)
DEKI_EDITOR_ONLY
int32_t fontSize = 16;
/// True when no baked variant of fontSize exists.
bool fontSizeUnavailable = false;
/// What the editor's font-resolve hook last synced font.guid from. Not
/// serialized. Working out the baked variant's GUID from (font.source,
/// fontSize) takes a string build, a GUID hash and an asset-pipeline path
/// lookup; these let the hook do that once per change instead of every
/// frame for every text.
std::string editorSyncedFontSource;
int32_t editorSyncedFontSize = -1;
std::string editorSyncedFontGuid;
#endif
void SetText(const char* text);
void SetText(const std::string& text);
const std::string& GetText() const { return text; }
/// Lets the editor resolve the font during RenderContent (GUID sync,
/// preview, baking). The callback returns the font to use, or nullptr to
/// fall back to font.Get().
using FontResolveCallback = BitmapFont* (*)(TextComponent*);
static void SetFontResolveCallback(FontResolveCallback cb);
/// The component does not own the font.
void SetFont(BitmapFont* f);
/// The font, or nullptr.
BitmapFont* GetFont() { return font.Get(); }
const BitmapFont* GetFont() const { return font.Get(); }
void SetColor(const Deki::Color& color);
void SetColor(uint8_t r, uint8_t g, uint8_t b);
const Deki::Color& GetColor() const { return color; }
void SetAlign(TextAlign alignVal) { align = alignVal; }
TextAlign GetAlign() const { return align; }
void SetVerticalAlign(TextVerticalAlign alignVal) { verticalAlign = alignVal; }
TextVerticalAlign GetVerticalAlign() const { return verticalAlign; }
/// Text box width, in meters.
void SetWidth(float w) { width = w; }
float GetWidth() const { return width; }
/// Text box height, in meters.
void SetHeight(float h) { height = h; }
float GetHeight() const { return height; }
/// Width of the text on one line, in pixels.
int32_t GetTextWidth() const;
/// Height of the text in pixels. Counts one line only; wrapping is not included.
int32_t GetTextHeight() const;
// Drops the cached font so it loads again; the editor calls it after re-baking a font.
void UnloadAssets() override;
// Rendering through QuadBlit.
// Culling extents (meters): the box the content is baked into.
bool GetContentExtents(float& outWidth, float& outHeight) const override
{
if (width <= 0.0f || height <= 0.0f)
{
return false;
}
outWidth = width;
outHeight = height;
return true;
}
/// Changes whenever the bake of the text is filled again in place.
bool GetContentVersion(uint32_t& outVersion) const override
{
outVersion = m_BakeVersion;
return true;
}
bool RenderContent(const Deki::Object* owner, QuadBlit::Source& outSource, float& outPivotX, float& outPivotY,
uint8_t& outTintR, uint8_t& outTintG, uint8_t& outTintB, uint8_t& outTintA) override;
// ========================================================================
// Layout methods (shared between runtime and editor)
// ========================================================================
struct GlyphLayout
{
const GlyphInfo* glyph; // Glyph data from font
float worldX; // X position relative to component center
float worldY; // Y position relative to component center
};
/// Positions every glyph of the text, relative to the component's centre.
/// `fontPtr` may differ from the component's font (the editor passes its
/// own). The runtime uses the positions as they are; the editor scales
/// them by its zoom.
void CalculateGlyphLayout(const BitmapFont* fontPtr, std::vector<GlyphLayout>& outGlyphs) const;
/// The text split into lines: at each newline, and word-wrapped to the
/// box width as measured with `fontPtr`. Public for the editor.
std::vector<std::string> WrapTextWithFont(const BitmapFont* fontPtr) const;
// ========================================================================
// Editor-visible properties (public for reflection)
// ========================================================================
DEKI_EXPORT
DEKI_TOOLTIP("Width of the text box in meters. Alignment is measured against this, so it matters even when the "
"text is shorter.")
DEKI_UNIT(Distance)
float width = 6.25f;
DEKI_EXPORT
DEKI_TOOLTIP("Height of the text box in meters. Vertical alignment is measured against this.")
DEKI_UNIT(Distance)
float height = 1.5f;
DEKI_EXPORT
DEKI_TOOLTIP("Colour of the glyphs.")
Deki::Color color;
/// Outline or shadow colour. Used only when the font (v4 or later) was
/// baked with a decoration.
DEKI_EXPORT
DEKI_TOOLTIP("Colour of the outline or shadow, when the font asset was baked with one. Ignored by a plain font.")
Deki::Color decorationColor;
/// Whole-number nearest-neighbour magnification (1x, 2x, 3x).
DEKI_EXPORT
DEKI_TOOLTIP("Whole-number magnification. 2 draws every glyph pixel as a 2x2 block, which keeps a pixel font crisp "
"instead of blurring it.")
int32_t pixelScale = 1;
DEKI_EXPORT
DEKI_TOOLTIP("Horizontal placement inside the width above.")
TextAlign align = TextAlign::Left;
/// Defaults to cap centre, which looks centred to the eye.
DEKI_EXPORT
DEKI_TOOLTIP("Vertical placement inside the height above. Cap-centre lines up the capital letters, which usually "
"looks centred to the eye; true centre includes descenders.")
TextVerticalAlign verticalAlign = TextVerticalAlign::CapCenter;
// Call when the text, font, colour or size changes.
void InvalidateRenderCache();
private:
static FontResolveCallback s_FontResolveCallback;
// The baked text, RGB565A8. Owning, and it knows its own size.
Deki::Buffer<uint8_t> m_CachedBuffer;
uint32_t m_BakeVersion = 0; // GetContentVersion and Source::revision: QuadBlit::NextRevision() each bake
std::string m_CachedText;
int32_t m_CachedWidth = 0;
int32_t m_CachedHeight = 0;
Deki::Color m_CachedColor;
Deki::Color m_CachedDecorationColor;
TextAlign m_CachedAlign = TextAlign::Left;
TextVerticalAlign m_CachedVerticalAlign = TextVerticalAlign::Top;
BitmapFont* m_CachedFont = nullptr;
int32_t m_CachedPixelScale = 1;
// The rows and columns of the bake that hold glyph pixels, and the pivot
// that keeps them where the whole bake was.
int32_t m_CropFirstRow = 0;
int32_t m_CropHeight = 0;
int32_t m_CropFirstCol = 0;
int32_t m_CropWidth = 0;
float m_CropPivotX = 0.5f;
float m_CropPivotY = 0.5f;
// The bake holds only each pixel's alpha (1 byte) when the text is one
// colour, which the blit then tints it with; a decorated font's two
// colours take RGB565A8 (3 bytes).
bool m_CachedAlphaOnly = false;
// The crop of the bake as a blit source; `bakeWidth` is the whole bake's.
QuadBlit::Source CroppedSource(int32_t bakeWidth) const;
// The tint the bake is drawn with: the text colour for an alpha-only bake,
// as RGB565 holds it (so the pixels are what a bake in that colour gives),
// and none for one in colour.
void BakeTint(uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& a) const;
};
} // namespace Deki2D