]> git.tdb.fi Git - libs/gltk.git/blob - text.cpp
08846a4c75acd587fa47bce8dd1b835cbd42e153
[libs/gltk.git] / 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::set(const string &t)
70 {
71         text = t;
72         find_lines();
73 }
74
75 void Text::erase(unsigned pos, unsigned len)
76 {
77         text.erase(pos, len);
78
79         vector<Line>::iterator i;
80         for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
81
82         if(pos+len>i->start+i->length)
83                 find_lines();
84         else
85         {
86                 i->length -= len;
87
88                 for(++i; i!=lines.end(); ++i)
89                         i->start -= len;
90         }
91 }
92
93 void Text::insert(unsigned pos, const string &s)
94 {
95         text.insert(pos, s);
96
97         if(s.find('\n')!=string::npos)
98                 find_lines();
99         else
100         {
101                 vector<Line>::iterator i;
102                 for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
103
104                 i->length += s.size();
105
106                 for(++i; i!=lines.end(); ++i)
107                         i->start += s.size();
108         }
109 }
110
111 unsigned Text::get_line_length(unsigned i) const
112 {
113         if(i>=lines.size())
114                 throw out_of_range("Text::get_line_length");
115         return lines[i].length;
116 }
117
118 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
119 {
120         if(lines.empty())
121         {
122                 row = 0;
123                 col = 0;
124                 return;
125         }
126
127         for(unsigned i=0; i<lines.size(); ++i)
128                 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].length)
129                 {
130                         row = i;
131                         col = offs-lines[i].start;
132                         return;
133                 }
134 }
135
136 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
137 {
138         if(row>=lines.size())
139                 return text.size();
140
141         return lines[row].start+min(col, lines[row].length);
142 }
143
144 Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
145 {
146         if(row>=lines.size())
147                 row = lines.size()-1;
148         const Line &line = lines[row];
149         if(col>line.length)
150                 col = line.length;
151
152         CoordsToGeomData data;
153         data.row = row;
154         data.col = col;
155
156         process_lines<CoordsToGeomData, &Text::coords_to_geom_line>(part, parent, first_row, data);
157
158         return data.result;
159 }
160
161 void Text::build(const Part &part, const Geometry &parent, CachedPart &cache) const
162 {
163         build(part, parent, 0, cache);
164 }
165
166 void Text::build(const Part &part, const Geometry &parent, unsigned first_row, CachedPart &cache) const
167 {
168         if(!style || lines.empty())
169         {
170                 cache.texture = 0;
171                 return;
172         }
173
174         cache.clear_mesh();
175         GL::MeshBuilder bld(*cache.mesh);
176
177         const GL::Font &font = style->get_font();
178         bld.color(style->get_font_color());
179
180         RenderData data;
181         data.bld = &bld;
182
183         cache.texture = &font.get_texture();
184
185         process_lines<RenderData, &Text::build_line>(part, parent, first_row, data);
186 }
187
188 Text &Text::operator=(const string &t)
189 {
190         set(t);
191         return *this;
192 }
193
194 void Text::find_lines()
195 {
196         lines.clear();
197         float font_size = (style ? style->get_font_size() : 1);
198         string::size_type start = 0;
199         while(1)
200         {
201                 string::size_type newline = text.find('\n', start);
202
203                 Line line;
204                 line.start = start;
205                 line.length = (newline==string::npos ? text.size() : newline)-start;
206                 line.width = line.length;
207                 if(style)
208                 {
209                         string str = text.substr(line.start, line.length);
210                         line.width = static_cast<unsigned>(style->get_font().get_string_width(str)*font_size);
211                 }
212                 lines.push_back(line);
213
214                 if(newline==string::npos)
215                         break;
216                 start = newline+1;
217         }
218 }
219
220 template<typename T, void (Text::*func)(unsigned, const Geometry &, T &) const>
221 void Text::process_lines(const Part &part, const Geometry &parent, unsigned first_row, T &data) const
222 {
223         if(!style)
224                 return;
225
226         const GL::Font &font = style->get_font();
227         float font_size = style->get_font_size();
228         unsigned line_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*font_size);
229         unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
230         unsigned height = line_height+(lines.size()-1)*line_spacing;
231         int y_offset = static_cast<int>(-font.get_descent()*font_size);
232
233         const Sides &margin = part.get_margin();
234         unsigned n_lines = min(lines.size(), max((parent.h-margin.top-margin.bottom)/line_spacing, 1U));
235         first_row = min(first_row, lines.size()-n_lines);
236
237         for(unsigned i=0; i<n_lines; ++i)
238         {
239                 const Line &line = lines[first_row+i];
240
241                 Geometry rgeom;
242                 rgeom.w = line.width;
243                 rgeom.h = height;
244                 rgeom.y = (n_lines-1-i)*line_spacing+y_offset;
245                 part.get_alignment().apply(rgeom, parent, part.get_margin());
246
247                 (this->*func)(first_row+i, rgeom, data);
248         }
249 }
250
251 void Text::build_line(unsigned i, const Geometry &rgeom, RenderData &data) const
252 {
253         const Line &line = lines[i];
254
255         GL::MatrixStack::Push _pushm(data.bld->matrix());
256         data.bld->matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
257         data.bld->matrix() *= GL::Matrix::scaling(style->get_font_size());
258
259         style->get_font().build_string(text.substr(line.start, line.length), *data.bld);
260 }
261
262 void Text::coords_to_geom_line(unsigned i, const Geometry &rgeom, CoordsToGeomData &data) const
263 {
264         if(i==data.row)
265         {
266                 float w = style->get_font().get_string_width(text.substr(lines[i].start, data.col));
267                 data.result = rgeom;
268                 data.result.x += static_cast<unsigned>(w*style->get_font_size());
269         }
270 }
271
272 } // namespace GLtk
273 } // namespace Msp