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() const
485     {
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() const
503     {
504         return localBounds;
505     }
506 
507     @property
508     {
509         /**
510          * The text's style.
511          *
512          * You can pass a combination of one or more styles, for example
513          * Style.Bold | Text.Italic.
514          */
515         Style style(Style newStyle)
516         {
517             if(m_style != newStyle)
518             {
519                 m_style = newStyle;
520                 m_geometryNeedUpdate = true;
521             }
522 
523             return m_style;
524         }
525 
526         /// ditto
527         Style style() const
528         {
529             return m_style;
530         }
531     }
532 
533     /**
534      * Set the text's style.
535      *
536      * You can pass a combination of one or more styles, for example
537      * Style.Bold | Text.Italic.
538      *
539      * Params:
540      *      newStyle = New style
541      *
542      * //deprecated: Use the 'style' property instead.
543      */
544     //deprecated("Use the 'style' property instead.")
545     void setStyle(Style newStyle)
546     {
547         style = newStyle;
548     }
549 
550     /**
551      * Get the text's style.
552      *
553      * Returns: Text's style.
554      *
555      * //deprecated: Use the 'style' property instead.
556      */
557     //deprecated("Use the 'style' property instead.")
558     Style getStyle() const
559     {
560         return style;
561     }
562 
563     @property
564     {
565         /**
566          * The text's string.
567          *
568          * A text's string is empty by default.
569          */
570         
571         
572         /*const(dchar)[] string(const(dchar)[] str)
573         {
574             // Because of the conversion, assume the text is new
575             stringCopy(str);
576             m_geometryNeedUpdate = true;
577             return m_string;
578         }
579         /// ditto
580         const(dchar)[] string() const
581         {
582             return m_string;
583         }*/
584 
585         const(T)[] string(T)(const(T)[] text)
586         if (is(T == dchar)||is(T == wchar)||is(T == char))
587         {
588             // Because of the conversion, assume the text is new
589             stringCopy(text);
590             m_geometryNeedUpdate = true;
591 
592             return string!T();
593         }
594 
595         const(T)[] string(T=char)() const 
596         if(is(T == dchar)||is(T==wchar)||is(T==char))
597         {
598             import std.utf: toUTF8, toUTF16, toUTF32;
599 
600             static if(is(T == char)){
601                 return toUTF8(m_string);
602             } else static if(is( T == wchar)){
603                 return toUTF16(m_string);
604             } else static if(is(T == dchar)){
605                 return toUTF32(m_string);
606             }
607         }
608 
609     }
610 
611     /**
612      * Set the text's string.
613      *
614      * A text's string is empty by default.
615      *
616      * Params:
617      * 		text	= New string
618      *
619      * //deprecated: Use the 'string' property instead.
620      */
621     //deprecated("Use the 'string' property instead.")
622     void setString(T)(const(T)[] text)
623         if (is(T == dchar)||is(T == wchar)||is(T == char))
624     {
625         // Because of the conversion, assume the text is new
626         stringCopy(text);
627         m_geometryNeedUpdate = true;
628     }
629 
630     /**
631      * Get a copy of the text's string.
632      *
633      * Returns: a copy of the text's string.
634      *
635      * //deprecated: Use the 'string' property instead.
636      */
637     //deprecated("Use the 'string' property instead.")
638     const(T)[] getString(T=char)() const
639         if (is(T == dchar)||is(T == wchar)||is(T == char))
640     {
641         import std.utf: toUTF8, toUTF16, toUTF32;
642 
643         static if(is(T == char))
644 		    return toUTF8(m_string);
645 	    else static if(is(T == wchar))
646 		    return toUTF16(m_string);
647 	    else static if(is(T == dchar))
648 		    return toUTF32(m_string);
649     }
650 
651     /**
652      * Draw the object to a render target.
653      *
654      * Params:
655      *  		renderTarget =	Render target to draw to
656      *  		renderStates =	Current render states
657      */
658     void draw(RenderTarget renderTarget, RenderStates renderStates)
659     {
660         if (m_font !is null)
661         {
662             ensureGeometryUpdate();
663 
664             renderStates.transform *= getTransform();
665             renderStates.texture =  m_font.getTexture(m_characterSize);
666 
667             // Only draw the outline if there is something to draw
668             if (m_outlineThickness != 0)
669                 renderTarget.draw(m_outlineVertices, renderStates);
670 
671             renderTarget.draw(m_vertices, renderStates);
672         }
673     }
674 
675     /**
676      * Return the position of the index-th character.
677      *
678      * This function computes the visual position of a character from its index
679      * in the string. The returned position is in global coordinates
680      * (translation, rotation, scale and origin are applied). If index is out of
681      * range, the position of the end of the string is returned.
682      *
683      * Params:
684      * 		index	= Index of the character
685      *
686      * Returns: Position of the character.
687      */
688     Vector2f findCharacterPos(size_t index)
689     {
690         // Make sure that we have a valid font
691         if(m_font is null)
692         {
693             return Vector2f(0,0);
694         }
695 
696         // Adjust the index if it's out of range
697         if(index > m_string.length)
698         {
699             index = m_string.length;
700         }
701 
702         // Precompute the variables needed by the algorithm
703         bool bold  = (m_style & Style.Bold) != 0;
704         float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance);
705         float vspace = cast(float)(m_font.getLineSpacing(m_characterSize));
706 
707         // Compute the position
708         Vector2f position;
709         dchar prevChar = 0;
710         for (size_t i = 0; i < index; ++i)
711         {
712             dchar curChar = m_string[i];
713 
714             // Apply the kerning offset
715             position.x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize));
716             prevChar = curChar;
717 
718             // Handle special characters
719             switch (curChar)
720             {
721                 case ' ' : position.x += hspace; continue;
722                 case '\t' : position.x += hspace * 4; continue;
723                 case '\n' : position.y += vspace; position.x = 0; continue;
724                 case '\v' : position.y += vspace * 4; continue;
725                 default : break;
726             }
727 
728             // For regular characters, add the advance offset of the glyph
729             position.x += cast(float)(m_font.getGlyph(curChar, m_characterSize, bold).advance);
730         }
731 
732         // Transform the position to global coordinates
733         position = getTransform().transformPoint(position);
734 
735         return position;
736     }
737 
738 private:
739     void ensureGeometryUpdate()
740     {
741         import std.math: floor;
742         import std.algorithm: max, min;
743 
744         // Add an underline or strikethrough line to the vertex array
745         static void addLine(VertexArray vertices, float lineLength,
746                             float lineTop, ref const(Color) color, float offset,
747                             float thickness, float outlineThickness = 0)
748         {
749             float top = floor(lineTop + offset - (thickness / 2) + 0.5f);
750             float bottom = top + floor(thickness + 0.5f);
751 
752             vertices.append(Vertex(Vector2f(-outlineThickness,             top    - outlineThickness), color, Vector2f(1, 1)));
753             vertices.append(Vertex(Vector2f(lineLength + outlineThickness, top    - outlineThickness), color, Vector2f(1, 1)));
754             vertices.append(Vertex(Vector2f(-outlineThickness,             bottom + outlineThickness), color, Vector2f(1, 1)));
755             vertices.append(Vertex(Vector2f(-outlineThickness,             bottom + outlineThickness), color, Vector2f(1, 1)));
756             vertices.append(Vertex(Vector2f(lineLength + outlineThickness, top    - outlineThickness), color, Vector2f(1, 1)));
757             vertices.append(Vertex(Vector2f(lineLength + outlineThickness, bottom + outlineThickness), color, Vector2f(1, 1)));
758         }
759 
760         // Add a glyph quad to the vertex array
761         static void addGlyphQuad(VertexArray vertices, Vector2f position,
762                                  ref const(Color) color, ref const(Glyph) glyph,
763                                  float italic, float outlineThickness = 0)
764         {
765             float left   = glyph.bounds.left;
766             float top    = glyph.bounds.top;
767             float right  = glyph.bounds.left + glyph.bounds.width;
768             float bottom = glyph.bounds.top  + glyph.bounds.height;
769 
770             float u1 = glyph.textureRect.left;
771             float v1 = glyph.textureRect.top;
772             float u2 = glyph.textureRect.left + glyph.textureRect.width;
773             float v2 = glyph.textureRect.top  + glyph.textureRect.height;
774 
775             vertices.append(Vertex(Vector2f(position.x + left  - italic * top    - outlineThickness, position.y + top    - outlineThickness), color, Vector2f(u1, v1)));
776             vertices.append(Vertex(Vector2f(position.x + right - italic * top    - outlineThickness, position.y + top    - outlineThickness), color, Vector2f(u2, v1)));
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 + left  - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u1, v2)));
779             vertices.append(Vertex(Vector2f(position.x + right - italic * top    - outlineThickness, position.y + top    - outlineThickness), color, Vector2f(u2, v1)));
780             vertices.append(Vertex(Vector2f(position.x + right - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u2, v2)));
781         }
782 
783         // Do nothing, if geometry has not changed
784         if (!m_geometryNeedUpdate)
785             return;
786 
787         // Mark geometry as updated
788         m_geometryNeedUpdate = false;
789 
790         // Clear the previous geometry
791         m_vertices.clear();
792         m_outlineVertices.clear();
793         m_bounds = FloatRect();
794 
795         // No font or text: nothing to draw
796         if (!m_font || m_string.length == 0)
797             return;
798 
799         // Compute values related to the text style
800         bool  bold               = (m_style & Style.Bold) != 0;
801         bool  underlined         = (m_style & Style.Underlined) != 0;
802         bool  strikeThrough      = (m_style & Style.StrikeThrough) != 0;
803         float italic             = (m_style & Style.Italic) ? 0.208f : 0.0f; // 12 degrees
804         float underlineOffset    = m_font.getUnderlinePosition(m_characterSize);
805         float underlineThickness = m_font.getUnderlineThickness(m_characterSize);
806 
807         // Compute the location of the strike through dynamically
808         // We use the center point of the lowercase 'x' glyph as the reference
809         // We reuse the underline thickness as the thickness of the strike through as well
810         FloatRect xBounds = m_font.getGlyph('x', m_characterSize, bold).bounds;
811         float strikeThroughOffset = xBounds.top + xBounds.height / 2.0f;
812 
813         // Precompute the variables needed by the algorithm
814         float hspace = m_font.getGlyph(' ', m_characterSize, bold).advance;
815         float vspace = m_font.getLineSpacing(m_characterSize);
816         float x      = 0.0f;
817         float y      = cast(float)m_characterSize;
818 
819         // Create one quad for each character
820         float minX = cast(float)m_characterSize;
821         float minY = cast(float)m_characterSize;
822         float maxX = 0.0f;
823         float maxY = 0.0f;
824         dchar prevChar = '\0';
825         for (size_t i = 0; i < m_string.length; ++i)
826         {
827             dchar curChar = m_string[i];
828 
829             // Apply the kerning offset
830             x += m_font.getKerning(prevChar, curChar, m_characterSize);
831             prevChar = curChar;
832 
833             // If we're using the underlined style and there's a new line, draw a line
834             if (underlined && (curChar == '\n'))
835             {
836                 addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
837 
838                 if (m_outlineThickness != 0)
839                     addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness);
840             }
841 
842             // If we're using the strike through style and there's a new line, draw a line across all characters
843             if (strikeThrough && (curChar == '\n'))
844             {
845                 addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness);
846 
847                 if (m_outlineThickness != 0)
848                     addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness);
849             }
850 
851             // Handle special characters
852             if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n'))
853             {
854                 // Update the current bounds (min coordinates)
855                 minX = min(minX, x);
856                 minY = min(minY, y);
857 
858                 switch (curChar)
859                 {
860                     case ' ':  x += hspace;        break;
861                     case '\t': x += hspace * 4;    break;
862                     case '\n': y += vspace; x = 0; break;
863                     default : break;
864                 }
865 
866                 // Update the current bounds (max coordinates)
867                 maxX = max(maxX, x);
868                 maxY = max(maxY, y);
869 
870                 // Next glyph, no need to create a quad for whitespace
871                 continue;
872             }
873 
874             // Apply the outline
875             if (m_outlineThickness != 0)
876             {
877                 Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold, m_outlineThickness);
878 
879                 float left   = glyph.bounds.left;
880                 float top    = glyph.bounds.top;
881                 float right  = glyph.bounds.left + glyph.bounds.width;
882                 float bottom = glyph.bounds.top  + glyph.bounds.height;
883 
884                 // Add the outline glyph to the vertices
885                 addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph, italic, m_outlineThickness);
886 
887                 // Update the current bounds with the outlined glyph bounds
888                 minX = min(minX, x + left   - italic * bottom - m_outlineThickness);
889                 maxX = max(maxX, x + right  - italic * top    - m_outlineThickness);
890                 minY = min(minY, y + top    - m_outlineThickness);
891                 maxY = max(maxY, y + bottom - m_outlineThickness);
892             }
893 
894             // Extract the current glyph's description
895             const Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold);
896 
897             // Add the glyph to the vertices
898             addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italic);
899 
900             // Update the current bounds with the non outlined glyph bounds
901             if (m_outlineThickness == 0)
902             {
903                 float left   = glyph.bounds.left;
904                 float top    = glyph.bounds.top;
905                 float right  = glyph.bounds.left + glyph.bounds.width;
906                 float bottom = glyph.bounds.top  + glyph.bounds.height;
907 
908                 minX = min(minX, x + left  - italic * bottom);
909                 maxX = max(maxX, x + right - italic * top);
910                 minY = min(minY, y + top);
911                 maxY = max(maxY, y + bottom);
912             }
913 
914             // Advance to the next character
915             x += glyph.advance;
916         }
917 
918         // If we're using the underlined style, add the last line
919         if (underlined && (x > 0))
920         {
921             addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
922 
923             if (m_outlineThickness != 0)
924                 addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness);
925         }
926 
927         // If we're using the strike through style, add the last line across all characters
928         if (strikeThrough && (x > 0))
929         {
930             addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness);
931 
932             if (m_outlineThickness != 0)
933                 addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness);
934         }
935 
936         // Update the bounding rectangle
937         m_bounds.left = minX;
938         m_bounds.top = minY;
939         m_bounds.width = maxX - minX;
940         m_bounds.height = maxY - minY;
941     }
942 }
943 
944 unittest
945 {
946     import std.stdio;
947     import nudsfml.graphics.rendertexture;
948 
949     writeln("Unit test for Text");
950 
951     auto renderTexture = new RenderTexture();
952 
953     renderTexture.create(400,200);
954 
955     auto font = new Font();
956     assert(font.loadFromFile("data/FiraMono-Regular.ttf"));
957 
958     Text regular = new Text("Regular", font, 20);
959     Text bold = new Text("Bold", font, 20);
960     Text italic = new Text("Italic", font, 20);
961     Text boldItalic = new Text("Bold Italic", font, 20);
962     Text strikeThrough = new Text("Strike Through", font, 20);
963     Text italicStrikeThrough = new Text("Italic Strike Through", font, 20);
964     Text boldStrikeThrough = new Text("Bold Strike Through", font, 20);
965     Text boldItalicStrikeThrough = new Text("Bold Italic Strike Through", font, 20);
966     Text outlined = new Text("Outlined", font, 20);
967     Text outlinedBoldItalicStrikeThrough = new Text("Outlined Bold Italic Strike Through", font, 20);
968 
969     bold.style = Text.Style.Bold;
970     bold.position = Vector2f(0,20);
971 
972     italic.style = Text.Style.Italic;
973     italic.position = Vector2f(0,40);
974 
975     boldItalic.style = Text.Style.Bold | Text.Style.Italic;
976     boldItalic.position = Vector2f(0,60);
977 
978     strikeThrough.style = Text.Style.StrikeThrough;
979     strikeThrough.position = Vector2f(0,80);
980 
981     italicStrikeThrough.style = Text.Style.Italic | Text.Style.StrikeThrough;
982     italicStrikeThrough.position = Vector2f(0,100);
983 
984     boldStrikeThrough.style = Text.Style.Bold | Text.Style.StrikeThrough;
985     boldStrikeThrough.position = Vector2f(0,120);
986 
987     boldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough;
988     boldItalicStrikeThrough.position = Vector2f(0,140);
989 
990     outlined.outlineColor = Color.Red;
991     outlined.outlineThickness = 0.5f;
992     outlined.position = Vector2f(0,160);
993 
994     outlinedBoldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough;
995     outlinedBoldItalicStrikeThrough.outlineColor = Color.Red;
996     outlinedBoldItalicStrikeThrough.outlineThickness = 0.5f;
997     outlinedBoldItalicStrikeThrough.position = Vector2f(0,180);
998 
999     writeln(regular..string);
1000     writeln(bold..string);
1001     bold..string = bold..string;
1002     writeln(italic..string);
1003     writeln(boldItalic..string);
1004     writeln(strikeThrough..string);
1005     writeln(italicStrikeThrough..string);
1006     writeln(boldStrikeThrough..string);
1007     writeln(boldItalicStrikeThrough..string);
1008     writeln(outlined..string);
1009     writeln(outlinedBoldItalicStrikeThrough..string);
1010 
1011 
1012     renderTexture.clear();
1013 
1014     renderTexture.draw(regular);
1015     renderTexture.draw(bold);
1016     renderTexture.draw(italic);
1017     renderTexture.draw(boldItalic);
1018     renderTexture.draw(strikeThrough);
1019     renderTexture.draw(italicStrikeThrough);
1020     renderTexture.draw(boldStrikeThrough);
1021     renderTexture.draw(boldItalicStrikeThrough);
1022     renderTexture.draw(outlined);
1023     renderTexture.draw(outlinedBoldItalicStrikeThrough);
1024 
1025     renderTexture.display();
1026 
1027     //grab that texture for usage
1028     auto texture = renderTexture.getTexture();
1029 
1030     writeln( texture.copyToImage().saveToFile("Text.png")) ;
1031 
1032     auto fontTexture = font.getTexture(20);
1033     writeln(fontTexture.copyToImage().saveToFile("Font.png"));
1034 
1035     writeln();
1036 
1037 }