+ text = t;
+ find_lines();
+}
+
+void Text::erase(unsigned pos, unsigned len)
+{
+ check_alignment(pos);
+ check_alignment(pos+len);
+ text.erase(pos, len);
+
+ vector<Line>::iterator i;
+ for(i=lines.begin(); (i!=lines.end() && i->start+i->bytes<pos); ++i) ;
+
+ if(pos+len>i->start+i->bytes)
+ find_lines();
+ else
+ {
+ i->bytes -= len;
+ i->length = count_characters(i->start, i->bytes);
+
+ for(++i; i!=lines.end(); ++i)
+ i->start -= len;
+ }
+}
+
+void Text::insert(unsigned pos, const string &s)
+{
+ check_alignment(pos);
+ text.insert(pos, s);
+
+ if(s.find('\n')!=string::npos)
+ find_lines();
+ else
+ {
+ vector<Line>::iterator i;
+ for(i=lines.begin(); (i!=lines.end() && i->start+i->bytes<pos); ++i) ;
+
+ i->bytes += s.size();
+ i->length = count_characters(i->start, i->bytes);
+
+ for(++i; i!=lines.end(); ++i)
+ i->start += s.size();
+ }
+}
+
+unsigned Text::get_visible_lines(const Part &part, const Geometry &parent, unsigned *fit_height) const
+{
+ const GL::Font &font = style->get_font();
+ float font_size = style->get_font_size();
+ unsigned line_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*font_size);
+ unsigned line_spacing = static_cast<unsigned>(font_size*6/5);
+
+ const Sides &margin = part.get_margin();
+ unsigned vmargin = margin.top+margin.bottom;
+ unsigned free_height = max(parent.h, vmargin)-vmargin+line_spacing-line_height;
+ unsigned n_lines = min<unsigned>(lines.size(), max(free_height/line_spacing, 1U));
+ if(fit_height)
+ *fit_height = line_height+(n_lines-1)*line_spacing;
+
+ return n_lines;
+}
+
+unsigned Text::get_line_length(unsigned i) const
+{
+ if(i>=lines.size())
+ throw out_of_range("Text::get_line_length");
+ return lines[i].length;
+}
+
+unsigned Text::move_offset(unsigned offs, int change) const
+{
+ check_alignment(offs);
+ if(!change)
+ return offs;
+
+ StringCodec::Utf8::Decoder dec(StringCodec::IGNORE_ERRORS);
+ string::const_iterator i = text.begin()+offs;
+ if(change>0)
+ {
+ for(; change>0; --change)
+ dec.decode_char(text, i);
+ }
+ else
+ {
+ while(change<0 && i!=text.begin())
+ {
+ --i;
+ string::const_iterator j = i;
+ if(dec.decode_char(text, j)!=-1)
+ ++change;
+ }
+ }
+ return i-text.begin();
+}
+
+void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
+{
+ if(lines.empty())
+ {
+ row = 0;
+ col = 0;
+ return;
+ }
+
+ for(unsigned i=0; i<lines.size(); ++i)
+ if(offs>=lines[i].start && offs<=lines[i].start+lines[i].bytes)
+ {
+ row = i;
+ if(lines[i].length==lines[i].bytes)
+ col = offs-lines[i].start;
+ else
+ col = count_characters(lines[i].start, offs-lines[i].start);
+ return;
+ }
+}
+
+unsigned Text::coords_to_offset(unsigned row, unsigned col) const
+{
+ if(row>=lines.size())
+ return text.size();
+ const Line &line = lines[row];
+ if(col>line.length)
+ col = line.length;
+
+ if(line.length==line.bytes)
+ return line.start+col;
+ else
+ {
+ StringCodec::Utf8::Decoder dec;
+ string::const_iterator i = text.begin()+line.start;
+ for(col=min(col, line.length); col; --col)
+ dec.decode_char(text, i);
+ return i-text.begin();
+ }
+}
+
+Geometry Text::coords_to_geometry(const Part &part, const Geometry &parent, unsigned first_row, unsigned row, unsigned col) const
+{
+ if(row>=lines.size())
+ row = lines.size()-1;
+ const Line &line = lines[row];
+ if(col>line.length)
+ col = line.length;
+
+ CoordsToGeomData data;
+ data.row = row;
+ data.col = col;
+
+ process_lines(part, parent, first_row, &Text::coords_to_geom_line, data);
+
+ return data.result;
+}
+
+void Text::build(const Part &part, State state, const Geometry &parent, PartCache &cache) const
+{
+ build(part, state, parent, 0, cache);
+}
+
+void Text::build(const Part &part, State state, const Geometry &parent, unsigned first_row, PartCache &cache) const
+{
+ if(!style || lines.empty())
+ return;
+
+ const GL::Font &font = style->get_font();
+ GL::MeshBuilder bld(cache.create_mesh(part, font.get_texture()));
+ bld.color(style->get_font_color(state));
+
+ RenderData data;
+ data.bld = &bld;
+
+ process_lines(part, parent, first_row, &Text::build_line, data);
+}
+
+Text &Text::operator=(const string &t)
+{
+ set(t);
+ return *this;
+}
+
+void Text::find_lines()
+{