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& operator=(const SDLTTF&) = delete;
20
21 static SDLTTF& instance();
22
23private:
24 SDLTTF();
25 ~SDLTTF();
26};
27
29{
30public:
31 TTFFontPool(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
38private:
39 TTFFontPool() = default;
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 {
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
69SDLTTF::SDLTTF()
70{
71 if (TTF_Init() < 0) {
72 throw FatalError("Couldn't initialize SDL_ttf: ", TTF_GetError());
73 }
74}
75
76SDLTTF::~SDLTTF()
77{
78 TTF_Quit();
79}
80
82{
83 static SDLTTF oneInstance;
84 return oneInstance;
85}
86
87
88// class TTFFontPool
89
90TTFFontPool::~TTFFontPool()
91{
92 assert(pool.empty());
93}
94
96{
97 static TTFFontPool oneInstance;
98 return oneInstance;
99}
100
101TTF_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
125void 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
138TTFFont::TTFFont(const std::string& filename, int ptSize)
139 : font(TTFFontPool::instance().get(filename, ptSize))
140{
141}
142
144{
145 if (!font) return;
146 TTFFontPool::instance().release(static_cast<TTF_Font*>(font));
147}
148
149SDLSurfacePtr TTFFont::render(std::string text, uint8_t r, uint8_t g, uint8_t 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 int width = 0;
180 int lineHeight = 0; // initialize to avoid warning
181 int numLines = 1;
182 while (true) {
183 auto [w, h] = getSize(std::string(current_line));
184 width = std::max(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 int 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 = (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
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
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
SDLTTF & operator=(const SDLTTF &)=delete
static SDLTTF & instance()
Definition TTFFont.cc:81
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
int getHeight() const
Return the height of the font.
Definition TTFFont.cc:233
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:149
int 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
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.
constexpr const char * c_str() const
void trimRight(string &str, const char *chars)
Definition StringOp.cc:29
auto split_view(std::string_view str, Separators separators)
Definition StringOp.hh:83
This file implemented 3 utility functions:
Definition Autofire.cc:9
auto find(InputRange &&range, const T &value)
Definition ranges.hh:160
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)