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
30TTFFont::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
47TTFFont::~TTFFont()
48{
49 TTF_CloseFont(m_font);
50}
51
52float
53TTFFont::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
89float
90TTFFont::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
103void
104TTFFont::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
138std::string
139TTFFont::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