openMSX
OSDText.cc
Go to the documentation of this file.
1 #include "OSDText.hh"
2 #include "TTFFont.hh"
3 #include "SDLImage.hh"
4 #include "OutputRectangle.hh"
5 #include "CommandException.hh"
6 #include "FileContext.hh"
7 #include "FileOperations.hh"
8 #include "TclObject.hh"
9 #include "StringOp.hh"
10 #include "utf8_core.hh"
11 #include "unreachable.hh"
12 #include "memory.hh"
13 #include "components.hh"
14 #include <cassert>
15 #if COMPONENT_GL
16 #include "GLImage.hh"
17 #endif
18 
19 using std::string;
20 using std::vector;
21 
22 namespace openmsx {
23 
24 OSDText::OSDText(const OSDGUI& gui, const string& name)
25  : OSDImageBasedWidget(gui, name)
26  , fontfile("skins/Vera.ttf.gz")
27  , size(12)
28  , wrapMode(NONE), wrapw(0.0), wraprelw(1.0)
29 {
30 }
31 
33 {
34 }
35 
36 vector<string_ref> OSDText::getProperties() const
37 {
38  auto result = OSDImageBasedWidget::getProperties();
39  static const char* const vals[] = {
40  "-text", "-font", "-size", "-wrap", "-wrapw", "-wraprelw",
41  "-query-size",
42  };
43  result.insert(end(result), std::begin(vals), std::end(vals));
44  return result;
45 }
46 
48  Interpreter& interp, string_ref name, const TclObject& value)
49 {
50  if (name == "-text") {
51  string_ref val = value.getString();
52  if (text != val) {
53  text = val.str();
54  // note: don't invalidate font (don't reopen font file)
57  }
58  } else if (name == "-font") {
59  string val = value.getString().str();
60  if (fontfile != val) {
61  string file = SystemFileContext().resolve(val);
62  if (!FileOperations::isRegularFile(file)) {
63  throw CommandException("Not a valid font file: " + val);
64  }
65  fontfile = val;
67  }
68  } else if (name == "-size") {
69  int size2 = value.getInt(interp);
70  if (size != size2) {
71  size = size2;
73  }
74  } else if (name == "-wrap") {
75  string_ref val = value.getString();
76  WrapMode wrapMode2;
77  if (val == "none") {
78  wrapMode2 = NONE;
79  } else if (val == "word") {
80  wrapMode2 = WORD;
81  } else if (val == "char") {
82  wrapMode2 = CHAR;
83  } else {
84  throw CommandException("Not a valid value for -wrap, "
85  "expected one of 'none word char', but got '" +
86  val + "'.");
87  }
88  if (wrapMode != wrapMode2) {
89  wrapMode = wrapMode2;
91  }
92  } else if (name == "-wrapw") {
93  double wrapw2 = value.getDouble(interp);
94  if (wrapw != wrapw2) {
95  wrapw = wrapw2;
97  }
98  } else if (name == "-wraprelw") {
99  double wraprelw2 = value.getDouble(interp);
100  if (wraprelw != wraprelw2) {
101  wraprelw = wraprelw2;
103  }
104  } else if (name == "-query-size") {
105  throw CommandException("-query-size property is readonly");
106  } else {
107  OSDImageBasedWidget::setProperty(interp, name, value);
108  }
109 }
110 
111 void OSDText::getProperty(string_ref name, TclObject& result) const
112 {
113  if (name == "-text") {
114  result.setString(text);
115  } else if (name == "-font") {
116  result.setString(fontfile);
117  } else if (name == "-size") {
118  result.setInt(size);
119  } else if (name == "-wrap") {
120  string wrapString;
121  switch (wrapMode) {
122  case NONE: wrapString = "none"; break;
123  case WORD: wrapString = "word"; break;
124  case CHAR: wrapString = "char"; break;
125  default: UNREACHABLE;
126  }
127  result.setString(wrapString);
128  } else if (name == "-wrapw") {
129  result.setDouble(wrapw);
130  } else if (name == "-wraprelw") {
131  result.setDouble(wraprelw);
132  } else if (name == "-query-size") {
133  double outX, outY;
134  getRenderedSize(outX, outY);
135  result.addListElement(outX);
136  result.addListElement(outY);
137  } else {
138  OSDImageBasedWidget::getProperty(name, result);
139  }
140 }
141 
142 void OSDText::invalidateLocal()
143 {
144  font = TTFFont(); // clear font
146 }
147 
148 
150 {
151  return "text";
152 }
153 
154 void OSDText::getWidthHeight(const OutputRectangle& /*output*/,
155  double& width, double& height) const
156 {
157  if (image) {
158  width = image->getWidth();
159  height = image->getHeight();
160  } else {
161  // we don't know the dimensions, must be because of an error
162  assert(hasError());
163  width = 0;
164  height = 0;
165  }
166 }
167 
168 byte OSDText::getFadedAlpha() const
169 {
170  return byte((getRGBA(0) & 0xff) * getRecursiveFadeValue());
171 }
172 
173 template <typename IMAGE> std::unique_ptr<BaseImage> OSDText::create(
174  OutputRectangle& output)
175 {
176  if (text.empty()) {
177  return make_unique<IMAGE>(0, 0, 0);
178  }
179  int scale = getScaleFactor(output);
180  if (font.empty()) {
181  try {
182  string file = SystemFileContext().resolve(fontfile);
183  int ptSize = size * scale;
184  font = TTFFont(file, ptSize);
185  } catch (MSXException& e) {
186  throw MSXException("Couldn't open font: " + e.getMessage());
187  }
188  }
189  try {
190  double pWidth, pHeight;
191  getParent()->getWidthHeight(output, pWidth, pHeight);
192  int maxWidth = int(wrapw * scale + wraprelw * pWidth + 0.5);
193  // Width can't be negative, if it is make it zero instead.
194  // This will put each character on a different line.
195  maxWidth = std::max(0, maxWidth);
196 
197  // TODO gradient???
198  unsigned rgba = getRGBA(0);
199  string wrappedText;
200  if (wrapMode == NONE) {
201  wrappedText = text; // don't wrap
202  } else if (wrapMode == WORD) {
203  wrappedText = getWordWrappedText(text, maxWidth);
204  } else if (wrapMode == CHAR) {
205  wrappedText = getCharWrappedText(text, maxWidth);
206  } else {
207  UNREACHABLE;
208  }
209  // An alternative is to pass vector<string> to TTFFont::render().
210  // That way we can avoid StringOp::join() (in the wrap functions)
211  // followed by // StringOp::split() (in TTFFont::render()).
212  SDLSurfacePtr surface(font.render(wrappedText,
213  (rgba >> 24) & 0xff, (rgba >> 16) & 0xff, (rgba >> 8) & 0xff));
214  if (surface) {
215  return make_unique<IMAGE>(std::move(surface));
216  } else {
217  return make_unique<IMAGE>(0, 0, 0);
218  }
219  } catch (MSXException& e) {
220  throw MSXException("Couldn't render text: " + e.getMessage());
221  }
222 }
223 
224 
225 // Search for a position strictly between min and max which also points to the
226 // start of a (possibly multi-byte) utf8-character. If no such position exits,
227 // this function returns 'min'.
228 static size_t findCharSplitPoint(const string& line, size_t min, size_t max)
229 {
230  auto pos = (min + max) / 2;
231  auto beginIt = line.data();
232  auto posIt = beginIt + pos;
233 
234  auto fwdIt = utf8::sync_forward(posIt);
235  auto maxIt = beginIt + max;
236  assert(fwdIt <= maxIt);
237  if (fwdIt != maxIt) {
238  return fwdIt - beginIt;
239  }
240 
241  auto bwdIt = utf8::sync_backward(posIt);
242  auto minIt = beginIt + min;
243  assert(minIt <= bwdIt); (void)minIt;
244  return bwdIt - beginIt;
245 }
246 
247 // Search for a position that's strictly between min and max and which points
248 // to a character directly following a delimiter character. if no such position
249 // exits, this function returns 'min'.
250 // This function works correctly with multi-byte utf8-encoding as long as
251 // all delimiter characters are single byte chars.
252 static size_t findWordSplitPoint(string_ref line, size_t min, size_t max)
253 {
254  static const char* const delimiters = " -/";
255 
256  // initial guess for a good position
257  assert(min < max);
258  size_t pos = (min + max) / 2;
259  if (pos == min) {
260  // can't reduce further
261  return min;
262  }
263 
264  // try searching backward (this also checks current position)
265  assert(pos > min);
266  auto pos2 = line.substr(min, pos - min).find_last_of(delimiters);
267  if (pos2 != string_ref::npos) {
268  pos2 += min + 1;
269  assert(min < pos2);
270  assert(pos2 <= pos);
271  return pos2;
272  }
273 
274  // try searching forward
275  auto pos3 = line.substr(pos, max - pos).find_first_of(delimiters);
276  if (pos3 != string_ref::npos) {
277  pos3 += pos;
278  assert(pos3 < max);
279  pos3 += 1; // char directly after a delimiter;
280  if (pos3 < max) {
281  return pos3;
282  }
283  }
284 
285  return min;
286 }
287 
288 static size_t takeSingleChar(const string& /*line*/, unsigned /*maxWidth*/)
289 {
290  return 1;
291 }
292 
293 template<typename FindSplitPointFunc, typename CantSplitFunc>
294 size_t OSDText::split(const string& line, unsigned maxWidth,
295  FindSplitPointFunc findSplitPoint,
296  CantSplitFunc cantSplit,
297  bool removeTrailingSpaces) const
298 {
299  if (line.empty()) {
300  // empty line always fits (explicitly handle this because
301  // SDL_TTF can't handle empty strings)
302  return 0;
303  }
304 
305  unsigned width, height;
306  font.getSize(line, width, height);
307  if (width <= maxWidth) {
308  // whole line fits
309  return line.size();
310  }
311 
312  // binary search till we found the largest initial substring that is
313  // not wider than maxWidth
314  size_t min = 0;
315  size_t max = line.size();
316  // invariant: line.substr(0, min) DOES fit
317  // line.substr(0, max) DOES NOT fit
318  size_t cur = findSplitPoint(line, min, max);
319  if (cur == 0) {
320  // Could not find a valid split point, then split on char
321  // (this also handles the case of a single too wide char)
322  return cantSplit(line, maxWidth);
323  }
324  while (true) {
325  assert(min < cur);
326  assert(cur < max);
327  string curStr = line.substr(0, cur);
328  if (removeTrailingSpaces) {
329  StringOp::trimRight(curStr, ' ');
330  }
331  font.getSize(curStr, width, height);
332  if (width <= maxWidth) {
333  // still fits, try to enlarge
334  size_t next = findSplitPoint(line, cur, max);
335  if (next == cur) {
336  return cur;
337  }
338  min = cur;
339  cur = next;
340  } else {
341  // doesn't fit anymore, try to shrink
342  size_t next = findSplitPoint(line, min, cur);
343  if (next == min) {
344  if (min == 0) {
345  // even the first word does not fit,
346  // split on char (see above)
347  return cantSplit(line, maxWidth);
348  }
349  return min;
350  }
351  max = cur;
352  cur = next;
353  }
354  }
355 }
356 
357 size_t OSDText::splitAtChar(const std::string& line, unsigned maxWidth) const
358 {
359  return split(line, maxWidth, findCharSplitPoint, takeSingleChar, false);
360 }
361 
362 struct SplitAtChar {
363  SplitAtChar(const OSDText& osdText_) : osdText(osdText_) {}
364  size_t operator()(const string& line, unsigned maxWidth) {
365  return osdText.splitAtChar(line, maxWidth);
366  }
367  const OSDText& osdText;
368 };
369 size_t OSDText::splitAtWord(const std::string& line, unsigned maxWidth) const
370 {
371  return split(line, maxWidth, findWordSplitPoint, SplitAtChar(*this), true);
372 }
373 
374 string OSDText::getCharWrappedText(const string& text, unsigned maxWidth) const
375 {
376  vector<string_ref> wrappedLines;
377  for (auto& line : StringOp::split(text, '\n')) {
378  do {
379  auto pos = splitAtChar(line.str(), maxWidth);
380  wrappedLines.push_back(line.substr(0, pos));
381  line = line.substr(pos);
382  } while (!line.empty());
383  }
384  return StringOp::join(wrappedLines, '\n');
385 }
386 
387 string OSDText::getWordWrappedText(const string& text, unsigned maxWidth) const
388 {
389  vector<string_ref> wrappedLines;
390  for (auto& line : StringOp::split(text, '\n')) {
391  do {
392  auto pos = splitAtWord(line.str(), maxWidth);
393  string_ref first = line.substr(0, pos);
394  StringOp::trimRight(first, ' '); // remove trailing spaces
395  wrappedLines.push_back(first);
396  line = line.substr(pos);
397  StringOp::trimLeft(line, ' '); // remove leading spaces
398  } while (!line.empty());
399  }
400  return StringOp::join(wrappedLines, '\n');
401 }
402 
403 void OSDText::getRenderedSize(double& outX, double& outY) const
404 {
405  SDL_Surface* surface = SDL_GetVideoSurface();
406  if (!surface) {
407  throw CommandException(
408  "Can't query size: no window visible");
409  }
410  DummyOutputRectangle output(surface->w, surface->h);
411  // force creating image (does not yet draw it on screen)
412  const_cast<OSDText*>(this)->createImage(output);
413 
414  unsigned width = 0;
415  unsigned height = 0;
416  if (image) {
417  width = image->getWidth();
418  height = image->getHeight();
419  }
420 
421  double scale = getScaleFactor(output);
422  outX = width / scale;
423  outY = height / scale;
424 }
425 
426 std::unique_ptr<BaseImage> OSDText::createSDL(OutputRectangle& output)
427 {
428  return create<SDLImage>(output);
429 }
430 
431 std::unique_ptr<BaseImage> OSDText::createGL(OutputRectangle& output)
432 {
433 #if COMPONENT_GL
434  return create<GLImage>(output);
435 #else
436  (void)&output;
437  return nullptr;
438 #endif
439 }
440 
441 } // namespace openmsx
void setDouble(double value)
Definition: TclObject.cc:88
OSDWidget * getParent()
Definition: OSDWidget.cc:141
int getScaleFactor(const OutputRectangle &surface) const
Definition: OSDWidget.cc:405
bool isRegularFile(const Stat &st)
virtual void setProperty(Interpreter &interp, string_ref name, const TclObject &value)
Definition: OSDText.cc:47
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:135
void invalidateRecursive()
Definition: OSDWidget.cc:342
std::string str() const
Definition: string_ref.cc:10
bool empty() const
Is this an empty font? (a default constructed object).
Definition: TTFFont.hh:47
int getInt(Interpreter &interp) const
Definition: TclObject.cc:150
string_ref getString() const
Definition: TclObject.cc:180
virtual void setProperty(Interpreter &interp, string_ref name, const TclObject &value)
uint32_t next(octet_iterator &it, octet_iterator end)
virtual string_ref getType() const
Definition: OSDText.cc:149
size_t operator()(const string &line, unsigned maxWidth)
Definition: OSDText.cc:364
virtual void getWidthHeight(const OutputRectangle &output, double &width, double &height) const =0
unsigned char byte
8 bit unsigned integer
Definition: openmsx.hh:33
std::unique_ptr< BaseImage > image
void trimLeft(string &str, const char *chars)
Definition: StringOp.cc:290
size_type find_last_of(string_ref s) const
Definition: string_ref.cc:121
string join(const vector< string_ref > &elems, char separator)
Definition: StringOp.cc:369
This class implements a subset of the proposal for std::string_ref (proposed for the next c++ standar...
Definition: string_ref.hh:18
virtual double getRecursiveFadeValue() const
SplitAtChar(const OSDText &osdText_)
Definition: OSDText.cc:363
Wrapper around a SDL_Surface.
mat4 scale(const vec3 &xyz)
Definition: gl_transform.hh:19
void trimRight(string &str, const char *chars)
Definition: StringOp.cc:259
const std::string resolve(string_ref filename) const
Definition: FileContext.cc:74
void createImage(OutputRectangle &output)
OSDText(const OSDGUI &gui, const std::string &name)
Definition: OSDText.cc:24
static const size_type npos
Definition: string_ref.hh:26
size_type find_first_of(string_ref s) const
Definition: string_ref.cc:107
octet_iterator sync_backward(octet_iterator it)
Definition: utf8_core.hh:247
vector< string_ref > split(string_ref str, char chars)
Definition: StringOp.cc:357
void invalidateChildren()
Definition: OSDWidget.cc:348
const OSDText & osdText
Definition: OSDText.cc:367
void addListElement(string_ref element)
Definition: TclObject.cc:110
void setString(string_ref value)
Definition: TclObject.cc:55
virtual void getProperty(string_ref name, TclObject &result) const
Definition: OSDText.cc:111
unsigned getRGBA(unsigned corner) const
size_t size() const
virtual std::vector< string_ref > getProperties() const
virtual void getProperty(string_ref name, TclObject &result) const
void setInt(int value)
Definition: TclObject.cc:66
friend struct SplitAtChar
Definition: OSDText.hh:53
string_ref substr(size_type pos, size_type n=npos) const
Definition: string_ref.cc:52
virtual std::vector< string_ref > getProperties() const
Definition: OSDText.cc:36
string_ref::const_iterator begin(const string_ref &x)
Definition: string_ref.hh:134
octet_iterator sync_forward(octet_iterator it)
Definition: utf8_core.hh:240
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:263
SDLSurfacePtr render(std::string text, byte r, byte g, byte b) const
Render the given text to a new SDL_Surface.
Definition: TTFFont.cc:168
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:170
#define UNREACHABLE
Definition: unreachable.hh:56