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