bitmap_font.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>,
  3. // Ingo Ruhnke <grumbel@gmail.com>
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #include "video/bitmap_font.hpp"
  18. #include <algorithm>
  19. #include <physfs.h>
  20. #include <cmath>
  21. #include <sstream>
  22. #include "physfs/physfs_sdl.hpp"
  23. #include "physfs/util.hpp"
  24. #include "util/file_system.hpp"
  25. #include "util/log.hpp"
  26. #include "util/reader_document.hpp"
  27. #include "util/reader_mapping.hpp"
  28. #include "util/utf8_iterator.hpp"
  29. #include "video/canvas.hpp"
  30. #include "video/drawing_request.hpp"
  31. #include "video/surface.hpp"
  32. #include "video/sdl_surface.hpp"
  33. namespace {
  34. bool vline_empty(const SDLSurfacePtr& surface, int x, int start_y, int end_y, Uint8 threshold)
  35. {
  36. const Uint8* pixels = static_cast<Uint8*>(surface->pixels);
  37. for (int y = start_y; y < end_y; ++y)
  38. {
  39. const Uint8& p = pixels[surface->pitch*y + x*surface->format->BytesPerPixel + 3];
  40. if (p > threshold)
  41. {
  42. return false;
  43. }
  44. }
  45. return true;
  46. }
  47. } // namespace
  48. BitmapFont::BitmapFont(GlyphWidth glyph_width_,
  49. const std::string& filename,
  50. int shadowsize_) :
  51. glyph_width(glyph_width_),
  52. glyph_surfaces(),
  53. shadow_surfaces(),
  54. char_height(),
  55. shadowsize(shadowsize_),
  56. border(0),
  57. rtl(false),
  58. glyphs(65536)
  59. {
  60. for (unsigned int i=0; i<65536;i++) glyphs[i].surface_idx = -1;
  61. const std::string fontdir = FileSystem::dirname(filename);
  62. const std::string fontname = FileSystem::basename(filename);
  63. // scan for prefix-filename in addons search path.
  64. physfsutil::enumerate_files(fontdir, [fontdir, fontname, this](const std::string& file) {
  65. std::string filepath = FileSystem::join(fontdir, file);
  66. if (file.rfind(fontname) != std::string::npos) {
  67. try {
  68. loadFontFile(filepath);
  69. }
  70. catch(const std::exception& e)
  71. {
  72. log_fatal << "Couldn't load font file: " << e.what() << std::endl;
  73. }
  74. }
  75. });
  76. }
  77. void
  78. BitmapFont::loadFontFile(const std::string &filename)
  79. {
  80. // FIXME: Workaround for a crash on MSYS2 when starting with --debug.
  81. log_debug_ << "Loading font: " << filename << std::endl;
  82. auto doc = ReaderDocument::from_file(filename);
  83. auto root = doc.get_root();
  84. if (root.get_name() != "supertux-font") {
  85. std::ostringstream msg;
  86. msg << "Font file:" << filename << ": is not a supertux-font file";
  87. throw std::runtime_error(msg.str());
  88. }
  89. auto config_l = root.get_mapping();
  90. int def_char_width=0;
  91. if ( !config_l.get("glyph-width",def_char_width) ) {
  92. log_warning << "Font:" << filename << ": misses default glyph-width" << std::endl;
  93. }
  94. if ( !config_l.get("glyph-height",char_height) ) {
  95. std::ostringstream msg;
  96. msg << "Font:" << filename << ": misses glyph-height";
  97. throw std::runtime_error(msg.str());
  98. }
  99. config_l.get("glyph-border", border);
  100. config_l.get("rtl", rtl);
  101. auto iter = config_l.get_iter();
  102. while (iter.next()) {
  103. const std::string& token = iter.get_key();
  104. if ( token == "surface" ) {
  105. auto glyphs_val = iter.as_mapping();
  106. int local_char_width;
  107. bool monospaced;
  108. GlyphWidth local_glyph_width;
  109. std::string glyph_image;
  110. std::string shadow_image;
  111. std::vector<std::string> chars;
  112. if ( ! glyphs_val.get("glyph-width", local_char_width) ) {
  113. local_char_width = def_char_width;
  114. }
  115. if ( ! glyphs_val.get("monospace", monospaced ) ) {
  116. local_glyph_width = glyph_width;
  117. }
  118. else {
  119. if ( monospaced ) local_glyph_width = FIXED;
  120. else local_glyph_width = VARIABLE;
  121. }
  122. if ( ! glyphs_val.get("glyphs", glyph_image) ) {
  123. std::ostringstream msg;
  124. msg << "Font:" << filename << ": missing glyphs image";
  125. throw std::runtime_error(msg.str());
  126. }
  127. if ( ! glyphs_val.get("shadows", shadow_image) ) {
  128. std::ostringstream msg;
  129. msg << "Font:" << filename << ": missing shadows image";
  130. throw std::runtime_error(msg.str());
  131. }
  132. if ( ! glyphs_val.get("chars", chars) || chars.size() == 0) {
  133. std::ostringstream msg;
  134. msg << "Font:" << filename << ": missing chars definition";
  135. throw std::runtime_error(msg.str());
  136. }
  137. if ( local_char_width==0 ) {
  138. std::ostringstream msg;
  139. msg << "Font:" << filename << ": misses glyph-width for some surface";
  140. throw std::runtime_error(msg.str());
  141. }
  142. loadFontSurface(glyph_image, shadow_image, chars,
  143. local_glyph_width, local_char_width);
  144. }
  145. }
  146. }
  147. void
  148. BitmapFont::loadFontSurface(const std::string& glyphimage,
  149. const std::string& shadowimage,
  150. const std::vector<std::string>& chars,
  151. GlyphWidth glyph_width_,
  152. int char_width)
  153. {
  154. SurfacePtr glyph_surface = Surface::from_file("images/engine/fonts/" + glyphimage);
  155. SurfacePtr shadow_surface = Surface::from_file("images/engine/fonts/" + shadowimage);
  156. int surface_idx = static_cast<int>(glyph_surfaces.size());
  157. glyph_surfaces.push_back(glyph_surface);
  158. shadow_surfaces.push_back(shadow_surface);
  159. int row = 0;
  160. int col = 0;
  161. int wrap = glyph_surface->get_width() / char_width;
  162. SDLSurfacePtr surface;
  163. if ( glyph_width_ == VARIABLE )
  164. {
  165. surface = SDLSurface::from_file("images/engine/fonts/" + glyphimage);
  166. SDL_LockSurface(surface.get());
  167. }
  168. for (unsigned int i = 0; i < chars.size(); ++i) {
  169. for (UTF8Iterator chr(chars[i]); !chr.done(); ++chr) {
  170. int y = row * (char_height + 2*border) + border;
  171. int x = col * (char_width + 2*border) + border;
  172. if ( ++col == wrap ) { col=0; row++; }
  173. if ( *chr == 0x0020 && glyphs[0x20].surface_idx != -1) continue;
  174. Glyph glyph;
  175. glyph.surface_idx = surface_idx;
  176. if ( glyph_width_ == FIXED || (*chr <= 255 && isdigit(*chr)) )
  177. {
  178. glyph.rect = Rectf(static_cast<float>(x),
  179. static_cast<float>(y),
  180. static_cast<float>(x + char_width),
  181. static_cast<float>(y + char_height));
  182. glyph.offset = Vector(0, 0);
  183. glyph.advance = static_cast<float>(char_width);
  184. }
  185. else
  186. {
  187. if (y + char_height > surface->h)
  188. {
  189. log_warning << "error: font definition contains more letter then the images: " << glyphimage << std::endl;
  190. goto abort;
  191. }
  192. int left = x;
  193. while (left < x + char_width && vline_empty(surface, left, y, y + char_height, 64))
  194. left += 1;
  195. int right = x + char_width - 1;
  196. while (right > left && vline_empty(surface, right, y, y + char_height, 64))
  197. right -= 1;
  198. if (left <= right)
  199. {
  200. glyph.offset = Vector(static_cast<float>(x) - static_cast<float>(left), 0.0f);
  201. glyph.advance = static_cast<float>(right - left + 1 + 1); // FIXME: might be useful to make spacing configurable.
  202. }
  203. else
  204. { // Glyph is completely transparent.
  205. glyph.offset = Vector(0, 0);
  206. glyph.advance = static_cast<float>(char_width + 1); // FIXME: might be useful to make spacing configurable.
  207. }
  208. glyph.rect = Rectf(static_cast<float>(x),
  209. static_cast<float>(y),
  210. static_cast<float>(x + char_width),
  211. static_cast<float>(y + char_height));
  212. }
  213. glyphs[*chr] = glyph;
  214. }
  215. if ( col>0 && col <= wrap ) {
  216. col = 0;
  217. row++;
  218. }
  219. }
  220. abort:
  221. if (surface) {
  222. SDL_UnlockSurface(surface.get());
  223. }
  224. }
  225. BitmapFont::~BitmapFont()
  226. {
  227. }
  228. float
  229. BitmapFont::get_text_width(const std::string& text) const
  230. {
  231. float curr_width = 0;
  232. float last_width = 0;
  233. for (UTF8Iterator it(text); !it.done(); ++it)
  234. {
  235. if (*it == '\n')
  236. {
  237. last_width = std::max(last_width, curr_width);
  238. curr_width = 0;
  239. }
  240. else
  241. {
  242. if ( glyphs.at(*it).surface_idx != -1 )
  243. curr_width += glyphs[*it].advance;
  244. else
  245. curr_width += glyphs[0x20].advance;
  246. }
  247. }
  248. return std::max(curr_width, last_width);
  249. }
  250. float
  251. BitmapFont::get_text_height(const std::string& text) const
  252. {
  253. std::string::size_type text_height = char_height;
  254. for (std::string::const_iterator it = text.begin(); it != text.end(); ++it)
  255. { // Since UTF8 multibyte characters are decoded with values
  256. // outside the ASCII range there is no risk of overlapping and
  257. // thus we don't need to decode the utf-8 string.
  258. if (*it == '\n')
  259. text_height += char_height + 2;
  260. }
  261. return static_cast<float>(text_height);
  262. }
  263. float
  264. BitmapFont::get_height() const
  265. {
  266. return static_cast<float>(char_height);
  267. }
  268. std::string
  269. BitmapFont::wrap_to_width(const std::string& s_, float width, std::string* overflow)
  270. {
  271. std::string s = s_;
  272. // If text is already smaller, return full text.
  273. if (get_text_width(s) <= width) {
  274. if (overflow) *overflow = "";
  275. return s;
  276. }
  277. // If we can find a whitespace character to break at, return text up to this character.
  278. for (int i = static_cast<int>(s.length()) - 1; i >= 0; i--) {
  279. std::string s2 = s.substr(0,i);
  280. if (s[i] != ' ') continue;
  281. if (get_text_width(s2) <= width) {
  282. if (overflow) *overflow = s.substr(i+1);
  283. return s.substr(0, i);
  284. }
  285. }
  286. // Hard-wrap at width, taking care of multibyte characters.
  287. unsigned int char_bytes = 1;
  288. for (int i = 0; i < static_cast<int>(s.length()); i += char_bytes) {
  289. // Calculate the number of bytes in the character.
  290. char_bytes = 1;
  291. auto iter = s.begin() + i + 1; // Iter points to next byte.
  292. while ( iter != s.end() && (*iter & 128) && !(*iter & 64) ) {
  293. // This is a "continuation" byte in the form 10xxxxxx.
  294. ++iter;
  295. ++char_bytes;
  296. }
  297. // Check whether text now goes over allowed width, and if so
  298. // return everything up to the character and put the rest in the overflow.
  299. std::string s2 = s.substr(0,i+char_bytes);
  300. if (get_text_width(s2) > width) {
  301. if (i == 0) i += char_bytes; // Edge case when even one char is too wide.
  302. if (overflow) *overflow = s.substr(i);
  303. return s.substr(0, i);
  304. }
  305. }
  306. // Should in theory never reach here.
  307. if (overflow) *overflow = "";
  308. return s;
  309. }
  310. void
  311. BitmapFont::draw_text(Canvas& canvas, const std::string& text,
  312. const Vector& pos_, FontAlignment alignment, int layer, const Color& color)
  313. {
  314. float x = pos_.x;
  315. float y = pos_.y;
  316. std::string::size_type last = 0;
  317. for (std::string::size_type i = 0;; ++i)
  318. {
  319. if (text[i] == '\n' || i == text.size())
  320. {
  321. std::string temp = text.substr(last, i - last);
  322. // Calculate X positions based on the alignment type.
  323. Vector pos = Vector(x, y);
  324. if (alignment == ALIGN_CENTER)
  325. pos.x -= get_text_width(temp) / 2;
  326. else if (alignment == ALIGN_RIGHT)
  327. pos.x -= get_text_width(temp);
  328. // Cast font position to integer to get a clean drawing result and
  329. // no blurring as we would get with subpixel positions.
  330. pos.x = std::truncf(pos.x);
  331. draw_text(canvas, temp, pos, layer, color);
  332. if (i == text.size())
  333. break;
  334. y += static_cast<float>(char_height) + 2.0f;
  335. last = i + 1;
  336. }
  337. }
  338. }
  339. void
  340. BitmapFont::draw_text(Canvas& canvas, const std::string& text, const Vector& pos, int layer, Color color) const
  341. {
  342. if (shadowsize > 0)
  343. draw_chars(canvas, false, rtl ? std::string(text.rbegin(), text.rend()) : text,
  344. pos + Vector(static_cast<float>(shadowsize), static_cast<float>(shadowsize)), layer,
  345. Color(1,1,1));
  346. draw_chars(canvas, true, rtl ? std::string(text.rbegin(), text.rend()) : text, pos, layer, color);
  347. }
  348. void
  349. BitmapFont::draw_chars(Canvas& canvas, bool notshadow, const std::string& text, const Vector& pos, int layer, Color color) const
  350. {
  351. Vector p = pos;
  352. for (UTF8Iterator it(text); !it.done(); ++it)
  353. {
  354. if (*it == '\n')
  355. {
  356. p.x = pos.x;
  357. p.y += static_cast<float>(char_height) + 2.0f;
  358. }
  359. else if (*it == ' ')
  360. {
  361. p.x += glyphs[0x20].advance;
  362. }
  363. else
  364. {
  365. Glyph glyph;
  366. if ( glyphs.at(*it).surface_idx != -1 )
  367. glyph = glyphs[*it];
  368. else
  369. glyph = glyphs[0x20];
  370. // FIXME: not supported! request.color = color;
  371. canvas.draw_surface_part(notshadow ?
  372. glyph_surfaces[glyph.surface_idx] :
  373. shadow_surfaces[glyph.surface_idx],
  374. glyph.rect,
  375. Rectf(p + glyph.offset, glyph.rect.get_size()),
  376. layer,
  377. PaintStyle().set_color(color));
  378. p.x += glyph.advance;
  379. }
  380. }
  381. }
  382. /* EOF */