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 dsfml.graphics.text;
75 
76 import dsfml.graphics.font;
77 import dsfml.graphics.glyph;
78 import dsfml.graphics.color;
79 import dsfml.graphics.rect;
80 import dsfml.graphics.transformable;
81 import dsfml.graphics.drawable;
82 import dsfml.graphics.texture;
83 import dsfml.graphics.vertexarray;
84 import dsfml.graphics.vertex;
85 import dsfml.graphics.rendertarget;
86 import dsfml.graphics.renderstates;
87 import dsfml.graphics.primitivetype;
88 
89 import dsfml.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     {
99         /// Regular characters, no style
100         Regular = 0,
101         /// Bold characters
102         Bold = 1 << 0,
103         /// Italic characters
104         Italic = 1 << 1,
105         /// Underlined characters
106         Underlined = 1 << 2,
107         /// Strike through characters
108         StrikeThrough = 1 << 3
109     }
110 
111     mixin NormalTransformable;
112 
113     private
114     {
115         dchar[] m_string;
116         Font m_font;
117         uint m_characterSize;
118         Style m_style;
119         Color m_fillColor;
120         Color m_outlineColor;
121         float m_outlineThickness;
122         VertexArray m_vertices;
123         VertexArray m_outlineVertices;
124         FloatRect m_bounds;
125         bool m_geometryNeedUpdate;
126 
127         //helper function to copy input string into character buffer
128         void stringCopy(T)(const(T)[] str)
129         if (is(T == dchar)||is(T == wchar)||is(T == char))
130         {
131             import std.utf: byDchar;
132 
133             //make a conservative estimate on how much room we'll need
134             m_string.reserve(dchar.sizeof * str.length);
135             m_string.length = 0;
136 
137             foreach(dchar c; str.byDchar())
138                 m_string~=c;
139         }
140     }
141 
142     /**
143      * Default constructor
144      *
145      * Creates an empty text.
146      */
147     this()
148     {
149         m_characterSize = 30;
150         m_style = Style.Regular;
151         m_fillColor = Color(255,255,255);
152         m_outlineColor = Color(0,0,0);
153         m_outlineThickness = 0;
154         m_vertices = new VertexArray(PrimitiveType.Triangles);
155         m_outlineVertices = new VertexArray(PrimitiveType.Triangles);
156         m_bounds = FloatRect();
157         m_geometryNeedUpdate = false;
158     }
159 
160     /**
161      * Construct the text from a string, font and size
162      *
163      * Note that if the used font is a bitmap font, it is not scalable, thus not
164      * all requested sizes will be available to use. This needs to be taken into
165      * consideration when setting the character size. If you need to display
166      * text of a certain size, make sure the corresponding bitmap font that
167      * supports that size is used.
168      *
169      * Params:
170      *	text          = Text assigned to the string
171      *	font          = Font used to draw the string
172      *	characterSize = Base size of characters, in pixels
173      *
174      * Deprecated: Use the constructor that takes a 'const(dchar)[]' instead.
175      */
176     deprecated("Use the constructor that takes a 'const(dchar)[]' instead.")
177     this(T)(const(T)[] text, Font font, uint characterSize = 30)
178         if (is(T == dchar)||is(T == wchar)||is(T == char))
179     {
180         stringCopy(text);
181         m_font = font;
182         m_characterSize = characterSize;
183         m_style = Style.Regular;
184         m_fillColor = Color(255,255,255);
185         m_outlineColor = Color(0,0,0);
186         m_outlineThickness = 0;
187         m_vertices = new VertexArray(PrimitiveType.Triangles);
188         m_outlineVertices = new VertexArray(PrimitiveType.Triangles);
189         m_bounds = FloatRect();
190         m_geometryNeedUpdate = true;
191     }
192 
193     /**
194      * Construct the text from a string, font and size
195      *
196      * Note that if the used font is a bitmap font, it is not scalable, thus not
197      * all requested sizes will be available to use. This needs to be taken into
198      * consideration when setting the character size. If you need to display
199      * text of a certain size, make sure the corresponding bitmap font that
200      * supports that size is used.
201      *
202      * Params:
203      *	text          = Text assigned to the string
204      *	font          = Font used to draw the string
205      *	characterSize = Base size of characters, in pixels
206      */
207     this(T)(const(dchar)[] text, Font font, uint characterSize = 30)
208     {
209         stringCopy(text);
210         m_font = font;
211         m_characterSize = characterSize;
212         m_style = Style.Regular;
213         m_fillColor = Color(255,255,255);
214         m_outlineColor = Color(0,0,0);
215         m_outlineThickness = 0;
216         m_vertices = new VertexArray(PrimitiveType.Triangles);
217         m_outlineVertices = new VertexArray(PrimitiveType.Triangles);
218         m_bounds = FloatRect();
219         m_geometryNeedUpdate = true;
220     }
221 
222     /// Destructor.
223     ~this()
224     {
225         import dsfml.system.config;
226         mixin(destructorOutput);
227     }
228 
229     @property
230     {
231         /**
232          * The character size in pixels.
233          *
234          * The default size is 30.
235          *
236          * Note that if the used font is a bitmap font, it is not scalable, thus
237          * not all requested sizes will be available to use. This needs to be
238          * taken into consideration when setting the character size. If you need
239          * to display text of a certain size, make sure the corresponding bitmap
240          * font that supports that size is used.
241          */
242         uint characterSize(uint size)
243         {
244             if(m_characterSize != size)
245             {
246                 m_characterSize = size;
247                 m_geometryNeedUpdate = true;
248             }
249             return m_characterSize;
250         }
251 
252         /// ditto
253         uint characterSize() const
254         {
255             return m_characterSize;
256         }
257     }
258 
259     /**
260      * Set the character size.
261      *
262      * The default size is 30.
263      *
264      * Note that if the used font is a bitmap font, it is not scalable, thus
265      * not all requested sizes will be available to use. This needs to be
266      * taken into consideration when setting the character size. If you need
267      * to display text of a certain size, make sure the corresponding bitmap
268      * font that supports that size is used.
269      *
270      * Params:
271      * 		size	= New character size, in pixels.
272      *
273      * Deprecated: Use the 'characterSize' property instead.
274      */
275     deprecated("Use the 'characterSize' property instead.")
276     void setCharacterSize(uint size)
277     {
278         characterSize = size;
279     }
280 
281     /**
282      * Get the character size.
283      *
284      * Returns: Size of the characters, in pixels.
285      *
286      * Deprecated: Use the 'characterSize' property instead.
287      */
288     deprecated("Use the 'characterSize' property instead.")
289     uint getCharacterSize() const
290     {
291         return characterSize;
292     }
293 
294     /**
295      * Set the fill color of the text.
296      *
297      * By default, the text's color is opaque white.
298      *
299      * Params:
300      * 		color	= New color of the text.
301      *
302      * Deprecated: Use the 'fillColor' or 'outlineColor' properties instead.
303      */
304     deprecated("Use the 'fillColor' or 'outlineColor' properties instead.")
305     void setColor(Color color)
306     {
307         fillColor = color;
308     }
309 
310     /**
311      * Get the fill color of the text.
312      *
313      * Returns: Fill color of the text.
314      *
315      * Deprecated: Use the 'fillColor' or 'outlineColor' properties instead.
316      */
317     deprecated("Use the 'fillColor' or 'outlineColor' properties instead.")
318     Color getColor() const
319     {
320         return fillColor;
321     }
322 
323     @property
324     {
325         /**
326         * The fill color of the text.
327         *
328         * By default, the text's fill color is opaque white. Setting the fill
329         * color to a transparent color with an outline will cause the outline to
330         * be displayed in the fill area of the text.
331         */
332         Color fillColor(Color color)
333         {
334             if(m_fillColor != color)
335             {
336                 m_fillColor = color;
337 
338                 // Change vertex colors directly, no need to update whole geometry
339                 // (if geometry is updated anyway, we can skip this step)
340                 if(!m_geometryNeedUpdate)
341                 {
342                     for(int i = 0; i < m_vertices.getVertexCount(); ++i)
343                     {
344                         m_vertices[i].color = m_fillColor;
345                     }
346                 }
347             }
348 
349             return m_fillColor;
350         }
351 
352         /// ditto
353         Color fillColor() const
354         {
355             return m_fillColor;
356         }
357     }
358 
359     @property
360     {
361         /**
362         * The outline color of the text.
363         *
364         * By default, the text's outline color is opaque black.
365         */
366         Color outlineColor(Color color)
367         {
368             if(m_outlineColor != color)
369             {
370                 m_outlineColor = color;
371 
372                 // Change vertex colors directly, no need to update whole geometry
373                 // (if geometry is updated anyway, we can skip this step)
374                 if(!m_geometryNeedUpdate)
375                 {
376                     for(int i = 0; i < m_outlineVertices.getVertexCount(); ++i)
377                     {
378                         m_outlineVertices[i].color = m_outlineColor;
379                     }
380                 }
381             }
382 
383             return m_outlineColor;
384         }
385 
386         /// ditto
387         Color outlineColor() const
388         {
389             return m_outlineColor;
390         }
391     }
392 
393     @property
394     {
395         /**
396         * The outline color of the text.
397         *
398         * By default, the text's outline color is opaque black.
399         */
400         float outlineThickness(float thickness)
401         {
402             if(m_outlineThickness != thickness)
403             {
404                 m_outlineThickness = thickness;
405                 m_geometryNeedUpdate = true;
406             }
407 
408             return m_outlineThickness;
409         }
410 
411         /// ditto
412         float outlineThickness() const
413         {
414             return m_outlineThickness;
415         }
416     }
417 
418     @property
419     {
420         /**
421         * The text's font.
422         */
423         const(Font) font(Font newFont)
424         {
425             if (m_font !is newFont)
426             {
427                 m_font = newFont;
428                 m_geometryNeedUpdate = true;
429             }
430 
431             return m_font;
432         }
433 
434         /// ditto
435         const(Font) font() const
436         {
437             return m_font;
438         }
439     }
440 
441     /**
442      * Set the text's font.
443      *
444      * Params:
445      * 		newFont	= New font
446      *
447      * Deprecated: Use the 'font' property instead.
448      */
449     deprecated("Use the 'font' property instead.")
450     void setFont(Font newFont)
451     {
452         font = newFont;
453     }
454 
455     /**
456      * Get thet text's font.
457      *
458      * Returns: Text's font.
459      *
460      * Deprecated: Use the 'font' property instead.
461      */
462     deprecated("Use the 'font' property instead.")
463     const(Font) getFont() const
464     {
465         return font;
466     }
467 
468     /**
469      * Get the global bounding rectangle of the entity.
470      *
471      * The returned rectangle is in global coordinates, which means that it
472      * takes in account the transformations (translation, rotation, scale, ...)
473      * that are applied to the entity. In other words, this function returns the
474      * bounds of the sprite in the global 2D world's coordinate system.
475      *
476      * Returns: Global bounding rectangle of the entity.
477      */
478     @property FloatRect globalBounds()
479     {
480         return getTransform().transformRect(localBounds);
481     }
482 
483     /**
484      * Get the global bounding rectangle of the entity.
485      *
486      * The returned rectangle is in global coordinates, which means that it
487      * takes in account the transformations (translation, rotation, scale, ...)
488      * that are applied to the entity. In other words, this function returns the
489      * bounds of the sprite in the global 2D world's coordinate system.
490      *
491      * Returns: Global bounding rectangle of the entity.
492      *
493      * Deprecated: Use the 'globalBounds' property instead.
494      */
495     deprecated("Use the 'globalBounds' property instead.")
496     FloatRect getGlobalBounds()
497     {
498         return globalBounds;
499     }
500 
501     /**
502      * Get the local bounding rectangle of the entity.
503      *
504      * The returned rectangle is in local coordinates, which means that it
505      * ignores the transformations (translation, rotation, scale, ...) that are
506      * applied to the entity. In other words, this function returns the bounds
507      * of the entity in the entity's coordinate system.
508      *
509      * Returns: Local bounding rectangle of the entity.
510      */
511     @property FloatRect localBounds() const
512     {
513         return m_bounds;
514     }
515 
516     /**
517      * Get the local bounding rectangle of the entity.
518      *
519      * The returned rectangle is in local coordinates, which means that it
520      * ignores the transformations (translation, rotation, scale, ...) that are
521      * applied to the entity. In other words, this function returns the bounds
522      * of the entity in the entity's coordinate system.
523      *
524      * Returns: Local bounding rectangle of the entity.
525      *
526      * Deprecated: Use the 'globalBounds' property instead.
527      */
528     deprecated("Use the 'localBounds' property instead.")
529     FloatRect getLocalBounds() const
530     {
531         return localBounds;
532     }
533 
534     @property
535     {
536         /**
537          * The text's style.
538          *
539          * You can pass a combination of one or more styles, for example
540          * Style.Bold | Text.Italic.
541          */
542         Style style(Style newStyle)
543         {
544             if(m_style != newStyle)
545             {
546                 m_style = newStyle;
547                 m_geometryNeedUpdate = true;
548             }
549 
550             return m_style;
551         }
552 
553         /// ditto
554         Style style() const
555         {
556             return m_style;
557         }
558     }
559 
560     /**
561      * Set the text's style.
562      *
563      * You can pass a combination of one or more styles, for example
564      * Style.Bold | Text.Italic.
565      *
566      * Params:
567      *      newStyle = New style
568      *
569      * Deprecated: Use the 'style' property instead.
570      */
571     deprecated("Use the 'style' property instead.")
572     void setStyle(Style newStyle)
573     {
574         style = newStyle;
575     }
576 
577     /**
578      * Get the text's style.
579      *
580      * Returns: Text's style.
581      *
582      * Deprecated: Use the 'style' property instead.
583      */
584     deprecated("Use the 'style' property instead.")
585     Style getStyle() const
586     {
587         return style;
588     }
589 
590     @property
591     {
592         /**
593          * The text's string.
594          *
595          * A text's string is empty by default.
596          */
597         const(dchar)[] string(const(dchar)[] str)
598         {
599             // Because of the conversion, assume the text is new
600             stringCopy(str);
601             m_geometryNeedUpdate = true;
602             return m_string;
603         }
604         /// ditto
605         const(dchar)[] string() const
606         {
607             return m_string;
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     version(DSFML_Unittest_Graphics)
947     {
948         import std.stdio;
949         import dsfml.graphics.rendertexture;
950 
951         writeln("Unit test for Text");
952 
953         auto renderTexture = new RenderTexture();
954 
955         renderTexture.create(400,200);
956 
957         auto font = new Font();
958         assert(font.loadFromFile("res/Warenhaus-Standard.ttf"));
959 
960         Text regular = new Text("Regular", font, 20);
961         Text bold = new Text("Bold", font, 20);
962         Text italic = new Text("Italic", font, 20);
963         Text boldItalic = new Text("Bold Italic", font, 20);
964         Text strikeThrough = new Text("Strike Through", font, 20);
965         Text italicStrikeThrough = new Text("Italic Strike Through", font, 20);
966         Text boldStrikeThrough = new Text("Bold Strike Through", font, 20);
967         Text boldItalicStrikeThrough = new Text("Bold Italic Strike Through", font, 20);
968         Text outlined = new Text("Outlined", font, 20);
969         Text outlinedBoldItalicStrikeThrough = new Text("Outlined Bold Italic Strike Through", font, 20);
970 
971         bold.style = Text.Style.Bold;
972         bold.position = Vector2f(0,20);
973 
974         italic.style = Text.Style.Italic;
975         italic.position = Vector2f(0,40);
976 
977         boldItalic.style = Text.Style.Bold | Text.Style.Italic;
978         boldItalic.position = Vector2f(0,60);
979 
980         strikeThrough.style = Text.Style.StrikeThrough;
981         strikeThrough.position = Vector2f(0,80);
982 
983         italicStrikeThrough.style = Text.Style.Italic | Text.Style.StrikeThrough;
984         italicStrikeThrough.position = Vector2f(0,100);
985 
986         boldStrikeThrough.style = Text.Style.Bold | Text.Style.StrikeThrough;
987         boldStrikeThrough.position = Vector2f(0,120);
988 
989         boldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough;
990         boldItalicStrikeThrough.position = Vector2f(0,140);
991 
992         outlined.outlineColor = Color.Red;
993         outlined.outlineThickness = 0.5f;
994         outlined.position = Vector2f(0,160);
995 
996         outlinedBoldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough;
997         outlinedBoldItalicStrikeThrough.outlineColor = Color.Red;
998         outlinedBoldItalicStrikeThrough.outlineThickness = 0.5f;
999         outlinedBoldItalicStrikeThrough.position = Vector2f(0,180);
1000 
1001         writeln(regular..string);
1002 
1003         renderTexture.clear();
1004 
1005         renderTexture.draw(regular);
1006         renderTexture.draw(bold);
1007         renderTexture.draw(italic);
1008         renderTexture.draw(boldItalic);
1009         renderTexture.draw(strikeThrough);
1010         renderTexture.draw(italicStrikeThrough);
1011         renderTexture.draw(boldStrikeThrough);
1012         renderTexture.draw(boldItalicStrikeThrough);
1013         renderTexture.draw(outlined);
1014         renderTexture.draw(outlinedBoldItalicStrikeThrough);
1015 
1016         renderTexture.display();
1017 
1018         //grab that texture for usage
1019         auto texture = renderTexture.getTexture();
1020 
1021         texture.copyToImage().saveToFile("Text.png");
1022 
1023         writeln();
1024     }
1025 }