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