| 1 | // SuperTux |
| 2 | // Copyright (C) 2018 Ingo Ruhnke <grumbel@gmail.com>, |
| 3 | // Tobias Markus <tobbi.bugs@googlemail.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 | |
| 18 | #include "video/ttf_font.hpp" |
| 19 | |
| 20 | #include <iostream> |
| 21 | #include <numeric> |
| 22 | #include <sstream> |
| 23 | |
| 24 | #include "util/line_iterator.hpp" |
| 25 | #include "physfs/physfs_sdl.hpp" |
| 26 | #include "video/canvas.hpp" |
| 27 | #include "video/surface.hpp" |
| 28 | #include "video/ttf_surface_manager.hpp" |
| 29 | |
| 30 | TTFFont::TTFFont(const std::string& filename, int font_size, float line_spacing, int shadow_size, int border) : |
| 31 | m_font(), |
| 32 | m_filename(filename), |
| 33 | m_font_size(font_size), |
| 34 | m_line_spacing(line_spacing), |
| 35 | m_shadow_size(shadow_size), |
| 36 | m_border(border) |
| 37 | { |
| 38 | m_font = TTF_OpenFontRW(get_physfs_SDLRWops(m_filename), 1, font_size); |
| 39 | if (!m_font) |
| 40 | { |
| 41 | std::ostringstream msg; |
| 42 | msg << "Couldn't load TTFFont: " << m_filename << ": " << SDL_GetError(); |
| 43 | throw std::runtime_error(msg.str()); |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | TTFFont::~TTFFont() |
| 48 | { |
| 49 | TTF_CloseFont(m_font); |
| 50 | } |
| 51 | |
| 52 | float |
| 53 | TTFFont::get_text_width(const std::string& text) const |
| 54 | { |
| 55 | if (text.empty()) |
| 56 | return 0.0f; |
| 57 | |
| 58 | float max_width = 0.0f; |
| 59 | |
| 60 | LineIterator iter(text); |
| 61 | while (iter.next()) |
| 62 | { |
| 63 | const std::string& line = iter.get(); |
| 64 | |
| 65 | // Since create_surface() takes a surface from the cache instead of |
| 66 | // generating it from scratch it should be faster than doing a whole |
| 67 | // layout. |
| 68 | if ((false)) |
| 69 | { |
| 70 | int w = 0; |
| 71 | int h = 0; |
| 72 | int ret = TTF_SizeUTF8(m_font, line.c_str(), &w, &h); |
| 73 | if (ret < 0) |
| 74 | { |
| 75 | std::cerr << "TTFFont::get_text_width(): " << TTF_GetError() << std::endl; |
| 76 | } |
| 77 | max_width = std::max(max_width, static_cast<float>(w)); |
| 78 | } |
| 79 | else |
| 80 | { |
| 81 | TTFSurfacePtr surface = TTFSurfaceManager::current()->create_surface(*this, line); |
| 82 | max_width = std::max(max_width, static_cast<float>(surface->get_width())); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | return max_width; |
| 87 | } |
| 88 | |
| 89 | float |
| 90 | TTFFont::get_text_height(const std::string& text) const |
| 91 | { |
| 92 | if (text.empty()) |
| 93 | return 0.0f; |
| 94 | |
| 95 | // since UTF8 multibyte characters are decoded with values |
| 96 | // outside the ASCII range there is no risk of overlapping and |
| 97 | // thus we don't need to decode the utf-8 string |
| 98 | return std::accumulate(text.begin(), text.end(), get_height(), [this] (float accumulator, const char c) { |
| 99 | return accumulator += (c == '\n' ? get_height() : 0.0f); |
| 100 | }); |
| 101 | } |
| 102 | |
| 103 | void |
| 104 | TTFFont::draw_text(Canvas& canvas, const std::string& text, |
| 105 | const Vector& pos, FontAlignment alignment, int layer, const Color& color) |
| 106 | |
| 107 | { |
| 108 | float last_y = pos.y - (static_cast<float>(TTF_FontHeight(m_font)) - get_height()) / 2.0f; |
| 109 | |
| 110 | LineIterator iter(text); |
| 111 | while (iter.next()) |
| 112 | { |
| 113 | const std::string& line = iter.get(); |
| 114 | |
| 115 | if (!line.empty()) |
| 116 | { |
| 117 | TTFSurfacePtr ttf_surface = TTFSurfaceManager::current()->create_surface(*this, line); |
| 118 | |
| 119 | Vector new_pos(pos.x, last_y); |
| 120 | |
| 121 | if (alignment == ALIGN_CENTER) |
| 122 | { |
| 123 | new_pos.x -= static_cast<float>(ttf_surface->get_width()) / 2.0f; |
| 124 | } |
| 125 | else if (alignment == ALIGN_RIGHT) |
| 126 | { |
| 127 | new_pos.x -= static_cast<float>(ttf_surface->get_width()); |
| 128 | } |
| 129 | |
| 130 | // draw text |
| 131 | canvas.draw_surface(ttf_surface->get_surface(), new_pos.floor(), 0.0f, color, Blend(), layer); |
| 132 | } |
| 133 | |
| 134 | last_y += get_height(); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | std::string |
| 139 | TTFFont::wrap_to_width(const std::string& text, float width, std::string* overflow) |
| 140 | { |
| 141 | std::string s = text; |
| 142 | |
| 143 | // if text is already smaller, return full text |
| 144 | if (get_text_width(s) <= width) { |
| 145 | if (overflow) *overflow = "" ; |
| 146 | return s; |
| 147 | } |
| 148 | |
| 149 | // if we can find a whitespace character to break at, return text up to this character |
| 150 | for (int i = static_cast<int>(s.length()) - 1; i >= 0; i--) { |
| 151 | std::string s2 = s.substr(0,i); |
| 152 | if (s[i] != ' ') continue; |
| 153 | if (get_text_width(s2) <= width) { |
| 154 | if (overflow) *overflow = s.substr(i+1); |
| 155 | return s.substr(0, i); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | // FIXME: hard-wrap at width, taking care of multibyte characters |
| 160 | if (overflow) *overflow = "" ; |
| 161 | return s; |
| 162 | } |
| 163 | |
| 164 | /* EOF */ |
| 165 | |