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