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