OpenRCT2/src/openrct2/drawing/Drawing.String.cpp

1064 lines
31 KiB
C++
Raw Normal View History

2014-10-06 18:36:58 +02:00
/*****************************************************************************
* Copyright (c) 2014-2023 OpenRCT2 developers
2014-10-06 18:36:58 +02:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2014-10-06 18:36:58 +02:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
2014-10-06 18:36:58 +02:00
*****************************************************************************/
#include "../drawing/Drawing.h"
#include "../Context.h"
#include "../common.h"
#include "../config/Config.h"
2020-10-14 20:40:24 +02:00
#include "../core/String.hpp"
#include "../drawing/IDrawingContext.h"
#include "../drawing/IDrawingEngine.h"
2018-01-06 00:45:53 +01:00
#include "../interface/Viewport.h"
2020-10-13 02:14:39 +02:00
#include "../localisation/Formatting.h"
2018-01-06 18:32:25 +01:00
#include "../localisation/Localisation.h"
2018-04-27 00:03:02 +02:00
#include "../localisation/LocalisationService.h"
#include "../platform/Platform.h"
2014-10-06 18:36:58 +02:00
#include "../sprites.h"
2017-12-13 13:02:24 +01:00
#include "../util/Util.h"
2018-01-05 22:14:20 +01:00
#include "TTF.h"
2014-10-06 18:36:58 +02:00
2018-06-22 22:59:03 +02:00
#include <algorithm>
2020-10-13 02:14:39 +02:00
using namespace OpenRCT2;
static int32_t TTFGetStringWidth(std::string_view text, FontStyle fontStyle, bool noFormatting);
2015-07-26 01:55:17 +02:00
2015-07-27 19:58:12 +02:00
/**
*
* rct2: 0x006C23B1
*/
int32_t GfxGetStringWidthNewLined(std::string_view text, FontStyle fontStyle)
2015-07-27 19:58:12 +02:00
{
u8string buffer;
2020-10-15 23:12:41 +02:00
std::optional<int32_t> maxWidth;
FmtString fmt(text);
for (const auto& token : fmt)
2018-06-22 22:59:03 +02:00
{
2020-10-16 01:13:52 +02:00
if (token.kind == FormatToken::Newline || token.kind == FormatToken::NewlineSmall)
2020-10-15 23:12:41 +02:00
{
auto width = GfxGetStringWidth(buffer, fontStyle);
2021-09-13 18:47:13 +02:00
if (!maxWidth.has_value() || maxWidth.value() > width)
2020-10-15 23:12:41 +02:00
{
maxWidth = width;
}
buffer.clear();
}
else
2018-06-22 22:59:03 +02:00
{
2020-10-15 23:12:41 +02:00
buffer.append(token.text);
}
}
2021-09-13 18:47:13 +02:00
if (!maxWidth.has_value())
2020-10-15 23:12:41 +02:00
{
maxWidth = GfxGetStringWidth(buffer, fontStyle);
2020-10-15 23:12:41 +02:00
}
2021-09-13 18:47:13 +02:00
return maxWidth.value();
}
2014-10-06 18:36:58 +02:00
/**
* Return the width of the string in buffer
2014-10-06 18:36:58 +02:00
*
* rct2: 0x006C2321
* buffer (esi)
*/
int32_t GfxGetStringWidth(std::string_view text, FontStyle fontStyle)
2014-10-06 18:36:58 +02:00
{
return TTFGetStringWidth(text, fontStyle, false);
2020-11-22 01:36:40 +01:00
}
int32_t GfxGetStringWidthNoFormatting(std::string_view text, FontStyle fontStyle)
2020-11-22 01:36:40 +01:00
{
return TTFGetStringWidth(text, fontStyle, true);
2014-10-06 18:36:58 +02:00
}
/**
* Clip the text in buffer to width, add ellipsis and return the new width of the clipped string
2014-10-06 18:36:58 +02:00
*
* rct2: 0x006C2460
* buffer (esi)
* width (edi)
*/
int32_t GfxClipString(utf8* text, int32_t width, FontStyle fontStyle)
2014-10-06 18:36:58 +02:00
{
2018-06-22 22:59:03 +02:00
if (width < 6)
{
*text = 0;
return 0;
}
2020-10-15 23:12:41 +02:00
// If width of the full string is less than allowed width then we don't need to clip
auto clippedWidth = GfxGetStringWidth(text, fontStyle);
2018-06-22 22:59:03 +02:00
if (clippedWidth <= width)
{
return clippedWidth;
}
2020-10-15 23:12:41 +02:00
// Append each character 1 by 1 with an ellipsis on the end until width is exceeded
thread_local std::string buffer;
buffer.clear();
size_t bestLength = 0;
int32_t bestWidth = 0;
FmtString fmt(text);
for (const auto& token : fmt)
2018-06-22 22:59:03 +02:00
{
2020-10-15 23:12:41 +02:00
CodepointView codepoints(token.text);
for (auto codepoint : codepoints)
2018-06-22 22:59:03 +02:00
{
2020-10-15 23:12:41 +02:00
// Add the ellipsis before checking the width
buffer.append("...");
auto currentWidth = GfxGetStringWidth(buffer, fontStyle);
2020-10-15 23:12:41 +02:00
if (currentWidth < width)
{
bestLength = buffer.size();
bestWidth = currentWidth;
2020-10-15 23:12:41 +02:00
// Trim the ellipsis
buffer.resize(bestLength - 3);
}
else
2018-06-22 22:59:03 +02:00
{
2020-10-15 23:12:41 +02:00
// Width exceeded, rollback to best length and put ellipsis back
buffer.resize(bestLength);
2020-10-19 21:37:07 +02:00
for (auto i = static_cast<int32_t>(bestLength) - 1; i >= 0 && i >= static_cast<int32_t>(bestLength) - 3; i--)
2020-10-15 23:12:41 +02:00
{
2020-10-22 22:01:58 +02:00
buffer[i] = '.';
2020-10-15 23:12:41 +02:00
}
// Copy buffer back to input text buffer
std::strcpy(text, buffer.c_str());
return bestWidth;
2018-06-22 22:59:03 +02:00
}
2020-10-15 23:12:41 +02:00
char cb[8]{};
UTF8WriteCodepoint(cb, codepoint);
2020-10-15 23:12:41 +02:00
buffer.append(cb);
}
}
return GfxGetStringWidth(text, fontStyle);
2014-10-06 18:36:58 +02:00
}
/**
* Wrap the text in buffer to width, returns width of longest line.
2014-10-06 18:36:58 +02:00
*
* Inserts NULL where line should break (as \n is used for something else),
* so the number of lines is returned in num_lines. font_height seems to be
* a control character for line height.
2014-10-06 18:36:58 +02:00
*
* rct2: 0x006C21E2
* buffer (esi)
* width (edi) - in
* num_lines (edi) - out
* font_height (ebx) - out
*/
int32_t GfxWrapString(u8string_view text, int32_t width, FontStyle fontStyle, u8string* outWrappedText, int32_t* outNumLines)
2014-10-06 18:36:58 +02:00
{
2020-10-13 02:14:39 +02:00
constexpr size_t NULL_INDEX = std::numeric_limits<size_t>::max();
u8string buffer;
2020-10-13 02:14:39 +02:00
size_t currentLineIndex = 0;
size_t splitIndex = NULL_INDEX;
2020-10-22 22:01:58 +02:00
size_t bestSplitIndex = NULL_INDEX;
2020-10-13 02:14:39 +02:00
size_t numLines = 0;
int32_t maxWidth = 0;
FmtString fmt(text);
2020-10-13 02:14:39 +02:00
for (const auto& token : fmt)
2018-06-22 22:59:03 +02:00
{
2020-10-13 02:14:39 +02:00
if (token.IsLiteral())
2018-06-22 22:59:03 +02:00
{
2020-10-13 02:14:39 +02:00
CodepointView codepoints(token.text);
for (auto codepoint : codepoints)
{
2020-10-15 23:12:41 +02:00
char cb[8]{};
UTF8WriteCodepoint(cb, codepoint);
2020-10-15 23:12:41 +02:00
buffer.append(cb);
auto lineWidth = GfxGetStringWidth(&buffer[currentLineIndex], fontStyle);
2020-10-22 22:01:58 +02:00
if (lineWidth <= width || (splitIndex == NULL_INDEX && bestSplitIndex == NULL_INDEX))
2020-10-13 02:14:39 +02:00
{
if (codepoint == ' ')
{
// Mark line split here
splitIndex = buffer.size() - 1;
}
else if (splitIndex == NULL_INDEX)
{
// Mark line split here (this is after first character of line)
2020-10-22 22:01:58 +02:00
bestSplitIndex = buffer.size();
2020-10-13 02:14:39 +02:00
}
}
else
{
// Insert new line before current word
2020-10-22 22:01:58 +02:00
if (splitIndex == NULL_INDEX)
{
splitIndex = bestSplitIndex;
}
2020-10-13 02:14:39 +02:00
buffer.insert(buffer.begin() + splitIndex, '\0');
2020-10-13 02:14:39 +02:00
// Recalculate the line length after splitting
lineWidth = GfxGetStringWidth(&buffer[currentLineIndex], fontStyle);
2020-10-13 02:14:39 +02:00
maxWidth = std::max(maxWidth, lineWidth);
numLines++;
currentLineIndex = splitIndex + 1;
splitIndex = NULL_INDEX;
2020-10-22 22:01:58 +02:00
bestSplitIndex = NULL_INDEX;
2020-10-13 02:14:39 +02:00
// Trim the beginning of the new line
while (buffer[currentLineIndex] == ' ')
{
buffer.erase(buffer.begin() + currentLineIndex);
}
}
}
2018-06-22 22:59:03 +02:00
}
2020-10-16 01:13:52 +02:00
else if (token.kind == FormatToken::Newline)
2018-06-22 22:59:03 +02:00
{
2020-10-13 02:14:39 +02:00
buffer.push_back('\0');
auto lineWidth = GfxGetStringWidth(&buffer[currentLineIndex], fontStyle);
2018-01-05 19:21:57 +01:00
maxWidth = std::max(maxWidth, lineWidth);
2020-10-13 02:14:39 +02:00
numLines++;
2020-10-22 22:01:58 +02:00
currentLineIndex = buffer.size();
splitIndex = NULL_INDEX;
bestSplitIndex = NULL_INDEX;
2018-06-22 22:59:03 +02:00
}
else
{
2020-10-13 02:14:39 +02:00
buffer.append(token.text);
}
}
2020-10-13 02:14:39 +02:00
{
// Final line width calculation
auto lineWidth = GfxGetStringWidth(&buffer[currentLineIndex], fontStyle);
2020-10-13 02:14:39 +02:00
maxWidth = std::max(maxWidth, lineWidth);
}
if (outWrappedText != nullptr)
{
*outWrappedText = std::move(buffer);
}
if (outNumLines != nullptr)
{
*outNumLines = static_cast<int32_t>(numLines);
}
2020-10-13 02:14:39 +02:00
return maxWidth;
2014-10-06 18:36:58 +02:00
}
/**
* Draws text that is left aligned and vertically centred.
*/
2023-02-24 22:05:07 +01:00
void GfxDrawStringLeftCentred(DrawPixelInfo& dpi, StringId format, void* args, colour_t colour, const ScreenCoordsXY& coords)
{
char buffer[CommonTextBufferSize];
auto bufferPtr = buffer;
FormatStringLegacy(bufferPtr, sizeof(buffer), format, args);
int32_t height = StringGetHeightRaw(bufferPtr, FontStyle::Medium);
GfxDrawString(dpi, coords - ScreenCoordsXY{ 0, (height / 2) }, bufferPtr, { colour });
}
2014-10-06 18:36:58 +02:00
/**
* Changes the palette so that the next character changes colour
*/
static void ColourCharacter(uint8_t colour, const uint16_t* current_font_flags, uint8_t* palette_pointer)
2018-06-22 22:59:03 +02:00
{
int32_t colour32 = 0;
const G1Element* g1 = GfxGetG1Element(SPR_TEXT_PALETTE);
2018-01-07 21:43:07 +01:00
if (g1 != nullptr)
2017-10-26 14:14:37 +02:00
{
uint32_t idx = (colour & 0xFF) * 4;
std::memcpy(&colour32, &g1->offset[idx], sizeof(colour32));
2017-10-26 14:14:37 +02:00
}
2014-10-06 18:36:58 +02:00
2018-09-16 15:07:32 +02:00
if (!(*current_font_flags & TEXT_DRAW_FLAG_OUTLINE))
2017-10-26 14:14:37 +02:00
{
colour32 = colour32 & 0x0FF0000FF;
}
// Adjust text palette. Store current colour?
2017-10-26 14:14:37 +02:00
palette_pointer[1] = colour32 & 0xFF;
palette_pointer[2] = (colour32 >> 8) & 0xFF;
palette_pointer[3] = (colour32 >> 16) & 0xFF;
palette_pointer[4] = (colour32 >> 24) & 0xFF;
2014-10-06 18:36:58 +02:00
}
/**
* Changes the palette so that the next character changes colour
* This is specific to changing to a predefined window related colour
2014-10-06 18:36:58 +02:00
*/
static void ColourCharacterWindow(uint8_t colour, const uint16_t* current_font_flags, uint8_t* palette_pointer)
2018-06-22 22:59:03 +02:00
{
int32_t eax;
colour = NOT_TRANSLUCENT(colour);
eax = ColourMapA[colour].colour_11;
2018-06-22 22:59:03 +02:00
if (*current_font_flags & TEXT_DRAW_FLAG_OUTLINE)
{
eax |= 0x0A0A00;
}
2018-06-22 22:59:03 +02:00
// Adjust text palette. Store current colour?
palette_pointer[1] = eax & 0xFF;
palette_pointer[2] = (eax >> 8) & 0xFF;
palette_pointer[3] = (eax >> 16) & 0xFF;
palette_pointer[4] = (eax >> 24) & 0xFF;
2014-10-06 18:36:58 +02:00
}
/**
*
* rct2: 0x006C1DB7
2014-10-06 18:36:58 +02:00
*
* left : cx
* top : dx
* numLines : bp
* text : esi
* dpi : edi
*/
void DrawStringCentredRaw(
2023-02-24 22:05:07 +01:00
DrawPixelInfo& dpi, const ScreenCoordsXY& coords, int32_t numLines, const utf8* text, FontStyle fontStyle)
2014-10-06 18:36:58 +02:00
{
2023-02-24 22:05:07 +01:00
ScreenCoordsXY screenCoords(dpi.x, dpi.y);
GfxDrawString(dpi, screenCoords, "", { COLOUR_BLACK, fontStyle });
screenCoords = coords;
2015-06-09 16:42:25 +02:00
2018-06-22 22:59:03 +02:00
for (int32_t i = 0; i <= numLines; i++)
{
int32_t width = GfxGetStringWidth(text, fontStyle);
GfxDrawString(dpi, screenCoords - ScreenCoordsXY{ width / 2, 0 }, text, { TEXT_COLOUR_254, fontStyle });
2015-06-09 16:42:25 +02:00
2018-06-22 22:59:03 +02:00
const utf8* ch = text;
const utf8* nextCh = nullptr;
while ((UTF8GetNext(ch, &nextCh)) != 0)
2018-06-22 22:59:03 +02:00
{
ch = nextCh;
}
text = const_cast<char*>(ch + 1);
2015-06-09 16:42:25 +02:00
screenCoords.y += FontGetLineHeight(fontStyle);
}
}
int32_t StringGetHeightRaw(std::string_view text, FontStyle fontStyle)
{
int32_t height = 0;
if (fontStyle <= FontStyle::Medium)
height += 10;
else if (fontStyle == FontStyle::Tiny)
height += 6;
2021-02-03 00:48:46 +01:00
FmtString fmt(text);
2020-10-16 01:13:52 +02:00
for (const auto& token : fmt)
2018-06-22 22:59:03 +02:00
{
2020-10-16 01:13:52 +02:00
switch (token.kind)
2018-06-22 22:59:03 +02:00
{
2020-10-16 01:13:52 +02:00
case FormatToken::Newline:
if (fontStyle == FontStyle::Small || fontStyle == FontStyle::Medium)
2018-06-22 22:59:03 +02:00
{
height += 10;
break;
}
2021-09-15 22:22:15 +02:00
if (fontStyle == FontStyle::Tiny)
2018-06-22 22:59:03 +02:00
{
height += 6;
break;
}
height += 18;
break;
2020-10-16 01:13:52 +02:00
case FormatToken::NewlineSmall:
if (fontStyle == FontStyle::Small || fontStyle == FontStyle::Medium)
2018-06-22 22:59:03 +02:00
{
height += 5;
break;
}
2021-09-15 22:22:15 +02:00
if (fontStyle == FontStyle::Tiny)
2018-06-22 22:59:03 +02:00
{
height += 3;
break;
}
height += 9;
break;
2020-10-16 01:13:52 +02:00
case FormatToken::FontTiny:
fontStyle = FontStyle::Tiny;
break;
2020-10-16 01:13:52 +02:00
case FormatToken::FontMedium:
fontStyle = FontStyle::Medium;
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::FontSmall:
fontStyle = FontStyle::Small;
2018-06-22 22:59:03 +02:00
break;
2020-10-19 21:37:07 +02:00
default:
break;
}
}
return height;
2015-06-28 02:57:50 +02:00
}
/**
*
* rct2: 0x006C1F57
2015-06-28 02:57:50 +02:00
*
* colour : al
* format : bx
* x : cx
* y : dx
* text : esi
* dpi : edi
* width : bp
* ticks : ebp >> 16
*/
void DrawNewsTicker(
2023-02-24 22:05:07 +01:00
DrawPixelInfo& dpi, const ScreenCoordsXY& coords, int32_t width, colour_t colour, StringId format, u8string_view args,
2018-06-22 22:59:03 +02:00
int32_t ticks)
2015-06-28 02:57:50 +02:00
{
int32_t numLines, lineHeight, lineY;
2023-02-24 22:05:07 +01:00
ScreenCoordsXY screenCoords(dpi.x, dpi.y);
GfxDrawString(dpi, screenCoords, "", { colour });
u8string wrappedString;
GfxWrapString(FormatStringID(format, args), width, FontStyle::Small, &wrappedString, &numLines);
lineHeight = FontGetLineHeight(FontStyle::Small);
int32_t numCharactersDrawn = 0;
int32_t numCharactersToDraw = ticks;
const utf8* buffer = wrappedString.data();
lineY = coords.y - ((numLines * lineHeight) / 2);
2018-06-22 22:59:03 +02:00
for (int32_t line = 0; line <= numLines; line++)
{
int32_t halfWidth = GfxGetStringWidth(buffer, FontStyle::Small) / 2;
2020-10-16 01:13:52 +02:00
FmtString fmt(buffer);
for (const auto& token : fmt)
2018-06-22 22:59:03 +02:00
{
2020-10-16 01:13:52 +02:00
bool doubleBreak = false;
if (token.IsLiteral())
2018-06-22 22:59:03 +02:00
{
2020-10-16 01:13:52 +02:00
CodepointView codepoints(token.text);
for (auto it = codepoints.begin(); it != codepoints.end(); it++)
2018-06-22 22:59:03 +02:00
{
2020-10-16 01:13:52 +02:00
numCharactersDrawn++;
if (numCharactersDrawn > numCharactersToDraw)
{
auto ch = const_cast<char*>(&token.text[it.GetIndex()]);
*ch = '\0';
doubleBreak = true;
break;
}
}
}
2020-10-16 01:13:52 +02:00
if (doubleBreak)
break;
}
screenCoords = { coords.x - halfWidth, lineY };
GfxDrawString(dpi, screenCoords, buffer, { TEXT_COLOUR_254, FontStyle::Small });
2018-06-22 22:59:03 +02:00
if (numCharactersDrawn > numCharactersToDraw)
{
break;
}
buffer = GetStringEnd(buffer) + 1;
lineY += lineHeight;
}
2015-07-26 01:55:17 +02:00
}
2023-02-24 22:05:07 +01:00
static void TTFDrawCharacterSprite(DrawPixelInfo& dpi, int32_t codepoint, TextDrawInfo* info)
2015-07-27 02:09:24 +02:00
{
int32_t characterWidth = FontSpriteGetCodepointWidth(info->FontStyle, codepoint);
auto sprite = FontSpriteGetCodepointSprite(info->FontStyle, codepoint);
2015-07-27 02:09:24 +02:00
2018-06-22 22:59:03 +02:00
if (!(info->flags & TEXT_DRAW_FLAG_NO_DRAW))
{
2020-06-21 14:05:20 +02:00
auto screenCoords = ScreenCoordsXY{ info->x, info->y };
2018-06-22 22:59:03 +02:00
if (info->flags & TEXT_DRAW_FLAG_Y_OFFSET_EFFECT)
{
2020-06-21 14:05:20 +02:00
screenCoords.y += *info->y_offset++;
}
PaletteMap paletteMap(info->palette);
2023-02-24 22:05:07 +01:00
GfxDrawGlyph(&dpi, sprite, screenCoords, paletteMap);
}
2015-07-27 02:09:24 +02:00
info->x += characterWidth;
2015-07-27 02:09:24 +02:00
}
2023-02-24 22:05:07 +01:00
static void TTFDrawStringRawSprite(DrawPixelInfo& dpi, std::string_view text, TextDrawInfo* info)
2015-07-26 14:58:53 +02:00
{
2020-10-13 02:14:39 +02:00
CodepointView codepoints(text);
for (auto codepoint : codepoints)
2018-06-22 22:59:03 +02:00
{
TTFDrawCharacterSprite(dpi, codepoint, info);
2020-10-13 02:14:39 +02:00
}
2015-07-26 14:58:53 +02:00
}
2017-06-25 01:18:08 +02:00
#ifndef NO_TTF
static int _ttfGlId = 0;
2023-02-24 22:05:07 +01:00
static void TTFDrawStringRawTTF(DrawPixelInfo& dpi, std::string_view text, TextDrawInfo* info)
2015-07-26 01:55:17 +02:00
{
if (!TTFInitialise())
return;
TTFFontDescriptor* fontDesc = TTFGetFontFromSpriteBase(info->FontStyle);
2018-06-22 22:59:03 +02:00
if (fontDesc->font == nullptr)
{
TTFDrawStringRawSprite(dpi, text, info);
return;
}
2018-06-22 22:59:03 +02:00
if (info->flags & TEXT_DRAW_FLAG_NO_DRAW)
{
info->x += TTFGetWidthCacheGetOrAdd(fontDesc->font, text);
return;
2018-06-22 22:59:03 +02:00
}
2021-09-15 22:22:15 +02:00
uint8_t colour = info->palette[1];
TTFSurface* surface = TTFSurfaceCacheGetOrAdd(fontDesc->font, text);
2021-09-15 22:22:15 +02:00
if (surface == nullptr)
return;
int32_t drawX = info->x + fontDesc->offset_x;
int32_t drawY = info->y + fontDesc->offset_y;
int32_t width = surface->w;
int32_t height = surface->h;
bool use_hinting = gConfigFonts.EnableHinting && fontDesc->hinting_threshold > 0;
2021-09-15 22:22:15 +02:00
if (OpenRCT2::GetContext()->GetDrawingEngineType() == DrawingEngine::OpenGL)
{
auto baseId = uint32_t(0x7FFFF) - 1024;
auto imageId = baseId + _ttfGlId;
2023-02-24 22:05:07 +01:00
auto drawingEngine = dpi.DrawingEngine;
auto drawingContext = drawingEngine->GetDrawingContext();
2023-12-03 18:58:28 +01:00
uint8_t hint_thresh = use_hinting ? fontDesc->hinting_threshold : 0;
2021-09-15 22:22:15 +02:00
drawingEngine->InvalidateImage(imageId);
2023-12-03 18:58:28 +01:00
drawingContext->DrawTTFBitmap(
&dpi, info, imageId, surface->pixels, surface->pitch, surface->h, drawX, drawY, hint_thresh);
2021-09-15 22:22:15 +02:00
_ttfGlId++;
if (_ttfGlId >= 1023)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
_ttfGlId = 0;
}
2023-12-03 18:58:28 +01:00
info->x += width;
2021-09-15 22:22:15 +02:00
return;
}
2023-02-24 22:05:07 +01:00
int32_t overflowX = (dpi.x + dpi.width) - (drawX + width);
int32_t overflowY = (dpi.y + dpi.height) - (drawY + height);
2021-09-15 22:22:15 +02:00
if (overflowX < 0)
width += overflowX;
if (overflowY < 0)
height += overflowY;
2023-02-24 22:05:07 +01:00
int32_t skipX = drawX - dpi.x;
int32_t skipY = drawY - dpi.y;
2021-09-15 22:22:15 +02:00
info->x += width;
2021-09-15 22:22:15 +02:00
auto src = static_cast<const uint8_t*>(surface->pixels);
2023-02-24 22:05:07 +01:00
uint8_t* dst = dpi.bits;
2021-09-15 22:22:15 +02:00
if (skipX < 0)
{
width += skipX;
src += -skipX;
skipX = 0;
}
if (skipY < 0)
{
height += skipY;
src += (-skipY * surface->pitch);
skipY = 0;
}
dst += skipX;
2023-02-24 22:05:07 +01:00
dst += skipY * (dpi.width + dpi.pitch);
2021-09-15 22:22:15 +02:00
int32_t srcScanSkip = surface->pitch - width;
2023-02-24 22:05:07 +01:00
int32_t dstScanSkip = dpi.width + dpi.pitch - width;
2021-09-15 22:22:15 +02:00
uint8_t* dst_orig = dst;
const uint8_t* src_orig = src;
// Draw shadow/outline
if (info->flags & TEXT_DRAW_FLAG_OUTLINE)
{
for (int32_t yy = 0; yy < height - 0; yy++)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
for (int32_t xx = 0; xx < width - 0; xx++)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
if (*src != 0)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
// right
2023-02-24 22:05:07 +01:00
if (xx + skipX < dpi.width + dpi.pitch - 1)
2021-09-15 22:22:15 +02:00
{
*(dst + 1) = info->palette[3];
}
// left
if (xx + skipX > 1)
{
*(dst - 1) = info->palette[3];
}
// top
if (yy + skipY > 1)
{
*(dst - width - dstScanSkip) = info->palette[3];
}
// bottom
2023-02-24 22:05:07 +01:00
if (yy + skipY < dpi.height - 1)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
*(dst + width + dstScanSkip) = info->palette[3];
}
}
2021-09-15 22:22:15 +02:00
src++;
dst++;
}
2021-09-15 22:22:15 +02:00
// Skip any remaining bits
src += srcScanSkip;
dst += dstScanSkip;
}
2021-09-15 22:22:15 +02:00
}
dst = dst_orig;
src = src_orig;
for (int32_t yy = 0; yy < height; yy++)
{
for (int32_t xx = 0; xx < width; xx++)
{
2021-09-15 22:22:15 +02:00
if (*src != 0)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
if (info->flags & TEXT_DRAW_FLAG_INSET)
{
*(dst + width + dstScanSkip + 1) = info->palette[3];
}
if (*src > 180 || !use_hinting)
{
// Centre of the glyph: use full colour.
*dst = colour;
}
else if (use_hinting && *src > fontDesc->hinting_threshold)
2018-06-22 22:59:03 +02:00
{
2021-09-15 22:22:15 +02:00
// Simulate font hinting by shading the background colour instead.
if (info->flags & TEXT_DRAW_FLAG_OUTLINE)
{
*dst = BlendColours(colour, info->palette[3]);
2021-09-15 22:22:15 +02:00
}
else
2018-06-22 22:59:03 +02:00
{
2023-01-16 21:14:50 +01:00
*dst = BlendColours(colour, *dst);
}
}
}
2021-09-15 22:22:15 +02:00
src++;
dst++;
}
2021-09-15 22:22:15 +02:00
src += srcScanSkip;
dst += dstScanSkip;
}
2015-07-26 01:55:17 +02:00
}
2017-01-03 22:57:15 +01:00
#endif // NO_TTF
2015-07-26 01:55:17 +02:00
2023-02-24 22:05:07 +01:00
static void TTFProcessFormatCode(DrawPixelInfo& dpi, const FmtString::Token& token, TextDrawInfo* info)
2015-07-26 01:55:17 +02:00
{
2020-10-13 02:14:39 +02:00
switch (token.kind)
{
2020-10-16 01:13:52 +02:00
case FormatToken::Move:
2020-10-15 00:46:09 +02:00
info->x = info->startX + token.parameter;
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::Newline:
2018-06-22 22:59:03 +02:00
info->x = info->startX;
info->y += FontGetLineHeight(info->FontStyle);
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::NewlineSmall:
2018-06-22 22:59:03 +02:00
info->x = info->startX;
info->y += FontGetLineHeightSmall(info->FontStyle);
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::FontTiny:
info->FontStyle = FontStyle::Tiny;
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::FontSmall:
info->FontStyle = FontStyle::Small;
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::FontMedium:
info->FontStyle = FontStyle::Medium;
2018-06-22 22:59:03 +02:00
break;
2020-10-16 01:13:52 +02:00
case FormatToken::OutlineEnable:
2018-06-22 22:59:03 +02:00
info->flags |= TEXT_DRAW_FLAG_OUTLINE;
break;
2020-10-16 01:13:52 +02:00
case FormatToken::OutlineDisable:
2018-06-22 22:59:03 +02:00
info->flags &= ~TEXT_DRAW_FLAG_OUTLINE;
break;
2020-10-16 01:13:52 +02:00
case FormatToken::ColourWindow1:
{
2018-06-22 22:59:03 +02:00
uint16_t flags = info->flags;
ColourCharacterWindow(gCurrentWindowColours[0], &flags, info->palette);
2018-06-22 22:59:03 +02:00
break;
}
2020-10-16 01:13:52 +02:00
case FormatToken::ColourWindow2:
2018-06-22 22:59:03 +02:00
{
uint16_t flags = info->flags;
ColourCharacterWindow(gCurrentWindowColours[1], &flags, info->palette);
2018-06-22 22:59:03 +02:00
break;
}
2020-10-16 01:13:52 +02:00
case FormatToken::ColourWindow3:
2018-06-22 22:59:03 +02:00
{
uint16_t flags = info->flags;
ColourCharacterWindow(gCurrentWindowColours[2], &flags, info->palette);
2018-06-22 22:59:03 +02:00
break;
}
2020-10-16 01:13:52 +02:00
case FormatToken::InlineSprite:
2020-10-15 00:46:09 +02:00
{
2022-09-28 23:00:58 +02:00
auto imageId = ImageId::FromUInt32(token.parameter);
auto g1 = GfxGetG1Element(imageId.GetIndex());
if (g1 != nullptr && g1->width <= 32 && g1->height <= 32)
2020-10-15 00:46:09 +02:00
{
if (!(info->flags & TEXT_DRAW_FLAG_NO_DRAW))
{
2023-04-03 20:38:28 +02:00
GfxDrawSprite(dpi, imageId, { info->x, info->y });
2020-10-15 00:46:09 +02:00
}
info->x += g1->width;
}
2018-06-22 22:59:03 +02:00
break;
2020-10-15 00:46:09 +02:00
}
2018-06-22 22:59:03 +02:00
default:
2020-10-16 01:13:52 +02:00
if (FormatTokenIsColour(token.kind))
2018-06-22 22:59:03 +02:00
{
uint16_t flags = info->flags;
2020-10-16 01:13:52 +02:00
auto colourIndex = FormatTokenGetTextColourIndex(token.kind);
ColourCharacter(static_cast<uint8_t>(colourIndex), &flags, info->palette);
2018-06-22 22:59:03 +02:00
}
break;
}
2015-07-26 01:55:17 +02:00
}
#ifndef NO_TTF
2020-10-19 21:37:07 +02:00
static bool ShouldUseSpriteForCodepoint(char32_t codepoint)
{
switch (codepoint)
{
case UnicodeChar::up:
case UnicodeChar::down:
case UnicodeChar::leftguillemet:
case UnicodeChar::tick:
case UnicodeChar::cross:
case UnicodeChar::right:
case UnicodeChar::rightguillemet:
case UnicodeChar::small_up:
case UnicodeChar::small_down:
case UnicodeChar::left:
case UnicodeChar::quote_open:
case UnicodeChar::quote_close:
case UnicodeChar::german_quote_open:
case UnicodeChar::plus:
case UnicodeChar::minus:
case UnicodeChar::variation_selector:
case UnicodeChar::eye:
case UnicodeChar::road:
case UnicodeChar::railway:
2020-10-19 21:37:07 +02:00
return true;
default:
return false;
}
}
#endif // NO_TTF
2020-10-19 21:37:07 +02:00
2023-02-24 22:05:07 +01:00
static void TTFProcessStringLiteral(DrawPixelInfo& dpi, std::string_view text, TextDrawInfo* info)
2015-07-26 01:55:17 +02:00
{
2017-01-04 13:51:12 +01:00
#ifndef NO_TTF
bool isTTF = info->flags & TEXT_DRAW_FLAG_TTF;
2017-01-04 13:51:12 +01:00
#else
bool isTTF = false;
2017-01-04 13:51:12 +01:00
#endif // NO_TTF
2020-10-13 02:14:39 +02:00
if (!isTTF)
2018-06-22 22:59:03 +02:00
{
TTFDrawStringRawSprite(dpi, text, info);
2018-06-22 22:59:03 +02:00
}
2020-10-19 21:37:07 +02:00
#ifndef NO_TTF
2018-06-22 22:59:03 +02:00
else
{
2020-10-13 02:14:39 +02:00
CodepointView codepoints(text);
std::optional<size_t> ttfRunIndex{};
2020-10-13 02:14:39 +02:00
for (auto it = codepoints.begin(); it != codepoints.end(); it++)
{
auto codepoint = *it;
2020-10-19 21:37:07 +02:00
if (ShouldUseSpriteForCodepoint(codepoint))
2020-10-13 02:14:39 +02:00
{
2021-09-13 18:47:13 +02:00
if (ttfRunIndex.has_value())
2020-10-13 02:14:39 +02:00
{
// Draw the TTF run
// This error suppression abomination is here to suppress https://github.com/OpenRCT2/OpenRCT2/issues/17371.
// Additionally, we have to suppress the error for the error suppression... :'-(
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105937 is fixed in GCC13
# if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
# endif
2021-09-13 18:47:13 +02:00
auto len = it.GetIndex() - ttfRunIndex.value();
TTFDrawStringRawTTF(dpi, text.substr(ttfRunIndex.value(), len), info);
# if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop
# endif
2020-10-22 22:01:58 +02:00
ttfRunIndex = std::nullopt;
2020-10-13 02:14:39 +02:00
}
// Draw the sprite font glyph
TTFDrawCharacterSprite(dpi, codepoint, info);
2020-10-13 02:14:39 +02:00
}
2020-10-22 22:01:58 +02:00
else
{
2021-09-13 18:47:13 +02:00
if (!ttfRunIndex.has_value())
2020-10-22 22:01:58 +02:00
{
ttfRunIndex = it.GetIndex();
}
}
}
2021-09-13 18:47:13 +02:00
if (ttfRunIndex.has_value())
2020-10-22 22:01:58 +02:00
{
// Final TTF run
auto len = text.size() - *ttfRunIndex;
TTFDrawStringRawTTF(dpi, text.substr(ttfRunIndex.value(), len), info);
2020-10-13 02:14:39 +02:00
}
}
2020-10-19 21:37:07 +02:00
#endif // NO_TTF
2015-07-26 01:55:17 +02:00
}
2023-02-24 22:05:07 +01:00
static void TTFProcessStringCodepoint(DrawPixelInfo& dpi, codepoint_t codepoint, TextDrawInfo* info)
2020-11-21 19:16:56 +01:00
{
char buffer[8]{};
UTF8WriteCodepoint(buffer, codepoint);
TTFProcessStringLiteral(dpi, buffer, info);
2020-11-21 19:16:56 +01:00
}
2023-02-24 22:05:07 +01:00
static void TTFProcessString(DrawPixelInfo& dpi, std::string_view text, TextDrawInfo* info)
2015-07-26 01:55:17 +02:00
{
2020-11-22 01:36:40 +01:00
if (info->flags & TEXT_DRAW_FLAG_NO_FORMATTING)
2018-06-22 22:59:03 +02:00
{
TTFProcessStringLiteral(dpi, text, info);
2018-01-05 19:21:57 +01:00
info->maxX = std::max(info->maxX, info->x);
info->maxY = std::max(info->maxY, info->y);
}
2020-11-22 01:36:40 +01:00
else
{
FmtString fmt(text);
for (const auto& token : fmt)
{
if (token.IsLiteral())
{
TTFProcessStringLiteral(dpi, token.text, info);
2020-11-22 01:36:40 +01:00
}
else if (token.IsCodepoint())
{
auto codepoint = token.GetCodepoint();
TTFProcessStringCodepoint(dpi, codepoint, info);
2020-11-22 01:36:40 +01:00
}
else
{
TTFProcessFormatCode(dpi, token, info);
2020-11-22 01:36:40 +01:00
}
info->maxX = std::max(info->maxX, info->x);
info->maxY = std::max(info->maxY, info->y);
}
}
2015-07-26 14:58:53 +02:00
}
static void TTFProcessInitialColour(int32_t colour, TextDrawInfo* info)
2015-07-26 14:58:53 +02:00
{
2018-06-22 22:59:03 +02:00
if (colour != TEXT_COLOUR_254 && colour != TEXT_COLOUR_255)
{
info->flags &= ~(TEXT_DRAW_FLAG_INSET | TEXT_DRAW_FLAG_OUTLINE);
2018-06-22 22:59:03 +02:00
if (colour & COLOUR_FLAG_OUTLINE)
{
info->flags |= TEXT_DRAW_FLAG_OUTLINE;
}
colour &= ~COLOUR_FLAG_OUTLINE;
2018-06-22 22:59:03 +02:00
if (!(colour & COLOUR_FLAG_INSET))
{
if (!(info->flags & TEXT_DRAW_FLAG_INSET))
{
uint16_t flags = info->flags;
ColourCharacterWindow(colour, &flags, reinterpret_cast<uint8_t*>(&info->palette));
}
2018-06-22 22:59:03 +02:00
}
else
{
info->flags |= TEXT_DRAW_FLAG_INSET;
colour &= ~COLOUR_FLAG_INSET;
uint32_t eax;
2018-06-22 22:59:03 +02:00
if (info->flags & TEXT_DRAW_FLAG_DARK)
{
if (info->flags & TEXT_DRAW_FLAG_EXTRA_DARK)
{
eax = ColourMapA[colour].mid_light;
eax = eax << 16;
eax = eax | ColourMapA[colour].dark;
2018-06-22 22:59:03 +02:00
}
else
{
eax = ColourMapA[colour].light;
eax = eax << 16;
eax = eax | ColourMapA[colour].mid_dark;
}
2018-06-22 22:59:03 +02:00
}
else
{
eax = ColourMapA[colour].lighter;
eax = eax << 16;
eax = eax | ColourMapA[colour].mid_light;
}
// Adjust text palette. Store current colour? ;
info->palette[1] = eax & 0xFF;
info->palette[2] = (eax >> 8) & 0xFF;
info->palette[3] = (eax >> 16) & 0xFF;
info->palette[4] = (eax >> 24) & 0xFF;
}
}
2015-07-26 14:58:53 +02:00
}
void TTFDrawString(
2023-02-24 22:05:07 +01:00
DrawPixelInfo& dpi, const_utf8string text, int32_t colour, const ScreenCoordsXY& coords, bool noFormatting,
FontStyle fontStyle, TextDarkness darkness)
2015-07-26 14:58:53 +02:00
{
2018-06-22 22:59:03 +02:00
if (text == nullptr)
return;
TextDrawInfo info;
info.FontStyle = fontStyle;
info.flags = 0;
info.startX = coords.x;
info.startY = coords.y;
info.x = coords.x;
info.y = coords.y;
2015-07-26 14:58:53 +02:00
2018-06-22 22:59:03 +02:00
if (LocalisationService_UseTrueTypeFont())
{
info.flags |= TEXT_DRAW_FLAG_TTF;
}
2015-07-26 14:58:53 +02:00
2020-11-22 01:36:40 +01:00
if (noFormatting)
{
info.flags |= TEXT_DRAW_FLAG_NO_FORMATTING;
}
if (darkness == TextDarkness::Dark)
{
info.flags |= TEXT_DRAW_FLAG_DARK;
}
else if (darkness == TextDarkness::ExtraDark)
{
info.flags |= (TEXT_DRAW_FLAG_DARK | TEXT_DRAW_FLAG_EXTRA_DARK);
}
std::memcpy(info.palette, gTextPalette, sizeof(info.palette));
TTFProcessInitialColour(colour, &info);
TTFProcessString(dpi, text, &info);
std::memcpy(gTextPalette, info.palette, sizeof(info.palette));
2015-07-26 01:55:17 +02:00
2023-02-24 22:05:07 +01:00
dpi.lastStringPos = { info.x, info.y };
2015-07-26 01:55:17 +02:00
}
static int32_t TTFGetStringWidth(std::string_view text, FontStyle fontStyle, bool noFormatting)
2015-07-26 01:55:17 +02:00
{
TextDrawInfo info;
info.FontStyle = fontStyle;
info.flags = 0;
info.startX = 0;
info.startY = 0;
info.x = 0;
info.y = 0;
info.maxX = 0;
info.maxY = 0;
info.flags |= TEXT_DRAW_FLAG_NO_DRAW;
2018-06-22 22:59:03 +02:00
if (LocalisationService_UseTrueTypeFont())
{
info.flags |= TEXT_DRAW_FLAG_TTF;
}
2015-07-26 01:55:17 +02:00
2020-11-22 01:36:40 +01:00
if (noFormatting)
{
info.flags |= TEXT_DRAW_FLAG_NO_FORMATTING;
}
2023-02-24 22:05:07 +01:00
DrawPixelInfo dummy{};
TTFProcessString(dummy, text, &info);
2015-07-26 01:55:17 +02:00
return info.maxX;
2015-07-26 01:55:17 +02:00
}
2015-08-03 19:06:54 +02:00
/**
*
* rct2: 0x00682F28
*/
void GfxDrawStringWithYOffsets(
2023-02-24 22:05:07 +01:00
DrawPixelInfo& dpi, const utf8* text, int32_t colour, const ScreenCoordsXY& coords, const int8_t* yOffsets,
bool forceSpriteFont, FontStyle fontStyle)
2015-08-03 19:06:54 +02:00
{
TextDrawInfo info;
info.FontStyle = fontStyle;
info.flags = 0;
info.startX = coords.x;
info.startY = coords.y;
info.x = coords.x;
info.y = coords.y;
info.y_offset = yOffsets;
2015-08-03 19:06:54 +02:00
info.flags |= TEXT_DRAW_FLAG_Y_OFFSET_EFFECT;
2015-08-03 19:06:54 +02:00
2018-06-22 22:59:03 +02:00
if (!forceSpriteFont && LocalisationService_UseTrueTypeFont())
{
info.flags |= TEXT_DRAW_FLAG_TTF;
}
2015-08-03 19:06:54 +02:00
std::memcpy(info.palette, gTextPalette, sizeof(info.palette));
TTFProcessInitialColour(colour, &info);
TTFProcessString(dpi, text, &info);
std::memcpy(gTextPalette, info.palette, sizeof(info.palette));
2015-08-03 19:06:54 +02:00
2023-02-24 22:05:07 +01:00
dpi.lastStringPos = { info.x, info.y };
2015-08-03 19:06:54 +02:00
}
2016-01-02 23:41:35 +01:00
2023-08-10 13:39:38 +02:00
u8string ShortenPath(const u8string& path, int32_t availableWidth, FontStyle fontStyle)
2016-01-02 23:41:35 +01:00
{
2023-08-10 13:39:38 +02:00
if (GfxGetStringWidth(path, fontStyle) <= availableWidth)
2018-06-22 22:59:03 +02:00
{
2023-08-10 13:39:38 +02:00
return path;
}
2023-08-10 13:39:38 +02:00
u8string shortenedPath = u8"...";
2023-08-10 13:39:38 +02:00
size_t begin = 0;
while (begin < path.size())
2018-06-22 22:59:03 +02:00
{
2023-08-10 13:39:38 +02:00
begin = path.find_first_of(*PATH_SEPARATOR, begin + 1);
if (begin == path.npos)
break;
2023-08-10 13:39:38 +02:00
shortenedPath = u8"..." + path.substr(begin);
if (GfxGetStringWidth(shortenedPath, fontStyle) <= availableWidth)
2018-06-22 22:59:03 +02:00
{
2023-08-10 13:39:38 +02:00
return shortenedPath;
}
}
2023-08-10 13:39:38 +02:00
return shortenedPath;
2016-01-02 23:41:35 +01:00
}