#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include "../addresses.h" #include "../interface/colour.h" #include "../interface/window.h" #include "../localisation/localisation.h" #include "../platform/platform.h" #include "../sprites.h" #include "../util/util.h" #include "../world/map.h" #include "drawing.h" static int ttf_get_string_width(const utf8 *text); static void ttf_draw_string(rct_drawpixelinfo *dpi, char *buffer, int colour, int x, int y); static bool _ttfInitialised = false; #define TTF_SURFACE_CACHE_SIZE 256 #define TTF_GETWIDTH_CACHE_SIZE 1024 typedef struct ttf_cache_entry { SDL_Surface *surface; TTF_Font *font; utf8 *text; uint32 lastUseTick; } ttf_cache_entry; static ttf_cache_entry _ttfSurfaceCache[TTF_SURFACE_CACHE_SIZE] = { 0 }; static int _ttfSurfaceCacheCount = 0; static int _ttfSurfaceCacheHitCount = 0; static int _ttfSurfaceCacheMissCount = 0; typedef struct ttf_getwidth_cache_entry { uint32 width; TTF_Font *font; utf8 *text; uint32 lastUseTick; } ttf_getwidth_cache_entry; static ttf_getwidth_cache_entry _ttfGetWidthCache[TTF_GETWIDTH_CACHE_SIZE] = { 0 }; static int _ttfGetWidthCacheCount = 0; static int _ttfGetWidthCacheHitCount = 0; static int _ttfGetWidthCacheMissCount = 0; /** * * rct2: 0x006C23B1 */ int gfx_get_string_width_new_lined(utf8 *text) { utf8 *ch = text; utf8 *firstCh = text; utf8 *nextCh; utf8 backup; int codepoint; int maxWidth = 0; while ((codepoint = utf8_get_next(ch, (const utf8**)&nextCh)) != 0) { if (codepoint == FORMAT_NEWLINE || codepoint == FORMAT_NEWLINE_SMALLER) { backup = *nextCh; *nextCh = 0; maxWidth = max(maxWidth, gfx_get_string_width(firstCh)); *nextCh = backup; firstCh = nextCh; } ch = nextCh; } maxWidth = max(maxWidth, gfx_get_string_width(firstCh)); return maxWidth; } /** * Return the width of the string in buffer * * rct2: 0x006C2321 * buffer (esi) */ int gfx_get_string_width(char* buffer) { return ttf_get_string_width(buffer); } /** * Clip the text in buffer to width, add ellipsis and return the new width of the clipped string * * rct2: 0x006C2460 * buffer (esi) * width (edi) */ int gfx_clip_string(utf8 *text, int width) { int clippedWidth; if (width < 6) { *text = 0; return 0; } clippedWidth = gfx_get_string_width(text); if (clippedWidth <= width) { return clippedWidth; } utf8 backup[4]; utf8 *ch = text; utf8 *nextCh = text; utf8 *clipCh = text; int codepoint; while ((codepoint = utf8_get_next(ch, (const utf8**)&nextCh)) != 0) { if (utf8_is_format_code(codepoint)) { ch = nextCh; ch += utf8_get_format_code_arg_length(codepoint); continue; } for (int i = 0; i < 4; i++) { backup[i] = nextCh[i]; }; for (int i = 0; i < 3; i++) { nextCh[i] = '.'; } nextCh[3] = 0; int queryWidth = gfx_get_string_width(text); if (queryWidth < width) { clipCh = nextCh; clippedWidth = queryWidth; } else { for (int i = 0; i < 3; i++) { clipCh[i] = '.'; } clipCh[3] = 0; return clippedWidth; } for (int i = 0; i < 4; i++) { nextCh[i] = backup[i]; }; ch = nextCh; } return gfx_get_string_width(text); } /** * Wrap the text in buffer to width, returns width of longest line. * * 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. * * rct2: 0x006C21E2 * buffer (esi) * width (edi) - in * num_lines (edi) - out * font_height (ebx) - out */ int gfx_wrap_string(utf8 *text, int width, int *outNumLines, int *outFontHeight) { int lineWidth = 0; int maxWidth = 0; *outNumLines = 0; // Pointer to the start of the current word utf8 *currentWord = NULL; // Width of line up to current word int currentWidth; utf8 *ch = text; utf8 *firstCh = text; utf8 *nextCh; int codepoint; int numCharactersOnLine = 0; while ((codepoint = utf8_get_next(ch, (const utf8**)&nextCh)) != 0) { if (codepoint == ' ') { currentWord = ch; currentWidth = lineWidth; numCharactersOnLine++; } else if (codepoint == FORMAT_NEWLINE) { *ch++ = 0; maxWidth = max(maxWidth, lineWidth); (*outNumLines)++; lineWidth = 0; currentWord = NULL; firstCh = ch; numCharactersOnLine = 0; continue; } else if (utf8_is_format_code(codepoint)) { ch = nextCh; ch += utf8_get_format_code_arg_length(codepoint); continue; } uint8 saveCh = *nextCh; *nextCh = 0; lineWidth = gfx_get_string_width(firstCh); *nextCh = saveCh; if (lineWidth <= width || numCharactersOnLine == 0) { ch = nextCh; numCharactersOnLine++; } else if (currentWord == NULL) { // Single word is longer than line, insert null terminator ch += utf8_insert_codepoint(ch, 0); maxWidth = max(maxWidth, lineWidth); (*outNumLines)++; lineWidth = 0; currentWord = NULL; firstCh = ch; numCharactersOnLine = 0; } else { ch = currentWord; *ch++ = 0; maxWidth = max(maxWidth, currentWidth); (*outNumLines)++; lineWidth = 0; currentWord = NULL; firstCh = ch; numCharactersOnLine = 0; } } maxWidth = max(maxWidth, lineWidth); *outFontHeight = gCurrentFontSpriteBase; return maxWidth == 0 ? lineWidth : maxWidth; } /** * Draws i formatted text string left aligned at i specified position but clips * the text with an elipsis if the text width exceeds the specified width. * rct2: 0x006C1B83 * dpi (edi) * format (bx) * args (esi) * colour (al) * x (cx) * y (dx) * width (bp) */ void gfx_draw_string_left_clipped(rct_drawpixelinfo* dpi, rct_string_id format, void* args, int colour, int x, int y, int width) { char* buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; // Clip text - return value is not needed gfx_clip_string(buffer, width); gfx_draw_string(dpi, buffer, colour, x, y); } /** * Draws i formatted text string centred at i specified position but clips the * text with an elipsis if the text width exceeds the specified width. * rct2: 0x006C1BBA * dpi (edi) * format (bx) * args (esi) * colour (al) * x (cx) * y (dx) * width (bp) */ void gfx_draw_string_centred_clipped(rct_drawpixelinfo *dpi, rct_string_id format, void *args, int colour, int x, int y, int width) { char* buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; // Clip text int text_width = gfx_clip_string(buffer, width); // Draw the text centred if (text_width <= 0xFFFF && text_width >= 0) { x -= (text_width - 1) / 2; gfx_draw_string(dpi, buffer, colour, x, y); } else { log_warning("improper text width %d for string %s", text_width, buffer); } } /** * Draws i formatted text string right aligned. * rct2: 0x006C1BFC * dpi (edi) * format (bx) * args (esi) * colour (al) * x (cx) * y (dx) */ void gfx_draw_string_right(rct_drawpixelinfo* dpi, rct_string_id format, void* args, int colour, int x, int y) { char* buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); // Measure text width short text_width = gfx_get_string_width(buffer); // Draw the text right aligned x -= text_width; gfx_draw_string(dpi, buffer, colour, x, y); } /** * Draws i formatted text string centred at i specified position. * rct2: 0x006C1D6C * dpi (edi) * format (bx) * x (cx) * y (dx) * colour (al) * args (esi) */ void gfx_draw_string_centred(rct_drawpixelinfo *dpi, rct_string_id format, int x, int y, int colour, void *args) { gfx_draw_string_centred_wrapped(dpi, args, x, y, INT32_MAX, format, colour); } /** * * rct2: 0x006C1E53 * dpi (edi) * args (esi) * x (cx) * y (dx) * width (bp) * colour (al) * format (ebx) */ int gfx_draw_string_centred_wrapped(rct_drawpixelinfo *dpi, void *args, int x, int y, int width, rct_string_id format, int colour) { int font_height, line_height, line_width, line_y, num_lines; char* buffer = RCT2_ADDRESS(0x009C383D, char); if (gCurrentFontSpriteBase >= 0) { gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; } gfx_draw_string(dpi, buffer, colour, dpi->x, dpi->y); buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; // line_width unused here line_width = gfx_wrap_string(buffer, width, &num_lines, &font_height); line_height = font_get_line_height(font_height); if (*buffer == FORMAT_OUTLINE) { line_height = line_height + 1; } font_height = (line_height / 2) * num_lines; line_y = y - font_height; gCurrentFontFlags = 0; for (int line = 0; line <= num_lines; ++line) { int half_width = gfx_get_string_width(buffer) / 2; gfx_draw_string(dpi, buffer, 0xFE, x - half_width, line_y); buffer = get_string_end(buffer) + 1; line_y += line_height; } return line_y - y; } /** * * rct2: 0x006C2105 * dpi (edi) * args (esi) * x (cx) * y (dx) * width (bp) * format (bx) * colour (al) */ int gfx_draw_string_left_wrapped(rct_drawpixelinfo *dpi, void *args, int x, int y, int width, rct_string_id format, int colour) { // font height might actually be something else int fontSpriteBase, lineHeight, lineY, numLines; gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; char* buffer = RCT2_ADDRESS(0x009C383D, char); gfx_draw_string(dpi, buffer, colour, dpi->x, dpi->y); buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; gfx_wrap_string(buffer, width, &numLines, &fontSpriteBase); lineHeight = font_get_line_height(fontSpriteBase); gCurrentFontFlags = 0; lineY = y; for (int line = 0; line <= numLines; ++line) { gfx_draw_string(dpi, buffer, 0xFE, x, lineY); buffer = get_string_end(buffer) + 1; lineY += lineHeight; } return lineY - y; } /** * Draws i formatted text string. * rct2: 0x006C1B2F * dpi (edi) * format (bx) * args (esi) * colour (al) * x (cx) * y (dx) */ void gfx_draw_string_left(rct_drawpixelinfo *dpi, rct_string_id format, void *args, int colour, int x, int y) { char* buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; gfx_draw_string(dpi, buffer, colour, x, y); } /** * Draws text that is left aligned and vertically centred. */ void gfx_draw_string_left_centred(rct_drawpixelinfo *dpi, rct_string_id format, void *args, int colour, int x, int y) { gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; char *buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, char); format_string(buffer, format, args); int height = string_get_height_raw(buffer); gfx_draw_string(dpi, buffer, colour, x, y - (height / 2)); } /** * Changes the palette so that the next character changes colour */ static void colour_char(uint8 colour, uint16* current_font_flags, uint8* palette_pointer) { int eax; rct_g1_element g1_element = g1Elements[SPR_TEXT_PALETTE]; eax = ((uint32*)g1_element.offset)[colour & 0xFF]; if (!(*current_font_flags & 2)) { eax = eax & 0x0FF0000FF; } // 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; unk_9ABDA4 = palette_pointer; } /** * Changes the palette so that the next character changes colour * This is specific to changing to a predefined window related colour */ static void colour_char_window(uint8 colour, uint16* current_font_flags,uint8* palette_pointer) { int eax; eax = ColourMapB[colour].b; if (*current_font_flags & 2) { eax |= 0x0A0A00; } //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; unk_9ABDA4 = palette_pointer; } /** * * rct2: 0x00682702 * dpi (edi) * buffer (esi) * colour (al) * x (cx) * y (dx) */ void gfx_draw_string(rct_drawpixelinfo *dpi, char *buffer, int colour, int x, int y) { ttf_draw_string(dpi, buffer, colour, x, y); } void draw_string_left_underline(rct_drawpixelinfo *dpi, rct_string_id format, void *args, int colour, int x, int y) { char buffer[128]; int width; format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; width = gfx_get_string_width(buffer); gfx_draw_string(dpi, buffer, colour, x, y); gfx_fill_rect(dpi, x, y + 11, x + width, y + 11, text_palette[1]); if (text_palette[2] != 0) gfx_fill_rect(dpi, x + 1, y + 12, x + width + 1, y + 12, text_palette[2]); } void draw_string_right_underline(rct_drawpixelinfo *dpi, rct_string_id format, void *args, int colour, int x, int y) { char buffer[128]; int width; format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; width = gfx_get_string_width(buffer); x -= width; gfx_draw_string(dpi, buffer, colour, x, y); gfx_fill_rect(dpi, x, y + 11, x + width, y + 11, text_palette[1]); if (text_palette[2] != 0) gfx_fill_rect(dpi, x + 1, y + 12, x + width + 1, y + 12, text_palette[2]); } void draw_string_centred_underline(rct_drawpixelinfo *dpi, rct_string_id format, void *args, int colour, int x, int y) { char buffer[128]; int width; format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; width = gfx_get_string_width(buffer); x -= width / 2; gfx_draw_string(dpi, buffer, colour, x, y); gfx_fill_rect(dpi, x, y + 11, x + width, y + 11, text_palette[1]); if (text_palette[2] != 0) gfx_fill_rect(dpi, x + 1, y + 12, x + width + 1, y + 12, text_palette[2]); } /** * * rct2: 0x006C1DB7 * * left : cx * top : dx * numLines : bp * text : esi * dpi : edi */ void draw_string_centred_raw(rct_drawpixelinfo *dpi, int x, int y, int numLines, char *text) { gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; gfx_draw_string(dpi, RCT2_ADDRESS(0x009C383D, char), 0, dpi->x, dpi->y); gCurrentFontFlags = 0; for (int i = 0; i <= numLines; i++) { int width = gfx_get_string_width(text); gfx_draw_string(dpi, text, 254, x - (width / 2), y); const utf8 *ch = text; const utf8 *nextCh = 0; int codepoint = 0; while ((codepoint = utf8_get_next(ch, (const utf8**)&nextCh)) != 0) { ch = nextCh; } text = (char*)(ch + 1); y += font_get_line_height(gCurrentFontSpriteBase); } } int string_get_height_raw(char *buffer) { uint16 fontBase = gCurrentFontSpriteBase; int height = 0; if (fontBase <= FONT_SPRITE_BASE_MEDIUM) height += 10; else if (fontBase == FONT_SPRITE_BASE_TINY) height += 6; char *ch = buffer; while (*ch != 0) { char c = *ch++; switch (c) { case FORMAT_NEWLINE: if (fontBase <= 224) { height += 10; break; } else if (fontBase == 448) { height += 6; break; } height += 18; break; case FORMAT_NEWLINE_SMALLER: if (fontBase <= 224) { height += 5; break; } else if (fontBase == 448) { height += 3; break; } height += 9; break; case FORMAT_TINYFONT: fontBase = 448; break; case FORMAT_BIGFONT: fontBase = 672; break; case FORMAT_MEDIUMFONT: fontBase = 224; break; case FORMAT_SMALLFONT: fontBase = 0; break; default: if (c >= 32) continue; if (c <= 4) { ch++; continue; } if (c <= 16) continue; ch += 2; if (c <= 22) continue; ch += 2; break; } } return height; } /** * * rct2: 0x006C1F57 * * colour : al * format : bx * x : cx * y : dx * text : esi * dpi : edi * width : bp * ticks : ebp >> 16 */ void gfx_draw_string_centred_wrapped_partial(rct_drawpixelinfo *dpi, int x, int y, int width, int colour, rct_string_id format, void *args, int ticks) { int numLines, fontSpriteBase, lineHeight, lineY; utf8 *buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, utf8); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; gfx_draw_string(dpi, RCT2_ADDRESS(0x009C383D, char), colour, dpi->x, dpi->y); format_string(buffer, format, args); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; gfx_wrap_string(buffer, width, &numLines, &fontSpriteBase); lineHeight = font_get_line_height(fontSpriteBase); int numCharactersDrawn = 0; int numCharactersToDraw = ticks; gCurrentFontFlags = 0; lineY = y - ((numLines * lineHeight) / 2); for (int line = 0; line <= numLines; line++) { int halfWidth = gfx_get_string_width(buffer) / 2; utf8 *ch = buffer; utf8 *nextCh; int codepoint; while ((codepoint = utf8_get_next(ch, (const utf8**)&nextCh)) != 0) { if (!utf8_is_format_code(codepoint)) { numCharactersDrawn++; if (numCharactersDrawn > numCharactersToDraw) { *ch = 0; break; } } ch = nextCh; } gfx_draw_string(dpi, buffer, 0xFE, x - halfWidth, lineY); if (numCharactersDrawn > numCharactersToDraw) { break; } buffer = get_string_end(buffer) + 1; lineY += lineHeight; } } static uint32 _ttf_surface_cache_hash(TTF_Font *font, const utf8 *text) { uint32 hash = ((uint32)font * 23) ^ 0xAAAAAAAA; for (const utf8 *ch = text; *ch != 0; ch++) { hash = ror32(hash, 3) ^ (*ch * 13); } return hash; } static void _ttf_surface_cache_dispose(ttf_cache_entry *entry) { if (entry->surface != NULL) { SDL_FreeSurface(entry->surface); free(entry->text); entry->surface = NULL; entry->font = NULL; entry->text = NULL; } } static void _ttf_surface_cache_dispose_all() { for (int i = 0; i < TTF_SURFACE_CACHE_SIZE; i++) { _ttf_surface_cache_dispose(&_ttfSurfaceCache[i]); _ttfSurfaceCacheCount--; } } SDL_Surface *ttf_surface_cache_get_or_add(TTF_Font *font, const utf8 *text) { ttf_cache_entry *entry; uint32 hash = _ttf_surface_cache_hash(font, text); int index = hash % TTF_SURFACE_CACHE_SIZE; for (int i = 0; i < TTF_SURFACE_CACHE_SIZE; i++) { entry = &_ttfSurfaceCache[index]; // Check if entry is a hit if (entry->surface == NULL) break; if (entry->font == font && strcmp(entry->text, text) == 0) { _ttfSurfaceCacheHitCount++; entry->lastUseTick = gCurrentDrawCount; return entry->surface; } // If entry hasn't been used for a while, replace it if (entry->lastUseTick < gCurrentDrawCount - 64) { break; } // Check if next entry is a hit if (++index >= TTF_SURFACE_CACHE_SIZE) index = 0; } // Cache miss, replace entry with new surface entry = &_ttfSurfaceCache[index]; _ttf_surface_cache_dispose(entry); SDL_Color c = { 0, 0, 0, 255 }; SDL_Surface *surface = TTF_RenderUTF8_Solid(font, text, c); if (surface == NULL) { return NULL; } _ttfSurfaceCacheMissCount++; // printf("CACHE HITS: %d MISSES: %d)\n", _ttfSurfaceCacheHitCount, _ttfSurfaceCacheMissCount); _ttfSurfaceCacheCount++; entry->surface = surface; entry->font = font; entry->text = _strdup(text); entry->lastUseTick = gCurrentDrawCount; return entry->surface; } static void _ttf_getwidth_cache_dispose(ttf_getwidth_cache_entry *entry) { if (entry->text != NULL) { free(entry->text); entry->width = 0; entry->font = NULL; entry->text = NULL; } } static void _ttf_getwidth_cache_dispose_all() { for (int i = 0; i < TTF_GETWIDTH_CACHE_SIZE; i++) { _ttf_getwidth_cache_dispose(&_ttfGetWidthCache[i]); _ttfGetWidthCacheCount--; } } static uint32 _ttf_getwidth_cache_get_or_add(TTF_Font *font, const utf8 *text) { ttf_getwidth_cache_entry *entry; uint32 hash = _ttf_surface_cache_hash(font, text); int index = hash % TTF_GETWIDTH_CACHE_SIZE; for (int i = 0; i < TTF_GETWIDTH_CACHE_SIZE; i++) { entry = &_ttfGetWidthCache[index]; // Check if entry is a hit if (entry->text == NULL) break; if (entry->font == font && strcmp(entry->text, text) == 0) { _ttfGetWidthCacheHitCount++; entry->lastUseTick = gCurrentDrawCount; return entry->width; } // If entry hasn't been used for a while, replace it if (entry->lastUseTick < gCurrentDrawCount - 64) { break; } // Check if next entry is a hit if (++index >= TTF_GETWIDTH_CACHE_SIZE) index = 0; } // Cache miss, replace entry with new width entry = &_ttfGetWidthCache[index]; _ttf_getwidth_cache_dispose(entry); int width, height; TTF_SizeUTF8(font, text, &width, &height); _ttfGetWidthCacheMissCount++; _ttfGetWidthCacheCount++; entry->width = width; entry->font = font; entry->text = _strdup(text); entry->lastUseTick = gCurrentDrawCount; return entry->width; } bool ttf_initialise() { if (!_ttfInitialised) { if (TTF_Init() != 0) { return false; } for (int i = 0; i < 4; i++) { TTFFontDescriptor *fontDesc = &(gCurrentTTFFontSet->size[i]); utf8 fontPath[MAX_PATH]; if (!platform_get_font_path(fontDesc, fontPath)) { log_error("Unable to load font '%s'", fontDesc->font_name); return false; } fontDesc->font = TTF_OpenFont(fontPath, fontDesc->ptSize); if (fontDesc->font == NULL) { log_error("Unable to load '%s'", fontPath); return false; } } _ttfInitialised = true; } return true; } void ttf_dispose() { if (!_ttfInitialised) return; _ttf_surface_cache_dispose_all(); _ttf_getwidth_cache_dispose_all(); for (int i = 0; i < 4; i++) { TTFFontDescriptor *fontDesc = &(gCurrentTTFFontSet->size[i]); if (fontDesc->font != NULL) { TTF_CloseFont(fontDesc->font); fontDesc->font = NULL; } } TTF_Quit(); _ttfInitialised = false; } TTFFontDescriptor *ttf_get_font_from_sprite_base(uint16 spriteBase) { return &gCurrentTTFFontSet->size[font_get_size_from_sprite_base(spriteBase)]; } enum { TEXT_DRAW_FLAG_INSET = 1 << 0, TEXT_DRAW_FLAG_OUTLINE = 1 << 1, TEXT_DRAW_FLAG_Y_OFFSET_EFFECT = 1 << 29, TEXT_DRAW_FLAG_TTF = 1 << 30, TEXT_DRAW_FLAG_NO_DRAW = 1 << 31 }; typedef struct text_draw_info { int startX; int startY; int x; int y; int maxX; int maxY; int flags; uint8 palette[8]; uint16 font_sprite_base; const sint8 *y_offset; } text_draw_info; static void ttf_draw_character_sprite(rct_drawpixelinfo *dpi, int codepoint, text_draw_info *info) { int characterWidth = font_sprite_get_codepoint_width(info->font_sprite_base, codepoint); int sprite = font_sprite_get_codepoint_sprite(info->font_sprite_base, codepoint); if (!(info->flags & TEXT_DRAW_FLAG_NO_DRAW)) { unk_9ABDA4 = &info->palette; RCT2_GLOBAL(0x00EDF81C, uint32) = (IMAGE_TYPE_USE_PALETTE << 28); int x = info->x; int y = info->y; if (info->flags & TEXT_DRAW_FLAG_Y_OFFSET_EFFECT) { y += *info->y_offset++; } gfx_draw_glpyh(dpi, sprite, x, y, info->palette); } info->x += characterWidth; } static void ttf_draw_string_raw_sprite(rct_drawpixelinfo *dpi, const utf8 *text, text_draw_info *info) { const utf8 *ch = text; int codepoint; while (!utf8_is_format_code(codepoint = utf8_get_next(ch, &ch))) { ttf_draw_character_sprite(dpi, codepoint, info); }; } static void ttf_draw_string_raw_ttf(rct_drawpixelinfo *dpi, const utf8 *text, text_draw_info *info) { if (!_ttfInitialised && !ttf_initialise()) return; TTFFontDescriptor *fontDesc = ttf_get_font_from_sprite_base(info->font_sprite_base); if (fontDesc->font == NULL) { ttf_draw_string_raw_sprite(dpi, text, info); return; } if (info->flags & TEXT_DRAW_FLAG_NO_DRAW) { info->x += _ttf_getwidth_cache_get_or_add(fontDesc->font, text); return; } else { uint8 colour = info->palette[1]; SDL_Surface *surface = ttf_surface_cache_get_or_add(fontDesc->font, text); if (surface == NULL) return; if (SDL_MUSTLOCK(surface)) { if (SDL_LockSurface(surface) != 0) { return; } } int drawX = info->x + fontDesc->offset_x; int drawY = info->y + fontDesc->offset_y; int width = surface->w; int height = surface->h; int overflowX = (dpi->x + dpi->width) - (drawX + width); int overflowY = (dpi->y + dpi->height) - (drawY + height); if (overflowX < 0) width += overflowX; if (overflowY < 0) height += overflowY; int skipX = drawX - dpi->x; int skipY = drawY - dpi->y; info->x += width; uint8 *src = surface->pixels; uint8 *dst = dpi->bits; if (skipX < 0) { width += skipX; src += -skipX; skipX = 0; } if (skipY < 0) { height += skipY; src += (-skipY * surface->pitch); skipY = 0; } dst += skipX; dst += skipY * (dpi->width + dpi->pitch); int srcScanSkip = surface->pitch - width; int dstScanSkip = dpi->width + dpi->pitch - width; uint8 *dst_orig = dst; uint8 *src_orig = src; // Draw shadow/outline if (info->flags & TEXT_DRAW_FLAG_OUTLINE) { for (int yy = 0; yy < height - 0; yy++) { for (int xx = 0; xx < width - 0; xx++) { if (*src != 0) { *(dst + 1) = info->palette[3]; // right *(dst - 1) = info->palette[3]; // left *(dst - width - dstScanSkip) = info->palette[3]; // top *(dst + width + dstScanSkip) = info->palette[3]; // bottom } src++; dst++; } // Skip any remaining bits src += srcScanSkip; dst += dstScanSkip; } } { dst = dst_orig; src = src_orig; for (int yy = 0; yy < height; yy++) { for (int xx = 0; xx < width; xx++) { if (*src != 0) { if (info->flags & TEXT_DRAW_FLAG_INSET) { *(dst + width + dstScanSkip + 1) = info->palette[3]; } *dst = colour; } src++; dst++; } src += srcScanSkip; dst += dstScanSkip; } } if (SDL_MUSTLOCK(surface)) { SDL_UnlockSurface(surface); } } } static void ttf_draw_string_raw(rct_drawpixelinfo *dpi, const utf8 *text, text_draw_info *info) { if (info->flags & TEXT_DRAW_FLAG_TTF) { ttf_draw_string_raw_ttf(dpi, text, info); } else { ttf_draw_string_raw_sprite(dpi, text, info); } } static const utf8 *ttf_process_format_code(rct_drawpixelinfo *dpi, const utf8 *text, text_draw_info *info) { const utf8 *nextCh; int codepoint; codepoint = utf8_get_next(text, &nextCh); switch (codepoint) { case FORMAT_MOVE_X: info->x = info->startX + (uint8)(*nextCh++); break; case FORMAT_ADJUST_PALETTE: { uint16 eax = palette_to_g1_offset[(uint8)*nextCh++]; rct_g1_element *g1Element = &g1Elements[eax]; uint32 ebx = g1Element->offset[249] + 256; if (!(info->flags & TEXT_DRAW_FLAG_OUTLINE)) { ebx = ebx & 0xFF; } info->palette[1] = ebx & 0xFF; info->palette[2] = (ebx >> 8) & 0xFF; // Adjust the text palette memcpy(info->palette + 3, &(g1Element->offset[247]), 2); memcpy(info->palette + 5, &(g1Element->offset[250]), 2); // Set the palette pointer unk_9ABDA4 = &info->palette; break; } case 3: case 4: nextCh++; break; case FORMAT_NEWLINE: info->x = info->startX; info->y += font_get_line_height(info->font_sprite_base); break; case FORMAT_NEWLINE_SMALLER: info->x = info->startX; info->y += font_get_line_height_small(info->font_sprite_base); break; case FORMAT_TINYFONT: info->font_sprite_base = 448; break; case FORMAT_SMALLFONT: info->font_sprite_base = 0; break; case FORMAT_MEDIUMFONT: info->font_sprite_base = 224; break; case FORMAT_BIGFONT: info->font_sprite_base = 672; break; case FORMAT_OUTLINE: info->flags |= TEXT_DRAW_FLAG_OUTLINE; break; case FORMAT_OUTLINE_OFF: info->flags &= ~TEXT_DRAW_FLAG_OUTLINE; break; case FORMAT_WINDOW_COLOUR_1: { uint16 flags = info->flags; colour_char_window(gCurrentWindowColours[0], &flags, info->palette); break; } case FORMAT_WINDOW_COLOUR_2: { uint16 flags = info->flags; colour_char_window(gCurrentWindowColours[1], &flags, info->palette); break; } case FORMAT_WINDOW_COLOUR_3: { uint16 flags = info->flags; colour_char_window(gCurrentWindowColours[2], &flags, info->palette); break; } case 0x10: break; case FORMAT_INLINE_SPRITE: { uint32 imageId = *((uint32*)(nextCh)); rct_g1_element *g1Element = &g1Elements[imageId & 0x7FFFF]; if (!(info->flags & TEXT_DRAW_FLAG_NO_DRAW)) { gfx_draw_sprite(dpi, imageId, info->x, info->y, 0); } info->x += g1Element->width; nextCh += 4; break; } default: if (codepoint >= FORMAT_COLOUR_CODE_START && codepoint <= FORMAT_COLOUR_CODE_END) { uint16 flags = info->flags; colour_char(codepoint - FORMAT_COLOUR_CODE_START, &flags, info->palette); } else if (codepoint <= 0x16) { //case 0x11? FORMAT_NEW_LINE_X_Y nextCh += 2; } else { nextCh += 4;//never happens? } break; } return nextCh; } static const utf8 *ttf_process_glyph_run(rct_drawpixelinfo *dpi, const utf8 *text, text_draw_info *info) { utf8 buffer[512]; const utf8 *ch = text; const utf8 *lastCh; int codepoint; bool isTTF = info->flags & TEXT_DRAW_FLAG_TTF; while (!utf8_is_format_code(codepoint = utf8_get_next(ch, &lastCh))) { if (isTTF && utf8_should_use_sprite_for_codepoint(codepoint)) { break; } ch = lastCh; } if (codepoint == 0) { ttf_draw_string_raw(dpi, text, info); return ch; } else { int length = ch - text; memcpy(buffer, text, length); buffer[length] = 0; ttf_draw_string_raw(dpi, buffer, info); return ch; } } static void ttf_process_string(rct_drawpixelinfo *dpi, const utf8 *text, text_draw_info *info) { const utf8 *ch = text; const utf8 *nextCh; int codepoint; bool isTTF = info->flags & TEXT_DRAW_FLAG_TTF; while ((codepoint = utf8_get_next(ch, &nextCh)) != 0) { if (utf8_is_format_code(codepoint)) { ch = ttf_process_format_code(dpi, ch, info); } else if (isTTF && utf8_should_use_sprite_for_codepoint(codepoint)) { ttf_draw_character_sprite(dpi, codepoint, info); ch = nextCh; } else { ch = ttf_process_glyph_run(dpi, ch, info); } info->maxX = max(info->maxX, info->x); info->maxY = max(info->maxY, info->y); } } static void ttf_process_initial_colour(int colour, text_draw_info *info) { if (colour != 254 && colour != 255) { info->flags &= ~(TEXT_DRAW_FLAG_INSET | TEXT_DRAW_FLAG_OUTLINE | 4 | 8); if ((sint16)info->font_sprite_base < 0) { info->flags |= 4; if ((sint16)info->font_sprite_base != -1) { info->flags |= 8; } info->font_sprite_base = 224; } if (colour & COLOUR_FLAG_OUTLINE) { info->flags |= TEXT_DRAW_FLAG_OUTLINE; } colour &= ~COLOUR_FLAG_OUTLINE; if (!(colour & COLOUR_FLAG_INSET)) { if (!(info->flags & TEXT_DRAW_FLAG_INSET)) { uint16 flags = info->flags; colour_char_window(colour, &flags, (uint8*)&info->palette); } } else { info->flags |= TEXT_DRAW_FLAG_INSET; colour &= ~COLOUR_FLAG_INSET; uint32 eax; if (info->flags & 4) { if (info->flags & 8) { eax = ColourMapA[colour].mid_light; eax = eax << 16; eax = eax | ColourMapA[colour].dark; } else { eax = ColourMapA[colour].light; eax = eax << 16; eax = eax | ColourMapA[colour].mid_dark; } } 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; unk_9ABDA4 = &info->palette; eax = 0; } } } static void ttf_draw_string(rct_drawpixelinfo *dpi, char *text, int colour, int x, int y) { if (text == NULL) return; text_draw_info info; info.font_sprite_base = gCurrentFontSpriteBase; info.flags = gCurrentFontFlags; info.startX = x; info.startY = x; info.x = x; info.y = y; if (gUseTrueTypeFont) info.flags |= TEXT_DRAW_FLAG_TTF; memcpy(info.palette, text_palette, sizeof(info.palette)); ttf_process_initial_colour(colour, &info); ttf_process_string(dpi, text, &info); memcpy(text_palette, info.palette, sizeof(info.palette)); gCurrentFontSpriteBase = info.font_sprite_base; gCurrentFontFlags = info.flags; gLastDrawStringX = info.x; gLastDrawStringY = info.y; } static int ttf_get_string_width(const utf8 *text) { text_draw_info info; info.font_sprite_base = gCurrentFontSpriteBase; info.flags = gCurrentFontFlags; 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; if (gUseTrueTypeFont) info.flags |= TEXT_DRAW_FLAG_TTF; ttf_process_string(NULL, text, &info); return info.maxX; } /** * * rct2: 0x00682F28 */ void gfx_draw_string_with_y_offsets(rct_drawpixelinfo *dpi, const utf8 *text, int colour, int x, int y, const sint8 *yOffsets, bool forceSpriteFont) { text_draw_info info; info.font_sprite_base = gCurrentFontSpriteBase; info.flags = gCurrentFontFlags; info.startX = x; info.startY = x; info.x = x; info.y = y; info.y_offset = yOffsets; info.flags |= TEXT_DRAW_FLAG_Y_OFFSET_EFFECT; if (!forceSpriteFont && gUseTrueTypeFont) { info.flags |= TEXT_DRAW_FLAG_TTF; } memcpy(info.palette, text_palette, sizeof(info.palette)); ttf_process_initial_colour(colour, &info); ttf_process_string(dpi, text, &info); memcpy(text_palette, info.palette, sizeof(info.palette)); gCurrentFontSpriteBase = info.font_sprite_base; gCurrentFontFlags = info.flags; gLastDrawStringX = info.x; gLastDrawStringY = info.y; } void shorten_path(utf8 *buffer, size_t bufferSize, const utf8 *path, int availableWidth) { int length = strlen(path); // Return full string if it fits if (gfx_get_string_width((char*)path) <= availableWidth) { safe_strcpy(buffer, path, bufferSize); return; } // Count path separators int path_separators = 0; for (int x = 0; x < length; x++) { if (path[x] == platform_get_path_separator()) { path_separators++; } } // TODO: Replace with unicode ellipsis when supported safe_strcpy(buffer, "...", bufferSize); // Abreviate beginning with xth separator int begin = -1; for (int x = 0; x < path_separators; x++){ do { begin++; } while (path[begin] != platform_get_path_separator()); safe_strcpy(buffer + 3, path + begin, bufferSize - 3); if (gfx_get_string_width(buffer) <= availableWidth) { return; } } safe_strcpy(buffer, path, bufferSize); }