openMSX
TTFFont.cc
Go to the documentation of this file.
1 #include "TTFFont.hh"
2 #include "LocalFileReference.hh"
3 #include "MSXException.hh"
4 #include "StringOp.hh"
5 #include "ranges.hh"
6 #include "stl.hh"
7 #include "xrange.hh"
8 #include <SDL_ttf.h>
9 #include <cassert>
10 #include <vector>
11 
12 using std::string;
13 
14 namespace openmsx {
15 
16 class SDLTTF
17 {
18 public:
19  static SDLTTF& instance();
20 
21 private:
22  SDLTTF();
23  ~SDLTTF();
24 };
25 
27 {
28 public:
29  static TTFFontPool& instance();
30  TTF_Font* get(const string& filename, int ptSize);
31  void release(TTF_Font* font);
32 
33 private:
34  TTFFontPool() = default;
35  ~TTFFontPool();
36 
37  // We want to keep the LocalFileReference object alive for as long as
38  // the SDL_ttf library uses the font. This solves a problem we had in
39  // the past on windows that temporary files were not cleaned up. The
40  // scenario went like this:
41  // 1. new LocalFileReference object (possibly) creates a temp file
42  // 2. SDL_ttf opens this file (and keeps it open)
43  // 3. LocalFileReference object goes out of scope and deletes the
44  // temp file
45  // 4. (much later) we're done using the font and SDL_ttf closes the
46  // file
47  // Step 3 goes wrong in windows because it's not possible to delete a
48  // still opened file (no problem in unix). Solved by swapping the order
49  // of step 3 and 4. Though this has the disadvantage that if openMSX
50  // crashes between step 3 and 4 the temp file is still left behind.
51  struct FontInfo {
52  LocalFileReference file;
53  TTF_Font* font;
54  std::string name;
55  int size;
56  int count;
57  };
58  std::vector<FontInfo> pool;
59 };
60 
61 
62 // class SDLTTF
63 
64 SDLTTF::SDLTTF()
65 {
66  if (TTF_Init() < 0) {
67  throw FatalError("Couldn't initialize SDL_ttf: ", TTF_GetError());
68  }
69 }
70 
71 SDLTTF::~SDLTTF()
72 {
73  TTF_Quit();
74 }
75 
77 {
78  static SDLTTF oneInstance;
79  return oneInstance;
80 }
81 
82 
83 // class TTFFontPool
84 
85 TTFFontPool::~TTFFontPool()
86 {
87  assert(pool.empty());
88 }
89 
91 {
92  static TTFFontPool oneInstance;
93  return oneInstance;
94 }
95 
96 TTF_Font* TTFFontPool::get(const string& filename, int ptSize)
97 {
98  auto it = ranges::find_if(pool, [&](auto& info) {
99  return (info.name == filename) && (info.size == ptSize);
100  });
101  if (it != end(pool)) {
102  ++it->count;
103  return it->font;
104  }
105 
106  SDLTTF::instance(); // init library
107  FontInfo info;
108  info.file = LocalFileReference(filename);
109  auto* result = TTF_OpenFont(info.file.getFilename().c_str(), ptSize);
110  if (!result) {
111  throw MSXException(TTF_GetError());
112  }
113  info.font = result;
114  info.name = filename;
115  info.size = ptSize;
116  info.count = 1;
117  pool.push_back(std::move(info));
118  return result;
119 }
120 
121 void TTFFontPool::release(TTF_Font* font)
122 {
123  auto it = rfind_if_unguarded(pool,
124  [&](const FontInfo& i) { return i.font == font; });
125  --it->count;
126  if (it->count == 0) {
127  TTF_CloseFont(it->font);
128  move_pop_back(pool, it);
129  }
130 }
131 
132 
133 // class TTFFont
134 
135 TTFFont::TTFFont(const std::string& filename, int ptSize)
136 {
137  font = TTFFontPool::instance().get(filename, ptSize);
138 }
139 
141 {
142  if (!font) return;
143  TTFFontPool::instance().release(static_cast<TTF_Font*>(font));
144 }
145 
146 SDLSurfacePtr TTFFont::render(std::string text, byte r, byte g, byte b) const
147 {
148  SDL_Color color = { r, g, b, 0 };
149 
150  // Optimization: remove trailing empty lines
151  StringOp::trimRight(text, " \n");
152  if (text.empty()) return SDLSurfacePtr(nullptr);
153 
154  // Split on newlines
155  auto lines = StringOp::split(text, '\n');
156  assert(!lines.empty());
157 
158  if (lines.size() == 1) {
159  // Special case for a single line: we can avoid the
160  // copy to an extra SDL_Surface
161  assert(!text.empty());
162  SDLSurfacePtr surface(
163  TTF_RenderUTF8_Blended(static_cast<TTF_Font*>(font),
164  text.c_str(), color));
165  if (!surface) {
166  throw MSXException(TTF_GetError());
167  }
168  return surface;
169  }
170 
171  // Determine maximum width and lineHeight
172  unsigned width = 0;
173  unsigned lineHeight = 0; // initialize to avoid warning
174  for (auto& s : lines) {
175  unsigned w;
176  getSize(string(s), w, lineHeight);
177  width = std::max(width, w);
178  }
179  // There might be extra space between two successive lines
180  // (so lineSkip might be bigger than lineHeight).
181  unsigned lineSkip = getHeight();
182  // We assume that height is the same for all lines.
183  // For the last line we don't include spacing between two lines.
184  auto height = unsigned((lines.size() - 1) * lineSkip + lineHeight);
185 
186  // Create destination surface (initial surface is fully transparent)
187  SDLSurfacePtr destination(SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,
188  32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
189  if (!destination) {
190  throw MSXException("Couldn't allocate surface for multiline text.");
191  }
192 
193  // Actually render the text:
194  for (auto i : xrange(lines.size())) {
195  // Render single line
196  if (lines[i].empty()) {
197  // SDL_TTF gives an error on empty lines, but we can
198  // simply skip such lines
199  continue;
200  }
201  SDLSurfacePtr line(TTF_RenderUTF8_Blended(
202  static_cast<TTF_Font*>(font),
203  string(lines[i]).c_str(), color));
204  if (!line) {
205  throw MSXException(TTF_GetError());
206  }
207 
208  // Copy line to destination surface
209  SDL_Rect rect;
210  rect.x = 0;
211  rect.y = Sint16(i * lineSkip);
212  SDL_SetSurfaceBlendMode(line.get(), SDL_BLENDMODE_NONE); // no blending during copy
213  SDL_BlitSurface(line.get(), nullptr, destination.get(), &rect);
214  }
215  return destination;
216 }
217 
218 unsigned TTFFont::getHeight() const
219 {
220  return TTF_FontLineSkip(static_cast<TTF_Font*>(font));
221 }
222 
224 {
225  return TTF_FontFaceIsFixedWidth(static_cast<TTF_Font*>(font)) != 0;
226 }
227 
228 unsigned TTFFont::getWidth() const
229 {
230  int advance;
231  if (TTF_GlyphMetrics(static_cast<TTF_Font*>(font), Uint16('M'),
232  nullptr /*minx*/, nullptr /*maxx*/,
233  nullptr /*miny*/, nullptr /*maxy*/,
234  &advance)) {
235  // error?
236  return 10; //fallback-width
237  }
238  return advance;
239 }
240 
241 void TTFFont::getSize(const std::string& text,
242  unsigned& width, unsigned& height) const
243 {
244  if (TTF_SizeUTF8(static_cast<TTF_Font*>(font), text.c_str(),
245  reinterpret_cast<int*>(&width),
246  reinterpret_cast<int*>(&height))) {
247  throw MSXException(TTF_GetError());
248  }
249 }
250 
251 } // namespace openmsx
TTFFont()=default
Construct an empty font.
auto xrange(T e)
Definition: xrange.hh:170
vector< string_view > split(string_view str, char chars)
Definition: StringOp.cc:193
SDL_Surface * get()
static SDLTTF & instance()
Definition: TTFFont.cc:76
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
SDLSurfacePtr render(std::string text, byte r, byte g, byte b) const
Render the given text to a new SDL_Surface.
Definition: TTFFont.cc:146
size_t size(std::string_view utf8)
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:177
unsigned getHeight() const
Return the height of the font.
Definition: TTFFont.cc:218
Wrapper around a SDL_Surface.
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
void advance(octet_iterator &it, distance_type n, octet_iterator end)
void trimRight(string &str, const char *chars)
Definition: StringOp.cc:91
constexpr const char *const filename
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition: lz4.cc:207
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
int g
void release(TTF_Font *font)
Definition: TTFFont.cc:121
static TTFFontPool & instance()
Definition: TTFFont.cc:90
void getSize(const std::string &text, unsigned &width, unsigned &height) const
Return the size in pixels of the text if it would be rendered.
Definition: TTFFont.cc:241
TTF_Font * get(const string &filename, int ptSize)
Definition: TTFFont.cc:96
unsigned getWidth() const
Return the width of the font.
Definition: TTFFont.cc:228
Helper class to use files in APIs other than openmsx::File.
bool isFixedWidth() const
Returns true iff this is a fixed-with (=mono-spaced) font.
Definition: TTFFont.cc:223
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:160