1 /*
2     Copyright © 2020, Luna Nielsen
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module engine.render.texture.font;
8 import bindbc.freetype;
9 import std.exception;
10 import std.conv : text;
11 import std.string;
12 import std.format;
13 import engine;
14 
15 private {
16     FT_Library lib;
17     Shader fontShader;
18     Camera2D fontCamera;
19 
20     // VAO and VP can be reused
21     GLuint vao;
22     GLint vp;
23 
24     /// How many entries in a Font batch
25     enum EntryCount = 10_000;
26 
27     // Various variables that make it easier to reference sizes
28     enum VecSize = 2;
29     enum UVSize = 2;
30     enum ColorSize = 4;
31     enum VertsCount = 6;
32     enum DataLength = VecSize+UVSize+ColorSize;
33     enum DataSize = DataLength*VertsCount;
34 
35     vec2 transformVerts(vec2 position, mat4 matrix) {
36         return vec2(matrix*vec4(position.x, position.y, 0, 1));
37     }
38 
39     vec2 transformVertsR(vec2 position, mat4 matrix) {
40         return vec2(vec4(position.x, position.y, 0, 1)*matrix);
41     }
42 }
43 
44 /**
45     Initialize the font subsystem
46 */
47 void initFontSystem() {
48     int err = FT_Init_FreeType(&lib);
49     if (err != FT_Err_Ok) {
50         AppLog.fatal("Font System", "FreeType returned error %s", err);
51     }
52 
53     // Set up batching
54     glGenVertexArrays(1, &vao);
55 
56     fontCamera = new Camera2D();
57     fontShader = new Shader(import("shaders/font.vert"), import("shaders/font.frag"));
58     vp = fontShader.getUniformLocation("vp");
59 }
60 
61 /**
62     A font
63 */
64 class Font {
65 private:
66     //
67     //    Glyph managment
68     //
69     struct Glyph {
70         vec4i area;
71         vec2 advance;
72         vec2 bearing;
73     }
74 
75     struct GlyphIndex {
76         dchar c;
77         int size;
78     }
79 
80     vec2 metrics;
81 
82     FT_Face fontFace;
83     TexturePacker fontPacker;
84     Texture fontTexture;
85     Glyph[GlyphIndex] glyphs;
86     int size;
87 
88     // Generates glyph for the specified character
89     bool genGlyphFor(dchar c) {
90 
91         // Find the character's index in the font
92         uint index = FT_Get_Char_Index(fontFace, c);
93 
94         // Load it if possible, otherwise tell the outside code
95         FT_Load_Glyph(fontFace, index, FT_LOAD_RENDER);
96         if (fontFace.glyph is null) return false;
97 
98         // Get width/height of character
99         immutable(uint) width = fontFace.glyph.bitmap.width;
100         immutable(uint) height = fontFace.glyph.bitmap.rows;
101 
102         // Get length of pixel data, then get it in to a slice
103         size_t dataLength = fontFace.glyph.bitmap.pitch*fontFace.glyph.bitmap.rows;
104         ubyte[] pixData = fontFace.glyph.bitmap.buffer[0..dataLength];
105 
106         // Find space in the texture and pack the glyph in there
107         vec4i area = fontPacker.packTexture(vec2i(width, height));
108         fontTexture.setDataRegion(pixData, area.x, area.y, width, height);
109 
110         // Add glyph to listing
111         glyphs[GlyphIndex(c, size)] = Glyph(
112             // Area (in texture)
113             area, 
114 
115             // Advance
116             vec2(fontFace.glyph.advance.x >> 6, fontFace.glyph.advance.y >> 6), 
117 
118             // Bearing
119             vec2(fontFace.glyph.bitmap_left, fontFace.glyph.bitmap_top)
120         );
121         return true;
122     }
123 
124     //
125     //    Font Batching
126     //
127     float[DataSize*EntryCount] data;
128     size_t dataOffset;
129     size_t tris;
130 
131     GLuint buffer;
132 
133     void addVertexData(vec2 position, vec2 uvs, vec4 color) {
134         data[dataOffset..dataOffset+DataLength] = [position.x, position.y, uvs.x, uvs.y, color.x, color.y, color.z, color.w];
135         dataOffset += DataLength;
136     }
137 
138 public:
139 
140     /**
141         Destroys the font
142     */
143     ~this() {
144         destroy(fontTexture);
145         FT_Done_Face(fontFace);
146         glDeleteBuffers(1, &buffer);
147     }
148 
149     /**
150         Constructs a new font
151     */
152     this(string file, int size) {
153         int err = FT_New_Face(lib, file.toStringz, 0, &fontFace);
154 
155         enforce(err != FT_Err_Unknown_File_Format, "Unknown file format for %s".format(file));
156         enforce(err == FT_Err_Ok, "Error %s while loading font file".format(err));
157 
158         // Change size of text
159         changeSize(size);
160 
161         // Select unicode so we can render i18n text
162         FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE);
163 
164         // Create the texture
165         fontTexture = new Texture(8192, 8192, GL_RED, 1);
166         fontPacker = new TexturePacker(vec2i(8192, 8192));
167 
168         glBindVertexArray(vao);
169         glGenBuffers(1, &buffer);
170         glBindBuffer(GL_ARRAY_BUFFER, buffer);
171         glBufferData(GL_ARRAY_BUFFER, float.sizeof*data.length, data.ptr, GL_DYNAMIC_DRAW);
172     }
173 
174 
175     //
176     //    Glyph managment
177     //
178 
179     /**
180         Changes size of font
181     */
182     final void changeSize(int size) {
183 
184         // Don't try to change size when it's the same
185         if (size == this.size) return;
186 
187         // Set the size of the font
188         FT_Set_Pixel_Sizes(fontFace, 0, size);
189         metrics = vec2(size, fontFace.size.metrics.height >> 6);
190         this.size = size;
191     }
192 
193     /**
194         Gets the advance of a glyph
195     */
196     vec2 advance(dchar glyph) {
197         
198         // Newline is special
199         if (glyph == '\n') return vec2(0);
200 
201         auto idx = GlyphIndex(glyph, size);
202 
203         // Make sure glyph is present
204         if (idx !in glyphs) enforce(genGlyphFor(glyph), "Could not find glyph for character %s".format(glyph));
205         
206         // Return the advance of the glyphs
207         return glyphs[idx].advance;
208     }
209 
210     /**
211         Measure size (width, height) of rectangle needed to contain the specified text
212     */
213     vec2 measure(dstring text) {
214 
215         int lines = 1;
216         float curLineLen = 0;
217         vec2 size = vec2(0);
218         
219         foreach(dchar c; text) {
220             if (c == '\n') {
221                 lines++;
222                 curLineLen = 0;
223                 continue;
224             }
225 
226             auto idx = GlyphIndex(c, this.size);
227 
228             // Try to generate glyph if not present
229             if (idx !in glyphs) {
230                 genGlyphFor(c);
231 
232                 // At this point if the glyph does not exist, skip it
233                 if (idx !in glyphs) continue;
234             }
235 
236             // Bitshift the X advance to make it be in pixels.
237             curLineLen += glyphs[idx].advance.x;
238 
239             // Update the bounding box if the length extends
240             if (curLineLen > size.x) size.x = curLineLen;
241         }
242         size.y = metrics.y*lines;
243         return size;
244     }
245 
246 
247     //
248     //    Font Batching
249     //
250 
251     /**
252         Basic string draw function
253     */
254     void draw(dstring text, vec2 position, vec4 color=vec4(1)) {
255         vec2 next = position;
256         size_t line;
257 
258         foreach(dchar c; text) {  
259 
260             // Skip newline
261             if (c == '\n') {
262                 line++;
263                 next.x = position.x;
264                 next.y += metrics.y;
265                 continue;
266             }
267 
268             auto idx = GlyphIndex(c, size);
269                   
270             // Load character if neccesary
271             if (idx !in glyphs) {
272                 genGlyphFor(c);
273 
274                 // At this point if the glyph does not exist, skip it
275                 if (idx !in glyphs) continue;
276             }
277 
278             draw(c, next, vec2(0), 0, color);
279             next.x += glyphs[idx].advance.x;
280         }
281     }
282 
283     /**
284         draws a character
285     */
286     void draw(dchar c, vec2 position, vec2 origin=vec2(0), float rotation=0, vec4 color=vec4(1)) {
287         
288         auto idx = GlyphIndex(c, size);
289 
290         // Load character if neccesary
291         if (idx !in glyphs) {
292             genGlyphFor(c);
293 
294             // At this point if the glyph does not exist, skip it
295             if (idx !in glyphs) return;
296         }
297 
298         // Flush if neccesary
299         if (dataOffset == DataSize*EntryCount) flush();
300         vec4 area = glyphs[idx].area;
301         vec2 bearing = glyphs[idx].bearing;
302 
303         vec2 pos = vec2(
304             position.x + bearing.x,
305             (position.y - bearing.y)+metrics.y
306         );
307 
308         mat4 transform =
309             mat4.translation(-origin.x, -origin.y, 0) *
310             mat4.translation(pos.x, pos.y, 0) *
311             mat4.translation(origin.x, origin.y, 0) *
312             mat4.zrotation(rotation) * 
313             mat4.translation(-origin.x, -origin.y, 0) *
314             mat4.scaling(area.z, area.w, 0);
315 
316 
317         vec4 uvArea = vec4(
318             (area.x)+0.25,
319             (area.y)+0.25,
320             (area.x+area.z)-0.25,
321             (area.y+area.w)-0.25,
322         );
323 
324         // Triangle 1
325         addVertexData(vec2(0, 1).transformVerts(transform), vec2(uvArea.x, uvArea.w), color);
326         addVertexData(vec2(1, 0).transformVerts(transform), vec2(uvArea.z, uvArea.y), color);
327         addVertexData(vec2(0, 0).transformVerts(transform), vec2(uvArea.x, uvArea.y), color);
328         
329         // Triangle 2
330         addVertexData(vec2(0, 1).transformVerts(transform), vec2(uvArea.x, uvArea.w), color);
331         addVertexData(vec2(1, 1).transformVerts(transform), vec2(uvArea.z, uvArea.w), color);
332         addVertexData(vec2(1, 0).transformVerts(transform), vec2(uvArea.z, uvArea.y), color);
333 
334         tris += 2;
335     }
336 
337     /**
338         Flush font rendering
339     */
340     void flush() {
341 
342         // Disable depth testing for the batcher
343         glDisable(GL_DEPTH_TEST);
344         // Bind VAO
345         glBindVertexArray(vao);
346 
347         // Bind just in case some shennanigans happen
348         glBindBuffer(GL_ARRAY_BUFFER, buffer);
349 
350         // Update with this draw round's data
351         glBufferSubData(GL_ARRAY_BUFFER, 0, dataOffset*float.sizeof, data.ptr);
352 
353         // Bind the texture
354         fontTexture.bind();
355 
356         // Use our sprite batcher shader
357         fontShader.use();
358         fontShader.setUniform(vp, fontCamera.matrix);
359 
360         // Vertex Buffer
361         glEnableVertexAttribArray(0);
362         glVertexAttribPointer(
363             0,
364             VecSize,
365             GL_FLOAT,
366             GL_FALSE,
367             DataLength*GLfloat.sizeof,
368             null,
369         );
370 
371         // UV Buffer
372         glEnableVertexAttribArray(1);
373         glVertexAttribPointer(
374             1,
375             UVSize,
376             GL_FLOAT,
377             GL_FALSE,
378             DataLength*GLfloat.sizeof,
379             cast(GLvoid*)(UVSize*GLfloat.sizeof),
380         );
381 
382         // UV Buffer
383         glEnableVertexAttribArray(2);
384         glVertexAttribPointer(
385             2,
386             ColorSize,
387             GL_FLOAT,
388             GL_FALSE,
389             DataLength*GLfloat.sizeof,
390             cast(GLvoid*)((VecSize+UVSize)*GLfloat.sizeof),
391         );
392 
393         // Draw the triangles
394         glDrawArrays(GL_TRIANGLES, 0, cast(int)(tris*3));
395 
396         // Reset the batcher's state
397         glDisableVertexAttribArray(0);
398         glDisableVertexAttribArray(1);
399         glDisableVertexAttribArray(2);
400         dataOffset = 0;
401         tris = 0;
402 
403         // Re-enable depth test Clear depth buffer
404         glEnable(GL_DEPTH_TEST);
405     }
406 
407 }