1 #include <msp/gl/matrix.h>
2 #include <msp/gl/meshbuilder.h>
3 #include <msp/gl/texture2d.h>
13 struct Text::RenderData
15 GL::PrimitiveBuilder *bld;
18 struct Text::CoordsToGeomData
34 lines.push_back(line);
37 Text::Text(const Style &s, const string &t):
43 void Text::set_style(const Style *s)
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);
56 unsigned Text::get_width() const
59 for(vector<Line>::const_iterator i=lines.begin(); i!=lines.end(); ++i)
60 width = max(width, i->width);
64 unsigned Text::get_height() const
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;
76 void Text::autosize(const Part &part, Geometry &geom) const
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);
83 void Text::set(const string &t)
89 void Text::erase(unsigned pos, unsigned len)
92 check_alignment(pos+len);
95 vector<Line>::iterator i;
96 for(i=lines.begin(); (i!=lines.end() && i->start+i->bytes<pos); ++i) ;
98 if(pos+len>i->start+i->bytes)
103 i->length = count_characters(i->start, i->bytes);
105 for(++i; i!=lines.end(); ++i)
110 void Text::insert(unsigned pos, const string &s)
112 check_alignment(pos);
115 if(s.find('\n')!=string::npos)
119 vector<Line>::iterator i;
120 for(i=lines.begin(); (i!=lines.end() && i->start+i->bytes<pos); ++i) ;
122 i->bytes += s.size();
123 i->length = count_characters(i->start, i->bytes);
125 for(++i; i!=lines.end(); ++i)
126 i->start += s.size();
130 unsigned Text::get_visible_lines(const Part &part, const Geometry &parent, unsigned *fit_height) const
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);
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));
142 *fit_height = line_height+(n_lines-1)*line_spacing;
147 unsigned Text::get_line_length(unsigned i) const
150 throw out_of_range("Text::get_line_length");
151 return lines[i].length;
154 unsigned Text::move_offset(unsigned offs, int change) const
156 check_alignment(offs);
160 StringCodec::Utf8::Decoder dec(StringCodec::IGNORE_ERRORS);
161 string::const_iterator i = text.begin()+offs;
164 for(; change>0; --change)
165 dec.decode_char(text, i);
169 while(change<0 && i!=text.begin())
172 string::const_iterator j = i;
173 if(dec.decode_char(text, j)!=-1)
177 return i-text.begin();
180 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
189 for(unsigned i=0; i<lines.size(); ++i)
190 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].bytes)
193 if(lines[i].length==lines[i].bytes)
194 col = offs-lines[i].start;
196 col = count_characters(lines[i].start, offs-lines[i].start);
201 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
203 if(row>=lines.size())
205 const Line &line = lines[row];
209 if(line.length==line.bytes)
210 return line.start+col;
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();
221 Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
223 if(row>=lines.size())
224 row = lines.size()-1;
225 const Line &line = lines[row];
229 CoordsToGeomData data;
233 process_lines(part, parent, first_row, &Text::coords_to_geom_line, data);
238 void Text::build(const Part &part, State state, const Geometry &parent, PartCache &cache) const
240 build(part, state, parent, 0, cache);
243 void Text::build(const Part &part, State state, const Geometry &parent, unsigned first_row, PartCache &cache) const
245 if(!style || lines.empty())
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));
255 process_lines(part, parent, first_row, &Text::build_line, data);
258 Text &Text::operator=(const string &t)
264 void Text::find_lines()
267 float font_size = (style ? style->get_font_size() : 1);
268 string::size_type start = 0;
271 string::size_type newline = text.find('\n', 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;
280 string str = text.substr(line.start, line.bytes);
281 line.width = static_cast<unsigned>(style->get_font().get_string_width(str)*font_size);
283 lines.push_back(line);
285 if(newline==string::npos)
291 unsigned Text::count_characters(unsigned start, unsigned bytes) const
293 StringCodec::Utf8::Decoder dec;
294 string::const_iterator i = text.begin()+start;
295 string::const_iterator end = i+bytes;
297 for(; i<end; dec.decode_char(text, i))
302 void Text::check_alignment(unsigned offs) const
304 StringCodec::Utf8::Decoder dec;
305 string::const_iterator i = text.begin()+offs;
306 dec.decode_char(text, i);
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
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);
321 unsigned n_lines = get_visible_lines(part, parent, &fit_height);
322 first_row = min<unsigned>(first_row, lines.size()-n_lines);
324 for(unsigned i=0; i<n_lines; ++i)
326 const Line &line = lines[first_row+i];
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());
334 (this->*func)(first_row+i, rgeom, data);
338 void Text::build_line(unsigned i, const Geometry &rgeom, RenderData &data) const
340 const Line &line = lines[i];
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()));
346 style->get_font().build_string(text.substr(line.start, line.bytes), *data.bld);
349 void Text::coords_to_geom_line(unsigned i, const Geometry &rgeom, CoordsToGeomData &data) const
353 string::const_iterator begin = text.begin()+lines[i].start;
354 string::const_iterator j = begin;
355 if(lines[i].length==lines[i].bytes)
359 StringCodec::Utf8::Decoder dec;
360 for(unsigned c=data.col; c; --c)
361 dec.decode_char(text, j);
363 float w = style->get_font().get_string_width(string(begin, j));
365 data.result.x += static_cast<unsigned>(w*style->get_font_size());