2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file gfx_layout_fallback.cpp Handling of laying out text as fallback. */
12 #include "gfx_layout_fallback.h"
13 #include "string_func.h"
14 #include "zoom_func.h"
16 #include "table/control_codes.h"
18 #include "safeguards.h"
20 /*** Paragraph layout ***/
22 * Class handling the splitting of a paragraph of text into lines and
25 * One constructs this class with the text that needs to be split into
26 * lines. Then nextLine is called with the maximum width until nullptr is
27 * returned. Each nextLine call creates VisualRuns which contain the
28 * length of text that are to be drawn with the same font. In other
29 * words, the result of this class is a list of sub strings with their
30 * font. The sub strings are then already fully laid out, and only
31 * need actual drawing.
33 * The positions in a visual run are sequential pairs of X,Y of the
34 * begin of each of the glyphs plus an extra pair to mark the end.
36 * @note This variant does not handle right-to-left properly.
38 class FallbackParagraphLayout
: public ParagraphLayouter
{
40 /** Visual run contains data about the bit of text with the same font. */
41 class FallbackVisualRun
: public ParagraphLayouter::VisualRun
{
42 std::vector
<GlyphID
> glyphs
; ///< The glyphs we're drawing.
43 std::vector
<Position
> positions
; ///< The positions of the glyphs.
44 std::vector
<int> glyph_to_char
; ///< The char index of the glyphs.
46 Font
*font
; ///< The font used to layout these.
49 FallbackVisualRun(Font
*font
, const char32_t
*chars
, int glyph_count
, int char_offset
, int x
);
50 const Font
*GetFont() const override
{ return this->font
; }
51 int GetGlyphCount() const override
{ return static_cast<int>(this->glyphs
.size()); }
52 std::span
<const GlyphID
> GetGlyphs() const override
{ return this->glyphs
; }
53 std::span
<const Position
> GetPositions() const override
{ return this->positions
; }
54 int GetLeading() const override
{ return this->GetFont()->fc
->GetHeight(); }
55 std::span
<const int> GetGlyphToCharMap() const override
{ return this->glyph_to_char
; }
58 /** A single line worth of VisualRuns. */
59 class FallbackLine
: public std::vector
<FallbackVisualRun
>, public ParagraphLayouter::Line
{
61 int GetLeading() const override
;
62 int GetWidth() const override
;
63 int CountRuns() const override
;
64 const ParagraphLayouter::VisualRun
&GetVisualRun(int run
) const override
;
66 int GetInternalCharLength(char32_t
) const override
{ return 1; }
69 const char32_t
*buffer_begin
; ///< Begin of the buffer.
70 const char32_t
*buffer
; ///< The current location in the buffer.
71 FontMap
&runs
; ///< The fonts we have to use for this paragraph.
73 FallbackParagraphLayout(char32_t
*buffer
, int length
, FontMap
&runs
);
74 void Reflow() override
;
75 std::unique_ptr
<const Line
> NextLine(int max_width
) override
;
79 * Get the actual ParagraphLayout for the given buffer.
80 * @param buff The begin of the buffer.
81 * @param buff_end The location after the last element in the buffer.
82 * @param fontMapping THe mapping of the fonts.
83 * @return The ParagraphLayout instance.
85 /* static */ ParagraphLayouter
*FallbackParagraphLayoutFactory::GetParagraphLayout(char32_t
*buff
, char32_t
*buff_end
, FontMap
&fontMapping
)
87 return new FallbackParagraphLayout(buff
, buff_end
- buff
, fontMapping
);
91 * Append a wide character to the internal buffer.
92 * @param buff The buffer to append to.
93 * @param buffer_last The end of the buffer.
94 * @param c The character to add.
95 * @return The number of buffer spaces that were used.
97 /* static */ size_t FallbackParagraphLayoutFactory::AppendToBuffer(char32_t
*buff
, [[maybe_unused
]] const char32_t
*buffer_last
, char32_t c
)
99 assert(buff
< buffer_last
);
105 * Create the visual run.
106 * @param font The font to use for this run.
107 * @param chars The characters to use for this run.
108 * @param char_count The number of characters in this run.
109 * @param char_offset This run's offset from the start of the layout input string.
110 * @param x The initial x position for this run.
112 FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font
*font
, const char32_t
*chars
, int char_count
, int char_offset
, int x
) :
115 const bool isbuiltin
= font
->fc
->IsBuiltInFont();
117 this->glyphs
.reserve(char_count
);
118 this->glyph_to_char
.reserve(char_count
);
119 this->positions
.reserve(char_count
);
122 for (int i
= 0; i
< char_count
; i
++) {
123 const GlyphID
&glyph_id
= this->glyphs
.emplace_back(font
->fc
->MapCharToGlyph(chars
[i
]));
124 int x_advance
= font
->fc
->GetGlyphWidth(glyph_id
);
126 this->positions
.emplace_back(advance
, advance
+ x_advance
- 1, font
->fc
->GetAscender()); // Apply sprite font's ascender.
127 } else if (chars
[i
] >= SCC_SPRITE_START
&& chars
[i
] <= SCC_SPRITE_END
) {
128 this->positions
.emplace_back(advance
, advance
+ x_advance
- 1, (font
->fc
->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font
->fc
->GetSize()))) / 2); // Align sprite font to centre
130 this->positions
.emplace_back(advance
, advance
+ x_advance
- 1, 0); // No ascender adjustment.
132 advance
+= x_advance
;
133 this->glyph_to_char
.push_back(char_offset
+ i
);
138 * Get the height of the line.
139 * @return The maximum height of the line.
141 int FallbackParagraphLayout::FallbackLine::GetLeading() const
144 for (const auto &run
: *this) {
145 leading
= std::max(leading
, run
.GetLeading());
152 * Get the width of this line.
153 * @return The width of the line.
155 int FallbackParagraphLayout::FallbackLine::GetWidth() const
157 if (this->empty()) return 0;
160 * The last X position of a run contains is the end of that run.
161 * Since there is no left-to-right support, taking this value of
162 * the last run gives us the end of the line and thus the width.
164 const auto &run
= this->GetVisualRun(this->CountRuns() - 1);
165 const auto &positions
= run
.GetPositions();
166 if (positions
.empty()) return 0;
167 return positions
.back().right
+ 1;
171 * Get the number of runs in this line.
172 * @return The number of runs.
174 int FallbackParagraphLayout::FallbackLine::CountRuns() const
176 return (uint
)this->size();
180 * Get a specific visual run.
181 * @return The visual run.
183 const ParagraphLayouter::VisualRun
&FallbackParagraphLayout::FallbackLine::GetVisualRun(int run
) const
185 return this->at(run
);
189 * Create a new paragraph layouter.
190 * @param buffer The characters of the paragraph.
191 * @param length The length of the paragraph.
192 * @param runs The font mapping of this paragraph.
194 FallbackParagraphLayout::FallbackParagraphLayout(char32_t
*buffer
, [[maybe_unused
]] int length
, FontMap
&runs
) : buffer_begin(buffer
), buffer(buffer
), runs(runs
)
196 assert(runs
.rbegin()->first
== length
);
200 * Reset the position to the start of the paragraph.
202 void FallbackParagraphLayout::Reflow()
204 this->buffer
= this->buffer_begin
;
208 * Construct a new line with a maximum width.
209 * @param max_width The maximum width of the string.
210 * @return A Line, or nullptr when at the end of the paragraph.
212 std::unique_ptr
<const ParagraphLayouter::Line
> FallbackParagraphLayout::NextLine(int max_width
)
215 * - split a line at a newline character, or at a space where we can break a line.
216 * - split for a visual run whenever a new line happens, or the font changes.
218 if (this->buffer
== nullptr) return nullptr;
220 std::unique_ptr
<FallbackLine
> l
= std::make_unique
<FallbackLine
>();
222 if (*this->buffer
== '\0') {
223 /* Only a newline. */
224 this->buffer
= nullptr;
225 l
->emplace_back(this->runs
.begin()->second
, this->buffer
, 0, 0, 0);
229 int offset
= this->buffer
- this->buffer_begin
;
230 FontMap::iterator iter
= this->runs
.begin();
231 while (iter
->first
<= offset
) {
233 assert(iter
!= this->runs
.end());
236 const FontCache
*fc
= iter
->second
->fc
;
237 const char32_t
*next_run
= this->buffer_begin
+ iter
->first
;
239 const char32_t
*begin
= this->buffer
;
240 const char32_t
*last_space
= nullptr;
241 const char32_t
*last_char
;
244 char32_t c
= *this->buffer
;
245 last_char
= this->buffer
;
248 this->buffer
= nullptr;
252 if (this->buffer
== next_run
) {
253 int w
= l
->GetWidth();
254 l
->emplace_back(iter
->second
, begin
, this->buffer
- begin
, begin
- this->buffer_begin
, w
);
256 assert(iter
!= this->runs
.end());
258 next_run
= this->buffer_begin
+ iter
->first
;
259 begin
= this->buffer
;
260 /* Since a next run is started, there is already some text that
261 * will be shown for this line. However, we do not want to break
262 * this line at the previous space, so pretend we passed a space
263 * just before this next run. */
264 last_space
= begin
- 1;
267 if (IsWhitespace(c
)) last_space
= this->buffer
;
269 if (IsPrintable(c
) && !IsTextDirectionChar(c
)) {
270 int char_width
= GetCharacterWidth(fc
->GetSize(), c
);
272 if (width
> max_width
) {
273 /* The string is longer than maximum width so we need to decide
274 * what to do with it. */
275 if (width
== char_width
) {
276 /* The character is wider than allowed width; don't know
277 * what to do with this case... bail out! */
278 this->buffer
= nullptr;
282 if (last_space
== nullptr) {
283 /* No space has been found. Just terminate at our current
284 * location. This usually happens for languages that do not
285 * require spaces in strings, like Chinese, Japanese and
286 * Korean. For other languages terminating mid-word might
287 * not be the best, but terminating the whole string instead
288 * of continuing the word at the next line is worse. */
289 last_char
= this->buffer
;
291 /* A space is found; perfect place to terminate */
292 this->buffer
= last_space
+ 1;
293 last_char
= last_space
;
302 if (l
->empty() || last_char
- begin
> 0) {
303 int w
= l
->GetWidth();
304 l
->emplace_back(iter
->second
, begin
, last_char
- begin
, begin
- this->buffer_begin
, w
);