]> git.tdb.fi Git - libs/gltk.git/blob - source/text.cpp
Bind the font texture in Text::render
[libs/gltk.git] / source / text.cpp
1 #include <msp/gl/immediate.h>
2 #include <msp/gl/matrix.h>
3 #include <msp/gl/texture2d.h>
4 #include "style.h"
5 #include "text.h"
6
7 using namespace std;
8
9 namespace Msp {
10 namespace GLtk {
11
12 struct Text::RenderData
13 {
14         GL::PrimitiveBuilder *bld;
15 };
16
17 struct Text::CoordsToGeomData
18 {
19         unsigned row;
20         unsigned col;
21         Geometry result;
22 };
23
24
25 Text::Text():
26         style(0)
27 { }
28
29 Text::Text(const Style &s, const string &t):
30         style(&s)
31 {
32         set(t);
33 }
34
35 void Text::set_style(const Style *s)
36 {
37         style = s;
38
39         if(style)
40         {
41                 float font_size = style->get_font()->get_default_size();
42                 for(vector<Line>::iterator i=lines.begin(); i!=lines.end(); ++i)
43                         i->width = static_cast<unsigned>(style->get_font()->get_string_width(text.substr(i->start, i->length))*font_size);
44         }
45 }
46
47 unsigned Text::get_width() const
48 {
49         unsigned width = 0;
50         for(vector<Line>::const_iterator i=lines.begin(); i!=lines.end(); ++i)
51                 width = max(width, i->width);
52         return width;
53 }
54
55 unsigned Text::get_height() const
56 {
57         if(!style)
58                 return lines.size();
59
60         const GL::Font *font = style->get_font();
61         float font_size = font->get_default_size();
62         unsigned line_height = static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
63         unsigned line_spacing = line_height*6/5;
64         return line_height+(lines.size()-1)*line_spacing;
65 }
66
67 void Text::set(const string &t)
68 {
69         text = t;
70         find_lines();
71 }
72
73 void Text::erase(unsigned pos, unsigned len)
74 {
75         text.erase(pos, len);
76
77         vector<Line>::iterator i;
78         for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
79
80         if(pos+len>i->start+i->length)
81                 find_lines();
82         else
83         {
84                 i->length -= len;
85
86                 for(++i; i!=lines.end(); ++i)
87                         i->start -= len;
88         }
89 }
90
91 void Text::insert(unsigned pos, const string &s)
92 {
93         text.insert(pos, s);
94
95         if(s.find('\n')!=string::npos)
96                 find_lines();
97         else
98         {
99                 vector<Line>::iterator i;
100                 for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
101
102                 i->length += s.size();
103
104                 for(++i; i!=lines.end(); ++i)
105                         i->start += s.size();
106         }
107 }
108
109 unsigned Text::get_line_length(unsigned i) const
110 {
111         if(i>=lines.size())
112                 throw out_of_range("Text::get_line_length");
113         return lines[i].length;
114 }
115
116 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
117 {
118         if(lines.empty())
119         {
120                 row = 0;
121                 col = 0;
122                 return;
123         }
124
125         for(unsigned i=0; i<lines.size(); ++i)
126                 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].length)
127                 {
128                         row = i;
129                         col = offs-lines[i].start;
130                         return;
131                 }
132 }
133
134 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
135 {
136         if(row>=lines.size())
137                 return text.size();
138
139         return lines[row].start+min(col, lines[row].length);
140 }
141
142 Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
143 {
144         if(row>=lines.size())
145                 row = lines.size()-1;
146         const Line &line = lines[row];
147         if(col>line.length)
148                 col = line.length;
149
150         CoordsToGeomData data;
151         data.row = row;
152         data.col = col;
153
154         process_lines<CoordsToGeomData, &Text::coords_to_geom_line>(part, parent, first_row, data);
155
156         return data.result;
157 }
158
159 void Text::render(const Part &part, const Geometry &parent, unsigned first_row) const
160 {
161         if(!style || lines.empty())
162                 return;
163
164         const GL::Font *font = style->get_font();
165         const GL::Color &color = style->get_font_color();
166         GL::Immediate imm((GL::COLOR4_UBYTE, GL::TEXCOORD2, GL::VERTEX2));
167         imm.color(color.r, color.g, color.b);
168
169         RenderData data;
170         data.bld = &imm;
171
172         GL::Bind bind_tex(font->get_texture());
173
174         process_lines<RenderData, &Text::render_line>(part, parent, first_row, data);
175 }
176
177 Text &Text::operator=(const string &t)
178 {
179         set(t);
180         return *this;
181 }
182
183 void Text::find_lines()
184 {
185         lines.clear();
186         float font_size = (style ? style->get_font()->get_default_size() : 1);
187         string::size_type start = 0;
188         while(1)
189         {
190                 string::size_type newline = text.find('\n', start);
191
192                 Line line;
193                 line.start = start;
194                 line.length = (newline==string::npos ? text.size() : newline)-start;
195                 line.width = line.length;
196                 if(style)
197                 {
198                         string str = text.substr(line.start, line.length);
199                         line.width = static_cast<unsigned>(style->get_font()->get_string_width(str)*font_size);
200                 }
201                 lines.push_back(line);
202
203                 if(newline==string::npos)
204                         break;
205                 start = newline+1;
206         }
207 }
208
209 template<typename T, void (Text::*func)(unsigned, const Geometry &, T &) const>
210 void Text::process_lines(const Part &part, const Geometry &parent, unsigned first_row, T &data) const
211 {
212         if(!style)
213                 return;
214
215         const GL::Font *font = style->get_font();
216         float font_size = font->get_default_size();
217         unsigned line_height = static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
218         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
219         unsigned height = line_height+(lines.size()-1)*line_spacing;
220         int y_offset = static_cast<int>(-font->get_descent()*font_size);
221
222         const Sides &margin = part.get_margin();
223         unsigned n_lines = min(lines.size(), max((parent.h-margin.top-margin.bottom)/line_spacing, 1U));
224         first_row = min(first_row, lines.size()-n_lines);
225
226         for(unsigned i=0; i<n_lines; ++i)
227         {
228                 const Line &line = lines[first_row+i];
229
230                 Geometry rgeom;
231                 rgeom.w = line.width;
232                 rgeom.h = height;
233                 rgeom.y = (n_lines-1-i)*line_spacing+y_offset;
234                 part.get_alignment().apply(rgeom, parent, part.get_margin());
235
236                 (this->*func)(first_row+i, rgeom, data);
237         }
238 }
239
240 void Text::render_line(unsigned i, const Geometry &rgeom, RenderData &data) const
241 {
242         const Line &line = lines[i];
243         const GL::Font *font = style->get_font();
244
245         GL::PushMatrix _pushm;
246         GL::translate(rgeom.x, rgeom.y, 0);
247         GL::scale_uniform(font->get_default_size());
248
249         font->build_string(text.substr(line.start, line.length), *data.bld);
250 }
251
252 void Text::coords_to_geom_line(unsigned i, const Geometry &rgeom, CoordsToGeomData &data) const
253 {
254         if(i==data.row)
255         {
256                 const Line &line = lines[i];
257                 const GL::Font *font = style->get_font();
258
259                 data.result = rgeom;
260                 data.result.x += static_cast<unsigned>(font->get_string_width(text.substr(line.start, data.col))*font->get_default_size());
261         }
262 }
263
264 } // namespace GLtk
265 } // namespace Msp