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