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