1 /**
2 Allows the user to load and create bitmap fonts
3 
4 To actually draw text on the screen use the Window draw functions
5 */
6 module dgt.font;
7 import derelict.sdl2.sdl, derelict.sdl2.ttf;
8 import dgt.array, dgt.geom, dgt.io, dgt.texture, dgt.window;
9 import dgt.util : nullTerminate;
10 
11 /**
12 An enum that determines character styling
13 */
14 enum FontStyle : int
15 {
16     normal = TTF_STYLE_NORMAL,
17     bold = TTF_STYLE_BOLD,
18     italic = TTF_STYLE_ITALIC,
19     underline = TTF_STYLE_UNDERLINE,
20     strikethrough = TTF_STYLE_STRIKETHROUGH
21 }
22 
23 /**
24 A structure that stores rendered font glyps for drawing on the screen
25 */
26 struct Font
27 {
28     static immutable FONT_MAX_CHARS = 223;
29     static immutable FONT_CHAR_OFFSET = 32;
30     private Texture source;
31 	private Array!Texture characterTextures;
32 	private int height;
33 
34     @disable this();
35     @disable this(this);
36 
37     @nogc nothrow public:
38     /**
39     Load a font from a TTF file with a given size and style
40     */
41 	this(in string filename, in int size, in FontStyle style)
42     {
43         //Pass the C function a null-terminated path to avoid string literal issues
44         auto pathNullTerminated = nullTerminate(filename);
45         TTF_Font* font = TTF_OpenFont(pathNullTerminated.ptr, size);
46 		pathNullTerminated.destroy();
47         TTF_SetFontStyle(font, style);
48         if (font == null)
49             println("Font with filename ", filename, " not found");
50 		SDL_Color color = SDL_Color(255, 255, 255, 255);
51         //Create a null-terminated buffer to send glyphs to the TTF library
52         char[2] buffer = ['\0', '\0'];
53         SDL_Surface*[FONT_MAX_CHARS] characters;
54         int total_width = 0;
55         height = 0;
56         //Render each ASCII character to a surface
57     	for (int i = 0; i < FONT_MAX_CHARS; i++)
58 		{
59     		buffer[0] = getCharFromIndex(i);
60             //Render the character and note how much space it takes up
61     		characters[i] = TTF_RenderText_Solid(font, buffer.ptr, color);
62     		total_width += characters[i].w;
63     		if (characters[i].h > height)
64     			height = characters[i].h;
65     	}
66 		TTF_CloseFont(font);
67         //Blit all of the characters to a large surface
68     	SDL_Surface* full = SDL_CreateRGBSurface(0, total_width, height, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
69     	float position = 0;
70     	for (int i = 0; i < FONT_MAX_CHARS; i++)
71 		{
72     		SDL_Rect dest = SDL_Rect(cast(int)position, 0, 0, 0);
73     		SDL_BlitSurface(characters[i], null, full, &dest);
74     		position += characters[i].w;
75     	}
76         //Load the surface into a texture
77         source = Texture(full);
78         SDL_FreeSurface(full);
79         //Add reference to the texture for each character
80         position = 0;
81         characterTextures = Array!Texture(FONT_MAX_CHARS);
82         for (int i = 0; i < FONT_MAX_CHARS; i++)
83 		{
84             characterTextures.add(source.getSlice(Rectangle(position, 0, characters[i].w, characters[i].h)));
85             position += characterTextures[i].size.width;
86             SDL_FreeSurface(characters[i]);
87         }
88     }
89 
90     ~this()
91     {
92         source.destroy();
93         characterTextures.destroy();
94     }
95 
96     pure:
97     /// Get the glyph for a given character
98     Texture render(in char c) const
99     {
100         return characterTextures[getIndexFromChar(c)];
101     }
102 
103     ///Find how much space a string would take when rendered
104     Rectangle getSizeOfString(in string str, float lineHeight = 1) const
105     {
106     	float position = 0;
107     	float width = 0, height = cast(int)(this.height * lineHeight);
108     	for(size_t i = 0; i < str.length; i++)
109         {
110             char c = str[i];
111     		if (position > width)
112     			width = position;
113             //a tab is equivalent to 4 space characters
114     		if (c == '\t')
115     			position += 4 * render(' ').size.width;
116             //Move down a line
117             else if (c == '\n')
118             {
119     			height += cast(int)(characterHeight * lineHeight);
120     			position = 0;
121     		}
122             else if (c != '\r')
123     			position += render(c).size.width;
124     	}
125     	return Rectangle(0, 0, width, height);
126     }
127 
128     //Get the pixel height of the characters in the font
129     @property int characterHeight() const { return height; }
130 
131     private int getIndexFromChar(in char c) const
132     {
133         return c - FONT_CHAR_OFFSET;
134     }
135 
136     private char getCharFromIndex(in int index) const
137     {
138         return cast(char)(index + FONT_CHAR_OFFSET);
139     }
140 }