From 8b14faaa4056189c6b53d7e04aab0ba77dc0822f Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 2 May 2025 22:59:55 +0100 Subject: [PATCH] Codechange: Add version of DrawStringMultiLine that performs clipping test. (#14189) Normally DrawStringMultiLine does not perform any clipping, as the return value may be needed if it the text is not drawn. In some specific cases the height is already known, so it is possible to test for clipping, which can cut down on layouting time for text which won't be visible. --- src/bridge_gui.cpp | 2 +- src/error_gui.cpp | 12 ++++++------ src/gfx.cpp | 35 +++++++++++++++++++++++++++++++++++ src/gfx_func.h | 6 ++++++ src/graph_gui.cpp | 4 ++-- src/settings_gui.cpp | 2 +- src/story_gui.cpp | 2 +- src/textfile_gui.cpp | 2 +- 8 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp index acfda71049..ab66588c2f 100644 --- a/src/bridge_gui.cpp +++ b/src/bridge_gui.cpp @@ -239,7 +239,7 @@ public: for (auto it = first; it != last; ++it) { const BridgeSpec *b = it->spec; DrawSpriteIgnorePadding(b->sprite, b->pal, tr.WithWidth(this->icon_width, rtl), SA_HOR_CENTER | SA_BOTTOM); - DrawStringMultiLine(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it)); + DrawStringMultiLineWithClipping(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it)); tr = tr.Translate(0, this->resize.step_height); } break; diff --git a/src/error_gui.cpp b/src/error_gui.cpp index 1a22c27267..0814850aea 100644 --- a/src/error_gui.cpp +++ b/src/error_gui.cpp @@ -194,14 +194,14 @@ public: case WID_EM_MESSAGE: if (this->detailed_msg.empty()) { - DrawStringMultiLine(r, this->summary_msg.GetDecodedString(), TC_FROMSTRING, SA_CENTER); + DrawStringMultiLineWithClipping(r, this->summary_msg.GetDecodedString(), TC_FROMSTRING, SA_CENTER); } else if (this->extra_msg.empty()) { /* Extra space when message is shorter than company face window */ int extra = (r.Height() - this->height_summary - this->height_detailed - WidgetDimensions::scaled.vsep_wide) / 2; /* Note: NewGRF supplied error message often do not start with a colour code, so default to white. */ - DrawStringMultiLine(r.WithHeight(this->height_summary + extra, false), this->summary_msg.GetDecodedString(), TC_WHITE, SA_CENTER); - DrawStringMultiLine(r.WithHeight(this->height_detailed + extra, true), this->detailed_msg.GetDecodedString(), TC_WHITE, SA_CENTER); + DrawStringMultiLineWithClipping(r.WithHeight(this->height_summary + extra, false), this->summary_msg.GetDecodedString(), TC_WHITE, SA_CENTER); + DrawStringMultiLineWithClipping(r.WithHeight(this->height_detailed + extra, true), this->detailed_msg.GetDecodedString(), TC_WHITE, SA_CENTER); } else { /* Extra space when message is shorter than company face window */ int extra = (r.Height() - this->height_summary - this->height_detailed - this->height_extra - (WidgetDimensions::scaled.vsep_wide * 2)) / 3; @@ -210,9 +210,9 @@ public: Rect top_section = r.WithHeight(this->height_summary + extra, false); Rect bottom_section = r.WithHeight(this->height_extra + extra, true); Rect middle_section = { top_section.left, top_section.bottom, top_section.right, bottom_section.top }; - DrawStringMultiLine(top_section, this->summary_msg.GetDecodedString(), TC_WHITE, SA_CENTER); - DrawStringMultiLine(middle_section, this->detailed_msg.GetDecodedString(), TC_WHITE, SA_CENTER); - DrawStringMultiLine(bottom_section, this->extra_msg.GetDecodedString(), TC_WHITE, SA_CENTER); + DrawStringMultiLineWithClipping(top_section, this->summary_msg.GetDecodedString(), TC_WHITE, SA_CENTER); + DrawStringMultiLineWithClipping(middle_section, this->detailed_msg.GetDecodedString(), TC_WHITE, SA_CENTER); + DrawStringMultiLineWithClipping(bottom_section, this->extra_msg.GetDecodedString(), TC_WHITE, SA_CENTER); } break; diff --git a/src/gfx.cpp b/src/gfx.cpp index 13d9776d12..364e9955b5 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -839,6 +839,41 @@ int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, return DrawStringMultiLine(left, right, top, bottom, GetString(str), colour, align, underline, fontsize); } +/** + * Draw a multiline string, possibly over multiple lines, if the region is within the current display clipping area. + * @note With clipping, it is not possible to determine how tall the rendered text will be, as it's not layouted. + * Regulard DrawStringMultiLine must be used if the height needs to be known. + * + * @param left The left most position to draw on. + * @param right The right most position to draw on. + * @param top The top most position to draw on. + * @param bottom The bottom most position to draw on. + * @param str String to draw. + * @param colour Colour used for drawing the string, for details see _string_colourmap in + * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h + * @param align The horizontal and vertical alignment of the string. + * @param underline Whether to underline all strings + * @param fontsize The size of the initial characters. + * + * @return true iff the string was drawn. + */ +bool DrawStringMultiLineWithClipping(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) +{ + /* The string may contain control chars to change the font, just use the biggest font for clipping. */ + int max_height = std::max({GetCharacterHeight(FS_SMALL), GetCharacterHeight(FS_NORMAL), GetCharacterHeight(FS_LARGE), GetCharacterHeight(FS_MONO)}); + + /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */ + int extra = max_height / 2; + + if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > bottom + extra || + _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) { + return false; + } + + DrawStringMultiLine(left, right, top, bottom, str, colour, align, underline, fontsize); + return true; +} + /** * Return the string dimension in pixels. The height and width are returned * in a single Dimension value. TINYFONT, BIGFONT modifiers are only diff --git a/src/gfx_func.h b/src/gfx_func.h index 0a3ab5b5c2..ed9d2c2cec 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -99,6 +99,7 @@ int DrawString(int left, int right, int top, std::string_view str, TextColour co int DrawString(int left, int right, int top, StringID str, TextColour colour = TC_FROMSTRING, StringAlignment align = SA_LEFT, bool underline = false, FontSize fontsize = FS_NORMAL); int DrawStringMultiLine(int left, int right, int top, int bottom, std::string_view str, TextColour colour = TC_FROMSTRING, StringAlignment align = (SA_TOP | SA_LEFT), bool underline = false, FontSize fontsize = FS_NORMAL); int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour = TC_FROMSTRING, StringAlignment align = (SA_TOP | SA_LEFT), bool underline = false, FontSize fontsize = FS_NORMAL); +bool DrawStringMultiLineWithClipping(int left, int right, int top, int bottom, std::string_view str, TextColour colour = TC_FROMSTRING, StringAlignment align = (SA_TOP | SA_LEFT), bool underline = false, FontSize fontsize = FS_NORMAL); void DrawCharCentered(char32_t c, const Rect &r, TextColour colour); @@ -129,6 +130,11 @@ inline int DrawStringMultiLine(const Rect &r, StringID str, TextColour colour = return DrawStringMultiLine(r.left, r.right, r.top, r.bottom, str, colour, align, underline, fontsize); } +inline bool DrawStringMultiLineWithClipping(const Rect &r, std::string_view str, TextColour colour = TC_FROMSTRING, StringAlignment align = (SA_TOP | SA_LEFT), bool underline = false, FontSize fontsize = FS_NORMAL) +{ + return DrawStringMultiLineWithClipping(r.left, r.right, r.top, r.bottom, str, colour, align, underline, fontsize); +} + inline void GfxFillRect(const Rect &r, int colour, FillRectMode mode = FILLRECT_OPAQUE) { GfxFillRect(r.left, r.top, r.right, r.bottom, colour, mode); diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index 011e1d41c2..38d1876301 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -454,11 +454,11 @@ protected: TimerGameEconomy::Year year = this->year; for (int i = 0; i < this->num_on_x_axis; i++) { if (rtl) { - DrawStringMultiLine(x + x_sep, x, y, this->height, + DrawStringMultiLineWithClipping(x + x_sep, x, y, this->height, GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year), GRAPH_AXIS_LABEL_COLOUR, SA_LEFT); } else { - DrawStringMultiLine(x, x + x_sep, y, this->height, + DrawStringMultiLineWithClipping(x, x + x_sep, y, this->height, GetString(month == 0 ? STR_GRAPH_X_LABEL_MONTH_YEAR : STR_GRAPH_X_LABEL_MONTH, STR_MONTH_ABBREV_JAN + month, year), GRAPH_AXIS_LABEL_COLOUR, SA_LEFT); } diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 4f2c76fdc7..70e3553733 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -928,7 +928,7 @@ struct GameOptionsWindow : Window { /* Draw the 'some search results are hidden' notice. */ if (this->warn_missing != WHR_NONE) { - DrawStringMultiLine(panel.WithHeight(this->warn_lines * GetCharacterHeight(FS_NORMAL)), + DrawStringMultiLineWithClipping(panel.WithHeight(this->warn_lines * GetCharacterHeight(FS_NORMAL)), GetString(warn_str, _game_settings_restrict_dropdown[this->filter.min_cat]), TC_BLACK, SA_CENTER); } diff --git a/src/story_gui.cpp b/src/story_gui.cpp index c74e840185..54707cc653 100644 --- a/src/story_gui.cpp +++ b/src/story_gui.cpp @@ -712,7 +712,7 @@ public: switch (ce.pe->type) { case SPET_TEXT: - y_offset = DrawStringMultiLine(ce.bounds.left, ce.bounds.right, ce.bounds.top - scrollpos, ce.bounds.bottom - scrollpos, + DrawStringMultiLineWithClipping(ce.bounds.left, ce.bounds.right, ce.bounds.top - scrollpos, ce.bounds.bottom - scrollpos, ce.pe->text.GetDecodedString(), TC_BLACK, SA_TOP | SA_LEFT); break; diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index 4b76254054..1046af2813 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -592,7 +592,7 @@ void TextfileWindow::AfterLoadMarkdown() int y_offset = (line.top - pos) * line_height; if (IsWidgetLowered(WID_TF_WRAPTEXT)) { - DrawStringMultiLine(fr.left, fr.right, y_offset, fr.bottom, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO); + DrawStringMultiLineWithClipping(fr.left, fr.right, y_offset, y_offset + (line.bottom - line.top) * line_height, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO); } else { DrawString(fr.left, fr.right, y_offset, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO); }