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