diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a378245..a756c06 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -85,11 +85,10 @@ IF(SDL2_FOUND)
target_link_libraries(freerct ${SDL2_LIBRARY})
ENDIF()
-find_package(SDL2_ttf REQUIRED)
-# Legacy variable names
-IF(SDL2_TTF_FOUND)
- include_directories(${SDL2TTF_INCLUDE_DIR})
- target_link_libraries(freerct ${SDL2TTF_LIBRARY})
+find_package(Freetype REQUIRED)
+IF(FREETYPE_FOUND)
+ include_directories(${FREETYPE_INCLUDE_DIRS})
+ target_link_libraries(freerct ${FREETYPE_LIBRARIES})
ENDIF()
# Translated messages are bad
diff --git a/src/sprite_store.cpp b/src/sprite_store.cpp
index ecdb06e..acc8489 100644
--- a/src/sprite_store.cpp
+++ b/src/sprite_store.cpp
@@ -1750,4 +1750,3 @@ PathStatus SpriteManager::GetPathStatus(PathType path_type)
const Path &path = this->store.path_sprites[path_type];
return path.status;
}
-
diff --git a/src/video.cpp b/src/video.cpp
index e87e2f4..2feaec0 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -198,29 +198,18 @@ std::string VideoSystem::Initialize(const char *font_name, int font_size)
SDL_StartTextInput(); // Enable Unicode character input.
- if (TTF_Init() != 0) {
- SDL_Quit();
- delete[] this->mem;
- std::string err = "TTF font initialization failed: ";
- err += TTF_GetError();
- return err;
+ /* Freetype init */
+ if (FT_Init_FreeType(&this->library) != 0) {
}
- this->font = TTF_OpenFont(font_name, font_size);
- if (this->font == nullptr) {
- std::string err = "TTF Opening font \"";
- err += font_name;
- err += "\" size ";
- err += std::to_string(font_size);
- err += " failed: ";
- err += TTF_GetError();
- TTF_Quit();
- SDL_Quit();
- delete[] this->mem;
- return err;
+ if (FT_New_Face(this->library, font_name, 0, &this->face) != 0) {
}
- this->font_height = TTF_FontLineSkip(this->font);
+ /** @todo use actual screen dpi? */
+ if (FT_Set_Char_Size(this->face, 0, font_size * 64, 0, 0) != 0) {
+ }
+
+ this->font_height = this->face->height / 64;
this->initialized = true;
this->dirty = true; // Ensure it gets painted.
this->missing_sprites = false;
@@ -486,8 +475,8 @@ void VideoSystem::MainLoop()
void VideoSystem::Shutdown()
{
if (this->initialized) {
- TTF_CloseFont(this->font);
- TTF_Quit();
+ FT_Done_Face(this->face);
+ FT_Done_FreeType(this->library);
SDL_Quit();
delete[] this->mem;
this->initialized = false;
@@ -714,6 +703,52 @@ void VideoSystem::BlitImages(const Point32 &pt, const ImageData *spr, uint16 num
}
/**
+ * Decode an UTF-8 character.
+ * @param data Pointer to the start of the data.
+ * @param length Length of the \a data buffer.
+ * @param[out] codepoint If decoding was successful, the value of the decoded character.
+ * @return Number of bytes read to decode the character, or \c 0 if reading failed.
+ */
+static int DecodeUtf8Char(const uint8 *data, size_t length, uint32 *codepoint)
+{
+ if (length < 1) return 0;
+ uint32 value = *data;
+ data++;
+ if ((value & 0x80) == 0) {
+ *codepoint = value;
+ return 1;
+ }
+ int size;
+ uint32 min_value;
+ if ((value & 0xE0) == 0xC0) {
+ size = 2;
+ min_value = 0x80;
+ value &= 0x1F;
+ } else if ((value & 0xF0) == 0xE0) {
+ size = 3;
+ min_value = 0x800;
+ value &= 0x0F;
+ } else if ((value & 0xF8) == 0xF0) {
+ size = 4;
+ min_value = 0x10000;
+ value &= 0x07;
+ } else {
+ return 0;
+ }
+
+ if (length < static_cast<size_t>(size)) return 0;
+ for (int n = 1; n < size; n++) {
+ uint8 val = *data;
+ data++;
+ if ((val & 0xC0) != 0x80) return 0;
+ value = (value << 6) | (val & 0x3F);
+ }
+ if (value < min_value || (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) return 0;
+ *codepoint = value;
+ return size;
+}
+
+/**
* Get the text-size of a string.
* @param text Text to calculate.
* @param width [out] Resulting width.
@@ -721,9 +756,34 @@ void VideoSystem::BlitImages(const Point32 &pt, const ImageData *spr, uint16 num
*/
void VideoSystem::GetTextSize(const uint8 *text, int *width, int *height)
{
- if (TTF_SizeUTF8(this->font, (const char *)text, width, height) != 0) {
- *width = 0;
- *height = 0;
+ *width = 0;
+ *height = 0;
+
+ bool use_kerning = FT_HAS_KERNING(this->face);
+ uint previous = 0;
+ FT_GlyphSlot slot = this->face->glyph;
+
+ for (const uint8 *pt = text; *pt != '\0';) {
+ uint32 u32;
+ int len = DecodeUtf8Char(pt, strlen((const char *)pt), &u32);
+ if (len == 0) break;
+ pt += len;
+
+ uint glyph = FT_Get_Char_Index(this->face, u32);
+
+ if (use_kerning && previous && glyph) {
+ FT_Vector delta;
+ FT_Get_Kerning(this->face, previous, glyph, FT_KERNING_DEFAULT, &delta);
+
+ *width += delta.x / 64;
+ }
+
+ if (FT_Load_Glyph(this->face, glyph, FT_LOAD_RENDER) != 0) {
+ }
+ *width += slot->advance.x / 64;
+ *height = std::max(*height, (int)slot->metrics.height / 64);
+
+ previous = glyph;
}
}
@@ -776,19 +836,12 @@ void VideoSystem::GetNumberRangeSize(int64 smallest, int64 biggest, int *width,
*/
void VideoSystem::BlitText(const uint8 *text, uint32 colour, int xpos, int ypos, int width, Alignment align)
{
- SDL_Color col = {0, 0, 0}; // Font colour does not matter as only the bitmap is used.
- SDL_Surface *surf = TTF_RenderUTF8_Solid(this->font, (const char *)text, col);
- if (surf == nullptr) {
- fprintf(stderr, "Rendering text failed (%s)\n", TTF_GetError());
- return;
- }
-
- if (surf->format->BitsPerPixel != 8 || surf->format->BytesPerPixel != 1) {
- fprintf(stderr, "Rendering text failed (Wrong surface format)\n");
- return;
- }
+ this->blit_rect.ValidateAddress();
- int real_w = std::min(surf->w, width);
+ int w, h;
+ this->GetTextSize(text, &w, &h);
+ ypos += h;
+ int real_w = std::min(w, width);
switch (align) {
case ALG_LEFT:
break;
@@ -804,45 +857,42 @@ void VideoSystem::BlitText(const uint8 *text, uint32 colour, int xpos, int ypos,
default: NOT_REACHED();
}
- this->blit_rect.ValidateAddress();
+ bool use_kerning = FT_HAS_KERNING(this->face);
+ uint previous = 0;
+ FT_GlyphSlot slot = this->face->glyph;
- uint8 *src = ((uint8 *)surf->pixels);
- uint32 *dest = this->blit_rect.address + xpos + ypos * this->blit_rect.pitch;
- int h = surf->h;
- if (ypos < 0) {
- h += ypos;
- src -= ypos * surf->pitch;
- dest -= ypos * this->blit_rect.pitch;
- ypos = 0;
- }
- while (h > 0) {
- if (ypos >= this->blit_rect.height) break;
- uint8 *src2 = src;
- uint32 *dest2 = dest;
- int w = real_w;
- int x = xpos;
- if (x < 0) {
- w += x;
- if (w <= 0) break;
- dest2 -= x;
- src2 -= x;
- x = 0;
+ for (const uint8 *pt = text; *pt != '\0';) {
+ uint32 u32;
+ int len = DecodeUtf8Char(pt, strlen((const char *)pt), &u32);
+ if (len == 0) break;
+ pt += len;
+
+ uint glyph = FT_Get_Char_Index(this->face, u32);
+
+ if (use_kerning && previous && glyph) {
+ FT_Vector delta;
+ FT_Get_Kerning(this->face, previous, glyph, FT_KERNING_DEFAULT, &delta);
+ xpos += delta.x >> 6;
}
- while (w > 0) {
- if (x >= this->blit_rect.width) break;
- if (*src2 != 0) *dest2 = colour;
- src2++;
- dest2++;
- x++;
- w--;
+
+ if (FT_Load_Glyph(this->face, glyph, FT_LOAD_RENDER) != 0) {
}
- ypos++;
- src += surf->pitch;
- dest += this->blit_rect.pitch;
- h--;
- }
- SDL_FreeSurface(surf);
+ uint32 *dest = this->blit_rect.address + (xpos + slot->bitmap_left) + (ypos - slot->bitmap_top) * this->blit_rect.pitch;
+ for (int i = 0; i < slot->bitmap.rows; i++) {
+ if (ypos - slot->bitmap_top + i < 0) continue;
+ for (int j = 0; j < slot->bitmap.pitch; j++) {
+ if (xpos + slot->bitmap_left + j < 0) continue;
+ if (slot->bitmap.buffer[i * slot->bitmap.pitch + j] != 0) {
+ dest[i * this->blit_rect.pitch + j] = colour;
+ }
+ }
+ }
+
+ previous = glyph;
+ xpos += slot->advance.x >> 6;
+ ypos += slot->advance.y >> 6;
+ }
}
/**
diff --git a/src/video.h b/src/video.h
index 4528f81..e273b79 100644
--- a/src/video.h
+++ b/src/video.h
@@ -13,8 +13,11 @@
#define VIDEO_H
#include <set>
+
#include <SDL.h>
-#include <SDL_ttf.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
#include "geometry.h"
#include "palette.h"
@@ -169,7 +172,9 @@ private:
bool initialized; ///< Video system is initialized.
bool dirty; ///< Video display needs being repainted.
- TTF_Font *font; ///< Opened text font.
+ FT_Library library;
+ FT_Face face;
+
SDL_Window *window; ///< %Window of the application.
SDL_Renderer *renderer; ///< GPU renderer to the application window.
SDL_Texture *texture; ///< GPU Texture storage of the application window.