BARE2D
Font.cpp
Go to the documentation of this file.
1 #include "Font.hpp"
2 
3 #include <SDL2/SDL.h>
4 #include <SDL2/SDL_ttf.h>
5 
6 #include "BAREErrors.hpp"
7 #include "GLContextManager.hpp"
8 
9 int closestPow2(int i)
10 {
11  // Do some wacky shit to find the closest power of 2 to `i`
12  i--;
13  int pi = 1;
14  while(i > 0)
15  {
16  i >>= 1;
17  pi <<= 1;
18  }
19  return pi;
20 }
21 
22 namespace BARE2D {
23 
25 {}
26 
28 {}
29 
30 void Font::init(const char* fontFile, int size)
31 {
32  // Initialize some font.
33  // Make sure that we initialize the TTF extension of SDL
34  if(!TTF_WasInit())
35  {
36  TTF_Init();
37  }
38 
39  // Now that we know for sure we can make a font!
40  TTF_Font* font = TTF_OpenFont(fontFile, size);
41 
42  // Check to make sure it was loaded properly
43  if(font == nullptr)
44  {
45  throwFatalError(BAREError::FONT_FAILURE, "Failed to load font " + std::string(fontFile));
46  }
47 
48  // Initialize some member variables.
49  m_height = TTF_FontHeight(font);
50 
53 
54  // Set the padding variable between letters.
55  int padding = size / 8;
56 
57  // Measure all glyphs separately
58  // To hold all glyph metrics given by TTF_GlyphMetrics
59  glm::ivec4* glyphRectangles = new glm::ivec4[m_regLength];
60  // Some incremental variables.
61  int i = 0;
62 
63  // Loop through every character
64  for(char c = m_regStart; (unsigned int)c < m_regStart + m_regLength; c++)
65  {
66  // Get the metrics for the character
67  const char str[2] = {c, '\0'};
68 
69  // Gets the size of a piece of text with the font, with a null-terminated c_str.
70  TTF_SizeText(font, str, &glyphRectangles[i].z, &glyphRectangles[i].w);
71  i++;
72  }
73 
74  unsigned int bestWidth = 0, bestHeight = 0, bestRows = 0;
75  std::vector<int>* bestPartition = nullptr;
76 
77  {
78  // Now that we have the sizes of all the glyphs, figure out which is the best partitioning sequence
79  // Various variables for optimization
80  unsigned int rows = 1;
81  int width, height, area = MAX_TEXTURE_RES * MAX_TEXTURE_RES;
82  // Gradually try more and more rows until we reach optimal partitioning
83  while(rows <= m_regLength)
84  {
85  // Set the height of the texture
86  height = rows * (padding + m_height) + padding;
87 
88  // Create a partition using the createRows function to determine if it's good!
89  // Also, record the minimum texture width we need (the maximum character width)
90  std::vector<int>* trialPartition = createRows(glyphRectangles, m_regLength, rows, padding, width);
91 
92  // Set width and height of final texture to the next highest power of two. OpenGL is going to do this
93  // anyways in memory so its equivalent for optimization
94  width = closestPow2(width);
95  height = closestPow2(height);
96 
97  // First check if the texture breaks our restrictions
98  if(width > MAX_TEXTURE_RES || height > MAX_TEXTURE_RES)
99  {
100  // Obviously this is not going to work. Try again with one more row.
101  rows++;
102  delete[] trialPartition;
103  continue;
104  }
105 
106  // We are within our restrictions, now check if it's optimal. We want the least area (data) that we can
107  // possibly accomodate all the letters with.
108  if(area >= width * height)
109  {
110  // This trial's area is smaller than our current best - that's good!
111  // Get rid of the old best
112  if(bestPartition)
113  delete[] bestPartition;
114 
115  // Set the best to this one
116  bestPartition = trialPartition;
117  bestWidth = width;
118  bestHeight = height;
119  bestRows = rows;
120  area = bestWidth * bestHeight;
121 
122  // Now keep searching for a better one!
123  rows++;
124  } else
125  {
126  // This trial is discarded as we already know of a better one.
127  delete[] trialPartition;
128 
129  // We can actually just break out of the loop if our optimization yields a worse result than the
130  // last feasible.
131  break;
132  }
133  }
134 
135  // Now we know how our glyphs should be arranged in the texture, so let's do that.
136 
137  // First, check if a font can be made at all
138  if(!bestPartition)
139  {
140  // We never actually found a feasible font arrangement
141  throwFatalError(BAREError::FONT_FAILURE, "Failed to create font texture. Try a lower resolution.");
142  }
143  }
144 
145  // Create the actual texture.
146  glGenTextures(1, &m_textureID);
148  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bestWidth, bestHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);
149 
150  // Now just draw all the individual glyphs onto that texture!
151  // For convenience
152  SDL_Color foreground = {255, 255, 255, 255};
153 
154  // The y offset sustains throughout all loops
155  int yOff = padding;
156  // Loop through every column in every row
157  for(unsigned int row = 0; row < bestRows; row++)
158  {
159  // The x offset should reset for each row
160  int xOff = padding;
161  for(unsigned int column = 0; column < bestPartition[row].size(); column++)
162  {
163  // The integer in the partition corresponds with a certain glyph ID
164  int glyph = bestPartition[row][column];
165 
166  // Get what SDL gives us for the actual rendering surface
167  SDL_Surface* glyphSurface =
168  TTF_RenderGlyph_Blended(font, (unsigned int)(FIRST_PRINTABLE_CHAR + glyph), foreground);
169 
170  // Now set some arduously-researched values
171  // The width is pitch / sizeof(unsigned int), as glyphSurface->w is NOT the width of the pixels
172  // memory block. Instead, pitch is defined as the size of a row in the block. Therefore, divide by
173  // the size of the type.
174  unsigned int glyph_width = glyphSurface->pitch / sizeof(unsigned int);
175  // The height is for some reason true. Ask the SDL2 devs.
176  unsigned int glyph_height = glyphSurface->h;
177 
178  unsigned char* pixel_data = (unsigned char*)glyphSurface->pixels;
179  // Number of pixels, times 4 for RGBA
180  // The memory buffer size is actually pitch*height, not
181  unsigned int num_pixel_data = glyph_width * glyph_height;
182  // Pre-multiplication. This just takes the alpha, and multiplies it in and grayscales.
183  for(unsigned int i = 0; i < num_pixel_data; i += 4)
184  {
185  float alpha = pixel_data[i + 3] / 255.0f;
186  // Calculate the grayscale intensity
187  float gray = (unsigned char)((float)pixel_data[i] * alpha);
188 
189  pixel_data[i] = gray;
190  pixel_data[i + 1] = gray;
191  pixel_data[i + 2] = gray;
192  }
193 
194  // Upload to the texture
195  glTexSubImage2D(GL_TEXTURE_2D, 0, xOff, bestHeight - yOff - 1 - glyphSurface->h, glyph_width,
196  glyph_height, GL_BGRA, GL_UNSIGNED_BYTE, pixel_data);
197 
198  // Update coordinates to what they are in the texture
199  glyphRectangles[glyph].x = xOff;
200  glyphRectangles[glyph].y = yOff;
201  glyphRectangles[glyph].z = glyphSurface->w;
202  glyphRectangles[glyph].w = glyphSurface->h;
203 
204  // Free the memory!
205  SDL_FreeSurface(glyphSurface);
206  glyphSurface = nullptr;
207 
208  xOff += glyphRectangles[glyph].z + padding;
209  }
210  yOff += m_height + padding;
211  }
212 
213  // Add in an "unsupported" glyph to symbolize any OOB characters
214  int zero_index = padding - 1;
215  int* pureWhiteSquare = new int[zero_index * zero_index];
216 
217  // Actually set the memory of the first character's spot
218  memset(pureWhiteSquare, 0xffffffff, zero_index * zero_index * sizeof(int));
219 
220  // Upload to the texture
221  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, zero_index, zero_index, GL_RGBA, GL_UNSIGNED_BYTE, pureWhiteSquare);
222 
223  // Free the memory we just sent to OpenGL
224  delete[] pureWhiteSquare;
225  pureWhiteSquare = nullptr;
226 
227  // Set the texture's basic parameters
228  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
229  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
230  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
231  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
232 
233  // Create character glyphs for the font renderer.
234  m_characterGlyphs = new CharacterGlyph[m_regLength + 1]; // all characters + unsupported character
235 
236  for(unsigned int i = 0; (unsigned int)i < m_regLength; i++)
237  {
238  // Set every glyph's basic characteristics
240  m_characterGlyphs[i].size = glm::vec2(glyphRectangles[i].z, glyphRectangles[i].w);
241  m_characterGlyphs[i].uvRect = glm::vec4(
242  (float)glyphRectangles[i].x / (float)bestWidth, (float)glyphRectangles[i].y / (float)bestHeight,
243  (float)glyphRectangles[i].z / (float)bestWidth, (float)glyphRectangles[i].w / (float)bestHeight);
244  }
245 
246  // Set the very end one, the unsupported one
250  glm::vec4(0.0f, 0.0f, (float)zero_index / (float)bestWidth, (float)zero_index / (float)bestHeight);
251 
252  // Now clean up! Unbind the texture
253  GLContextManager::getContext()->bindTexture(GL_TEXTURE_2D, 0);
254  // free memory
255  delete[] glyphRectangles;
256  delete[] bestPartition;
257  // Tell SDL we're done here.
258  TTF_CloseFont(font);
259 }
260 
262 {
263  // Destroy all necessary stuff lol
264  // Free texture
265  if(m_textureID != 0)
266  {
267  glDeleteTextures(1, &m_textureID);
268  m_textureID = 0;
269  }
270  if(m_characterGlyphs != nullptr)
271  {
272  delete[] m_characterGlyphs;
273  m_characterGlyphs = nullptr;
274  }
275 }
276 
277 glm::vec2 Font::measure(const char* s)
278 {
279  // Measure the text if it were being drawn with this font
280  // It is fixed height, so we just need to calculate the x value of the size, plus any newlines that contribute to
281  // height
282  glm::vec2 size(0, m_height);
283 
284  float columnWidth = 0.0f;
285 
286  // Loop through every character in the given text
287  for(int charIndex = 0; s[charIndex] != 0; charIndex++)
288  {
289  char c = s[charIndex];
290  if(c == '\n')
291  {
292  // We were given a new line, add to the height and (possibly) set the width of the whole text
293  size.y += m_height;
294  if(size.x < columnWidth)
295  {
296  size.x = columnWidth;
297  }
298 
299  // Reset the column width for a new column
300  columnWidth = 0;
301  } else
302  {
303  // Check to make sure we actually have this glyph
304  unsigned int glyphIndex = c - m_regStart;
305  if(glyphIndex < 0 || glyphIndex >= m_regLength)
306  {
307  // We don't actually have that character in the font, just add the width of the unknown
308  // characer
309  glyphIndex = m_regLength;
310  }
311  columnWidth += m_characterGlyphs[glyphIndex].size.x;
312  }
313  }
314 
315  // One last time, just make sure that our column width is right
316  if(size.x < columnWidth)
317  size.x = columnWidth;
318 
319  return size;
320 }
321 
322 std::vector<int>* Font::createRows(glm::ivec4* rectangles, int rectanglesLength, int rows, int padding, int& width)
323 {
324  // Create the returning vector
325  std::vector<int>* ret = new std::vector<int>[rows]();
326 
327  // initialize some column widths. Initially, just the padding. Adding glyphs adds width obviously
328  int* columnWidths = new int[rows]();
329  // Set initial values.
330  for(int i = 0; i < rows; i++)
331  {
332  columnWidths[i] = padding;
333  }
334 
335  // Loop through every glyph, adding them as necessary
336  for(int i = 0; i < rectanglesLength; i++)
337  {
338  // Find what row this should be in (the thinnest)
339  int rowIndex = 0;
340  for(int rowIndexTest = 0; rowIndexTest < rows; rowIndexTest++)
341  {
342  // Test each row index, search for least wide
343  if(columnWidths[rowIndexTest] < columnWidths[rowIndex])
344  rowIndex = rowIndexTest;
345  }
346 
347  // Add this glyph to that row (just add its width, and the other side of the padding)
348  columnWidths[rowIndex] += rectangles[i].z + padding;
349 
350  // Actually add the index of the glyph added, for future reference
351  ret[rowIndex].push_back(i);
352  }
353 
354  // Record the max width, which is put into `width`
355  width = 0;
356  for(int i = 0; i < rows; i++)
357  {
358  if(columnWidths[i] > width)
359  width = columnWidths[i];
360  }
361 
362  delete[] columnWidths;
363 
364  return ret;
365 }
366 
367 } // namespace BARE2D
FIRST_PRINTABLE_CHAR
#define FIRST_PRINTABLE_CHAR
Definition: Font.hpp:8
BARE2D
Definition: App.cpp:13
BARE2D::Font::createRows
static std::vector< int > * createRows(glm::ivec4 *rectangles, int rectanglesLength, int rows, int padding, int &width)
Definition: Font.cpp:322
BARE2D::GLContext::bindTexture
void bindTexture(GLenum target, GLenum texture)
Binds a texture to target in the currently active texture slot.
Definition: GLContextManager.cpp:22
BARE2D::Font::~Font
~Font()
Definition: Font.cpp:27
BARE2D::CharacterGlyph
Represents a render glyph, modified for fonts!
Definition: Font.hpp:18
BARE2D::CharacterGlyph::size
glm::vec2 size
Definition: Font.hpp:22
BARE2D::Font::m_height
unsigned int m_height
Definition: Font.hpp:97
MAX_TEXTURE_RES
#define MAX_TEXTURE_RES
Definition: Font.hpp:10
closestPow2
int closestPow2(int i)
Definition: Font.cpp:9
BAREErrors.hpp
GLContextManager.hpp
BARE2D::Font::m_regLength
unsigned int m_regLength
Definition: Font.hpp:100
Font.hpp
BARE2D::Font::measure
glm::vec2 measure(const char *s)
Measures the dimensions of some given text.
Definition: Font.cpp:277
BARE2D::CharacterGlyph::uvRect
glm::vec4 uvRect
Definition: Font.hpp:21
BARE2D::Font::m_textureID
GLuint m_textureID
Definition: Font.hpp:94
BARE2D::Font::Font
Font()
Definition: Font.cpp:24
BARE2D::GLContextManager::getContext
static GLContext * getContext()
Definition: GLContextManager.cpp:44
BARE2D::Font::m_regStart
unsigned int m_regStart
Definition: Font.hpp:100
BARE2D::Font::m_characterGlyphs
CharacterGlyph * m_characterGlyphs
Definition: Font.hpp:103
BARE2D::CharacterGlyph::character
char character
Definition: Font.hpp:20
BARE2D::Font::dispose
void dispose()
Destroys font resources.
Definition: Font.cpp:261
BARE2D::BAREError::FONT_FAILURE
@ FONT_FAILURE
BARE2D::Font::init
void init(const char *fontFile, int size)
Creates font resources.
Definition: Font.cpp:30
BARE2D::throwFatalError
void throwFatalError(BAREError err, std::string message)
Throws an error (fatal). Also calls displayErrors and exits the program.
Definition: BAREErrors.cpp:178
LAST_PRINTABLE_CHAR
#define LAST_PRINTABLE_CHAR
Definition: Font.hpp:9