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 | |