1 /*
2 DSFML - The Simple and Fast Multimedia Library for D
3 
4 Copyright (c) 2013 - 2015 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 use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose, including commercial applications,
10 and to alter it and redistribute it freely, subject to the following restrictions:
11 
12 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
13 If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
14 
15 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
16 
17 3. This notice may not be removed or altered from any source distribution
18 */
19 
20 module dsfml.graphics.text;
21 
22 import dsfml.graphics.font;
23 import dsfml.graphics.glyph;
24 import dsfml.graphics.color;
25 import dsfml.graphics.rect;
26 import dsfml.graphics.transformable;
27 import dsfml.graphics.drawable;
28 import dsfml.graphics.texture;
29 import dsfml.graphics.vertexarray;
30 import dsfml.graphics.vertex;
31 import dsfml.graphics.rendertarget;
32 import dsfml.graphics.renderstates;
33 import dsfml.graphics.primitivetype;
34 
35 import dsfml.system.vector2;
36 
37 /++
38  + Graphical text that can be drawn to a render target.
39  + 
40  + Text is a drawable class that allows to easily display some text with custom style and color on a render target.
41  + 
42  + It inherits all the functions from Transformable: position, rotation, scale, origin. It also adds text-specific properties such as the font to use, the character size, the font style (bold, italic, underlined), the global color and the text to display of course. It also provides convenience functions to calculate the graphical size of the text, or to get the global position of a given character.
43  + 
44  + Text works in combination with the Font class, which loads and provides the glyphs (visual characters) of a given font.
45  + 
46  + The separation of Font and Text allows more flexibility and better performances: indeed a Font is a heavy resource, and any operation on it is slow (often too slow for real-time applications). On the other side, a Text is a lightweight object which can combine the glyphs data and metrics of a Font to display any text on a render target.
47  + 
48  + It is important to note that the Text instance doesn't copy the font that it uses, it only keeps a reference to it. Thus, a Font must not be destructed while it is used by a Text (i.e. never write a function that uses a local Font instance for creating a text).
49  + 
50  + Authors: Laurent Gomila, Jeremy DeHaan
51  + See_Also: http://sfml-dev.org/documentation/2.0/classsf_1_1Text.php#details
52  +/
53 class Text : Drawable, Transformable
54 {
55 	/// Enumeration of the string drawing styles.
56 	enum Style
57 	{
58 		Regular = 0, /// Regular characters, no style
59 		Bold = 1 << 0, /// Bold characters
60 		Italic = 1 << 1, /// Italic characters
61 		Underlined = 1 << 2 /// Underlined characters
62 	}
63 
64 	mixin NormalTransformable;
65 
66 	private
67 	{
68 		dstring m_string;
69 		Rebindable!(const(Font)) m_font;
70 		uint m_characterSize;
71 		Style m_style;
72 		Color m_color;
73 		VertexArray m_vertices;
74 		FloatRect m_bounds;
75 
76 		//used for caching the font texture
77 		uint lastSizeUsed = 0;
78 		Rebindable!(const(Texture)) lastTextureUsed;
79 	}
80 
81 	this()
82 	{
83 		m_string = "";
84 		m_characterSize = 30;
85 		m_style = Style.Regular;
86 		m_color = Color(255,255,255);
87 		m_vertices = new VertexArray(PrimitiveType.Quads,0);
88 		m_bounds = FloatRect();
89 	}
90 	
91 	this(T)(immutable(T)[] text, const(Font) font, uint characterSize = 30)
92 		if (is(T == dchar)||is(T == wchar)||is(T == char))
93 	{
94 		import dsfml.system..string;
95 
96 		m_string = stringConvert!(T, dchar)(text);
97 		m_characterSize = characterSize;
98 		m_style = Style.Regular;
99 		m_color = Color(255,255,255);
100 		m_vertices = new VertexArray(PrimitiveType.Quads,0);
101 		m_bounds = FloatRect();
102 		m_font = font;
103 		updateGeometry();
104 	}
105 
106 	~this()
107 	{
108 		import dsfml.system.config;
109 		mixin(destructorOutput);
110 	}
111 
112 	/**
113 	 * Get the character size.
114 	 * 
115 	 * Returns: Size of the characters, in pixels.
116 	 */
117 	uint getCharacterSize() const
118 	{
119 		return m_characterSize;
120 	}
121 
122 	/**
123 	 * Get the global color of the text.
124 	 * 
125 	 * Returns: Global color of the text.
126 	 */
127 	Color getColor() const
128 	{
129 		return m_color;
130 	}
131 
132 	/**
133 	 * Get thet text's font.
134 	 * 
135 	 * If the text has no font attached, a NULL pointer is returned. The returned reference is const, which means that you cannot modify the font when you get it from this function.
136 	 * 
137 	 * Returns: Text's font.
138 	 */
139 	const(Font) getFont() const
140 	{
141 		if(m_font is null)
142 		{
143 			return null;
144 		}
145 		else
146 		{
147 			return m_font;
148 		}
149 	}
150 
151 	/**
152 	 * Get the global bounding rectangle of the entity.
153 	 * 
154 	 * The returned rectangle is in global coordinates, which means that it takes in account the transformations (translation, rotation, scale, ...) that are applied to the entity. In other words, this function returns the bounds of the sprite in the global 2D world's coordinate system.
155 	 * 
156 	 * Returns: Global bounding rectangle of the entity.
157 	 */
158 	FloatRect getGlobalBounds()
159 	{
160 		return getTransform().transformRect(getLocalBounds());
161 	}
162 
163 	/**
164 	 * Get the local bounding rectangle of the entity.
165 	 * 
166 	 * The returned rectangle is in local coordinates, which means that it ignores the transformations (translation, rotation, scale, ...) that are applied to the entity. In other words, this function returns the bounds of the entity in the entity's coordinate system.
167 	 * 
168 	 * Returns: Local bounding rectangle of the entity.
169 	 */
170 	FloatRect getLocalBounds() const
171 	{
172 		return m_bounds;
173 	}
174 
175 	//TODO: maybe a getString!dstring or getString!wstring etc template would be appropriate?
176 	/**
177 	 * Get the text's string.
178 	 * 
179 	 * The returned string is a dstring, a unicode type.
180 	 */
181 	immutable(T)[] getString(T=char)() const
182 		if (is(T == dchar)||is(T == wchar)||is(T == char))
183 	{
184 		import dsfml.system..string;
185 		return stringConvert!(dchar, T)(m_string);
186 	}
187 
188 	/**
189 	 * Get the text's style.
190 	 * 
191 	 * Returns: Text's style.
192 	 */
193 	Style getStyle() const
194 	{
195 		return m_style;
196 	}
197 
198 	/**
199 	 * Set the character size.
200 	 * 
201 	 * The default size is 30.
202 	 * 
203 	 * Params:
204 	 * 		size	= New character size, in pixels.
205 	 */
206 	void setCharacterSize(uint size)
207 	{
208 		m_characterSize = size;
209 		updateGeometry();
210 	}
211 
212 	/**
213 	 * Set the global color of the text.
214 	 * 
215 	 * By default, the text's color is opaque white.
216 	 * 
217 	 * Params:
218 	 * 		color	= New color of the text.
219 	 */
220 	void setColor(Color color)
221 	{
222 		m_color = color;
223 		updateGeometry();
224 	}
225 
226 	/**
227 	 * Set the text's font.
228 	 * 
229 	 * The font argument refers to a font that must exist as long as the text uses it. Indeed, the text doesn't store its own copy of the font, but rather keeps a pointer to the one that you passed to this function. If the font is destroyed and the text tries to use it, the behaviour is undefined.
230 	 * 
231 	 * Params:
232 	 * 		font	= New font
233 	 */
234 	void setFont(const(Font) font)
235 	{
236 		m_font = font;
237 		updateGeometry();
238 	}
239 
240 	/**
241 	 * Set the text's string.
242 	 * 
243 	 * A text's string is empty by default.
244 	 * 
245 	 * Params:
246 	 * 		text	= New string
247 	 */
248 	void setString(T)(immutable(T)[] text)
249 		if (is(T == dchar)||is(T == wchar)||is(T == char))
250 	{
251 		import dsfml.system..string;
252 		m_string = stringConvert!(T,dchar)(text);
253 		updateGeometry();
254 	}
255 
256 	//TODO: Does doing binary operations on Styles like the docs suggest actually work?
257 	/**
258 	 * Set the text's style.
259 	 * 
260 	 * You can pass a combination of one or more styles, for example Style.Bold | Text.Italic.
261 	 */
262 	void setStyle(Style style)
263 	{
264 		m_style = style;
265 		updateGeometry();
266 	}
267 
268 	/**
269 	 * Draw the object to a render target.
270 	 * 
271 	 * Params:
272 	 *  		renderTarget =	Render target to draw to
273 	 *  		renderStates =	Current render states
274 	 */
275 	void draw(RenderTarget renderTarget, RenderStates renderStates)
276 	{
277 	    import std.stdio;
278 	    
279 		if ((m_font !is null) && (m_characterSize>0))
280 		{
281 			renderStates.transform *= getTransform();
282 			
283 			//only call getTexture if the size has changed
284 			if(m_characterSize != lastSizeUsed)
285 			{
286 				//update the size
287 				lastSizeUsed = m_characterSize;
288 				//grab the new texture
289 				lastTextureUsed = m_font.getTexture(m_characterSize);
290 			}
291 			
292 			//writeln("Update Geometry");
293 			updateGeometry();
294 			
295 			//writeln("Setting renderstates tecture");
296 			renderStates.texture =  m_font.getTexture(m_characterSize);
297 			
298 			if(renderStates.texture is null)
299 			{
300 			    //writeln("Texture don't exist!");
301 			}
302 			
303 			//writeln("Trying to draw!");
304 			renderTarget.draw(m_vertices, renderStates);
305 		}
306 	}
307 
308 	/**
309 	 * Return the position of the index-th character.
310 	 * 
311 	 * This function computes the visual position of a character from its index in the string. The returned position is in global coordinates (translation, rotation, scale and origin are applied). If index is out of range, the position of the end of the string is returned.
312 	 * 
313 	 * Params:
314 	 * 		index	= Index of the character
315 	 * 
316 	 * Returns: Position of the character.
317 	 */
318 	Vector2f findCharacterPos(size_t index)
319 	{
320 		// Make sure that we have a valid font
321 		if(m_font is null)
322 		{
323 			return Vector2f(0,0);
324 		}
325 		
326 		// Adjust the index if it's out of range
327 		if(index > m_string.length)
328 		{
329 			index = m_string.length;
330 		}
331 
332 		bool bold  = (m_style & Style.Bold) != 0;
333 
334 		float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance);
335 		float vspace = cast(float)(m_font.getLineSpacing(m_characterSize));
336 		
337 		Vector2f position;
338 		dchar prevChar = 0;
339 		for (size_t i = 0; i < index; ++i)
340 		{
341 			dchar curChar = m_string[i];
342 			
343 			// Apply the kerning offset
344 			position.x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize));
345 			prevChar = curChar;
346 			
347 			// Handle special characters
348 			switch (curChar)
349 			{
350 				case ' ' : position.x += hspace; continue;
351 				case '\t' : position.x += hspace * 4; continue;
352 				case '\n' : position.y += vspace; position.x = 0; continue;
353 				case '\v' : position.y += vspace * 4; continue;
354 				default:
355 					break;
356 			}
357 			
358 			// For regular characters, add the advance offset of the glyph
359 			position.x += cast(float)(m_font.getGlyph(curChar, m_characterSize, bold).advance);
360 		}
361 		
362 		// Transform the position to global coordinates
363 		position = getTransform().transformPoint(position);
364 		
365 		return position;
366 	}
367 
368 private:
369 	void updateGeometry()
370 	{
371 		import std.algorithm;
372 
373 		// Clear the previous geometry
374 		m_vertices.clear();
375 		m_bounds = FloatRect();
376 		
377 		// No font: nothing to draw
378 		if (m_font is null)
379 			return;
380 
381 		// No text: nothing to draw
382 		if (m_string.length == 0)
383 			return;
384 		// Compute values related to the text style
385 		bool bold = (m_style & Style.Bold) != 0;
386 		bool underlined = (m_style & Style.Underlined) != 0;
387 		float italic = (m_style & Style.Italic) ? 0.208f : 0f; // 12 degrees
388 		float underlineOffset = m_characterSize * 0.1f;
389 		float underlineThickness = m_characterSize * (bold ? 0.1f : 0.07f);
390 		
391 		// Precompute the variables needed by the algorithm
392 		float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance);
393 		float vspace = cast(float)(m_font.getLineSpacing(m_characterSize));
394 		float x = 0f;
395 		float y = cast(float)(m_characterSize);
396 		
397 		// Create one quad for each character
398 		float minX = m_characterSize, minY = m_characterSize, maxX = 0, maxY = 0;
399 		dchar prevChar = 0;
400 		for (size_t i = 0; i < m_string.length; ++i)
401 		{
402 			dchar curChar = m_string[i];
403 
404 			
405 			// Apply the kerning offset
406 			x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize));
407 
408 			prevChar = curChar;
409 
410 			// If we're using the underlined style and there's a new line, draw a line
411 			if (underlined && (curChar == '\n'))
412 			{
413 				float top = y + underlineOffset;
414 				float bottom = top + underlineThickness;
415 				
416 				m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1)));
417 				m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1)));
418 				m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1)));
419 				m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
420 			}
421 			
422 			// Handle special characters
423 			if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n') || (curChar == '\v'))
424 			{
425 				// Update the current bounds (min coordinates)
426 				minX = min(minX, x);
427 				minY = min(minY, y);
428 				
429 				final switch (curChar)
430 				{
431 					case ' ' : x += hspace; break;
432 					case '\t' : x += hspace * 4; break;
433 					case '\n' : y += vspace; x = 0; break;
434 					case '\v' : y += vspace * 4; break;
435 				}
436 				
437 				// Update the current bounds (max coordinates)
438 				maxX = max(maxX, x);
439 				maxY = max(maxY, y);
440 				
441 				// Next glyph, no need to create a quad for whitespace
442 				continue;
443 			}
444 
445 
446 
447 			// Extract the current glyph's description
448 			Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold);
449 			
450 			int left = glyph.bounds.left;
451 			int top = glyph.bounds.top;
452 			int right = glyph.bounds.left + glyph.bounds.width;
453 			int bottom = glyph.bounds.top + glyph.bounds.height;
454 			
455 			float u1 = cast(float)(glyph.textureRect.left);
456 			float v1 = cast(float)(glyph.textureRect.top);
457 			float u2 = cast(float)(glyph.textureRect.left + glyph.textureRect.width);
458 			float v2 = cast(float)(glyph.textureRect.top + glyph.textureRect.height);
459 			
460 			// Add a quad for the current character
461 			m_vertices.append(Vertex(Vector2f(x + left - italic * top, y + top), m_color, Vector2f(u1, v1)));
462 			m_vertices.append(Vertex(Vector2f(x + right - italic * top, y + top), m_color, Vector2f(u2, v1)));
463 			m_vertices.append(Vertex(Vector2f(x + right - italic * bottom, y + bottom), m_color, Vector2f(u2, v2)));
464 			m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2)));
465 
466 			// Update the current bounds
467 			minX = min(minX, x + left - italic * bottom);
468 			maxX = max(maxX, x + right - italic * top);
469 			minY = min(minY, y + top);
470 			maxY = max(maxY, y + bottom);
471 
472 			// Advance to the next character
473 			x += glyph.advance;
474 		}
475 		
476 		// If we're using the underlined style, add the last line
477 		if (underlined)
478 		{
479 			float top = y + underlineOffset;
480 			float bottom = top + underlineThickness;
481 			
482 			m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1)));
483 			m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1)));
484 			m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1)));
485 			m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
486 		}
487 		
488 		// Update the bounding rectangle
489 		m_bounds.left = minX;
490 		m_bounds.top = minY;
491 		m_bounds.width = maxX - minX;
492 		m_bounds.height = maxY - minY;
493 	}
494 }
495 
496 unittest
497 {
498 	version(DSFML_Unittest_Graphics)
499 	{
500 		import std.stdio;
501 		import dsfml.graphics.rendertexture;
502 
503 		writeln("Unit test for Text");
504 
505 		auto renderTexture = new RenderTexture();
506 		
507 		renderTexture.create(100,100);
508 
509 		auto font = new Font();
510 		assert(font.loadFromFile("res/Warenhaus-Standard.ttf"));
511 
512 		Text text;
513 		text = new Text("Sample String", font);
514 
515 		string temp = text.getString();
516 
517 		writeln(temp);
518 
519 		renderTexture.clear();
520 
521 		renderTexture.draw(text);
522 
523 		renderTexture.display();
524 
525 		//grab that texture for usage
526 		auto texture = renderTexture.getTexture();
527 
528 		texture.copyToImage().saveToFile("Text.png");
529 
530 		writeln();
531 	}
532 }