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:
parent
901f0a8477
commit
012069440e
5 changed files with 32 additions and 102 deletions
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Add table
Reference in a new issue