If text is too long to render, fall back to line-by-line (fixes: #3265)

This still requires a huge surface to render all of the text on to,
but is an easier fix than redesigning the credits screen.

This removes the only caller of utils::vertical_split, which is why
that utility function is also removed.
This commit is contained in:
Steve Cotton 2019-08-07 18:38:25 +02:00
parent 901f0a8477
commit 012069440e
5 changed files with 32 additions and 102 deletions

View file

@ -13,6 +13,7 @@
* Move Wose Sapling from TRoW to core (PR#4226)
### Miscellaneous and bug fixes
* use the 1.15 add-ons server
* Fix a crash if the credits (including all add-ons) are very long (PR#4207)
## Version 1.15.0
### AI

View file

@ -44,7 +44,6 @@ pango_text::pango_text()
: context_(pango_font_map_create_context(pango_cairo_font_map_get_default()), g_object_unref)
, layout_(pango_layout_new(context_.get()), g_object_unref)
, rect_()
, sublayouts_()
, surface_()
, text_()
, markedup_text_(false)
@ -284,7 +283,6 @@ point pango_text::get_column_line(const point& position) const
bool pango_text::set_text(const std::string& text, const bool markedup)
{
if(markedup != markedup_text_ || text != text_) {
sublayouts_.clear();
if(layout_ == nullptr) {
layout_.reset(pango_layout_new(context_.get()));
}
@ -632,20 +630,7 @@ void pango_text::render(PangoLayout& layout, const PangoRectangle& rect, const s
std::unique_ptr<cairo_t, std::function<void(cairo_t*)>> cr(cairo_create(cairo_surface.get()), cairo_destroy);
if(cairo_status(cr.get()) == CAIRO_STATUS_INVALID_SIZE) {
if(!is_surface_split()) {
split_surface();
PangoRectangle upper_rect = calculate_size(*sublayouts_[0]);
PangoRectangle lower_rect = calculate_size(*sublayouts_[1]);
render(*sublayouts_[0], upper_rect, 0u, stride);
render(*sublayouts_[1], lower_rect, upper_rect.height * stride, stride);
return;
} else {
// If this occurs in practice, it can be fixed by implementing recursive splitting.
throw std::length_error("Text is too long to render");
}
throw std::length_error("Text is too long to render");
}
//
@ -705,7 +690,36 @@ void pango_text::rerender(const bool force)
return;
}
render(*layout_, rect_, 0u, stride);
try {
// Try rendering the whole text in one go
render(*layout_, rect_, 0u, stride);
} catch (std::length_error&) {
// Try rendering line-by-line, this is a workaround for cairo
// surfaces being limited to approx 2**15 pixels in height. If this
// also throws a length_error then leave it to the caller to
// handle.
std::size_t cumulative_height = 0u;
auto start_of_line = text_.cbegin();
while (start_of_line != text_.cend()) {
auto end_of_line = std::find(start_of_line, text_.cend(), '\n');
auto part_layout = std::unique_ptr<PangoLayout, std::function<void(void*)>> { pango_layout_new(context_.get()), g_object_unref};
auto line = utils::string_view(&*start_of_line, std::distance(start_of_line, end_of_line));
set_markup(line, *part_layout);
copy_layout_properties(*layout_, *part_layout);
auto part_rect = calculate_size(*part_layout);
render(*part_layout, part_rect, cumulative_height * stride, stride);
cumulative_height += part_rect.height;
start_of_line = end_of_line;
if (start_of_line != text_.cend()) {
// skip over the \n
++start_of_line;
}
}
}
// The cairo surface is in CAIRO_FORMAT_ARGB32 which uses
// pre-multiplied alpha. SDL doesn't use that so the pixels need to be
@ -834,27 +848,6 @@ bool pango_text::validate_markup(utils::string_view text, char** raw_text, std::
return true;
}
void pango_text::split_surface()
{
auto text_parts = utils::vertical_split(text_);
PangoLayout* upper_layout = pango_layout_new(context_.get());
PangoLayout* lower_layout = pango_layout_new(context_.get());
set_markup(text_parts.first, *upper_layout);
set_markup(text_parts.second, *lower_layout);
copy_layout_properties(*layout_, *upper_layout);
copy_layout_properties(*layout_, *lower_layout);
sublayouts_.emplace_back(upper_layout, g_object_unref);
sublayouts_.emplace_back(lower_layout, g_object_unref);
// Freeing the old layout causes all text to use
// default line spacing in the future.
// layout_.reset(nullptr);
}
void pango_text::copy_layout_properties(PangoLayout& src, PangoLayout& dst)
{
pango_layout_set_alignment(&dst, pango_layout_get_alignment(&src));

View file

@ -257,9 +257,6 @@ private:
std::unique_ptr<PangoLayout, std::function<void(void*)>> layout_;
mutable PangoRectangle rect_;
// Used if the text is too long to fit into a single Cairo surface.
std::vector<std::unique_ptr<PangoLayout, std::function<void(void*)>>> sublayouts_;
/** The SDL surface to render upon used as a cache. */
mutable surface surface_;
@ -421,25 +418,6 @@ private:
bool validate_markup(utils::string_view text, char** raw_text, std::string& semi_escaped) const;
/** Splits the text to two Cairo surfaces.
*
* The implementation isn't recursive: the function only splits the text once.
* As a result, it only doubles the maximum surface height to 64,000 pixels
* or so.
* The reason for this is that a recursive implementation would be more complex
* and it's unnecessary for now, as the longest surface in the game
* (end credits) is only about 40,000 pixels high with the default_large widget
* definition.
* If we need even larger surfaces in the future, the implementation can be made
* recursive.
*/
void split_surface();
bool is_surface_split() const
{
return sublayouts_.size() > 0;
}
static void copy_layout_properties(PangoLayout& src, PangoLayout& dst);
std::vector<std::string> find_links(utils::string_view text) const;

View file

@ -382,35 +382,6 @@ std::vector<std::string> parenthetical_split(const std::string& val,
return res;
}
std::pair<string_view, string_view> vertical_split(const std::string& val)
{
// Count the number of lines.
int num_lines = std::count(val.begin(), val.end(), '\n') + 1;
if(num_lines < 2) {
throw std::logic_error("utils::vertical_split: the string contains only one line");
}
// Split the string at the point where we have encountered
// (number of lines / 2 - 1) line separators.
int split_point = 0;
int num_found_line_separators = 0;
for(std::size_t i = 0; i < val.size(); ++i) {
if(val[i] == '\n') {
++num_found_line_separators;
if(num_found_line_separators >= num_lines / 2 - 1) {
split_point = i;
break;
}
}
}
assert(split_point != 0);
return { string_view(val.data(), split_point),
string_view(&val[split_point + 1], val.size() - (split_point + 1)) };
}
// Modify a number by string representing integer difference, or optionally %
int apply_modifier( const int number, const std::string &amount, const int minimum ) {
// wassert( amount.empty() == false );

View file

@ -147,19 +147,6 @@ std::vector<std::string> square_parenthetical_split(
const std::string& right = ")]",
const int flags = REMOVE_EMPTY | STRIP_SPACES);
/**
* Splits a string into two parts as evenly as possible based on lines.
* For example, if the string contains 3288 lines, then both parts will
* be 1644 lines long.
*
* The line separator in between won't be in either of the parts the
* function returns.
*
* Because this function is intended for extremely long strings
* (kilobytes long), it returns string_views for performance.
*/
std::pair<string_view, string_view> vertical_split(const std::string& val);
/**
* Generates a new string joining container items in a list.
*