ui::label tagged text support wip: refactor internal data & shaping algorithm
This commit is contained in:
parent
8414af776a
commit
057cf1c4c7
2 changed files with 280 additions and 173 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
namespace psemek::ui
|
namespace psemek::ui
|
||||||
{
|
{
|
||||||
|
|
@ -74,7 +75,6 @@ namespace psemek::ui
|
||||||
virtual void on_state_changed();
|
virtual void on_state_changed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string text_;
|
|
||||||
halignment halign_ = halignment::left;
|
halignment halign_ = halignment::left;
|
||||||
valignment valign_ = valignment::top;
|
valignment valign_ = valignment::top;
|
||||||
bool wrap_ = true;
|
bool wrap_ = true;
|
||||||
|
|
@ -83,6 +83,17 @@ namespace psemek::ui
|
||||||
|
|
||||||
box_shape shape_;
|
box_shape shape_;
|
||||||
|
|
||||||
|
struct text_chunk
|
||||||
|
{
|
||||||
|
std::string_view text;
|
||||||
|
text_style style; // to be OR'd with base style
|
||||||
|
};
|
||||||
|
|
||||||
|
using chunk = std::variant<text_chunk>;
|
||||||
|
|
||||||
|
std::string text_;
|
||||||
|
std::vector<chunk> chunks_;
|
||||||
|
|
||||||
struct cached_state
|
struct cached_state
|
||||||
{
|
{
|
||||||
struct image
|
struct image
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,17 @@ namespace psemek::ui
|
||||||
{
|
{
|
||||||
|
|
||||||
label::label(std::string text)
|
label::label(std::string text)
|
||||||
: text_(std::move(text))
|
{
|
||||||
{}
|
set_text(std::move(text));
|
||||||
|
}
|
||||||
|
|
||||||
void label::set_text(std::string text)
|
void label::set_text(std::string text)
|
||||||
{
|
{
|
||||||
text_ = std::move(text);
|
text_ = std::move(text);
|
||||||
|
|
||||||
|
chunks_.clear();
|
||||||
|
chunks_.push_back(text_chunk{{text_.data(), text_.size()}, text_style{}});
|
||||||
|
|
||||||
on_state_changed();
|
on_state_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,232 +139,323 @@ namespace psemek::ui
|
||||||
{
|
{
|
||||||
auto state = cached_state{};
|
auto state = cached_state{};
|
||||||
|
|
||||||
if (text_.empty()) return state;
|
if (chunks_.empty()) return state;
|
||||||
|
|
||||||
auto st = merged_own_style();
|
auto st = merged_own_style();
|
||||||
if (!st) return state;
|
|
||||||
|
|
||||||
auto font = st->text_style->is_set(text_style_flag::bold) ? st->bold_font.get() : st->font.get();
|
auto const font_height = st->font->size()[1] * (*st->text_scale);
|
||||||
|
|
||||||
bool const underline = st->text_style->is_set(text_style_flag::underline);
|
// Shape glyphs chunk by chunk
|
||||||
bool const strikethrough = st->text_style->is_set(text_style_flag::strikethrough);
|
// All glyphs within a chunk have
|
||||||
|
// the exact same font properties
|
||||||
|
|
||||||
if (!font) return state;
|
struct glyph_chunk
|
||||||
|
|
||||||
shape_options opts;
|
|
||||||
opts.scale = *st->text_scale;
|
|
||||||
auto glyphs = font->shape(text_, opts);
|
|
||||||
|
|
||||||
geom::box<float, 2> raw_bbox;
|
|
||||||
for (auto const & g : glyphs)
|
|
||||||
raw_bbox |= g.position;
|
|
||||||
|
|
||||||
std::size_t max_lines = 1;
|
|
||||||
|
|
||||||
if (wrap_)
|
|
||||||
{
|
{
|
||||||
max_lines = std::isfinite(bbox[1].length())
|
std::size_t end;
|
||||||
? std::max<std::size_t>(1, std::floor(bbox[1].length() / font->size()[1] / (*st->text_scale)))
|
text_style style;
|
||||||
: std::numeric_limits<std::size_t>::max();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<std::size_t, std::size_t>> lines;
|
std::vector<glyph> glyphs;
|
||||||
|
std::vector<glyph_chunk> glyph_chunks;
|
||||||
|
|
||||||
std::size_t current_glyph = 0;
|
|
||||||
|
|
||||||
for (std::size_t line = 0; line < max_lines; ++line)
|
|
||||||
{
|
{
|
||||||
if (line != 0)
|
geom::point<float, 2> pen{0.f, 0.f};
|
||||||
++current_glyph;
|
shape_options opts;
|
||||||
|
opts.scale = *st->text_scale;
|
||||||
|
|
||||||
if (skip_spaces_)
|
for (auto const & chunk : chunks_)
|
||||||
{
|
{
|
||||||
while (current_glyph < glyphs.size() && std::isspace(glyphs[current_glyph].character))
|
if (auto text = std::get_if<text_chunk>(&chunk))
|
||||||
++current_glyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t line_begin = current_glyph;
|
|
||||||
std::size_t line_end = line_begin;
|
|
||||||
|
|
||||||
geom::interval<float> x_range;
|
|
||||||
while (line_end < glyphs.size())
|
|
||||||
{
|
|
||||||
if (is_newline(glyphs[line_end].character))
|
|
||||||
break;
|
|
||||||
|
|
||||||
x_range |= glyphs[line_end].position[0];
|
|
||||||
|
|
||||||
if (x_range.length() > bbox[0].length())
|
|
||||||
break;
|
|
||||||
|
|
||||||
++line_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line_end < glyphs.size())
|
|
||||||
{
|
|
||||||
std::size_t space_pos = line_end;
|
|
||||||
while (space_pos > line_begin && !std::isspace(glyphs[space_pos].character))
|
|
||||||
--space_pos;
|
|
||||||
|
|
||||||
if (space_pos > line_begin)
|
|
||||||
line_end = space_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line + 1 == max_lines && line_end < glyphs.size())
|
|
||||||
{
|
|
||||||
switch (overflow_)
|
|
||||||
{
|
{
|
||||||
case overflow_mode::ignore:
|
if (text->text.find("yo") != std::string::npos)
|
||||||
line_end = glyphs.size();
|
|
||||||
break;
|
|
||||||
case overflow_mode::drop:
|
|
||||||
glyphs.erase(glyphs.begin() + line_end, glyphs.end());
|
|
||||||
break;
|
|
||||||
case overflow_mode::ellipsis:
|
|
||||||
{
|
{
|
||||||
std::size_t el_start = std::max<std::size_t>(3, line_end) - 3;
|
int x = 42;
|
||||||
|
(void)x;
|
||||||
float x_offset = 0.f;
|
|
||||||
if (el_start > 0)
|
|
||||||
x_offset = glyphs[el_start - 1].position[0].max;
|
|
||||||
|
|
||||||
char const el_str[] = "...";
|
|
||||||
auto els = font->shape(el_str, opts);
|
|
||||||
for (std::size_t i = el_start; i < line_end; ++i)
|
|
||||||
{
|
|
||||||
glyphs[i] = els[i - el_start];
|
|
||||||
glyphs[i].position[0] += x_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
glyphs.erase(glyphs.begin() + line_end, glyphs.end());
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
auto const merged_text_style = *st->text_style | text->style;
|
||||||
|
|
||||||
|
bool const bold = merged_text_style.is_set(text_style_flag::bold);
|
||||||
|
|
||||||
|
auto font = bold ? st->bold_font.get() : st->font.get();
|
||||||
|
|
||||||
|
auto chunk_glyphs = font->shape(text->text, opts, pen);
|
||||||
|
|
||||||
|
glyphs.reserve(glyphs.size() + chunk_glyphs.size());
|
||||||
|
glyphs.insert(glyphs.end(), chunk_glyphs.begin(), chunk_glyphs.end());
|
||||||
|
|
||||||
|
glyph_chunks.push_back({glyphs.size(), merged_text_style});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current_glyph = line_end;
|
|
||||||
lines.push_back({line_begin, line_end});
|
|
||||||
if (line_end == glyphs.size())
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float max_line_size = 0.f;
|
// Break glyphs into lines
|
||||||
|
|
||||||
float const underline_width = *(st->text_scale);
|
struct line_range
|
||||||
float const strikethrough_width = *(st->text_scale);
|
{
|
||||||
|
std::size_t begin;
|
||||||
|
std::size_t end;
|
||||||
|
};
|
||||||
|
|
||||||
std::vector<geom::box<float, 2>> underline_box(lines.size());
|
std::vector<line_range> lines;
|
||||||
std::vector<geom::box<float, 2>> strikethrough_box(lines.size());
|
|
||||||
|
|
||||||
|
{
|
||||||
|
std::size_t current_glyph = 0;
|
||||||
|
while (current_glyph < glyphs.size())
|
||||||
|
{
|
||||||
|
if (skip_spaces_)
|
||||||
|
{
|
||||||
|
while (current_glyph < glyphs.size() && std::isspace(glyphs[current_glyph].character))
|
||||||
|
++current_glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t line_begin = current_glyph;
|
||||||
|
|
||||||
|
bool newline_end = false;
|
||||||
|
bool wrap_end = false;
|
||||||
|
|
||||||
|
geom::interval<float> x_range;
|
||||||
|
while (current_glyph < glyphs.size())
|
||||||
|
{
|
||||||
|
if (is_newline(glyphs[current_glyph].character))
|
||||||
|
{
|
||||||
|
newline_end = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
x_range |= glyphs[current_glyph].position[0];
|
||||||
|
|
||||||
|
if (x_range.length() > bbox[0].length() && wrap_)
|
||||||
|
{
|
||||||
|
wrap_end = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++current_glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrap_end && current_glyph > line_begin)
|
||||||
|
{
|
||||||
|
std::size_t space_pos = current_glyph - 1;
|
||||||
|
while (space_pos > line_begin && !std::isspace(glyphs[space_pos].character))
|
||||||
|
--space_pos;
|
||||||
|
|
||||||
|
if (space_pos > line_begin)
|
||||||
|
current_glyph = space_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push_back({line_begin, current_glyph});
|
||||||
|
|
||||||
|
if (newline_end)
|
||||||
|
++current_glyph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute line bboxes
|
||||||
|
|
||||||
|
std::vector<geom::box<float, 2>> line_bbox(lines.size());
|
||||||
for (std::size_t l = 0; l < lines.size(); ++l)
|
for (std::size_t l = 0; l < lines.size(); ++l)
|
||||||
{
|
{
|
||||||
geom::interval<float> x_range;
|
geom::box<float, 2> bbox;
|
||||||
int spaces = 0;
|
// Glyphs might be smaller than total text height, so
|
||||||
|
// hardcode font height as min line height
|
||||||
|
bbox[1] = {0.f, 1.f * font_height};
|
||||||
|
for (std::size_t i = lines[l].begin; i < lines[l].end; ++i)
|
||||||
|
bbox |= glyphs[i].position;
|
||||||
|
line_bbox[l] = bbox;
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t i = lines[l].first; i < lines[l].second; ++i)
|
// Compute total text size
|
||||||
{
|
|
||||||
x_range |= glyphs[i].position[0];
|
|
||||||
if (std::isspace(glyphs[i].character))
|
|
||||||
++spaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
max_line_size = std::max(max_line_size, x_range.length());
|
geom::vector<float, 2> text_size{0.f, 0.f};
|
||||||
|
for (auto const & l : line_bbox)
|
||||||
|
{
|
||||||
|
text_size[0] = std::max(text_size[0], l[0].length());
|
||||||
|
text_size[1] += l[1].length();
|
||||||
|
}
|
||||||
|
|
||||||
geom::vector<float, 2> offset;
|
// TODO: handle text overflow
|
||||||
float space_extra = 0.f;
|
|
||||||
|
|
||||||
switch (halign_)
|
// Position lines & glyphs inside label bbox
|
||||||
{
|
|
||||||
case halignment::left:
|
|
||||||
offset[0] = bbox[0].min - x_range.min;
|
|
||||||
break;
|
|
||||||
case halignment::center:
|
|
||||||
offset[0] = bbox[0].center() - x_range.length() / 2.f - x_range.min;
|
|
||||||
break;
|
|
||||||
case halignment::right:
|
|
||||||
offset[0] = bbox[0].max - x_range.length() - x_range.min;
|
|
||||||
break;
|
|
||||||
case halignment::stretch:
|
|
||||||
offset[0] = bbox[0].min - x_range.min;
|
|
||||||
if ((l + 1 != lines.size() && text_[lines[l].second] != '\n') && spaces > 0)
|
|
||||||
space_extra = (bbox[0].length() - x_range.length()) / spaces;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
std::vector<geom::vector<float, 2>> line_offset(lines.size());
|
||||||
|
{
|
||||||
|
float current_y;
|
||||||
switch (valign_)
|
switch (valign_)
|
||||||
{
|
{
|
||||||
case valignment::top:
|
case valignment::top:
|
||||||
offset[1] = bbox[1].min + l * (*st->text_scale) * font->size()[1];
|
current_y = bbox[1].min;
|
||||||
break;
|
break;
|
||||||
case valignment::center:
|
case valignment::center:
|
||||||
offset[1] = bbox[1].center() + (l - lines.size() / 2.f) * (*st->text_scale) * font->size()[1];
|
current_y = bbox[1].center() - text_size[1] / 2.f;
|
||||||
break;
|
break;
|
||||||
case valignment::bottom:
|
case valignment::bottom:
|
||||||
offset[1] = bbox[1].max + (l - lines.size() * 1.f) * (*st->text_scale) * font->size()[1];
|
current_y = bbox[1].max - text_size[1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
underline_box[l][0] = x_range + offset[0];
|
for (std::size_t l = 0; l < lines.size(); ++l)
|
||||||
underline_box[l][1].min = offset[1] + font->size()[1] * (*st->text_scale);
|
|
||||||
underline_box[l][1].max = underline_box[l][1].min + underline_width;
|
|
||||||
|
|
||||||
strikethrough_box[l][0] = x_range + offset[0];
|
|
||||||
strikethrough_box[l][1].min = offset[1] + font->size()[1] * (*st->text_scale) / 2.f;
|
|
||||||
strikethrough_box[l][1].max = strikethrough_box[l][1].min + strikethrough_width;
|
|
||||||
|
|
||||||
for (std::size_t i = lines[l].first; i < lines[l].second; ++i)
|
|
||||||
{
|
{
|
||||||
glyphs[i].position += offset;
|
int spaces = 0;
|
||||||
if (std::isspace(glyphs[i].character))
|
|
||||||
offset[0] += space_extra;
|
for (std::size_t i = lines[l].begin; i < lines[l].end; ++i)
|
||||||
|
if (std::isspace(glyphs[i].character))
|
||||||
|
++spaces;
|
||||||
|
|
||||||
|
geom::vector<float, 2> offset{0.f, current_y};
|
||||||
|
float space_extra = 0.f;
|
||||||
|
|
||||||
|
switch (halign_)
|
||||||
|
{
|
||||||
|
case halignment::left:
|
||||||
|
offset[0] = bbox[0].min - line_bbox[l][0].min;
|
||||||
|
break;
|
||||||
|
case halignment::center:
|
||||||
|
offset[0] = bbox[0].center() - line_bbox[l][0].length() / 2.f - line_bbox[l][0].min;
|
||||||
|
break;
|
||||||
|
case halignment::right:
|
||||||
|
offset[0] = bbox[0].max - line_bbox[l][0].length() - line_bbox[l][0].min;
|
||||||
|
break;
|
||||||
|
case halignment::stretch:
|
||||||
|
offset[0] = bbox[0].min - line_bbox[l][0].min;
|
||||||
|
if ((l + 1 != lines.size() && text_[lines[l].end] != '\n') && spaces > 0)
|
||||||
|
space_extra = (bbox[0].length() - line_bbox[l][0].length()) / spaces;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
line_offset[l] = offset;
|
||||||
|
|
||||||
|
for (std::size_t i = lines[l].begin; i < lines[l].end; ++i)
|
||||||
|
{
|
||||||
|
glyphs[i].position += offset;
|
||||||
|
if (std::isspace(glyphs[i].character))
|
||||||
|
offset[0] += space_extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_y += line_bbox[l][1].length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert chunks into image batches
|
||||||
|
|
||||||
{
|
{
|
||||||
auto & batch = state.batches.emplace_back();
|
std::size_t begin = 0;
|
||||||
batch.texture = &font->atlas();
|
for (auto const & ch : glyph_chunks)
|
||||||
|
|
||||||
for (auto const & g : glyphs)
|
|
||||||
{
|
{
|
||||||
auto tc = font->texcoords(g.character);
|
auto & batch = state.batches.emplace_back();
|
||||||
if (!tc) continue;
|
|
||||||
|
|
||||||
auto & image = batch.images.emplace_back();
|
auto font = ch.style.is_set(text_style_flag::bold) ? st->bold_font.get() : st->font.get();
|
||||||
image.position = g.position;
|
|
||||||
image.position[0] += (std::round(image.position[0].min) - image.position[0].min);
|
batch.texture = &font->atlas();
|
||||||
image.position[1] += (std::round(image.position[1].min) - image.position[1].min);
|
|
||||||
image.texcoords = *tc;
|
for (std::size_t i = begin; i < ch.end; ++i)
|
||||||
|
{
|
||||||
|
auto & g = glyphs[i];
|
||||||
|
auto tc = font->texcoords(g.character);
|
||||||
|
if (!tc) continue;
|
||||||
|
|
||||||
|
g.position[0] += (std::round(g.position[0].min) - g.position[0].min);
|
||||||
|
g.position[1] += (std::round(g.position[1].min) - g.position[1].min);
|
||||||
|
|
||||||
|
auto & image = batch.images.emplace_back();
|
||||||
|
image.position = g.position;
|
||||||
|
image.texcoords = *tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
begin = ch.end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (underline)
|
// Convert chunks into underline & strikethrough batches
|
||||||
|
|
||||||
{
|
{
|
||||||
|
float const line_width = 1.f * (*st->text_scale);
|
||||||
|
|
||||||
auto & batch = state.batches.emplace_back();
|
auto & batch = state.batches.emplace_back();
|
||||||
batch.texture = single_white_pixel_texture().get();
|
batch.texture = single_white_pixel_texture().get();
|
||||||
|
|
||||||
for (auto const & u : underline_box)
|
std::size_t ch_begin = 0;
|
||||||
|
std::size_t ch = 0;
|
||||||
|
for (std::size_t l = 0; l < lines.size(); ++l)
|
||||||
{
|
{
|
||||||
auto & image = batch.images.emplace_back();
|
float underline_y = std::round(line_offset[l][1] + font_height - line_width);
|
||||||
image.position = u;
|
float strikethrough_y = std::round(line_offset[l][1] + font_height / 2.f - line_width / 2.f);
|
||||||
image.texcoords = {{{0.f, 1.f}, {0.f, 1.f}}};
|
|
||||||
|
std::optional<geom::interval<float>> current_underline;
|
||||||
|
std::optional<geom::interval<float>> current_strikethrough;
|
||||||
|
|
||||||
|
auto flush_underline = [&]{
|
||||||
|
auto & image = batch.images.emplace_back();
|
||||||
|
image.position[0] = *current_underline;
|
||||||
|
image.position[1] = {underline_y, underline_y + line_width};
|
||||||
|
current_underline = std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto flush_strikethrough = [&]{
|
||||||
|
auto & image = batch.images.emplace_back();
|
||||||
|
image.position[0] = *current_strikethrough;
|
||||||
|
image.position[1] = {strikethrough_y, strikethrough_y + line_width};
|
||||||
|
current_strikethrough = std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (; ch < glyph_chunks.size(); ++ch)
|
||||||
|
{
|
||||||
|
auto const & chunk = glyph_chunks[ch];
|
||||||
|
|
||||||
|
bool const underline = chunk.style.is_set(text_style_flag::underline);
|
||||||
|
bool const strikethrough = chunk.style.is_set(text_style_flag::strikethrough);
|
||||||
|
|
||||||
|
std::size_t ibegin = std::max(ch_begin, lines[l].begin);
|
||||||
|
std::size_t iend = std::min(chunk.end, lines[l].end);
|
||||||
|
|
||||||
|
if (ibegin < iend)
|
||||||
|
{
|
||||||
|
geom::interval<float> x_range;
|
||||||
|
for (std::size_t i = ibegin; i < iend; ++i)
|
||||||
|
x_range |= glyphs[i].position[0];
|
||||||
|
|
||||||
|
if (underline && current_underline)
|
||||||
|
{
|
||||||
|
*current_underline |= x_range;
|
||||||
|
}
|
||||||
|
else if (underline && !current_underline)
|
||||||
|
{
|
||||||
|
current_underline = x_range;
|
||||||
|
}
|
||||||
|
else if (!underline && current_underline)
|
||||||
|
{
|
||||||
|
flush_underline();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strikethrough && current_strikethrough)
|
||||||
|
{
|
||||||
|
*current_strikethrough |= x_range;
|
||||||
|
}
|
||||||
|
else if (strikethrough && !current_strikethrough)
|
||||||
|
{
|
||||||
|
current_strikethrough = x_range;
|
||||||
|
}
|
||||||
|
else if (!strikethrough && current_strikethrough)
|
||||||
|
{
|
||||||
|
flush_strikethrough();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.end > lines[l].end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ch_begin = chunk.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_underline)
|
||||||
|
flush_underline();
|
||||||
|
|
||||||
|
if (current_strikethrough)
|
||||||
|
flush_strikethrough();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strikethrough)
|
state.size = text_size;
|
||||||
{
|
|
||||||
auto & batch = state.batches.emplace_back();
|
|
||||||
batch.texture = single_white_pixel_texture().get();
|
|
||||||
|
|
||||||
for (auto const & u : strikethrough_box)
|
|
||||||
{
|
|
||||||
auto & image = batch.images.emplace_back();
|
|
||||||
image.position = u;
|
|
||||||
image.texcoords = {{{0.f, 1.f}, {0.f, 1.f}}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.size[0] = max_line_size;
|
|
||||||
state.size[1] = lines.size() * (*st->text_scale) * font->size()[1];
|
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue