1 /**
2 The Sharpest Font Library For D Game Development
3 */
4 module razor_font;
5 
6 import std.conv;
7 import std.file;
8 import std.json;
9 import std.typecons;
10 import color;
11 import png;
12 import std.math;
13 
14 //  ____________________________
15 // |         RAZOR FONT         |
16 // |____________________________|
17 //  \            /\            /
18 //  /            \/            \
19 // | The Sharpest Font Library  |
20 // |   For D Game Development   |
21 // |____________________________|
22 
23 /**
24 Stores IMPORTANT font data to be reused by Razor Font - These are stored in static memory in the program
25 Counts are so we can grab a slice of this information because anything after it WILL be garbage data
26 */
27 /// The current character limit (letters in string)
28 private immutable int CHARACTER_LIMIT = 4096;
29 /// 4 vec2 (so 8 per char) vertex positions
30 private double[4 * 2 * CHARACTER_LIMIT] vertexCache;
31 // 4 vec4 (so 16 per char) colors - defaults to 0,0,0,1 rgba
32 private double[4 * 4 * CHARACTER_LIMIT] colorCache;
33 
34 /// 4 vec2 (so 8 per char) texture coordinate positions
35 private double[8 * CHARACTER_LIMIT] textureCoordinateCache;
36 /// 2 tris (so 6 per char) indices
37 private int[6 * CHARACTER_LIMIT] indicesCache;
38 /// The count of each of these so we can grab a slice of data fresh out of the oven, delicious!
39 private int vertexCount            = 0;
40 private int textureCoordinateCount = 0;
41 private int indicesCount           = 0;
42 private int colorCount             = 0;
43 private int chars                  = 0;
44 
45 /**
46 This allows batch rendering to a "canvas" ala vertex positionining
47 With this you can shovel one giant lump of data into a vao or whatever you're using.
48 This is optional though, you can do whatever you want!
49 */
50 private double canvasWidth  = -1;
51 private double canvasHeight = -1;
52 
53 /**
54 These store constant data that is highly repetitive
55 */
56 private immutable double[8] RAW_VERTEX  = [ 0,0, 0,1, 1,1, 1,0 ];
57 private immutable int[6]    RAW_INDICES = [ 0,1,2, 2,3,0 ];
58 
59 /**
60 The offset of the text shadowing.
61 
62 Note: Since offset is only proportional to the font size when rendering,
63 the offset is completely detached from the font spec!
64 
65 The font spec has no bearing on how the offset is calculated. Only font size.
66 
67 0.05 by default because I think it looks nice. :)
68 */
69 private double shadowOffsetX = 0.05;
70 private double shadowOffsetY = 0.05;
71 
72 /**
73 The RGBA components of the shadow
74 */
75 private double[4] shadowColor = [0,0,0,1];
76 
77 /**
78 Are shadows enabled?
79 
80 They get disabled everytime you run renderToCanvas().
81 This is so there basically isn't a "shadow memory leak".
82 
83 As in: Oops I forgot to disable shadows now everything after has a 
84 shadow for some reason!
85 */
86 private bool shadowsEnabled = false;
87 
88 /**
89 Allows turning off the shadowing color fill for performance.
90 Say you want a rainbow shadow, you can use this for that.
91 */
92 private bool shadowColoringEnabled = true;
93 
94 /**
95 This is a very simple fix for static memory arrays being filled with no.
96 A simple on switch for initialization.
97 To use RazorFont, you must create a font, so it runs this in there.
98 */
99 private bool initializedColorArray = false;
100 private void initColorArray() {
101     if (initializedColorArray) {
102         return;
103     }
104     initializedColorArray = true;
105     for (int i = 0; i < 16 * CHARACTER_LIMIT; i += 4) {
106         colorCache[i]     = 0;
107         colorCache[i + 1] = 0;
108         colorCache[i + 2] = 0;
109         colorCache[i + 3] = 1;
110     }
111 }
112 
113 /**
114 Caches the current font in use.
115 Think of this like the golfball on an IBM Selectric.
116 You can use one ball, type out in one font. Then flush to your render target.
117 Then you can swap to another ball and type in another font.
118 
119 Just remember, you must flush or this is going to throw an error because
120 it would create garbage text data without a lock when swapping golfballs, aka fonts.
121 */
122 private RazorFont currentFont = null;
123 
124 /// This stores the current font name as a string
125 private string currentFontName;
126 
127 /// This is the lock described in the comment above;
128 private bool fontLock = false;
129 
130 /// Stores all fonts
131 private RazorFont[string] razorFonts;
132 
133 /// A simple struct to get the font data for the shader
134 struct RazorFontData {
135     double[] vertexPositions;
136     double[] textureCoordinates;
137     int[]    indices;
138     double[] colors;
139 }
140 /// A simple struct to get the width and height of rendered text
141 struct RazorTextSize {
142     double width  = 0.0;
143     double height = 0.0;
144 }
145 
146 // Allows an automatic upload into whatever render target (OpenGL, Vulkan, Metal, DX) as a string file location
147 private void delegate(string) renderTargetAPICallString = null;
148 
149 // Allows DIRECT automatic upload into whatever render target (OpenGL, Vulkan, Metal, DX) as RAW data
150 private void delegate(ubyte[], int, int) renderTargetAPICallRAW = null;
151 
152 // Allows an automate render into whatever render target (OpenGL, Vulkan, Metal, DX) simply by calling render()
153 private void delegate(RazorFontData) renderApiRenderCall = null;
154 
155 
156 /**
157 Allows automatic render target (OpenGL, Vulkan, Metal, DX) passthrough instantiation.
158 This can basically pass a file location off to your rendering engine and auto load it into memory.
159 */
160 void setRenderTargetAPICallString(void delegate(string) apiStringFunction) {
161     if (renderTargetAPICallRAW !is null) {
162         throw new Exception("Razor Font: You already set the RAW api integration function!");
163     }
164     renderTargetAPICallString = apiStringFunction;
165 }
166 
167 
168 /**
169 Allows automatic render target (OpenGL, Vulkan, Metal, DX) DIRECT instantiation.
170 This allows the render engine to AUTOMATICALLY upload the image as RAW data.
171 ubyte[] = raw data. int = width. int = height.
172 */
173 void setRenderTargetAPICallRAW(void delegate(ubyte[], int, int) apiRAWFunction) {
174     if (renderTargetAPICallString !is null) {
175         throw new Exception("Razor Font: You already set the STRING api integration function!");
176     }
177     renderTargetAPICallRAW = apiRAWFunction;
178 }
179 
180 /**
181 Allows automatic render target (OpenGL, Vulkan, Metal, DX) DIRECT rendering via RazorFont.
182 You can simply call render() on the library and it will automatically do whatever you
183 tell it to with this delegate function. This will also automatically run flush().
184 */
185 void setRenderFunc(void delegate(RazorFontData) renderApiRenderFunction) {
186     if (renderApiRenderCall !is null) {
187         throw new Exception("Razor Font: You already set the RENDER api integration function!");
188     }
189     renderApiRenderCall = renderApiRenderFunction;
190 }
191 
192 
193 // A simple font container
194 private class RazorFont {
195 
196     // Font base pallet width (in pixels)
197     int palletWidth  = 0;
198     int palletHeight = 0;
199 
200     // Pixel space (literally) between characters in pallet
201     int border = 0;
202 
203     // Number of characters (horizontal, aka X)
204     int rows = 0;
205 
206     // How far the letters are from each other
207     double spacing = 1.0;
208 
209     // How big the space character is (' ')
210     double spaceCharacterSize = 4.0;
211 
212     // Character pallet (individual) in pixels
213     int characterWidth   = 0;
214     int charactertHeight = 0;
215     
216     // Readonly specifier if kerning was enabled
217     bool kerned = false;
218 
219     // Readonly specifier if trimming was enabled
220     bool trimmedX = false;
221     bool trimmedY = false;
222 
223     // Readonly directory for texture (entire, including the .png)
224     string fileLocation;
225 
226     // Character map - stored as a linear associative array for O(1) retrieval
227     /**
228     Stores as:
229     [
230         -x -y,
231         -x +y, 
232         +x +y,
233         +x -y
234     ]
235     or this, if it's easier to understand:
236     [
237         top    left,
238         bottom left,
239         bottom right,
240         top    right
241     ]
242     GPU optimized vertex positions!
243 
244     Accessed as:
245     double[] myCoolBlah = map["whatever letter/unicode thing you're getting"];
246 
247     The last 1 values specify width of the character
248     */
249     double[9][dchar] map;
250 
251     // Stores the map raw as a linear array before processed
252     string rawMap;
253 }
254 
255 /**
256 Create a font from your PNG JSON pairing in the directory.
257 
258 You do not specify an extension.
259 
260 So if you have: cool.png and cool.json
261 You would call this as: createFont("fonts/cool")
262 
263 Name is an optional. You will call into Razor Font by this name.
264 
265 If you do not specify a name, you must call into Razor Font by the fileLocation, literal.
266 
267 If you turn on trimming, your font will go from monospace to proportional.
268 
269 Spacing is how far the letters are from each other. Default: 1.0 pixel
270 
271 spaceCharacterSize is how big the ' ' (space) character is. By default, it's 4 pixels wide.
272 */
273 void createFont(string fileLocation, string name = "", bool trimming = false, double spacing = 1.0, double spaceCharacterSize = 4.0) {
274 
275     // This is the fix explained above
276     initColorArray();
277 
278     //! Place holder for future
279     bool kerning = false;
280 
281     // Are we using the fileLocation as the key, or did they specify a name?
282     const string key = name == "" ? fileLocation : name;
283 
284     const string pngLocation = fileLocation ~ ".png";
285     const string jsonLocation = fileLocation ~ ".json";
286 
287     // Make sure the files exist
288     checkFilesExist(pngLocation, jsonLocation);
289 
290     // Automate existing engine integration
291     tryCallingRAWApi(pngLocation);
292     tryCallingStringApi(pngLocation);
293 
294     // Create the Font object
295     RazorFont fontObject = new RazorFont();
296 
297     // Store the file location in the object
298     fontObject.fileLocation = pngLocation;
299 
300     // Now parse the json, and pass it into object
301     parseJson(fontObject, jsonLocation);
302 
303     // Now encode the linear string as a keymap of raw graphics positions
304     encodeGraphics(fontObject, kerning, trimming, spacing, spaceCharacterSize);
305 
306     // Finally add it into the library
307     razorFonts[key] = fontObject;
308 
309 }
310 
311 //* ============================ BEGIN GRAPHICS DISPATCH ===========================
312 
313 /**
314 Allows you to blanket set the color for the entire canvas.
315 
316 Be careful though, this overwrites the entire color cache
317 after the currently rendered character position in memory!
318 */
319 void switchColors(double r, double g, double b, double a = 1.0) {
320     for (int i = colorCount; i < colorCache.length; i += 4) {
321         colorCache[i]     = r;
322         colorCache[i + 1] = g;
323         colorCache[i + 2] = b;
324         colorCache[i + 3] = a;
325     }
326 }
327 
328 /**
329 Allows you to set the offet of the text shadowing.
330 
331 This is RELATIVE via the font size so it will remain consistent
332 across any font size!
333 
334 Remember: Offset will become reset to default when you call renderToCanvas()
335 */
336 void setShadowOffset(double x, double y) {
337     shadowOffsetX = x / 10.0;
338     shadowOffsetY = y / 10.0;
339 }
340 
341 /**
342 Allows you to blanket set the shadow color for the entire canvas after the current character.
343 
344 Remember: When you renderToCanvas() shadow colors will default back to black.
345 */
346 void switchShadowColor(double r, double g, double b, double a = 1.0) {
347     shadowColor[0] = r;
348     shadowColor[1] = g;
349     shadowColor[2] = b;
350     shadowColor[3] = a;
351 }
352 
353 
354 /**
355 Allows you to blanket a range of characters in the canvas with a color.
356 
357 So if you have: abcdefg
358 And run setColorRange(0.5,0.5,0.5, 1, 3, 5)
359 Now e and f are gray. Alpha 1.0
360 */
361 void setColorRange(int start, int end, double r, double g, double b, double a) {
362     for (int i = start * 16; i < end * 16; i += 4) {
363         colorCache[i]     = r;
364         colorCache[i + 1] = g;
365         colorCache[i + 2] = b;
366         colorCache[i + 3] = a;
367     }
368 }
369 
370 /**
371 Allows you to set individual character colors
372 */
373 void setColorChar(int charIndex, double r, double g, double b, double a = 1.0) {
374     const int startIndex = charIndex * 16;
375     for (int i = startIndex; i < startIndex + 16; i += 4) {
376         colorCache[i]     = r;
377         colorCache[i + 1] = g;
378         colorCache[i + 2] = b;
379         colorCache[i + 3] = a;
380     }
381 }
382 
383 /**
384 Allows you to directly work on vertex position colors in a character.
385 Using direct points (verbose)
386 */
387 void setColorPoints(
388     int charIndex,
389 
390     double topLeftR,
391     double topLeftG,
392     double topLeftB,
393     double topLeftA,
394 
395     double bottomLeftR,
396     double bottomLeftG,
397     double bottomLeftB,
398     double bottomLeftA,
399 
400     double bottomRightR,
401     double bottomRightG,
402     double bottomRightB,
403     double bottomRightA,
404 
405     double topRightR,
406     double topRightG,
407     double topRightB,
408     double topRightA
409 ) {
410     const int startIndex = charIndex * 16;
411     
412     // It's already immensely verbose, let's just add on to this verbosity
413     
414     colorCache[startIndex]      = topLeftR;
415     colorCache[startIndex + 1]  = topLeftG;
416     colorCache[startIndex + 2]  = topLeftB;
417     colorCache[startIndex + 3]  = topLeftA;
418 
419     colorCache[startIndex + 4]  = bottomLeftR;
420     colorCache[startIndex + 5]  = bottomLeftG;
421     colorCache[startIndex + 6]  = bottomLeftB;
422     colorCache[startIndex + 7]  = bottomLeftA;
423 
424     colorCache[startIndex + 8]  = bottomRightR;
425     colorCache[startIndex + 9]  = bottomRightG;
426     colorCache[startIndex + 10] = bottomRightB;
427     colorCache[startIndex + 11] = bottomRightA;
428 
429     colorCache[startIndex + 12] = topRightR;
430     colorCache[startIndex + 13] = topRightG;
431     colorCache[startIndex + 14] = topRightB;
432     colorCache[startIndex + 15] = topRightA;
433 }
434 
435 /**
436 Allows you to directly work on vertex position colors in a character.
437 Using direct points (tidy).
438 double vec is [R,G,B,A]
439 */
440 void setColorPoints(int charIndex, double[4] topLeft, double[4] bottomLeft, double[4] bottomRight, double[4] topRight) {
441     const int startIndex = charIndex * 16;  
442     foreach(externalIndex, vec4; [topLeft, bottomLeft, bottomRight, topRight]) {
443         foreach (index, value; vec4) {
444             colorCache[startIndex + (externalIndex * 4) + index] = value;
445         }
446     }
447 }
448 
449 /// Allows you to get the max amount of characters allowed in canvas
450 int getMaxChars() {
451     return CHARACTER_LIMIT;
452 }
453 
454 /**
455 Allows you to index the current amount of characters on the canvas. This does
456 not include spaces and carriage returns. You MUST call renderToCanvas before
457 calling this otherwise this will always be 0 when you call it.
458 */
459 int getCurrentCharacterIndex() {
460     return chars;
461 }
462 
463 /**
464 Allows you to extract the current font PNG file location automatically
465 */
466 string getCurrentFontTextureFileLocation() {
467     if (currentFont is null) {
468         throw new Exception("Razor Font: Can't get a font file location! You didn't select one!");
469     }
470     return currentFont.fileLocation;
471 }
472 
473 /**
474 Turns on shadowing.
475 
476 Rememeber: This creates twice as many characters because
477 you have to render a background, then a foreground.
478 
479 You can also do some crazy stuff with shadows because the shadow
480 colors are stored in the same color cache as regular text.
481 
482 Remember: When you renderToCanvas() shadows turn off.
483 */
484 void enableShadows() {
485     shadowsEnabled = true;
486 }
487 
488 
489 /// Allows you to render to a canvas using top left as a base position
490 void setCanvasSize(double width, double height) {
491     // Dividing by 2.0 because my test environment shader renders to center on pos(0,0) top left
492     canvasWidth = width / 2.0;
493     canvasHeight = height / 2.0;
494 }
495 
496 /**
497 Automatically flushes out the cache, handing the data structure off to
498 the delegate function you defined via setRenderFunc()
499 */
500 void render() {
501     if (renderApiRenderCall is null) {
502         throw new Exception("Razor Font: You did not set a render api call!");
503     }
504 
505     renderApiRenderCall(flush());
506 }
507 
508 
509 /// Flushes out the cache, gives you back a font struct containing the raw data
510 RazorFontData flush() {
511 
512     fontLock = false;
513     
514     RazorFontData returningStruct = RazorFontData(
515         vertexCache[0..vertexCount],
516         textureCoordinateCache[0..textureCoordinateCount],
517         indicesCache[0..indicesCount],
518         colorCache[0..colorCount]
519     );
520 
521     // Reset the counters
522     vertexCount = 0;
523     textureCoordinateCount = 0;
524     indicesCount = 0;
525     colorCount = 0;
526     chars = 0;
527 
528     return returningStruct;
529 }
530 
531 /// Allows you to get text size to do interesting things. Returns as RazorTextSize struct
532 RazorTextSize getTextSize(double fontSize, string text) {
533     double accumulatorX = 0.0;
534     double accumulatorY = 0.0;
535     // Cache spacing
536     const double spacing = currentFont.spacing * fontSize;
537     // Cache space (' ') character
538     const double spaceCharacterSize = currentFont.spaceCharacterSize * fontSize;
539 
540     // Can't get the size if there's no font!
541     if (currentFont is null) {
542         throw new Exception("Razor Font: Tried to get text size without selecting a font! " ~
543             "You must select a font before getting the size of text with it!");
544     }
545     
546     foreach (key, character; text) {
547 
548         // Skip space
549         if (character == ' ') {
550             accumulatorX += spaceCharacterSize;
551             continue;
552         }
553         // Move down 1 space Y
554         if (character == '\n') {
555             accumulatorY += fontSize;
556             continue;
557         }
558         
559         // Skip unknown character
560         if (character !in currentFont.map) {
561             continue;
562         }
563 
564         // Font stores character width in index 9 (8 [0 count])
565         accumulatorX += (currentFont.map[character][8] * fontSize) + spacing;
566     }
567 
568     // Add a last bit of the height offset
569     accumulatorY += fontSize;
570     // Remove the last bit of spacing
571     accumulatorX -= spacing;
572 
573     // Finally, if shadowing is enabled, add in shadowing offset
574     if (shadowsEnabled) {
575         accumulatorX += (shadowOffsetX * fontSize);
576         accumulatorY += (shadowOffsetY * fontSize);
577     }
578 
579     return RazorTextSize(accumulatorX, accumulatorY);
580 }
581 
582 /**
583 Selects and caches the font of your choosing.
584 
585 Remember: You must flush the cache before choosing a new font.
586 
587 This is done because all fonts are different. It would create garbage
588 data on screen without this.
589 */
590 void selectFont(string font) {
591 
592     if (fontLock) {
593         throw new Exception("You must flush() out the cache before selecting a new font!");
594     }
595 
596     // Can't render if that font doesn't exist
597     if (font !in razorFonts) {
598         throw new Exception(font ~ " is not a registered font!");
599     }
600 
601     // Now store and lock
602     currentFont = razorFonts[font];
603     currentFontName = font;
604     fontLock = true;
605 }
606 
607 /**
608 Render to the canvas. Remember: You must run flush() to collect this canvas.
609 If rounding is enabled, it will attempt to keep your text aligned with the pixels on screen
610 to avoid wavy/blurry/jagged text. This will automatically render shadows for you as well.
611 */
612 void renderToCanvas(double posX, double posY, const double fontSize, string text, bool rounding = true) {
613 
614     // Keep square pixels
615     if (rounding) {
616         posX = round(posX);
617         posY = round(posY);
618     }
619 
620     // Can't render if no font is selected
621     if (currentFont is null) {
622         throw new Exception("Razor Font: Tried to render without selecting a font! " ~
623             "You must select a font before rendering to canvas!");
624     }
625 
626     // Can't render to canvas if there IS no canvas
627     if (canvasWidth == -1 && canvasHeight == -1) {
628         throw new Exception("Razor Font: You have to set the canvas size to render to it!");
629     }
630 
631     // Store how far the arm has moved to the right
632     double typeWriterArmX = 0.0;
633     // Store how far the arm has moved down
634     double typeWriterArmY = 0.0;
635 
636     // Top left of canvas is root position (X: 0, y: 0)
637     const positionX = posX - canvasWidth;
638     const positionY = posY - canvasHeight;
639 
640     // Cache spacing
641     const double spacing = currentFont.spacing * fontSize;
642 
643     // Cache space (' ') character
644     const double spaceCharacterSize = currentFont.spaceCharacterSize * fontSize;
645 
646     foreach (key, const(dchar) character; text) {
647 
648         // Skip space
649         if (character == ' ') {
650             typeWriterArmX += spaceCharacterSize;
651             continue;
652         }
653         // Move down 1 space Y and to space 0 X
654         if (character == '\n') {
655             typeWriterArmY += fontSize;
656             typeWriterArmX = 0.0;
657             continue;
658         }
659         
660         // Skip unknown character
661         if (character !in currentFont.map) {
662             continue;
663         }
664 
665         // Font stores character width in index 9 (8 [0 count])
666         double[9] rawData = currentFont.map[character];
667 
668         // Keep on the stack
669         double[8] textureData = rawData[0..8];
670         //Now dispatch into the cache
671         for (int i = 0; i < 8; i++) {
672             textureCoordinateCache[i + textureCoordinateCount] = textureData[i];
673         }
674 
675         // This is the width of the character
676         // Keep on the stack
677         double characterWidth = rawData[8];
678         
679         // Keep this on the stack
680         double[8] rawVertex = RAW_VERTEX;
681 
682 
683         // ( 0 x 1 y 2 x 3 y ) <- left side ( 4 x 5 y 6 x 7 y ) <- right side is goal
684         // Now apply trimming
685         for (int i = 4; i < 8; i += 2) {
686             rawVertex[i] = characterWidth;
687         }
688 
689         // Now scale
690         foreach (ref double vertexPosition; rawVertex) {
691             vertexPosition *= fontSize;
692         }
693 
694         // Shifting
695         for (int i = 0; i < 8; i += 2) {
696             // Now shift right
697             rawVertex[i] += typeWriterArmX + positionX;
698             // Now shift down
699             rawVertex[i + 1] += typeWriterArmY + positionY;
700         }
701 
702         typeWriterArmX += (characterWidth * fontSize) + spacing;
703 
704         // vertexData ~= rawVertex;
705         // Now dispatch into the cache
706         for (int i = 0; i < 8; i++) {
707             vertexCache[i + vertexCount] = rawVertex[i];
708         }
709 
710         // Keep this on the stack
711         int[6] rawIndices = RAW_INDICES;
712         foreach (ref value; rawIndices) {
713             // Using vertexCount because we're targeting vertex positions
714             value += vertexCount / 2;
715         }
716         // Now dispatch into the cache
717         for (int i = 0; i < 6; i++) {
718             indicesCache[i + indicesCount] = rawIndices[i];
719         }
720 
721         // Now hold cursor position (count) in arrays
722         vertexCount  += 8;
723         textureCoordinateCount += 8;
724         indicesCount += 6;
725         colorCount += 16;
726         // This one is characters literal
727         chars++;
728 
729         if (vertexCount >= CHARACTER_LIMIT || indicesCount >= CHARACTER_LIMIT) {
730             throw new Exception("Character limit is: " ~ to!string(CHARACTER_LIMIT));
731         }
732     }
733 
734     /**
735     Because there is no Z buffer in 2d, OpenGL seems to NOT overwrite pixel data of existing
736     framebuffer pixels. Since this is my testbed, I must assume that this is how
737     Vulkan, Metal, DX, and so-on do this. This is GUARANTEED to not affect software renderers.
738     So we have to do the shadowing AFTER the foreground.
739 
740     We need to poll, THEN disable the shadow variable because without that it would be
741     an infinite recursion, aka a stack overflow.
742     */
743     const bool shadowsWereEnabled = shadowsEnabled;
744     shadowsEnabled = false;
745     if (shadowsWereEnabled) {
746         const int textLength = getTextRenderableCharsLength(text);
747         const int currentIndex = getCurrentCharacterIndex();
748         if (shadowColoringEnabled) {
749             setColorRange(
750                 currentIndex,
751                 currentIndex + textLength,
752                 shadowColor[0],
753                 shadowColor[1],
754                 shadowColor[2],
755                 shadowColor[3]
756             );
757         }
758         renderToCanvas(posX + (shadowOffsetX * fontSize), posY + (shadowOffsetY * fontSize), fontSize, text, false);
759 
760         shadowOffsetX = 0.05;
761         shadowOffsetY = 0.05;
762     }
763     
764     // Turn this back on because it can become a confusing nightmare
765     shadowColoringEnabled = true;
766     // Switch back to black because this also can become a confusing nightmare
767     switchShadowColor(0,0,0);
768 }
769 
770 /**
771 Processes your input string, then sends you how long it would be when rendering.
772 Helpful for repositioning your "cursor" in the texture cache!
773 
774 Note: This will return cursor position into the beginning index of the background
775 of the shadowed text if you're using it for subtraction.
776 */
777 int getTextRenderableCharsLength(string input) {
778     import std.array;
779     return cast(int)input.replace(" ", "").replace("\n", "").length;
780 }
781 
782 /**
783 Processes your input text string with shadows to see how long it would be when rendering.
784 Helpful for positioning your "cursor" in the texture cache!
785 
786 Note: This will return cursor position into the beginning index of the foreground
787 of the shadowed text if you're using it for subtraction.
788 */
789 int getTextRenderableCharsLengthWithShadows(string input) {
790     return getTextRenderableCharsLength(input) * 2;
791 }
792 
793 /**
794 Allows you to disable shadow coloring for a teeny tiny bit of performance
795 when you're doing cool custom shadow coloring!
796 
797 Important Note: When renderToCanvas() is called, shadow coloring is turned
798 back on because it can become a confusing nightmare if not done like this.
799 */
800 void disableShadowColoring() {
801     shadowColoringEnabled = false;
802 }
803 
804 /**
805 Allows you to manually move around characters.
806 
807 Note: You can manually move around shadows by getting the
808 renderable text size before turning on shadows, then offset
809 your current index into the string by this size.
810 
811 Note: This is in pixel coordinates.
812 */
813 void moveChar(int index, double posX, double posY) {
814     // This gets a bit confusing, so I'm going to write it out verbosely to be able to read/maintain it
815 
816     // Move to cursor position in vertexCache
817     const int baseIndex = index * 8;
818 
819     // Top left
820     vertexCache[baseIndex    ] += posX; // X
821     vertexCache[baseIndex + 1] -= posY; // Y
822 
823     // Bottom left
824     vertexCache[baseIndex + 2] += posX; // X
825     vertexCache[baseIndex + 3] -= posY; // Y
826 
827     // Bottom right
828     vertexCache[baseIndex + 4] += posX; // X
829     vertexCache[baseIndex + 5] -= posY; // Y
830 
831     // Top right
832     vertexCache[baseIndex + 6] += posX; // X
833     vertexCache[baseIndex + 7] -= posY; // Y
834 }
835 
836 /**
837 Rotate a character around the centerpoint of it's face.
838 
839 Note: This defaults to radians by default.
840 
841 Note: If you use moveChar() with this, you MUST do moveChar() first!
842 */
843 void rotateChar(int index, double rotation, bool isDegrees = false) {
844 
845     // This is why my doml is required
846     import doml.vector_3d;
847     import doml.matrix_4d;
848 
849     if (isDegrees) {
850         immutable radToDegrees = 180.0 / PI;
851         rotation *= radToDegrees;
852     }
853 
854     /**
855     This is written out even more verbosely than moveChar()
856     so you can see why you must do moveChar() first.
857     */
858 
859     // Move to cursor position in vertexCache
860     const int baseIndex = index * 8;
861 
862     // Convert to 3d to suppliment to 4x4 matrix
863     Vector3d topLeft     = Vector3d(vertexCache[baseIndex    ], vertexCache[baseIndex + 1], 0);
864     Vector3d bottomLeft  = Vector3d(vertexCache[baseIndex + 2], vertexCache[baseIndex + 3], 0);
865     Vector3d bottomRight = Vector3d(vertexCache[baseIndex + 4], vertexCache[baseIndex + 5], 0);
866     Vector3d topRight    = Vector3d(vertexCache[baseIndex + 6], vertexCache[baseIndex + 7], 0);
867     
868     Vector3d centerPoint = Vector3d((topLeft.x + topRight.x) / 2.0,  (topLeft.y + bottomLeft.y) / 2.0, 0);
869 
870     Vector3d topLeftDiff      = Vector3d(topLeft)    .sub(centerPoint);
871     Vector3d bottomLeftDiff   = Vector3d(bottomLeft) .sub(centerPoint);
872     Vector3d bottomRighttDiff = Vector3d(bottomRight).sub(centerPoint);
873     Vector3d topRighttDiff    = Vector3d(topRight)   .sub(centerPoint);
874 
875     // These calculations also store the new data in the variables we created above
876     // We must center the coordinates into real coordinates
877 
878     Matrix4d().rotate(rotation, 0,0,1).translate(topLeftDiff)     .getTranslation(topLeft);
879     Matrix4d().rotate(rotation, 0,0,1).translate(bottomLeftDiff)  .getTranslation(bottomLeft);
880     Matrix4d().rotate(rotation, 0,0,1).translate(bottomRighttDiff).getTranslation(bottomRight);
881     Matrix4d().rotate(rotation, 0,0,1).translate(topRighttDiff)   .getTranslation(topRight);
882 
883 
884     topLeft.x += centerPoint.x;
885     topLeft.y += centerPoint.y;
886 
887     bottomLeft.x += centerPoint.x;
888     bottomLeft.y += centerPoint.y;
889 
890     bottomRight.x += centerPoint.x;
891     bottomRight.y += centerPoint.y;
892 
893     topRight.x += centerPoint.x;
894     topRight.y += centerPoint.y;
895 
896     vertexCache[baseIndex    ] = topLeft.x;
897     vertexCache[baseIndex + 1] = topLeft.y;
898 
899     vertexCache[baseIndex + 2] = bottomLeft.x;
900     vertexCache[baseIndex + 3] = bottomLeft.y;
901 
902     vertexCache[baseIndex + 4] = bottomRight.x;
903     vertexCache[baseIndex + 5] = bottomRight.y;
904 
905     vertexCache[baseIndex + 6] = topRight.x;
906     vertexCache[baseIndex + 7] = topRight.y;
907 }
908 
909 //! ============================ END GRAPHICS DISPATCH =============================
910 
911 //* ========================= BEGIN GRAPHICS ENCODING ==============================
912 
913 private void encodeGraphics(ref RazorFont fontObject, bool kerning, bool trimming, double spacing, double spaceCharacterSize) {
914     
915     // Store all this on the stack
916 
917     // Total image size
918     const double palletWidth = cast(double)fontObject.palletWidth;
919     const double palletHeight = cast(double)fontObject.palletHeight;
920 
921     // How many characters (width, then height)
922     const int rows = fontObject.rows;
923 
924     // How wide and tall are the characters in pixels
925     const int characterWidth = fontObject.characterWidth;
926     const int characterHeight = fontObject.charactertHeight;
927 
928     // The border between the characters in pixels
929     const int border = fontObject.border;
930 
931     // Store font spacing here as it's a one shot operation
932     fontObject.spacing = spacing / characterWidth;
933 
934     // Store space character width as it's a one shot operation
935     fontObject.spaceCharacterSize = spaceCharacterSize / characterWidth;
936 
937     // Cache a raw true color image for trimming if requested
938     const TrueColorImage tempImageObject = trimming == false ? null : readPng(fontObject.fileLocation).getAsTrueColorImage();
939 
940     foreach (size_t i, const(dchar) value; fontObject.rawMap) {
941 
942         // Starts off as a normal monospace size
943         int thisCharacterWidth = characterWidth;
944 
945         // Turn off annoying casting suggestions
946         const int index = cast(int) i;
947 
948         // Now get where the typewriter is
949         const int currentRow = index % rows;
950         const int currentColum = index / rows;
951 
952         // Now get literal pixel position (top left)
953         int intPosX = (characterWidth + border) * currentRow;
954         int intPosY = (characterHeight + border) * currentColum;
955         
956         // left  top,
957         // left  bottom,
958         // right bottom,
959         // right top
960 
961         // Now calculate limiters
962         // +1 on max because the GL texture stops on the top left of the point in the texture pixel
963         int minX = intPosX;
964         int maxX = intPosX + characterWidth + 1;
965 
966         const int minY = intPosY;
967         const int maxY = intPosY + characterHeight + 1;
968 
969         // Now trim it if requested
970         if (trimming) {
971 
972             // Create temp workers
973             int newMinX = minX;
974             int newMaxX = maxX;
975 
976             // Trim left side
977             outer1: foreach(x; minX..maxX) {
978                 newMinX = x;
979                 foreach (y; minY..maxY) {
980                     // This is ubyte (0-255)
981                     if (tempImageObject.getPixel(x,y).a > 0) {
982                         break outer1;
983                     }
984                 }
985             }
986             
987             // Trim right side
988             outer2: foreach_reverse(x; minX..maxX) {
989                 // +1 because of the reason stated above assigning minX and maxX
990                 newMaxX = x + 1;
991                 foreach (y; minY..maxY) {
992                     // This is ubyte (0-255)
993                     if (tempImageObject.getPixel(x,y).a > 0) {
994                         break outer2;
995                     }
996                 }
997             }
998             
999             // I was going to throw a blank space check, but maybe someone has a reason for that
1000 
1001             minX = newMinX;
1002             maxX = newMaxX;
1003 
1004             thisCharacterWidth = maxX - minX;
1005             
1006         }
1007 
1008         // Now shovel it into a raw array so we can easily use it - iPos stands for Integral Positions
1009         // -1 on maxY because the position was overshot, now we reverse it
1010         int[] iPos = [
1011             minX, minY,     // Top left
1012             minX, maxY - 1, // Bottom left
1013             maxX, maxY - 1, // Bottom right
1014             maxX, minY,    // Top right
1015             
1016             thisCharacterWidth, // Width
1017         ];
1018 
1019         // Now calculate REAL graphical texture map
1020         double[9] glPositions  = [
1021             iPos[0] / palletWidth, iPos[1] / palletHeight, 
1022             iPos[2] / palletWidth, iPos[3] / palletHeight,
1023             iPos[4] / palletWidth, iPos[5] / palletHeight,
1024             iPos[6] / palletWidth, iPos[7] / palletHeight,
1025 
1026             // Now store char width - Find the new double size by comparing it to original
1027             // Will simply be 1.0 with monospaced fonts
1028             cast(double)iPos[8] / cast(double)characterWidth
1029         ];        
1030 
1031         // Now dump it into the dictionary
1032         fontObject.map[value] = glPositions;
1033     }
1034 }
1035 
1036 
1037 
1038 
1039 
1040 //! ========================= END GRAPICS ENCODING ================================ 
1041 
1042 
1043 //* ========================== BEGIN JSON DECODING ==================================
1044 // Run through the required data to assemble a font object
1045 private void parseJson(ref RazorFont fontObject, const string jsonLocation) {
1046     void[] rawData = read(jsonLocation);
1047     string jsonString = cast(string)rawData;
1048     JSONValue jsonData = parseJSON(jsonString);
1049 
1050     foreach (string key,JSONValue value; jsonData.objectNoRef) {
1051         switch(key) {
1052             case "pallet_width": {
1053                 assert(value.type == JSONType.integer);
1054                 fontObject.palletWidth = cast(int)value.integer;
1055                 break;
1056             }
1057             case "pallet_height": {
1058                 assert(value.type == JSONType.integer);
1059                 fontObject.palletHeight = cast(int)value.integer;
1060                 break;
1061             }
1062             case "border": {
1063                 assert(value.type == JSONType.integer);
1064                 fontObject.border = cast(int)value.integer;
1065                 break;
1066             }
1067             case "rows": {
1068                 assert(value.type == JSONType.integer);
1069                 fontObject.rows = cast(int)value.integer;
1070                 break;
1071             }
1072             case "character_width": {
1073                 assert(value.type == JSONType.integer);
1074                 fontObject.characterWidth = cast(int)value.integer;
1075                 break;
1076             }
1077             case "charactert_height": {
1078                 assert(value.type == JSONType.integer);
1079                 fontObject.charactertHeight = cast(int)value.integer;
1080                 break;
1081             }
1082             case "character_map": {
1083                 assert(value.type == JSONType..string);
1084                 fontObject.rawMap = value.str;
1085                 break;
1086             }
1087             default: // Unknown
1088         }
1089     }
1090 }
1091 
1092 
1093 //!============================ END JSON DECODING ==================================
1094 
1095 //* ========================== BEGIN API AGNOSTIC CALLS ============================
1096 // Attempts to automate the api RAW call
1097 private void tryCallingRAWApi(string fileLocation) {
1098     if (renderTargetAPICallRAW is null) {
1099         return;
1100     }
1101 
1102     // Use ADR's awesome framework library to convert the png into a raw data stream.
1103     TrueColorImage tempImageObject = readPng(fileLocation).getAsTrueColorImage();
1104 
1105     const int width = tempImageObject.width();
1106     const int height = tempImageObject.height();
1107 
1108     renderTargetAPICallRAW(tempImageObject.imageData.bytes, width, height);
1109 }
1110 
1111 // Attemps to automate the api String call
1112 private void tryCallingStringApi(string fileLocation) {
1113     if (renderTargetAPICallString is null) {
1114         return;
1115     }
1116     
1117     renderTargetAPICallString(fileLocation);
1118 }
1119 
1120 //! ======================= END API AGNOSTIC CALLS ================================
1121 
1122 //* ===================== BEGIN ETC FUNCTIONS ===============================
1123 
1124 
1125 // Makes sure there's data where there should be
1126 private void checkFilesExist(string pngLocation, string jsonLocation) {
1127     if (!exists(pngLocation)) {
1128         throw new Exception("Razor Font: " ~ pngLocation ~ " does not exist!");
1129     }
1130 
1131     if (!exists(jsonLocation)) {
1132         throw new Exception("Razor Font: " ~ jsonLocation ~ " does not exist!");
1133     }
1134 }
1135 
1136 //! ===================== END ETC FUNCTIONS =====================================