1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "../Context.h"
11 #include "../common.h"
12 #include "../config/Config.h"
13 #include "../core/String.hpp"
14 #include "../drawing/Drawing.h"
15 #include "../drawing/IDrawingContext.h"
16 #include "../drawing/IDrawingEngine.h"
17 #include "../interface/Viewport.h"
18 #include "../localisation/Formatting.h"
19 #include "../localisation/Localisation.h"
20 #include "../localisation/LocalisationService.h"
21 #include "../platform/platform.h"
22 #include "../sprites.h"
23 #include "../util/Util.h"
24 #include "TTF.h"
25 
26 #include <algorithm>
27 
28 using namespace OpenRCT2;
29 
30 enum : uint32_t
31 {
32     TEXT_DRAW_FLAG_INSET = 1 << 0,
33     TEXT_DRAW_FLAG_OUTLINE = 1 << 1,
34     TEXT_DRAW_FLAG_DARK = 1 << 2,
35     TEXT_DRAW_FLAG_EXTRA_DARK = 1 << 3,
36     TEXT_DRAW_FLAG_NO_FORMATTING = 1 << 28,
37     TEXT_DRAW_FLAG_Y_OFFSET_EFFECT = 1 << 29,
38     TEXT_DRAW_FLAG_TTF = 1 << 30,
39     TEXT_DRAW_FLAG_NO_DRAW = 1u << 31
40 };
41 
42 static int32_t ttf_get_string_width(std::string_view text, FontSpriteBase fontSpriteBase, bool noFormatting);
43 
44 /**
45  *
46  *  rct2: 0x006C23B1
47  */
gfx_get_string_width_new_lined(std::string_view text,FontSpriteBase fontSpriteBase)48 int32_t gfx_get_string_width_new_lined(std::string_view text, FontSpriteBase fontSpriteBase)
49 {
50     thread_local std::string buffer;
51     buffer.clear();
52 
53     std::optional<int32_t> maxWidth;
54     FmtString fmt(text);
55     for (const auto& token : fmt)
56     {
57         if (token.kind == FormatToken::Newline || token.kind == FormatToken::NewlineSmall)
58         {
59             auto width = gfx_get_string_width(buffer, fontSpriteBase);
60             if (!maxWidth.has_value() || maxWidth.value() > width)
61             {
62                 maxWidth = width;
63             }
64             buffer.clear();
65         }
66         else
67         {
68             buffer.append(token.text);
69         }
70     }
71     if (!maxWidth.has_value())
72     {
73         maxWidth = gfx_get_string_width(buffer, fontSpriteBase);
74     }
75     return maxWidth.value();
76 }
77 
78 /**
79  * Return the width of the string in buffer
80  *
81  *  rct2: 0x006C2321
82  * buffer (esi)
83  */
gfx_get_string_width(std::string_view text,FontSpriteBase fontSpriteBase)84 int32_t gfx_get_string_width(std::string_view text, FontSpriteBase fontSpriteBase)
85 {
86     return ttf_get_string_width(text, fontSpriteBase, false);
87 }
88 
gfx_get_string_width_no_formatting(std::string_view text,FontSpriteBase fontSpriteBase)89 int32_t gfx_get_string_width_no_formatting(std::string_view text, FontSpriteBase fontSpriteBase)
90 {
91     return ttf_get_string_width(text, fontSpriteBase, true);
92 }
93 
94 /**
95  * Clip the text in buffer to width, add ellipsis and return the new width of the clipped string
96  *
97  *  rct2: 0x006C2460
98  * buffer (esi)
99  * width (edi)
100  */
gfx_clip_string(utf8 * text,int32_t width,FontSpriteBase fontSpriteBase)101 int32_t gfx_clip_string(utf8* text, int32_t width, FontSpriteBase fontSpriteBase)
102 {
103     if (width < 6)
104     {
105         *text = 0;
106         return 0;
107     }
108 
109     // If width of the full string is less than allowed width then we don't need to clip
110     auto clippedWidth = gfx_get_string_width(text, fontSpriteBase);
111     if (clippedWidth <= width)
112     {
113         return clippedWidth;
114     }
115 
116     // Append each character 1 by 1 with an ellipsis on the end until width is exceeded
117     thread_local std::string buffer;
118     buffer.clear();
119 
120     size_t bestLength = 0;
121     int32_t bestWidth = 0;
122 
123     FmtString fmt(text);
124     for (const auto& token : fmt)
125     {
126         CodepointView codepoints(token.text);
127         for (auto codepoint : codepoints)
128         {
129             // Add the ellipsis before checking the width
130             buffer.append("...");
131 
132             auto currentWidth = gfx_get_string_width(buffer, fontSpriteBase);
133             if (currentWidth < width)
134             {
135                 bestLength = buffer.size();
136                 bestWidth = currentWidth;
137 
138                 // Trim the ellipsis
139                 buffer.resize(bestLength - 3);
140             }
141             else
142             {
143                 // Width exceeded, rollback to best length and put ellipsis back
144                 buffer.resize(bestLength);
145                 for (auto i = static_cast<int32_t>(bestLength) - 1; i >= 0 && i >= static_cast<int32_t>(bestLength) - 3; i--)
146                 {
147                     buffer[i] = '.';
148                 }
149 
150                 // Copy buffer back to input text buffer
151                 std::strcpy(text, buffer.c_str());
152                 return bestWidth;
153             }
154 
155             char cb[8]{};
156             utf8_write_codepoint(cb, codepoint);
157             buffer.append(cb);
158         }
159     }
160     return gfx_get_string_width(text, fontSpriteBase);
161 }
162 
163 /**
164  * Wrap the text in buffer to width, returns width of longest line.
165  *
166  * Inserts NULL where line should break (as \n is used for something else),
167  * so the number of lines is returned in num_lines. font_height seems to be
168  * a control character for line height.
169  *
170  *  rct2: 0x006C21E2
171  * buffer (esi)
172  * width (edi) - in
173  * num_lines (edi) - out
174  * font_height (ebx) - out
175  */
gfx_wrap_string(utf8 * text,int32_t width,FontSpriteBase fontSpriteBase,int32_t * outNumLines)176 int32_t gfx_wrap_string(utf8* text, int32_t width, FontSpriteBase fontSpriteBase, int32_t* outNumLines)
177 {
178     constexpr size_t NULL_INDEX = std::numeric_limits<size_t>::max();
179     thread_local std::string buffer;
180     buffer.resize(0);
181 
182     size_t currentLineIndex = 0;
183     size_t splitIndex = NULL_INDEX;
184     size_t bestSplitIndex = NULL_INDEX;
185     size_t numLines = 0;
186     int32_t maxWidth = 0;
187 
188     FmtString fmt = text;
189     for (const auto& token : fmt)
190     {
191         if (token.IsLiteral())
192         {
193             CodepointView codepoints(token.text);
194             for (auto codepoint : codepoints)
195             {
196                 char cb[8]{};
197                 utf8_write_codepoint(cb, codepoint);
198                 buffer.append(cb);
199 
200                 auto lineWidth = gfx_get_string_width(&buffer[currentLineIndex], fontSpriteBase);
201                 if (lineWidth <= width || (splitIndex == NULL_INDEX && bestSplitIndex == NULL_INDEX))
202                 {
203                     if (codepoint == ' ')
204                     {
205                         // Mark line split here
206                         splitIndex = buffer.size() - 1;
207                     }
208                     else if (splitIndex == NULL_INDEX)
209                     {
210                         // Mark line split here (this is after first character of line)
211                         bestSplitIndex = buffer.size();
212                     }
213                 }
214                 else
215                 {
216                     // Insert new line before current word
217                     if (splitIndex == NULL_INDEX)
218                     {
219                         splitIndex = bestSplitIndex;
220                     }
221                     buffer.insert(buffer.begin() + splitIndex, '\0');
222 
223                     // Recalculate the line length after splitting
224                     lineWidth = gfx_get_string_width(&buffer[currentLineIndex], fontSpriteBase);
225                     maxWidth = std::max(maxWidth, lineWidth);
226                     numLines++;
227 
228                     currentLineIndex = splitIndex + 1;
229                     splitIndex = NULL_INDEX;
230                     bestSplitIndex = NULL_INDEX;
231 
232                     // Trim the beginning of the new line
233                     while (buffer[currentLineIndex] == ' ')
234                     {
235                         buffer.erase(buffer.begin() + currentLineIndex);
236                     }
237                 }
238             }
239         }
240         else if (token.kind == FormatToken::Newline)
241         {
242             buffer.push_back('\0');
243 
244             auto lineWidth = gfx_get_string_width(&buffer[currentLineIndex], fontSpriteBase);
245             maxWidth = std::max(maxWidth, lineWidth);
246             numLines++;
247 
248             currentLineIndex = buffer.size();
249             splitIndex = NULL_INDEX;
250             bestSplitIndex = NULL_INDEX;
251         }
252         else
253         {
254             buffer.append(token.text);
255         }
256     }
257     {
258         // Final line width calculation
259         auto lineWidth = gfx_get_string_width(&buffer[currentLineIndex], fontSpriteBase);
260         maxWidth = std::max(maxWidth, lineWidth);
261     }
262 
263     std::memcpy(text, buffer.data(), buffer.size() + 1);
264     *outNumLines = static_cast<int32_t>(numLines);
265     return maxWidth;
266 }
267 
268 /**
269  * Draws text that is left aligned and vertically centred.
270  */
gfx_draw_string_left_centred(rct_drawpixelinfo * dpi,rct_string_id format,void * args,colour_t colour,const ScreenCoordsXY & coords)271 void gfx_draw_string_left_centred(
272     rct_drawpixelinfo* dpi, rct_string_id format, void* args, colour_t colour, const ScreenCoordsXY& coords)
273 {
274     char* buffer = gCommonStringFormatBuffer;
275     format_string(buffer, 256, format, args);
276     int32_t height = string_get_height_raw(buffer, FontSpriteBase::MEDIUM);
277     gfx_draw_string(dpi, coords - ScreenCoordsXY{ 0, (height / 2) }, buffer, { colour });
278 }
279 
280 /**
281  * Changes the palette so that the next character changes colour
282  */
colour_char(uint8_t colour,const uint16_t * current_font_flags,uint8_t * palette_pointer)283 static void colour_char(uint8_t colour, const uint16_t* current_font_flags, uint8_t* palette_pointer)
284 {
285     int32_t colour32 = 0;
286     const rct_g1_element* g1 = gfx_get_g1_element(SPR_TEXT_PALETTE);
287     if (g1 != nullptr)
288     {
289         uint32_t idx = (colour & 0xFF) * 4;
290         std::memcpy(&colour32, &g1->offset[idx], sizeof(colour32));
291     }
292 
293     if (!(*current_font_flags & TEXT_DRAW_FLAG_OUTLINE))
294     {
295         colour32 = colour32 & 0x0FF0000FF;
296     }
297     // Adjust text palette. Store current colour?
298     palette_pointer[1] = colour32 & 0xFF;
299     palette_pointer[2] = (colour32 >> 8) & 0xFF;
300     palette_pointer[3] = (colour32 >> 16) & 0xFF;
301     palette_pointer[4] = (colour32 >> 24) & 0xFF;
302 }
303 
304 /**
305  * Changes the palette so that the next character changes colour
306  * This is specific to changing to a predefined window related colour
307  */
colour_char_window(uint8_t colour,const uint16_t * current_font_flags,uint8_t * palette_pointer)308 static void colour_char_window(uint8_t colour, const uint16_t* current_font_flags, uint8_t* palette_pointer)
309 {
310     int32_t eax;
311 
312     colour = NOT_TRANSLUCENT(colour);
313     eax = ColourMapA[colour].colour_11;
314     if (*current_font_flags & TEXT_DRAW_FLAG_OUTLINE)
315     {
316         eax |= 0x0A0A00;
317     }
318     // Adjust text palette. Store current colour?
319     palette_pointer[1] = eax & 0xFF;
320     palette_pointer[2] = (eax >> 8) & 0xFF;
321     palette_pointer[3] = (eax >> 16) & 0xFF;
322     palette_pointer[4] = (eax >> 24) & 0xFF;
323 }
324 
325 /**
326  *
327  *  rct2: 0x006C1DB7
328  *
329  * left     : cx
330  * top      : dx
331  * numLines : bp
332  * text     : esi
333  * dpi      : edi
334  */
draw_string_centred_raw(rct_drawpixelinfo * dpi,const ScreenCoordsXY & coords,int32_t numLines,char * text,FontSpriteBase fontSpriteBase)335 void draw_string_centred_raw(
336     rct_drawpixelinfo* dpi, const ScreenCoordsXY& coords, int32_t numLines, char* text, FontSpriteBase fontSpriteBase)
337 {
338     ScreenCoordsXY screenCoords(dpi->x, dpi->y);
339     gfx_draw_string(dpi, screenCoords, "", { COLOUR_BLACK, fontSpriteBase });
340     screenCoords = coords;
341 
342     for (int32_t i = 0; i <= numLines; i++)
343     {
344         int32_t width = gfx_get_string_width(text, fontSpriteBase);
345         gfx_draw_string(dpi, screenCoords - ScreenCoordsXY{ width / 2, 0 }, text, { TEXT_COLOUR_254, fontSpriteBase });
346 
347         const utf8* ch = text;
348         const utf8* nextCh = nullptr;
349 
350         while ((utf8_get_next(ch, &nextCh)) != 0)
351         {
352             ch = nextCh;
353         }
354         text = const_cast<char*>(ch + 1);
355 
356         screenCoords.y += font_get_line_height(fontSpriteBase);
357     }
358 }
359 
string_get_height_raw(std::string_view text,FontSpriteBase fontBase)360 int32_t string_get_height_raw(std::string_view text, FontSpriteBase fontBase)
361 {
362     int32_t height = 0;
363     if (fontBase <= FontSpriteBase::MEDIUM)
364         height += 10;
365     else if (fontBase == FontSpriteBase::TINY)
366         height += 6;
367 
368     FmtString fmt(text);
369     for (const auto& token : fmt)
370     {
371         switch (token.kind)
372         {
373             case FormatToken::Newline:
374                 if (fontBase == FontSpriteBase::SMALL || fontBase == FontSpriteBase::MEDIUM)
375                 {
376                     height += 10;
377                     break;
378                 }
379 
380                 if (fontBase == FontSpriteBase::TINY)
381                 {
382                     height += 6;
383                     break;
384                 }
385                 height += 18;
386                 break;
387             case FormatToken::NewlineSmall:
388                 if (fontBase == FontSpriteBase::SMALL || fontBase == FontSpriteBase::MEDIUM)
389                 {
390                     height += 5;
391                     break;
392                 }
393 
394                 if (fontBase == FontSpriteBase::TINY)
395                 {
396                     height += 3;
397                     break;
398                 }
399                 height += 9;
400                 break;
401             case FormatToken::FontTiny:
402                 fontBase = FontSpriteBase::TINY;
403                 break;
404             case FormatToken::FontMedium:
405                 fontBase = FontSpriteBase::MEDIUM;
406                 break;
407             case FormatToken::FontSmall:
408                 fontBase = FontSpriteBase::SMALL;
409                 break;
410             default:
411                 break;
412         }
413     }
414     return height;
415 }
416 
417 /**
418  *
419  *  rct2: 0x006C1F57
420  *
421  * colour   : al
422  * format   : bx
423  * x        : cx
424  * y        : dx
425  * text     : esi
426  * dpi      : edi
427  * width    : bp
428  * ticks    : ebp >> 16
429  */
DrawNewsTicker(rct_drawpixelinfo * dpi,const ScreenCoordsXY & coords,int32_t width,colour_t colour,rct_string_id format,void * args,int32_t ticks)430 void DrawNewsTicker(
431     rct_drawpixelinfo* dpi, const ScreenCoordsXY& coords, int32_t width, colour_t colour, rct_string_id format, void* args,
432     int32_t ticks)
433 {
434     int32_t numLines, lineHeight, lineY;
435     utf8* buffer = gCommonStringFormatBuffer;
436     ScreenCoordsXY screenCoords(dpi->x, dpi->y);
437 
438     gfx_draw_string(dpi, screenCoords, "", { colour });
439     format_string(buffer, 256, format, args);
440 
441     gfx_wrap_string(buffer, width, FontSpriteBase::SMALL, &numLines);
442     lineHeight = font_get_line_height(FontSpriteBase::SMALL);
443 
444     int32_t numCharactersDrawn = 0;
445     int32_t numCharactersToDraw = ticks;
446 
447     lineY = coords.y - ((numLines * lineHeight) / 2);
448     for (int32_t line = 0; line <= numLines; line++)
449     {
450         int32_t halfWidth = gfx_get_string_width(buffer, FontSpriteBase::SMALL) / 2;
451 
452         FmtString fmt(buffer);
453         for (const auto& token : fmt)
454         {
455             bool doubleBreak = false;
456             if (token.IsLiteral())
457             {
458                 CodepointView codepoints(token.text);
459                 for (auto it = codepoints.begin(); it != codepoints.end(); it++)
460                 {
461                     numCharactersDrawn++;
462                     if (numCharactersDrawn > numCharactersToDraw)
463                     {
464                         auto ch = const_cast<char*>(&token.text[it.GetIndex()]);
465                         *ch = '\0';
466                         doubleBreak = true;
467                         break;
468                     }
469                 }
470             }
471             if (doubleBreak)
472                 break;
473         }
474 
475         screenCoords = { coords.x - halfWidth, lineY };
476         gfx_draw_string(dpi, screenCoords, buffer, { TEXT_COLOUR_254, FontSpriteBase::SMALL });
477 
478         if (numCharactersDrawn > numCharactersToDraw)
479         {
480             break;
481         }
482 
483         buffer = get_string_end(buffer) + 1;
484         lineY += lineHeight;
485     }
486 }
487 
488 struct text_draw_info
489 {
490     int32_t startX;
491     int32_t startY;
492     int32_t x;
493     int32_t y;
494     int32_t maxX;
495     int32_t maxY;
496     int32_t flags;
497     uint8_t palette[8];
498     FontSpriteBase font_sprite_base;
499     const int8_t* y_offset;
500 };
501 
ttf_draw_character_sprite(rct_drawpixelinfo * dpi,int32_t codepoint,text_draw_info * info)502 static void ttf_draw_character_sprite(rct_drawpixelinfo* dpi, int32_t codepoint, text_draw_info* info)
503 {
504     int32_t characterWidth = font_sprite_get_codepoint_width(info->font_sprite_base, codepoint);
505     int32_t sprite = font_sprite_get_codepoint_sprite(info->font_sprite_base, codepoint);
506 
507     if (!(info->flags & TEXT_DRAW_FLAG_NO_DRAW))
508     {
509         auto screenCoords = ScreenCoordsXY{ info->x, info->y };
510         if (info->flags & TEXT_DRAW_FLAG_Y_OFFSET_EFFECT)
511         {
512             screenCoords.y += *info->y_offset++;
513         }
514 
515         PaletteMap paletteMap(info->palette);
516         gfx_draw_glyph(dpi, sprite, screenCoords, paletteMap);
517     }
518 
519     info->x += characterWidth;
520 }
521 
ttf_draw_string_raw_sprite(rct_drawpixelinfo * dpi,std::string_view text,text_draw_info * info)522 static void ttf_draw_string_raw_sprite(rct_drawpixelinfo* dpi, std::string_view text, text_draw_info* info)
523 {
524     CodepointView codepoints(text);
525     for (auto codepoint : codepoints)
526     {
527         ttf_draw_character_sprite(dpi, codepoint, info);
528     }
529 }
530 
531 #ifndef NO_TTF
532 
533 static int _ttfGlId = 0;
ttf_draw_string_raw_ttf(rct_drawpixelinfo * dpi,std::string_view text,text_draw_info * info)534 static void ttf_draw_string_raw_ttf(rct_drawpixelinfo* dpi, std::string_view text, text_draw_info* info)
535 {
536     if (!ttf_initialise())
537         return;
538 
539     TTFFontDescriptor* fontDesc = ttf_get_font_from_sprite_base(info->font_sprite_base);
540     if (fontDesc->font == nullptr)
541     {
542         ttf_draw_string_raw_sprite(dpi, text, info);
543         return;
544     }
545 
546     if (info->flags & TEXT_DRAW_FLAG_NO_DRAW)
547     {
548         info->x += ttf_getwidth_cache_get_or_add(fontDesc->font, text);
549         return;
550     }
551 
552     uint8_t colour = info->palette[1];
553     TTFSurface* surface = ttf_surface_cache_get_or_add(fontDesc->font, text);
554     if (surface == nullptr)
555         return;
556 
557     int32_t drawX = info->x + fontDesc->offset_x;
558     int32_t drawY = info->y + fontDesc->offset_y;
559     int32_t width = surface->w;
560     int32_t height = surface->h;
561 
562     if (OpenRCT2::GetContext()->GetDrawingEngineType() == DrawingEngine::OpenGL)
563     {
564         auto pixels = reinterpret_cast<uint8_t*>(const_cast<void*>(surface->pixels));
565         auto pixelsLen = static_cast<size_t>(surface->pitch) * surface->h;
566         for (size_t pp = 0; pp < pixelsLen; pp++)
567         {
568             if (pixels[pp] != 0)
569             {
570                 pixels[pp] = colour;
571             }
572             else
573             {
574                 pixels[pp] = PALETTE_INDEX_0;
575             }
576         }
577 
578         auto baseId = uint32_t(0x7FFFF) - 1024;
579         auto imageId = baseId + _ttfGlId;
580         auto drawingEngine = dpi->DrawingEngine;
581         auto drawingContext = drawingEngine->GetDrawingContext();
582         drawingEngine->InvalidateImage(imageId);
583         drawingContext->DrawBitmap(dpi, imageId, surface->pixels, surface->pitch, surface->h, drawX, drawY);
584 
585         _ttfGlId++;
586         if (_ttfGlId >= 1023)
587         {
588             _ttfGlId = 0;
589         }
590         return;
591     }
592 
593     int32_t overflowX = (dpi->x + dpi->width) - (drawX + width);
594     int32_t overflowY = (dpi->y + dpi->height) - (drawY + height);
595     if (overflowX < 0)
596         width += overflowX;
597     if (overflowY < 0)
598         height += overflowY;
599     int32_t skipX = drawX - dpi->x;
600     int32_t skipY = drawY - dpi->y;
601     info->x += width;
602 
603     auto src = static_cast<const uint8_t*>(surface->pixels);
604     uint8_t* dst = dpi->bits;
605 
606     if (skipX < 0)
607     {
608         width += skipX;
609         src += -skipX;
610         skipX = 0;
611     }
612     if (skipY < 0)
613     {
614         height += skipY;
615         src += (-skipY * surface->pitch);
616         skipY = 0;
617     }
618 
619     dst += skipX;
620     dst += skipY * (dpi->width + dpi->pitch);
621 
622     int32_t srcScanSkip = surface->pitch - width;
623     int32_t dstScanSkip = dpi->width + dpi->pitch - width;
624     uint8_t* dst_orig = dst;
625     const uint8_t* src_orig = src;
626 
627     // Draw shadow/outline
628     if (info->flags & TEXT_DRAW_FLAG_OUTLINE)
629     {
630         for (int32_t yy = 0; yy < height - 0; yy++)
631         {
632             for (int32_t xx = 0; xx < width - 0; xx++)
633             {
634                 if (*src != 0)
635                 {
636                     // right
637                     if (xx + skipX < dpi->width + dpi->pitch - 1)
638                     {
639                         *(dst + 1) = info->palette[3];
640                     }
641                     // left
642                     if (xx + skipX > 1)
643                     {
644                         *(dst - 1) = info->palette[3];
645                     }
646                     // top
647                     if (yy + skipY > 1)
648                     {
649                         *(dst - width - dstScanSkip) = info->palette[3];
650                     }
651                     // bottom
652                     if (yy + skipY < dpi->height - 1)
653                     {
654                         *(dst + width + dstScanSkip) = info->palette[3];
655                     }
656                 }
657                 src++;
658                 dst++;
659             }
660             // Skip any remaining bits
661             src += srcScanSkip;
662             dst += dstScanSkip;
663         }
664     }
665 
666     dst = dst_orig;
667     src = src_orig;
668     bool use_hinting = gConfigFonts.enable_hinting && fontDesc->hinting_threshold > 0;
669     for (int32_t yy = 0; yy < height; yy++)
670     {
671         for (int32_t xx = 0; xx < width; xx++)
672         {
673             if (*src != 0)
674             {
675                 if (info->flags & TEXT_DRAW_FLAG_INSET)
676                 {
677                     *(dst + width + dstScanSkip + 1) = info->palette[3];
678                 }
679 
680                 if (*src > 180 || !use_hinting)
681                 {
682                     // Centre of the glyph: use full colour.
683                     *dst = colour;
684                 }
685                 else if (use_hinting && *src > fontDesc->hinting_threshold)
686                 {
687                     // Simulate font hinting by shading the background colour instead.
688                     if (info->flags & TEXT_DRAW_FLAG_OUTLINE)
689                     {
690                         // As outlines are black, these texts should always use a darker shade
691                         // of the foreground colour for font hinting.
692                         *dst = blendColours(colour, PALETTE_INDEX_0);
693                     }
694                     else
695                     {
696                         *dst = blendColours(colour, *dst);
697                     }
698                 }
699             }
700             src++;
701             dst++;
702         }
703         src += srcScanSkip;
704         dst += dstScanSkip;
705     }
706 }
707 
708 #endif // NO_TTF
709 
ttf_process_format_code(rct_drawpixelinfo * dpi,const FmtString::token & token,text_draw_info * info)710 static void ttf_process_format_code(rct_drawpixelinfo* dpi, const FmtString::token& token, text_draw_info* info)
711 {
712     switch (token.kind)
713     {
714         case FormatToken::Move:
715             info->x = info->startX + token.parameter;
716             break;
717         case FormatToken::Newline:
718             info->x = info->startX;
719             info->y += font_get_line_height(info->font_sprite_base);
720             break;
721         case FormatToken::NewlineSmall:
722             info->x = info->startX;
723             info->y += font_get_line_height_small(info->font_sprite_base);
724             break;
725         case FormatToken::FontTiny:
726             info->font_sprite_base = FontSpriteBase::TINY;
727             break;
728         case FormatToken::FontSmall:
729             info->font_sprite_base = FontSpriteBase::SMALL;
730             break;
731         case FormatToken::FontMedium:
732             info->font_sprite_base = FontSpriteBase::MEDIUM;
733             break;
734         case FormatToken::OutlineEnable:
735             info->flags |= TEXT_DRAW_FLAG_OUTLINE;
736             break;
737         case FormatToken::OutlineDisable:
738             info->flags &= ~TEXT_DRAW_FLAG_OUTLINE;
739             break;
740         case FormatToken::ColourWindow1:
741         {
742             uint16_t flags = info->flags;
743             colour_char_window(gCurrentWindowColours[0], &flags, info->palette);
744             break;
745         }
746         case FormatToken::ColourWindow2:
747         {
748             uint16_t flags = info->flags;
749             colour_char_window(gCurrentWindowColours[1], &flags, info->palette);
750             break;
751         }
752         case FormatToken::ColourWindow3:
753         {
754             uint16_t flags = info->flags;
755             colour_char_window(gCurrentWindowColours[2], &flags, info->palette);
756             break;
757         }
758         case FormatToken::InlineSprite:
759         {
760             auto g1 = gfx_get_g1_element(token.parameter & 0x7FFFF);
761             if (g1 != nullptr)
762             {
763                 if (!(info->flags & TEXT_DRAW_FLAG_NO_DRAW))
764                 {
765                     gfx_draw_sprite(dpi, token.parameter, { info->x, info->y }, 0);
766                 }
767                 info->x += g1->width;
768             }
769             break;
770         }
771         default:
772             if (FormatTokenIsColour(token.kind))
773             {
774                 uint16_t flags = info->flags;
775                 auto colourIndex = FormatTokenGetTextColourIndex(token.kind);
776                 colour_char(static_cast<uint8_t>(colourIndex), &flags, info->palette);
777             }
778             break;
779     }
780 }
781 
782 #ifndef NO_TTF
ShouldUseSpriteForCodepoint(char32_t codepoint)783 static bool ShouldUseSpriteForCodepoint(char32_t codepoint)
784 {
785     switch (codepoint)
786     {
787         case UnicodeChar::up:
788         case UnicodeChar::down:
789         case UnicodeChar::leftguillemet:
790         case UnicodeChar::tick:
791         case UnicodeChar::cross:
792         case UnicodeChar::right:
793         case UnicodeChar::rightguillemet:
794         case UnicodeChar::small_up:
795         case UnicodeChar::small_down:
796         case UnicodeChar::left:
797         case UnicodeChar::quote_open:
798         case UnicodeChar::quote_close:
799         case UnicodeChar::german_quote_open:
800         case UnicodeChar::plus:
801         case UnicodeChar::minus:
802             return true;
803         default:
804             return false;
805     }
806 }
807 #endif // NO_TTF
808 
ttf_process_string_literal(rct_drawpixelinfo * dpi,std::string_view text,text_draw_info * info)809 static void ttf_process_string_literal(rct_drawpixelinfo* dpi, std::string_view text, text_draw_info* info)
810 {
811 #ifndef NO_TTF
812     bool isTTF = info->flags & TEXT_DRAW_FLAG_TTF;
813 #else
814     bool isTTF = false;
815 #endif // NO_TTF
816 
817     if (!isTTF)
818     {
819         ttf_draw_string_raw_sprite(dpi, text, info);
820     }
821 #ifndef NO_TTF
822     else
823     {
824         CodepointView codepoints(text);
825         std::optional<size_t> ttfRunIndex;
826         for (auto it = codepoints.begin(); it != codepoints.end(); it++)
827         {
828             auto codepoint = *it;
829             if (ShouldUseSpriteForCodepoint(codepoint))
830             {
831                 if (ttfRunIndex.has_value())
832                 {
833                     // Draw the TTF run
834                     auto len = it.GetIndex() - ttfRunIndex.value();
835                     ttf_draw_string_raw_ttf(dpi, text.substr(ttfRunIndex.value(), len), info);
836                     ttfRunIndex = std::nullopt;
837                 }
838 
839                 // Draw the sprite font glyph
840                 ttf_draw_character_sprite(dpi, codepoint, info);
841             }
842             else
843             {
844                 if (!ttfRunIndex.has_value())
845                 {
846                     ttfRunIndex = it.GetIndex();
847                 }
848             }
849         }
850 
851         if (ttfRunIndex.has_value())
852         {
853             // Final TTF run
854             auto len = text.size() - *ttfRunIndex;
855             ttf_draw_string_raw_ttf(dpi, text.substr(ttfRunIndex.value(), len), info);
856         }
857     }
858 #endif // NO_TTF
859 }
860 
ttf_process_string_codepoint(rct_drawpixelinfo * dpi,codepoint_t codepoint,text_draw_info * info)861 static void ttf_process_string_codepoint(rct_drawpixelinfo* dpi, codepoint_t codepoint, text_draw_info* info)
862 {
863     char buffer[8]{};
864     utf8_write_codepoint(buffer, codepoint);
865     ttf_process_string_literal(dpi, buffer, info);
866 }
867 
ttf_process_string(rct_drawpixelinfo * dpi,std::string_view text,text_draw_info * info)868 static void ttf_process_string(rct_drawpixelinfo* dpi, std::string_view text, text_draw_info* info)
869 {
870     if (info->flags & TEXT_DRAW_FLAG_NO_FORMATTING)
871     {
872         ttf_process_string_literal(dpi, text, info);
873         info->maxX = std::max(info->maxX, info->x);
874         info->maxY = std::max(info->maxY, info->y);
875     }
876     else
877     {
878         FmtString fmt(text);
879         for (const auto& token : fmt)
880         {
881             if (token.IsLiteral())
882             {
883                 ttf_process_string_literal(dpi, token.text, info);
884             }
885             else if (token.IsCodepoint())
886             {
887                 auto codepoint = token.GetCodepoint();
888                 ttf_process_string_codepoint(dpi, codepoint, info);
889             }
890             else
891             {
892                 ttf_process_format_code(dpi, token, info);
893             }
894             info->maxX = std::max(info->maxX, info->x);
895             info->maxY = std::max(info->maxY, info->y);
896         }
897     }
898 }
899 
ttf_process_initial_colour(int32_t colour,text_draw_info * info)900 static void ttf_process_initial_colour(int32_t colour, text_draw_info* info)
901 {
902     if (colour != TEXT_COLOUR_254 && colour != TEXT_COLOUR_255)
903     {
904         info->flags &= ~(TEXT_DRAW_FLAG_INSET | TEXT_DRAW_FLAG_OUTLINE | TEXT_DRAW_FLAG_DARK | TEXT_DRAW_FLAG_EXTRA_DARK);
905         if (info->font_sprite_base == FontSpriteBase::MEDIUM_DARK
906             || info->font_sprite_base == FontSpriteBase::MEDIUM_EXTRA_DARK)
907         {
908             info->flags |= TEXT_DRAW_FLAG_DARK;
909             if (info->font_sprite_base == FontSpriteBase::MEDIUM_EXTRA_DARK)
910             {
911                 info->flags |= TEXT_DRAW_FLAG_EXTRA_DARK;
912             }
913             info->font_sprite_base = FontSpriteBase::MEDIUM;
914         }
915         if (colour & COLOUR_FLAG_OUTLINE)
916         {
917             info->flags |= TEXT_DRAW_FLAG_OUTLINE;
918         }
919         colour &= ~COLOUR_FLAG_OUTLINE;
920         if (!(colour & COLOUR_FLAG_INSET))
921         {
922             if (!(info->flags & TEXT_DRAW_FLAG_INSET))
923             {
924                 uint16_t flags = info->flags;
925                 colour_char_window(colour, &flags, reinterpret_cast<uint8_t*>(&info->palette));
926             }
927         }
928         else
929         {
930             info->flags |= TEXT_DRAW_FLAG_INSET;
931             colour &= ~COLOUR_FLAG_INSET;
932 
933             uint32_t eax;
934             if (info->flags & TEXT_DRAW_FLAG_DARK)
935             {
936                 if (info->flags & TEXT_DRAW_FLAG_EXTRA_DARK)
937                 {
938                     eax = ColourMapA[colour].mid_light;
939                     eax = eax << 16;
940                     eax = eax | ColourMapA[colour].dark;
941                 }
942                 else
943                 {
944                     eax = ColourMapA[colour].light;
945                     eax = eax << 16;
946                     eax = eax | ColourMapA[colour].mid_dark;
947                 }
948             }
949             else
950             {
951                 eax = ColourMapA[colour].lighter;
952                 eax = eax << 16;
953                 eax = eax | ColourMapA[colour].mid_light;
954             }
955 
956             // Adjust text palette. Store current colour? ;
957             info->palette[1] = eax & 0xFF;
958             info->palette[2] = (eax >> 8) & 0xFF;
959             info->palette[3] = (eax >> 16) & 0xFF;
960             info->palette[4] = (eax >> 24) & 0xFF;
961         }
962     }
963 }
964 
ttf_draw_string(rct_drawpixelinfo * dpi,const_utf8string text,int32_t colour,const ScreenCoordsXY & coords,bool noFormatting,FontSpriteBase fontSpriteBase)965 void ttf_draw_string(
966     rct_drawpixelinfo* dpi, const_utf8string text, int32_t colour, const ScreenCoordsXY& coords, bool noFormatting,
967     FontSpriteBase fontSpriteBase)
968 {
969     if (text == nullptr)
970         return;
971 
972     text_draw_info info;
973     info.font_sprite_base = fontSpriteBase;
974     info.flags = 0;
975     info.startX = coords.x;
976     info.startY = coords.y;
977     info.x = coords.x;
978     info.y = coords.y;
979 
980     if (LocalisationService_UseTrueTypeFont())
981     {
982         info.flags |= TEXT_DRAW_FLAG_TTF;
983     }
984 
985     if (noFormatting)
986     {
987         info.flags |= TEXT_DRAW_FLAG_NO_FORMATTING;
988     }
989 
990     std::memcpy(info.palette, text_palette, sizeof(info.palette));
991     ttf_process_initial_colour(colour, &info);
992     ttf_process_string(dpi, text, &info);
993     std::memcpy(text_palette, info.palette, sizeof(info.palette));
994 
995     dpi->lastStringPos = { info.x, info.y };
996 }
997 
ttf_get_string_width(std::string_view text,FontSpriteBase fontSpriteBase,bool noFormatting)998 static int32_t ttf_get_string_width(std::string_view text, FontSpriteBase fontSpriteBase, bool noFormatting)
999 {
1000     text_draw_info info;
1001     info.font_sprite_base = fontSpriteBase;
1002     info.flags = 0;
1003     info.startX = 0;
1004     info.startY = 0;
1005     info.x = 0;
1006     info.y = 0;
1007     info.maxX = 0;
1008     info.maxY = 0;
1009 
1010     info.flags |= TEXT_DRAW_FLAG_NO_DRAW;
1011     if (LocalisationService_UseTrueTypeFont())
1012     {
1013         info.flags |= TEXT_DRAW_FLAG_TTF;
1014     }
1015 
1016     if (noFormatting)
1017     {
1018         info.flags |= TEXT_DRAW_FLAG_NO_FORMATTING;
1019     }
1020 
1021     ttf_process_string(nullptr, text, &info);
1022 
1023     return info.maxX;
1024 }
1025 
1026 /**
1027  *
1028  *  rct2: 0x00682F28
1029  */
gfx_draw_string_with_y_offsets(rct_drawpixelinfo * dpi,const utf8 * text,int32_t colour,const ScreenCoordsXY & coords,const int8_t * yOffsets,bool forceSpriteFont,FontSpriteBase fontSpriteBase)1030 void gfx_draw_string_with_y_offsets(
1031     rct_drawpixelinfo* dpi, const utf8* text, int32_t colour, const ScreenCoordsXY& coords, const int8_t* yOffsets,
1032     bool forceSpriteFont, FontSpriteBase fontSpriteBase)
1033 {
1034     text_draw_info info;
1035     info.font_sprite_base = fontSpriteBase;
1036     info.flags = 0;
1037     info.startX = coords.x;
1038     info.startY = coords.y;
1039     info.x = coords.x;
1040     info.y = coords.y;
1041     info.y_offset = yOffsets;
1042 
1043     info.flags |= TEXT_DRAW_FLAG_Y_OFFSET_EFFECT;
1044 
1045     if (!forceSpriteFont && LocalisationService_UseTrueTypeFont())
1046     {
1047         info.flags |= TEXT_DRAW_FLAG_TTF;
1048     }
1049 
1050     std::memcpy(info.palette, text_palette, sizeof(info.palette));
1051     ttf_process_initial_colour(colour, &info);
1052     ttf_process_string(dpi, text, &info);
1053     std::memcpy(text_palette, info.palette, sizeof(info.palette));
1054 
1055     dpi->lastStringPos = { info.x, info.y };
1056 }
1057 
shorten_path(utf8 * buffer,size_t bufferSize,const utf8 * path,int32_t availableWidth,FontSpriteBase fontSpriteBase)1058 void shorten_path(utf8* buffer, size_t bufferSize, const utf8* path, int32_t availableWidth, FontSpriteBase fontSpriteBase)
1059 {
1060     size_t length = strlen(path);
1061 
1062     // Return full string if it fits
1063     if (gfx_get_string_width(const_cast<char*>(path), fontSpriteBase) <= availableWidth)
1064     {
1065         safe_strcpy(buffer, path, bufferSize);
1066         return;
1067     }
1068 
1069     // Count path separators
1070     int32_t path_separators = 0;
1071     for (size_t x = 0; x < length; x++)
1072     {
1073         if (path[x] == *PATH_SEPARATOR || path[x] == '/')
1074         {
1075             path_separators++;
1076         }
1077     }
1078 
1079     // TODO: Replace with unicode ellipsis when supported
1080     safe_strcpy(buffer, "...", bufferSize);
1081 
1082     // Abbreviate beginning with xth separator
1083     int32_t begin = -1;
1084     for (int32_t x = 0; x < path_separators; x++)
1085     {
1086         do
1087         {
1088             begin++;
1089         } while (path[begin] != *PATH_SEPARATOR && path[begin] != '/');
1090 
1091         safe_strcpy(buffer + 3, path + begin, bufferSize - 3);
1092         if (gfx_get_string_width(buffer, fontSpriteBase) <= availableWidth)
1093         {
1094             return;
1095         }
1096     }
1097 
1098     safe_strcpy(buffer, path, bufferSize);
1099 }
1100