Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overlays: Fix wrapped text in native UI #12345

Merged
merged 3 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 103 additions & 30 deletions rpcs3/Emu/RSX/Overlays/overlay_controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,72 +355,145 @@ namespace rsx
return font_ref ? font_ref : fontmgr::get("Arial", 12);
}

std::vector<vertex> overlay_element::render_text(const char32_t *string, f32 x, f32 y)
std::vector<vertex> overlay_element::render_text(const char32_t* string, f32 x, f32 y)
{
auto renderer = get_font();

f32 text_extents_w = 0.f;
u16 clip_width = clip_text ? w : umax;
const u16 clip_width = clip_text ? w : umax;
std::vector<vertex> result = renderer->render_text(string, clip_width, wrap_text);

if (!result.empty())
{
for (auto &v : result)
const auto apply_transform = [&]()
{
// Check for real text region extent
// TODO: Ellipsis
text_extents_w = std::max(v.values[0], text_extents_w);

// Apply transform.
// (0, 0) has text sitting one line off the top left corner (text is outside the rect) hence the offset by text height
v.values[0] += x + padding_left;
v.values[1] += y + padding_top + static_cast<f32>(renderer->get_size_px());
}
const f32 size_px = renderer->get_size_px();

if (alignment != text_align::left)
for (vertex& v : result)
{
// Apply transform.
// (0, 0) has text sitting one line off the top left corner (text is outside the rect) hence the offset by text height
v.x() += x + padding_left;
v.y() += y + padding_top + size_px;
}
};

if (alignment == text_align::left)
{
apply_transform();
}
else
{
// Scan for lines and measure them
// Reposition them to the center or right depending on the alignment
std::vector<std::pair<u32, u32>> lines;
std::vector<std::tuple<u32, u32, f32>> lines;
u32 line_begin = 0;
u32 line_end = 0;
u32 word_end = 0;
u32 ctr = 0;
f32 text_extents_w = w;

for (auto c : text)
for (const auto& c : text)
{
switch (c)
{
case '\r':
{
word_end = line_end = line_begin = ctr;
continue;
}
case '\n':
lines.emplace_back(line_begin, ctr);
line_begin = ctr;
{
lines.emplace_back(line_begin, std::min(word_end, line_end), text_extents_w);
word_end = line_end = line_begin = ctr;
text_extents_w = w;
continue;
}
default:
{
ctr += 4;

if (c == ' ')
{
if (line_end == line_begin)
{
// Ignore leading whitespace
word_end = line_end = line_begin = ctr;
}
else
{
line_end = ctr;
}
}
else
{
word_end = line_end = ctr;

// Check for real text region extent
text_extents_w = std::max(result[ctr - 1].x(), text_extents_w);
}
continue;
}
}
}

lines.emplace_back(line_begin, ctr);
const auto max_region_w = std::max<f32>(text_extents_w, w);
// Add final line
lines.emplace_back(line_begin, std::min(word_end, line_end), std::max<f32>(text_extents_w, w));

const f32 offset_extent = (alignment == text_align::center ? 0.5f : 1.0f);
const f32 size_px = renderer->get_size_px() * 0.5f;

for (auto p : lines)
// Apply padding
apply_transform();

// Moves all glyphs of a line by the correct amount to get a nice alignment.
const auto move_line = [&result, &offset_extent](u32 begin, u32 end, f32 max_region_w)
{
if (p.first >= p.second)
continue;
const f32 line_length = result[end - 1].x() - result[begin].x();

const f32 line_length = result[p.second - 1].values[0] - result[p.first].values[0];
const bool wrapped = std::fabs(result[p.second - 1].values[1] - result[p.first + 3].values[1]) >= (renderer->get_size_px() * 0.5f);
if (line_length < max_region_w)
{
const f32 offset = (max_region_w - line_length) * offset_extent;
for (auto n = begin; n < end; ++n)
{
result[n].x() += offset;
}
}
};

if (wrapped)
// Properly place all lines individually
for (const auto& [begin, end, max_region_w] : lines)
{
if (begin >= end)
continue;

if (line_length < max_region_w)
// Check if there's any wrapped text
if (std::fabs(result[end - 1].y() - result[begin + 3].y()) < size_px)
{
const f32 offset = (max_region_w - line_length) * offset_extent;
for (auto n = p.first; n < p.second; ++n)
// No wrapping involved. We can just move the entire line.
move_line(begin, end, max_region_w);
continue;
}

// Wrapping involved. We have to search for the line breaks and move each line seperately.
for (u32 i_begin = begin, i_next = begin + 4;; i_next += 4)
kd-11 marked this conversation as resolved.
Show resolved Hide resolved
{
// Check if this is the last glyph in the line of text.
const bool is_last_glyph = i_next >= end;

// The line may be wrapped, so we need to check if the next glyph's position is below the current position.
if (is_last_glyph || (std::fabs(result[i_next - 1].y() - result[i_begin + 3].y()) >= size_px))
{
result[n].values[0] += offset;
// Whenever we reached the end of a visual line we need to move its glyphs accordingly.
const u32 i_end = i_next - (is_last_glyph ? 0 : 4);

move_line(i_begin, i_end, max_region_w);

i_begin = i_end;

if (is_last_glyph)
{
break;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion rpcs3/Emu/RSX/Overlays/overlay_controls.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ namespace rsx
virtual void align_text(text_align align);
virtual void set_wrap_text(bool state);
virtual font* get_font() const;
virtual std::vector<vertex> render_text(const char32_t *string, f32 x, f32 y);
virtual std::vector<vertex> render_text(const char32_t* string, f32 x, f32 y);
virtual compiled_resource& get_compiled();
void measure_text(u16& width, u16& height, bool ignore_word_wrap = false) const;
};
Expand Down
185 changes: 82 additions & 103 deletions rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,128 +294,107 @@ namespace rsx
return;
}

usz i = 0u;
bool skip_whitespace = false;

while (true)
// Render as many characters as possible as glyphs.
for (usz i = 0u, begin_of_word = 0u; i < char_limit; i++)
{
if (auto c = text[i++]; c && (i <= char_limit))
switch (const auto& c = text[i])
{
switch (c)
{
case '\n':
case '\0':
{
// We're done.
return;
}
case '\n':
{
// Reset x to 0 and increase y to advance to the new line.
x_advance = 0.f;
y_advance += size_px + 2.f;
begin_of_word = result.size();
continue;
}
case '\r':
{
// Reset x to 0.
x_advance = 0.f;
begin_of_word = result.size();
continue;
}
default:
{
const bool is_whitespace = c == ' ';
stbtt_aligned_quad quad{};

if (is_whitespace)
{
y_advance += size_px + 2.f;
x_advance = 0.f;
continue;
// Skip whitespace if we are at the start of a line.
if (x_advance > 0.f)
{
// Get the glyph size.
quad = get_char(c, x_advance, y_advance);

// Reset the result if the glyph would protrude out of the given space anyway.
if (x_advance > max_width)
{
quad = {};
}
}
}
case '\r':
else
{
x_advance = 0.f;
continue;
// No whitespace. Get the glyph size.
quad = get_char(c, x_advance, y_advance);
}
default:

// Add the glyph's vertices.
result.emplace_back(quad.x0, quad.y0, quad.s0, quad.t0);
result.emplace_back(quad.x1, quad.y0, quad.s1, quad.t0);
result.emplace_back(quad.x0, quad.y1, quad.s0, quad.t1);
result.emplace_back(quad.x1, quad.y1, quad.s1, quad.t1);

// The next word will begin after any whitespaces.
if (is_whitespace)
kd-11 marked this conversation as resolved.
Show resolved Hide resolved
{
stbtt_aligned_quad quad;
if (skip_whitespace && text[i - 1] == ' ')
{
quad = {};
}
else
{
quad = get_char(c, x_advance, y_advance);
skip_whitespace = false;
}
begin_of_word = result.size();
}

if (x_advance > max_width)
// Check if we reached the end of the available space.
if (x_advance > max_width)
{
// Try to wrap the protruding text
if (wrap)
{
bool wrapped = false;
bool non_whitespace_break = false;
// Increase y to advance to the next line.
y_advance += size_px + 2.f;

if (wrap)
// We can just reset x and move on to the next character if this is a whitespace.
if (is_whitespace)
{
// scan previous chars
for (usz j = i - 1, nb_chars = 0; j > 0; j--, nb_chars++)
{
if (text[j] == '\n')
break;

if (text[j] == ' ')
{
non_whitespace_break = true;
continue;
}

if (non_whitespace_break)
{
if (nb_chars > 1)
{
nb_chars--;

auto first_affected = result.size() - (nb_chars * 4);
f32 base_x = result[first_affected].values[0];

for (usz n = first_affected; n < result.size(); ++n)
{
auto char_index = n / 4;
if (text[char_index] == ' ')
{
// Skip character
result[n++].vec2(0.f, 0.f);
result[n++].vec2(0.f, 0.f);
result[n++].vec2(0.f, 0.f);
result[n].vec2(0.f, 0.f);
continue;
}

result[n].values[0] -= base_x;
result[n].values[1] += size_px + 2.f;
}

x_advance = result.back().values[0];
}
else
{
x_advance = 0.f;
}

wrapped = true;
y_advance += size_px + 2.f;

if (text[i - 1] == ' ')
{
quad = {};
skip_whitespace = true;
}
else
{
quad = get_char(c, x_advance, y_advance);
}

break;
}
}
x_advance = 0.f;
break;
}

if (!wrapped)
// Get the leftmost offset of the current word.
const f32 base_x = result[begin_of_word].x();

// Move all characters of the current word one line down and to the left.
for (usz n = begin_of_word; n < result.size(); ++n)
{
// TODO: Ellipsize
break;
result[n].x() -= base_x;
result[n].y() += size_px + 2.f;
}
}

result.emplace_back(quad.x0, quad.y0, quad.s0, quad.t0);
result.emplace_back(quad.x1, quad.y0, quad.s1, quad.t0);
result.emplace_back(quad.x0, quad.y1, quad.s0, quad.t1);
result.emplace_back(quad.x1, quad.y1, quad.s1, quad.t1);
break;
// Set x offset to the rightmost position of the current word
x_advance = result.back().x();
}
else
{
// TODO: Ellipsize
}
}
} // switch
}
else
{

break;
}
} // switch
}
}

Expand Down
Loading