1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  *
24  *
25  * DSFML is based on SFML (Copyright Laurent Gomila)
26  */
27 
28 /**
29  * $(U Text) is a drawable class that allows one to easily display some text
30  * with a custom style and color on a render target.
31  *
32  * It inherits all the functions from $(TRANSFORMABLE_LINK): position, rotation,
33  * scale, origin. It also adds text-specific properties such as the font to use,
34  * the character size, the font style (bold, italic, underlined), the global
35  * color and the text to display of course. It also provides convenience
36  * functions to calculate the graphical size of the text, or to get the global
37  * position of a given character.
38  *
39  * $(U Text) works in combination with the $(FONT_LINK) class, which loads and
40  * provides the glyphs (visual characters) of a given font.
41  *
42  * The separation of $(FONT_LINK) and $(U Text) allows more flexibility and
43  * better performances: indeed a $(FONT_LINK) is a heavy resource, and any
44  * operation on it is slow (often too slow for real-time applications). On the
45  * other side, a $(U Text) is a lightweight object which can combine the glyphs
46  * data and metrics of a $(FONT_LINK) to display any text on a render target.
47  *
48  * It is important to note that the $(U Text) instance doesn't copy the font
49  * that it uses, it only keeps a reference to it. Thus, a $(FONT_LINK) must not
50  * be destructed while it is used by a $(U Text).
51  *
52  * See also the note on coordinates and undistorted rendering in
53  * $(TRANSFORMABLE_LINK).
54  *
55  * example:
56  * ---
57  * // Declare and load a font
58  * auto font = new Font();
59  * font.loadFromFile("arial.ttf");
60  *
61  * // Create a text
62  * auto text = new Text("hello", font);
63  * text.setCharacterSize(30);
64  * text.setStyle(Text.Style.Bold);
65  * text.setColor(Color.Red);
66  *
67  * // Draw it
68  * window.draw(text);
69  * ---
70  *
71  * See_Also:
72  * $(FONT_LINK), $(TRANSFORMABLE_LINK)
73  */
74 module nudsfml.graphics.text;
75 
76 import nudsfml.graphics.font;
77 import nudsfml.graphics.glyph;
78 import nudsfml.graphics.color;
79 import nudsfml.graphics.rect;
80 import nudsfml.graphics.transformable;
81 import nudsfml.graphics.drawable;
82 import nudsfml.graphics.texture;
83 import nudsfml.graphics.vertexarray;
84 import nudsfml.graphics.vertex;
85 import nudsfml.graphics.rendertarget;
86 import nudsfml.graphics.renderstates;
87 import nudsfml.graphics.primitivetype;
88 
89 import nudsfml.system.vector2;
90 
91 /**
92  * Graphical text that can be drawn to a render target.
93  */
94 class Text : Drawable, Transformable
95 {
96     /// Enumeration of the string drawing styles.
97     enum Style {
98         /// Regular characters, no style
99         Regular = 0,
100         /// Bold characters
101         Bold = 1 << 0,
102         /// Italic characters
103         Italic = 1 << 1,
104         /// Underlined characters
105         Underlined = 1 << 2,
106         /// Strike through characters
107         StrikeThrough = 1 << 3
108     }
109 
110     mixin NormalTransformable;
111 
112     private
113     {
114         dchar[] m_string;
115         Font m_font;
116         uint m_characterSize;
117         Style m_style;
118         Color m_fillColor;
119         Color m_outlineColor;
120         float m_outlineThickness;
121         VertexArray m_vertices;
122         VertexArray m_outlineVertices;
123         FloatRect m_bounds;
124         bool m_geometryNeedUpdate;
125 
126         //helper function to copy input string into character buffer
127         void stringCopy(T)(const(T)[] str)
128         if (is(T == dchar)||is(T == wchar)||is(T == char)) {
129             import std.utf: byDchar;
130 
131             //make a conservative estimate on how much room we'll need
132             m_string.reserve(dchar.sizeof * str.length);
133             m_string.length = 0;
134 
135             foreach(dchar c; str.byDchar())
136                 m_string~=c;
137         }
138     }
139 
140     /**
141      * Default constructor
142      *
143      * Creates an empty text.
144      */
145     this() {
146         m_characterSize = 30;
147         m_style = Style.Regular;
148         m_fillColor = Color(255,255,255);
149         m_outlineColor = Color(0,0,0);
150         m_outlineThickness = 0;
151         m_vertices = new VertexArray(PrimitiveType.Triangles);
152         m_outlineVertices = new VertexArray(PrimitiveType.Triangles);
153         m_bounds = FloatRect();
154         m_geometryNeedUpdate = false;
155     }
156 
157     /**
158      * Construct the text from a string, font and size
159      *
160      * Note that if the used font is a bitmap font, it is not scalable, thus not
161      * all requested sizes will be available to use. This needs to be taken into
162      * consideration when setting the character size. If you need to display
163      * text of a certain size, make sure the corresponding bitmap font that
164      * supports that size is used.
165      *
166      * Params:
167      *	text          = Text assigned to the string
168      *	font          = Font used to draw the string
169      *	characterSize = Base size of characters, in pixels
170      *
171      * //deprecated: Use the constructor that takes a 'const(dchar)[]' instead.
172      */
173     //deprecated("Use the constructor that takes a 'const(dchar)[]' instead.")
174     this(T)(const(T)[] text, Font font, uint characterSize = 30)
175         if (is(T == dchar)||is(T == wchar)||is(T == char))
176     {
177         stringCopy(text);
178         m_font = font;
179         m_characterSize = characterSize;
180         m_style = Style.Regular;
181         m_fillColor = Color(255,255,255);
182         m_outlineColor = Color(0,0,0);
183         m_outlineThickness = 0;
184         m_vertices = new VertexArray(PrimitiveType.Triangles);
185         m_outlineVertices = new VertexArray(PrimitiveType.Triangles);
186         m_bounds = FloatRect();
187         m_geometryNeedUpdate = true;
188     }
189 
190     /**
191      * Construct the text from a string, font and size
192      *
193      * Note that if the used font is a bitmap font, it is not scalable, thus not
194      * all requested sizes will be available to use. This needs to be taken into
195      * consideration when setting the character size. If you need to display
196      * text of a certain size, make sure the corresponding bitmap font that
197      * supports that size is used.
198      *
199      * Params:
200      *	text          = Text assigned to the string
201      *	font          = Font used to draw the string
202      *	characterSize = Base size of characters, in pixels
203      */
204     this(T)(const(dchar)[] text, Font font, uint characterSize = 30)
205     {
206         stringCopy(text);
207         m_font = font;
208         m_characterSize = characterSize;
209         m_style = Style.Regular;
210         m_fillColor = Color(255,255,255);
211         m_outlineColor = Color(0,0,0);
212         m_outlineThickness = 0;
213         m_vertices = new VertexArray(PrimitiveType.Triangles);
214         m_outlineVertices = new VertexArray(PrimitiveType.Triangles);
215         m_bounds = FloatRect();
216         m_geometryNeedUpdate = true;
217     }
218 
219     /// Destructor.
220     ~this()
221     {
222         //import nudsfml.system.config;
223         //mixin(destructorOutput);
224     }
225 
226     @property
227     {
228         /**
229          * The character size in pixels.
230          *
231          * The default size is 30.
232          *
233          * Note that if the used font is a bitmap font, it is not scalable, thus
234          * not all requested sizes will be available to use. This needs to be
235          * taken into consideration when setting the character size. If you need
236          * to display text of a certain size, make sure the corresponding bitmap
237          * font that supports that size is used.
238          */
239         uint characterSize(uint size) {
240             if(m_characterSize != size) {
241                 m_characterSize = size;
242                 m_geometryNeedUpdate = true;
243             }
244             return m_characterSize;
245         }
246 
247         /// ditto
248         uint characterSize() const {
249             return m_characterSize;
250         }
251     }
252 
253     /**
254      * Set the character size.
255      *
256      * The default size is 30.
257      *
258      * Note that if the used font is a bitmap font, it is not scalable, thus
259      * not all requested sizes will be available to use. This needs to be
260      * taken into consideration when setting the character size. If you need
261      * to display text of a certain size, make sure the corresponding bitmap
262      * font that supports that size is used.
263      *
264      * Params:
265      * 		size	= New character size, in pixels.
266      *
267      * //deprecated: Use the 'characterSize' property instead.
268      */
269     //deprecated("Use the 'characterSize' property instead.")
270     void setCharacterSize(uint size){
271         characterSize = size;
272     }
273 
274     /**
275      * Get the character size.
276      *
277      * Returns: Size of the characters, in pixels.
278      *
279      * //deprecated: Use the 'characterSize' property instead.
280      */
281     //deprecated("Use the 'characterSize' property instead.")
282     uint getCharacterSize() const {
283         return characterSize;
284     }
285 
286     /**
287      * Set the fill color of the text.
288      *
289      * By default, the text's color is opaque white.
290      *
291      * Params:
292      * 		color	= New color of the text.
293      *
294      * //deprecated: Use the 'fillColor' or 'outlineColor' properties instead.
295      */
296     //deprecated("Use the 'fillColor' or 'outlineColor' properties instead.")
297     void setColor(Color color) {
298         fillColor = color;
299     }
300 
301     /**
302      * Get the fill color of the text.
303      *
304      * Returns: Fill color of the text.
305      *
306      * //deprecated: Use the 'fillColor' or 'outlineColor' properties instead.
307      */
308     //deprecated("Use the 'fillColor' or 'outlineColor' properties instead.")
309     Color getColor() const {
310         return fillColor;
311     }
312 
313     @property
314     {
315         /**
316         * The fill color of the text.
317         *
318         * By default, the text's fill color is opaque white. Setting the fill
319         * color to a transparent color with an outline will cause the outline to
320         * be displayed in the fill area of the text.
321         */
322         Color fillColor(Color color) {
323             if(m_fillColor != color) {
324                 m_fillColor = color;
325 
326                 // Change vertex colors directly, no need to update whole geometry
327                 // (if geometry is updated anyway, we can skip this step)
328                 if(!m_geometryNeedUpdate) {
329                     for(int i = 0; i < m_vertices.getVertexCount(); ++i) {
330                         m_vertices[i].color = m_fillColor;
331                     }
332                 }
333             }
334 
335             return m_fillColor;
336         }
337 
338         /// ditto
339         Color fillColor() const {
340             return m_fillColor;
341         }
342     }
343 
344     @property
345     {
346         /**
347         * The outline color of the text.
348         *
349         * By default, the text's outline color is opaque black.
350         */
351         Color outlineColor(Color color) {
352             if(m_outlineColor != color){
353                 m_outlineColor = color;
354 
355                 // Change vertex colors directly, no need to update whole geometry
356                 // (if geometry is updated anyway, we can skip this step)
357                 if(!m_geometryNeedUpdate) {
358                     for(int i = 0; i < m_outlineVertices.getVertexCount(); ++i) {
359                         m_outlineVertices[i].color = m_outlineColor;
360                     }
361                 }
362             }
363 
364             return m_outlineColor;
365         }
366 
367         /// ditto
368         Color outlineColor() const {
369             return m_outlineColor;
370         }
371     }
372 
373     @property
374     {
375         /**
376         * The outline color of the text.
377         *
378         * By default, the text's outline color is opaque black.
379         */
380         float outlineThickness(float thickness) {
381             if(m_outlineThickness != thickness) {
382                 m_outlineThickness = thickness;
383                 m_geometryNeedUpdate = true;
384             }
385 
386             return m_outlineThickness;
387         }
388 
389         /// ditto
390         float outlineThickness() const {
391             return m_outlineThickness;
392         }
393     }
394 
395     @property {
396         /**
397         * The text's font.
398         */
399         const(Font) font(Font newFont) {
400             if (m_font !is newFont){
401                 m_font = newFont;
402                 m_geometryNeedUpdate = true;
403             }
404 
405             return m_font;
406         }
407 
408         /// ditto
409         const(Font) font() const{
410             return m_font;
411         }
412     }
413 
414     /**
415      * Set the text's font.
416      *
417      * Params:
418      * 		newFont	= New font
419      *
420      * //deprecated: Use the 'font' property instead.
421      */
422     //deprecated("Use the 'font' property instead.")
423     void setFont(Font newFont)
424     {
425         font = newFont;
426     }
427 
428     /**
429      * Get thet text's font.
430      *
431      * Returns: Text's font.
432      *
433      * //deprecated: Use the 'font' property instead.
434      */
435     //deprecated("Use the 'font' property instead.")
436     const(Font) getFont() const
437     {
438         return font;
439     }
440 
441     /**
442      * Get the global bounding rectangle of the entity.
443      *
444      * The returned rectangle is in global coordinates, which means that it
445      * takes in account the transformations (translation, rotation, scale, ...)
446      * that are applied to the entity. In other words, this function returns the
447      * bounds of the sprite in the global 2D world's coordinate system.
448      *
449      * Returns: Global bounding rectangle of the entity.
450      */
451     @property FloatRect globalBounds()
452     {
453         return getTransform().transformRect(localBounds);
454     }
455 
456     /**
457      * Get the global bounding rectangle of the entity.
458      *
459      * The returned rectangle is in global coordinates, which means that it
460      * takes in account the transformations (translation, rotation, scale, ...)
461      * that are applied to the entity. In other words, this function returns the
462      * bounds of the sprite in the global 2D world's coordinate system.
463      *
464      * Returns: Global bounding rectangle of the entity.
465      *
466      * //deprecated: Use the 'globalBounds' property instead.
467      */
468     //deprecated("Use the 'globalBounds' property instead.")
469     FloatRect getGlobalBounds()
470     {
471         return globalBounds;
472     }
473 
474     /**
475      * Get the local bounding rectangle of the entity.
476      *
477      * The returned rectangle is in local coordinates, which means that it
478      * ignores the transformations (translation, rotation, scale, ...) that are
479      * applied to the entity. In other words, this function returns the bounds
480      * of the entity in the entity's coordinate system.
481      *
482      * Returns: Local bounding rectangle of the entity.
483      */
484     @property FloatRect localBounds() {
485         ensureGeometryUpdate();
486         return m_bounds;
487     }
488 
489     /**
490      * Get the local bounding rectangle of the entity.
491      *
492      * The returned rectangle is in local coordinates, which means that it
493      * ignores the transformations (translation, rotation, scale, ...) that are
494      * applied to the entity. In other words, this function returns the bounds
495      * of the entity in the entity's coordinate system.
496      *
497      * Returns: Local bounding rectangle of the entity.
498      *
499      * //deprecated: Use the 'globalBounds' property instead.
500      */
501     //deprecated("Use the 'localBounds' property instead.")
502     FloatRect getLocalBounds(){
503         return localBounds;
504     }
505 
506     @property
507     {
508         /**
509          * The text's style.
510          *
511          * You can pass a combination of one or more styles, for example
512          * Style.Bold | Text.Italic.
513          */
514         Style style(Style newStyle)
515         {
516             if(m_style != newStyle)
517             {
518                 m_style = newStyle;
519                 m_geometryNeedUpdate = true;
520             }
521 
522             return m_style;
523         }
524 
525         /// ditto
526         Style style() const
527         {
528             return m_style;
529         }
530     }
531 
532     /**
533      * Set the text's style.
534      *
535      * You can pass a combination of one or more styles, for example
536      * Style.Bold | Text.Italic.
537      *
538      * Params:
539      *      newStyle = New style
540      *
541      * //deprecated: Use the 'style' property instead.
542      */
543     //deprecated("Use the 'style' property instead.")
544     void setStyle(Style newStyle)
545     {
546         style = newStyle;
547     }
548 
549     /**
550      * Get the text's style.
551      *
552      * Returns: Text's style.
553      *
554      * //deprecated: Use the 'style' property instead.
555      */
556     //deprecated("Use the 'style' property instead.")
557     Style getStyle() const
558     {
559         return style;
560     }
561 
562     @property
563     {
564         /**
565          * The text's string.
566          *
567          * A text's string is empty by default.
568          */
569         
570         
571         /*const(dchar)[] string(const(dchar)[] str)
572         {
573             // Because of the conversion, assume the text is new
574             stringCopy(str);
575             m_geometryNeedUpdate = true;
576             return m_string;
577         }
578         /// ditto
579         const(dchar)[] string() const
580         {
581             return m_string;
582         }*/
583 
584         const(T)[] string(T)(const(T)[] text)
585         if (is(T == dchar)||is(T == wchar)||is(T == char))
586         {
587             // Because of the conversion, assume the text is new
588             stringCopy(text);
589             m_geometryNeedUpdate = true;
590 
591             return string!T();
592         }
593 
594         const(T)[] string(T=char)() const 
595         if(is(T == dchar)||is(T==wchar)||is(T==char))
596         {
597             import std.utf: toUTF8, toUTF16, toUTF32;
598 
599             static if(is(T == char)){
600                 return toUTF8(m_string);
601             } else static if(is( T == wchar)){
602                 return toUTF16(m_string);
603             } else static if(is(T == dchar)){
604                 return toUTF32(m_string);
605             }
606         }
607 
608     }
609 
610     /**
611      * Set the text's string.
612      *
613      * A text's string is empty by default.
614      *
615      * Params:
616      * 		text	= New string
617      *
618      * //deprecated: Use the 'string' property instead.
619      */
620     //deprecated("Use the 'string' property instead.")
621     void setString(T)(const(T)[] text)
622         if (is(T == dchar)||is(T == wchar)||is(T == char))
623     {
624         // Because of the conversion, assume the text is new
625         stringCopy(text);
626         m_geometryNeedUpdate = true;
627     }
628 
629     /**
630      * Get a copy of the text's string.
631      *
632      * Returns: a copy of the text's string.
633      *
634      * //deprecated: Use the 'string' property instead.
635      */
636     //deprecated("Use the 'string' property instead.")
637     const(T)[] getString(T=char)() const
638         if (is(T == dchar)||is(T == wchar)||is(T == char))
639     {
640         import std.utf: toUTF8, toUTF16, toUTF32;
641 
642         static if(is(T == char))
643 		    return toUTF8(m_string);
644 	    else static if(is(T == wchar))
645 		    return toUTF16(m_string);
646 	    else static if(is(T == dchar))
647 		    return toUTF32(m_string);
648     }
649 
650     /**
651      * Draw the object to a render target.
652      *
653      * Params:
654      *  		renderTarget =	Render target to draw to
655      *  		renderStates =	Current render states
656      */
657     void draw(RenderTarget renderTarget, RenderStates renderStates)
658     {
659         if (m_font !is null)
660         {
661             ensureGeometryUpdate();
662 
663             renderStates.transform *= getTransform();
664             renderStates.texture =  m_font.getTexture(m_characterSize);
665 
666             // Only draw the outline if there is something to draw
667             if (m_outlineThickness != 0)
668                 renderTarget.draw(m_outlineVertices, renderStates);
669 
670             renderTarget.draw(m_vertices, renderStates);
671         }
672     }
673 
674     /**
675      * Return the position of the index-th character.
676      *
677      * This function computes the visual position of a character from its index
678      * in the string. The returned position is in global coordinates
679      * (translation, rotation, scale and origin are applied). If index is out of
680      * range, the position of the end of the string is returned.
681      *
682      * Params:
683      * 		index	= Index of the character
684      *
685      * Returns: Position of the character.
686      */
687     Vector2f findCharacterPos(size_t index)
688     {
689         // Make sure that we have a valid font
690         if(m_font is null)
691         {
692             return Vector2f(0,0);
693         }
694 
695         // Adjust the index if it's out of range
696         if(index > m_string.length)
697         {
698             index = m_string.length;
699         }
700 
701         // Precompute the variables needed by the algorithm
702         bool bold  = (m_style & Style.Bold) != 0;
703         float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance);
704         float vspace = cast(float)(m_font.getLineSpacing(m_characterSize));
705 
706         // Compute the position
707         Vector2f position;
708         dchar prevChar = 0;
709         for (size_t i = 0; i < index; ++i)
710         {
711             dchar curChar = m_string[i];
712 
713             // Apply the kerning offset
714             position.x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize));
715             prevChar = curChar;
716 
717             // Handle special characters
718             switch (curChar)
719             {
720                 case ' ' : position.x += hspace; continue;
721                 case '\t' : position.x += hspace * 4; continue;
722                 case '\n' : position.y += vspace; position.x = 0; continue;
723                 case '\v' : position.y += vspace * 4; continue;
724                 default : break;
725             }
726 
727             // For regular characters, add the advance offset of the glyph
728             position.x += cast(float)(m_font.getGlyph(curChar, m_characterSize, bold).advance);
729         }
730 
731         // Transform the position to global coordinates
732         position = getTransform().transformPoint(position);
733 
734         return position;
735     }
736 
737 private:
738     void ensureGeometryUpdate()
739     {
740         import std.math: floor;
741         import std.algorithm: max, min;
742 
743         // Add an underline or strikethrough line to the vertex array
744         static void addLine(VertexArray vertices, float lineLength,
745                             float lineTop, ref const(Color) color, float offset,
746                             float thickness, float outlineThickness = 0)
747         {
748             float top = floor(lineTop + offset - (thickness / 2) + 0.5f);
749             float bottom = top + floor(thickness + 0.5f);
750 
751             vertices.append(Vertex(Vector2f(-outlineThickness,             top    - outlineThickness), color, Vector2f(1, 1)));
752             vertices.append(Vertex(Vector2f(lineLength + outlineThickness, top    - outlineThickness), color, Vector2f(1, 1)));
753             vertices.append(Vertex(Vector2f(-outlineThickness,             bottom + outlineThickness), color, Vector2f(1, 1)));
754             vertices.append(Vertex(Vector2f(-outlineThickness,             bottom + outlineThickness), color, Vector2f(1, 1)));
755             vertices.append(Vertex(Vector2f(lineLength + outlineThickness, top    - outlineThickness), color, Vector2f(1, 1)));
756             vertices.append(Vertex(Vector2f(lineLength + outlineThickness, bottom + outlineThickness), color, Vector2f(1, 1)));
757         }
758 
759         // Add a glyph quad to the vertex array
760         static void addGlyphQuad(VertexArray vertices, Vector2f position,
761                                  ref const(Color) color, ref const(Glyph) glyph,
762                                  float italic, float outlineThickness = 0)
763         {
764             float left   = glyph.bounds.left;
765             float top    = glyph.bounds.top;
766             float right  = glyph.bounds.left + glyph.bounds.width;
767             float bottom = glyph.bounds.top  + glyph.bounds.height;
768 
769             float u1 = glyph.textureRect.left;
770             float v1 = glyph.textureRect.top;
771             float u2 = glyph.textureRect.left + glyph.textureRect.width;
772             float v2 = glyph.textureRect.top  + glyph.textureRect.height;
773 
774             vertices.append(Vertex(Vector2f(position.x + left  - italic * top    - outlineThickness, position.y + top    - outlineThickness), color, Vector2f(u1, v1)));
775             vertices.append(Vertex(Vector2f(position.x + right - italic * top    - outlineThickness, position.y + top    - outlineThickness), color, Vector2f(u2, v1)));
776             vertices.append(Vertex(Vector2f(position.x + left  - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u1, v2)));
777             vertices.append(Vertex(Vector2f(position.x + left  - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u1, v2)));
778             vertices.append(Vertex(Vector2f(position.x + right - italic * top    - outlineThickness, position.y + top    - outlineThickness), color, Vector2f(u2, v1)));
779             vertices.append(Vertex(Vector2f(position.x + right - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u2, v2)));
780         }
781 
782         // Do nothing, if geometry has not changed
783         if (!m_geometryNeedUpdate)
784             return;
785 
786         // Mark geometry as updated
787         m_geometryNeedUpdate = false;
788 
789         // Clear the previous geometry
790         m_vertices.clear();
791         m_outlineVertices.clear();
792         m_bounds = FloatRect();
793 
794         // No font or text: nothing to draw
795         if (!m_font || m_string.length == 0)
796             return;
797 
798         // Compute values related to the text style
799         bool  bold               = (m_style & Style.Bold) != 0;
800         bool  underlined         = (m_style & Style.Underlined) != 0;
801         bool  strikeThrough      = (m_style & Style.StrikeThrough) != 0;
802         float italic             = (m_style & Style.Italic) ? 0.208f : 0.0f; // 12 degrees
803         float underlineOffset    = m_font.getUnderlinePosition(m_characterSize);
804         float underlineThickness = m_font.getUnderlineThickness(m_characterSize);
805 
806         // Compute the location of the strike through dynamically
807         // We use the center point of the lowercase 'x' glyph as the reference
808         // We reuse the underline thickness as the thickness of the strike through as well
809         FloatRect xBounds = m_font.getGlyph('x', m_characterSize, bold).bounds;
810         float strikeThroughOffset = xBounds.top + xBounds.height / 2.0f;
811 
812         // Precompute the variables needed by the algorithm
813         float hspace = m_font.getGlyph(' ', m_characterSize, bold).advance;
814         float vspace = m_font.getLineSpacing(m_characterSize);
815         float x      = 0.0f;
816         float y      = cast(float)m_characterSize;
817 
818         // Create one quad for each character
819         float minX = cast(float)m_characterSize;
820         float minY = cast(float)m_characterSize;
821         float maxX = 0.0f;
822         float maxY = 0.0f;
823         dchar prevChar = '\0';
824         for (size_t i = 0; i < m_string.length; ++i)
825         {
826             dchar curChar = m_string[i];
827 
828             // Apply the kerning offset
829             x += m_font.getKerning(prevChar, curChar, m_characterSize);
830             prevChar = curChar;
831 
832             // If we're using the underlined style and there's a new line, draw a line
833             if (underlined && (curChar == '\n'))
834             {
835                 addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
836 
837                 if (m_outlineThickness != 0)
838                     addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness);
839             }
840 
841             // If we're using the strike through style and there's a new line, draw a line across all characters
842             if (strikeThrough && (curChar == '\n'))
843             {
844                 addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness);
845 
846                 if (m_outlineThickness != 0)
847                     addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness);
848             }
849 
850             // Handle special characters
851             if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n'))
852             {
853                 // Update the current bounds (min coordinates)
854                 minX = min(minX, x);
855                 minY = min(minY, y);
856 
857                 switch (curChar)
858                 {
859                     case ' ':  x += hspace;        break;
860                     case '\t': x += hspace * 4;    break;
861                     case '\n': y += vspace; x = 0; break;
862                     default : break;
863                 }
864 
865                 // Update the current bounds (max coordinates)
866                 maxX = max(maxX, x);
867                 maxY = max(maxY, y);
868 
869                 // Next glyph, no need to create a quad for whitespace
870                 continue;
871             }
872 
873             // Apply the outline
874             if (m_outlineThickness != 0)
875             {
876                 Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold, m_outlineThickness);
877 
878                 float left   = glyph.bounds.left;
879                 float top    = glyph.bounds.top;
880                 float right  = glyph.bounds.left + glyph.bounds.width;
881                 float bottom = glyph.bounds.top  + glyph.bounds.height;
882 
883                 // Add the outline glyph to the vertices
884                 addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph, italic, m_outlineThickness);
885 
886                 // Update the current bounds with the outlined glyph bounds
887                 minX = min(minX, x + left   - italic * bottom - m_outlineThickness);
888                 maxX = max(maxX, x + right  - italic * top    - m_outlineThickness);
889                 minY = min(minY, y + top    - m_outlineThickness);
890                 maxY = max(maxY, y + bottom - m_outlineThickness);
891             }
892 
893             // Extract the current glyph's description
894             const Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold);
895 
896             // Add the glyph to the vertices
897             addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italic);
898 
899             // Update the current bounds with the non outlined glyph bounds
900             if (m_outlineThickness == 0)
901             {
902                 float left   = glyph.bounds.left;
903                 float top    = glyph.bounds.top;
904                 float right  = glyph.bounds.left + glyph.bounds.width;
905                 float bottom = glyph.bounds.top  + glyph.bounds.height;
906 
907                 minX = min(minX, x + left  - italic * bottom);
908                 maxX = max(maxX, x + right - italic * top);
909                 minY = min(minY, y + top);
910                 maxY = max(maxY, y + bottom);
911             }
912 
913             // Advance to the next character
914             x += glyph.advance;
915         }
916 
917         // If we're using the underlined style, add the last line
918         if (underlined && (x > 0))
919         {
920             addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
921 
922             if (m_outlineThickness != 0)
923                 addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness);
924         }
925 
926         // If we're using the strike through style, add the last line across all characters
927         if (strikeThrough && (x > 0))
928         {
929             addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness);
930 
931             if (m_outlineThickness != 0)
932                 addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness);
933         }
934 
935         // Update the bounding rectangle
936         m_bounds.left = minX;
937         m_bounds.top = minY;
938         m_bounds.width = maxX - minX;
939         m_bounds.height = maxY - minY;
940     }
941 }
942 
943 unittest
944 {
945     import std.stdio;
946     import nudsfml.graphics.rendertexture;
947 
948     writeln("Unit test for Text");
949 
950     auto renderTexture = new RenderTexture();
951 
952     renderTexture.create(400,200);
953 
954     auto font = new Font();
955     assert(font.loadFromFile("data/FiraMono-Regular.ttf"));
956 
957     Text regular = new Text("Regular", font, 20);
958     Text bold = new Text("Bold", font, 20);
959     Text italic = new Text("Italic", font, 20);
960     Text boldItalic = new Text("Bold Italic", font, 20);
961     Text strikeThrough = new Text("Strike Through", font, 20);
962     Text italicStrikeThrough = new Text("Italic Strike Through", font, 20);
963     Text boldStrikeThrough = new Text("Bold Strike Through", font, 20);
964     Text boldItalicStrikeThrough = new Text("Bold Italic Strike Through", font, 20);
965     Text outlined = new Text("Outlined", font, 20);
966     Text outlinedBoldItalicStrikeThrough = new Text("Outlined Bold Italic Strike Through", font, 20);
967 
968     bold.style = Text.Style.Bold;
969     bold.position = Vector2f(0,20);
970 
971     italic.style = Text.Style.Italic;
972     italic.position = Vector2f(0,40);
973 
974     boldItalic.style = Text.Style.Bold | Text.Style.Italic;
975     boldItalic.position = Vector2f(0,60);
976 
977     strikeThrough.style = Text.Style.StrikeThrough;
978     strikeThrough.position = Vector2f(0,80);
979 
980     italicStrikeThrough.style = Text.Style.Italic | Text.Style.StrikeThrough;
981     italicStrikeThrough.position = Vector2f(0,100);
982 
983     boldStrikeThrough.style = Text.Style.Bold | Text.Style.StrikeThrough;
984     boldStrikeThrough.position = Vector2f(0,120);
985 
986     boldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough;
987     boldItalicStrikeThrough.position = Vector2f(0,140);
988 
989     outlined.outlineColor = Color.Red;
990     outlined.outlineThickness = 0.5f;
991     outlined.position = Vector2f(0,160);
992 
993     outlinedBoldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough;
994     outlinedBoldItalicStrikeThrough.outlineColor = Color.Red;
995     outlinedBoldItalicStrikeThrough.outlineThickness = 0.5f;
996     outlinedBoldItalicStrikeThrough.position = Vector2f(0,180);
997 
998     writeln(regular..string);
999     writeln(bold..string);
1000     bold..string = bold..string;
1001     writeln(italic..string);
1002     writeln(boldItalic..string);
1003     writeln(strikeThrough..string);
1004     writeln(italicStrikeThrough..string);
1005     writeln(boldStrikeThrough..string);
1006     writeln(boldItalicStrikeThrough..string);
1007     writeln(outlined..string);
1008     writeln(outlinedBoldItalicStrikeThrough..string);
1009 
1010 
1011     renderTexture.clear();
1012 
1013     renderTexture.draw(regular);
1014     renderTexture.draw(bold);
1015     renderTexture.draw(italic);
1016     renderTexture.draw(boldItalic);
1017     renderTexture.draw(strikeThrough);
1018     renderTexture.draw(italicStrikeThrough);
1019     renderTexture.draw(boldStrikeThrough);
1020     renderTexture.draw(boldItalicStrikeThrough);
1021     renderTexture.draw(outlined);
1022     renderTexture.draw(outlinedBoldItalicStrikeThrough);
1023 
1024     renderTexture.display();
1025 
1026     //grab that texture for usage
1027     auto texture = renderTexture.getTexture();
1028 
1029     writeln( texture.copyToImage().saveToFile("Text.png")) ;
1030 
1031     auto fontTexture = font.getTexture(20);
1032     writeln(fontTexture.copyToImage().saveToFile("Font.png"));
1033 
1034     writeln();
1035 
1036 }