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