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