]> git.tdb.fi Git - libs/gltk.git/blob - source/text.cpp
a3ed762a2cd560754dcd973094e547d5977eae20
[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->bytes))*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         check_alignment(pos);
85         check_alignment(pos+len);
86         text.erase(pos, len);
87
88         vector<Line>::iterator i;
89         for(i=lines.begin(); (i!=lines.end() && i->start+i->bytes<pos); ++i) ;
90
91         if(pos+len>i->start+i->bytes)
92                 find_lines();
93         else
94         {
95                 i->bytes -= len;
96                 i->length = count_characters(i->start, i->bytes);
97
98                 for(++i; i!=lines.end(); ++i)
99                         i->start -= len;
100         }
101 }
102
103 void Text::insert(unsigned pos, const string &s)
104 {
105         check_alignment(pos);
106         text.insert(pos, s);
107
108         if(s.find('\n')!=string::npos)
109                 find_lines();
110         else
111         {
112                 vector<Line>::iterator i;
113                 for(i=lines.begin(); (i!=lines.end() && i->start+i->bytes<pos); ++i) ;
114
115                 i->bytes += s.size();
116                 i->length = count_characters(i->start, i->bytes);
117
118                 for(++i; i!=lines.end(); ++i)
119                         i->start += s.size();
120         }
121 }
122
123 unsigned Text::get_visible_lines(const Part &part, const Geometry &parent, unsigned *fit_height) const
124 {
125         const GL::Font &font = style->get_font();
126         float font_size = style->get_font_size();
127         unsigned line_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*font_size);
128         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
129
130         const Sides &margin = part.get_margin();
131         unsigned vmargin = margin.top+margin.bottom;
132         unsigned free_height = max(parent.h, vmargin)-vmargin+line_spacing-line_height;
133         unsigned n_lines = min<unsigned>(lines.size(), max(free_height/line_spacing, 1U));
134         if(fit_height)
135                 *fit_height = line_height+(n_lines-1)*line_spacing;
136
137         return n_lines;
138 }
139
140 unsigned Text::get_line_length(unsigned i) const
141 {
142         if(i>=lines.size())
143                 throw out_of_range("Text::get_line_length");
144         return lines[i].length;
145 }
146
147 unsigned Text::move_offset(unsigned offs, int change) const
148 {
149         check_alignment(offs);
150         if(!change)
151                 return offs;
152
153         StringCodec::Utf8::Decoder dec(StringCodec::IGNORE_ERRORS);
154         string::const_iterator i = text.begin()+offs;
155         if(change>0)
156         {
157                 for(; change>0; --change)
158                         dec.decode_char(text, i);
159         }
160         else
161         {
162                 while(change<0 && i!=text.begin())
163                 {
164                         --i;
165                         string::const_iterator j = i;
166                         if(dec.decode_char(text, j)!=-1)
167                                 ++change;
168                 }
169         }
170         return i-text.begin();
171 }
172
173 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
174 {
175         if(lines.empty())
176         {
177                 row = 0;
178                 col = 0;
179                 return;
180         }
181
182         for(unsigned i=0; i<lines.size(); ++i)
183                 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].bytes)
184                 {
185                         row = i;
186                         if(lines[i].length==lines[i].bytes)
187                                 col = offs-lines[i].start;
188                         else
189                                 col = count_characters(lines[i].start, offs-lines[i].start);
190                         return;
191                 }
192 }
193
194 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
195 {
196         if(row>=lines.size())
197                 return text.size();
198         const Line &line = lines[row];
199         if(col>line.length)
200                 col = line.length;
201
202         if(line.length==line.bytes)
203                 return line.start+col;
204         else
205         {
206                 StringCodec::Utf8::Decoder dec;
207                 string::const_iterator i = text.begin()+line.start;
208                 for(col=min(col, line.length); col; --col)
209                         dec.decode_char(text, i);
210                 return i-text.begin();
211         }
212 }
213
214 Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
215 {
216         if(row>=lines.size())
217                 row = lines.size()-1;
218         const Line &line = lines[row];
219         if(col>line.length)
220                 col = line.length;
221
222         CoordsToGeomData data;
223         data.row = row;
224         data.col = col;
225
226         process_lines(part, parent, first_row, &Text::coords_to_geom_line, data);
227
228         return data.result;
229 }
230
231 void Text::build(const Part &part, State state, const Geometry &parent, PartCache &cache) const
232 {
233         build(part, state, parent, 0, cache);
234 }
235
236 void Text::build(const Part &part, State state, const Geometry &parent, unsigned first_row, PartCache &cache) const
237 {
238         if(!style || lines.empty())
239                 return;
240
241         const GL::Font &font = style->get_font();
242         GL::MeshBuilder bld(cache.create_mesh(part, font.get_texture()));
243         bld.color(style->get_font_color(state));
244
245         RenderData data;
246         data.bld = &bld;
247
248         process_lines(part, parent, first_row, &Text::build_line, data);
249 }
250
251 Text &Text::operator=(const string &t)
252 {
253         set(t);
254         return *this;
255 }
256
257 void Text::find_lines()
258 {
259         lines.clear();
260         float font_size = (style ? style->get_font_size() : 1);
261         string::size_type start = 0;
262         while(1)
263         {
264                 string::size_type newline = text.find('\n', start);
265
266                 Line line;
267                 line.start = start;
268                 line.bytes = (newline==string::npos ? text.size() : newline)-start;
269                 line.length = count_characters(line.start, line.bytes);
270                 line.width = line.length;
271                 if(style)
272                 {
273                         string str = text.substr(line.start, line.bytes);
274                         line.width = static_cast<unsigned>(style->get_font().get_string_width(str)*font_size);
275                 }
276                 lines.push_back(line);
277
278                 if(newline==string::npos)
279                         break;
280                 start = newline+1;
281         }
282 }
283
284 unsigned Text::count_characters(unsigned start, unsigned bytes) const
285 {
286         StringCodec::Utf8::Decoder dec;
287         string::const_iterator i = text.begin()+start;
288         string::const_iterator end = i+bytes;
289         unsigned count = 0;
290         for(; i<end; dec.decode_char(text, i))
291                 ++count;
292         return count;
293 }
294
295 void Text::check_alignment(unsigned offs) const
296 {
297         StringCodec::Utf8::Decoder dec;
298         string::const_iterator i = text.begin()+offs;
299         dec.decode_char(text, i);
300 }
301
302 template<typename T>
303 void Text::process_lines(const Part &part, const Geometry &parent, unsigned first_row, void (Text::*func)(unsigned, const Geometry &, T &) const, T &data) const
304 {
305         if(!style)
306                 return;
307
308         const GL::Font &font = style->get_font();
309         float font_size = style->get_font_size();
310         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
311         int y_offset = static_cast<int>(-font.get_descent()*font_size);
312
313         unsigned fit_height;
314         unsigned n_lines = get_visible_lines(part, parent, &fit_height);
315         first_row = min<unsigned>(first_row, lines.size()-n_lines);
316
317         for(unsigned i=0; i<n_lines; ++i)
318         {
319                 const Line &line = lines[first_row+i];
320
321                 Geometry rgeom;
322                 rgeom.w = line.width;
323                 rgeom.h = fit_height;
324                 rgeom.y = (n_lines-1-i)*line_spacing+y_offset;
325                 part.get_alignment().apply(rgeom, parent, part.get_margin());
326
327                 (this->*func)(first_row+i, rgeom, data);
328         }
329 }
330
331 void Text::build_line(unsigned i, const Geometry &rgeom, RenderData &data) const
332 {
333         const Line &line = lines[i];
334
335         GL::MatrixStack::Push _pushm(data.bld->matrix());
336         data.bld->matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
337         data.bld->matrix() *= GL::Matrix::scaling(style->get_font_size());
338
339         style->get_font().build_string(text.substr(line.start, line.bytes), *data.bld);
340 }
341
342 void Text::coords_to_geom_line(unsigned i, const Geometry &rgeom, CoordsToGeomData &data) const
343 {
344         if(i==data.row)
345         {
346                 string::const_iterator begin = text.begin()+lines[i].start;
347                 string::const_iterator j = begin;
348                 if(lines[i].length==lines[i].bytes)
349                         j += data.col;
350                 else
351                 {
352                         StringCodec::Utf8::Decoder dec;
353                         for(unsigned c=data.col; c; --c)
354                                 dec.decode_char(text, j);
355                 }
356                 float w = style->get_font().get_string_width(string(begin, j));
357                 data.result = rgeom;
358                 data.result.x += static_cast<unsigned>(w*style->get_font_size());
359         }
360 }
361
362 } // namespace GLtk
363 } // namespace Msp