Text.cpp
1// Copyright 2019 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4
5#include <algorithm>
6#include <codecvt>
7#include <iostream>
8#include <locale>
9#include <smk/Font.hpp>
10#include <smk/RenderTarget.hpp>
11#include <smk/Text.hpp>
12#include <smk/VertexArray.hpp>
13#include <vector>
14
15namespace smk {
16
17// From UTF8 encoded string |input|, eat in between 1 and 4 byte representing
18// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
19// |end| to represent the beginning of the next byte to eat for consecutive
20// executions.
21bool EatCodePoint(const std::string& input,
22 size_t start,
23 size_t* end,
24 uint32_t* ucs) {
25 if (start >= input.size()) {
26 *end = start + 1;
27 return false;
28 }
29 const uint8_t C0 = input[start];
30
31 // 1 byte string.
32 if ((C0 & 0b1000'0000) == 0b0000'0000) { // NOLINT
33 *ucs = C0 & 0b0111'1111; // NOLINT
34 *end = start + 1;
35 return true;
36 }
37
38 // 2 byte string.
39 if ((C0 & 0b1110'0000) == 0b1100'0000 && // NOLINT
40 start + 1 < input.size()) {
41 const uint8_t C1 = input[start + 1];
42 *ucs = 0;
43 *ucs += C0 & 0b0001'1111; // NOLINT
44 *ucs <<= 6; // NOLINT
45 *ucs += C1 & 0b0011'1111; // NOLINT
46 *end = start + 2;
47 return true;
48 }
49
50 // 3 byte string.
51 if ((C0 & 0b1111'0000) == 0b1110'0000 && // NOLINT
52 start + 2 < input.size()) {
53 const uint8_t C1 = input[start + 1];
54 const uint8_t C2 = input[start + 2];
55 *ucs = 0;
56 *ucs += C0 & 0b0000'1111; // NOLINT
57 *ucs <<= 6; // NOLINT
58 *ucs += C1 & 0b0011'1111; // NOLINT
59 *ucs <<= 6; // NOLINT
60 *ucs += C2 & 0b0011'1111; // NOLINT
61 *end = start + 3;
62 return true;
63 }
64
65 // 4 byte string.
66 if ((C0 & 0b1111'1000) == 0b1111'0000 && // NOLINT
67 start + 3 < input.size()) {
68 const uint8_t C1 = input[start + 1];
69 const uint8_t C2 = input[start + 2];
70 const uint8_t C3 = input[start + 3];
71 *ucs = 0;
72 *ucs += C0 & 0b0000'0111; // NOLINT
73 *ucs <<= 6; // NOLINT
74 *ucs += C1 & 0b0011'1111; // NOLINT
75 *ucs <<= 6; // NOLINT
76 *ucs += C2 & 0b0011'1111; // NOLINT
77 *ucs <<= 6; // NOLINT
78 *ucs += C3 & 0b0011'1111; // NOLINT
79 *end = start + 4;
80 return true;
81 }
82
83 *end = start + 1;
84 return false;
85}
86
87
88/// Convert a std::wstring into a UTF8 std::string.
89std::wstring to_wstring(const std::string& s) {
90 std::wstring out;
91
92 size_t i = 0;
93 uint32_t codepoint = 0;
94 while (EatCodePoint(s, i, &i, &codepoint)) {
95 // On linux wstring are UTF32 encoded:
96 if constexpr (sizeof(wchar_t) == 4) {
97 out.push_back(codepoint); // NOLINT
98 continue;
99 }
100
101 // On Windows, wstring are UTF16 encoded:
102
103 // Codepoint encoded using 1 word:
104 // NOLINTNEXTLINE
105 if (codepoint < 0xD800 || (codepoint > 0xDFFF && codepoint < 0x10000)) {
106 uint16_t p0 = codepoint; // NOLINT
107 out.push_back(p0); // NOLINT
108 continue;
109 }
110
111 // Codepoint encoded using 2 words:
112 codepoint -= 0x010000; // NOLINT
113 uint16_t p0 = (((codepoint << 12) >> 22) + 0xD800); // NOLINT
114 uint16_t p1 = (((codepoint << 22) >> 22) + 0xDC00); // NOLINT
115 out.push_back(p0); // NOLINT
116 out.push_back(p1); // NOLINT
117 }
118 return out;
119}
120
121/// Construct a null Text. It can't be drawn.
122Text::Text() = default;
123
124/// @brief Constructor.
125/// @param font The Font to be used for drawing glyphs.
127 SetFont(font);
128}
129
130/// @brief Constructor.
131/// @param font The Font to be used for drawing glyphs.
132/// @param text The character string to be drawn.
133Text::Text(Font& font, const std::string& text) : Text(font) {
134 SetString(text);
135}
136
137/// @brief Constructor.
138/// @param font The Font to be used for drawing glyphs.
139/// @param text The character string to be drawn.
140Text::Text(Font& font, const std::wstring& text) : Text(font) {
141 SetString(text);
142}
143
144/// Update the text to be drawn.
145void Text::SetString(const std::wstring& wide_string) {
146 string_ = wide_string;
147}
148
149/// Update the text to be drawn.
150void Text::SetString(const std::string& string) {
151 string_ = to_wstring(string);
152}
153
154/// Update the Font to be used.
155void Text::SetFont(Font& font) {
156 font_ = &font;
157}
158
159/// Draw the Text to the screen.
160void Text::Draw(RenderTarget& target, RenderState state) const {
161 state.color *= color();
162 glm::mat4 transformation = state.view * this->transformation();
163 float advance_x = 0.f;
164 float advance_y = font_->baseline_position();
165
166 state.vertex_array = VertexArray(std::vector<Vertex>({
167 {{0.f, 0.f}, {0.f, 0.f}},
168 {{0.f, 1.f}, {0.f, 1.f}},
169 {{1.f, 1.f}, {1.f, 1.f}},
170 {{0.f, 0.f}, {0.f, 0.f}},
171 {{1.f, 1.f}, {1.f, 1.f}},
172 {{1.f, 0.f}, {1.f, 0.f}},
173 }));
174
175 for (const auto& it : string_) {
176 if (it == U'\n') {
177 advance_x = 0.f;
178 advance_y += font_->line_height();
179 continue;
180 }
181
182 auto* character = font_->FetchGlyph(it);
183 if (!character) {
184 continue;
185 }
186
187 if (character->texture.id()) {
188 const float x = advance_x + float(character->bearing.x);
189 const float y = advance_y + float(character->bearing.y);
190 const float w = float(character->texture.width());
191 const float h = float(character->texture.height());
192 state.texture = character->texture;
193 state.view = transformation * glm::mat4(w, 0.f, 0.f, 0.f, //
194 0.f, h, 0.f, 0.f, //
195 0.f, 0.f, 1.f, 0.f, //
196 x, y, 0.f, 1.f //
197 );
198
199 target.Draw(state);
200 }
201 advance_x += character->advance;
202 }
203}
204
205/// Compute the dimension of the text when drawn to the screen.
206/// @return a tuple (width,height).
207glm::vec2 Text::ComputeDimensions() const {
208 glm::vec2 dimension(0.f, 0.f);
209 float advance_x = 0.f;
210 dimension.y += font_->line_height();
211 for (const auto& it : string_) {
212 if (it == U'\n') {
213 advance_x = 0.f;
214 dimension.y += font_->line_height();
215 continue;
216 }
217 auto* character = font_->FetchGlyph(it);
218 if (!character) {
219 continue;
220 }
221 advance_x += character->advance;
222
223 dimension.x = std::max(dimension.x, advance_x);
224 }
225 return dimension;
226}
227
228} // namespace smk
virtual void Draw(const Drawable &drawable)
Draw on the surface.
A class for displaying text.
Definition: Text.hpp:37
glm::vec2 ComputeDimensions() const
Definition: Text.cpp:207
void SetFont(Font &font)
Update the Font to be used.
Definition: Text.cpp:155
void Draw(RenderTarget &target, RenderState state) const override
Draw the Text to the screen.
Definition: Text.cpp:160
Text()
Construct a null Text. It can't be drawn.
void SetString(const std::wstring &wide_string)
Update the text to be drawn.
Definition: Text.cpp:145
glm::mat4 transformation() const override
Increase or decrease the size of the object being drawn.
An array of smk::Vertex moved to the GPU memory. This represent a set of triangles to be drawn by the...
Definition: VertexArray.hpp:20
Contain all the data needed to draw.
Definition: RenderState.hpp:17
glm::mat4 view
The "view" transformation.
Definition: RenderState.hpp:21
glm::vec4 color
The masking color.
Definition: RenderState.hpp:22
Texture texture
The texture 0 bound.
Definition: RenderState.hpp:19
VertexArray vertex_array
The shape to to be drawn.
Definition: RenderState.hpp:20