diff --git a/Makefile b/Makefile index 8bbaddb7..191ccf8d 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,8 @@ ifeq ($(FIX_BUGS), 1) CXXFLAGS += -DFIX_BUGS endif -CXXFLAGS += `sdl2-config --cflags` -LIBS += `sdl2-config --static-libs` -lSDL2_ttf -lfreetype -lharfbuzz -lfreetype -lbz2 -lpng -lz -lgraphite2 -lRpcrt4 -lDwrite -lusp10 +CXXFLAGS += `sdl2-config --cflags` `pkg-config freetype2 --cflags` +LIBS += `sdl2-config --static-libs` -lfreetype -lharfbuzz -lfreetype -lbz2 -lpng -lz -lgraphite2 -lRpcrt4 -lDwrite -lusp10 -liconv # For an accurate result to the original's code, compile in alphabetical order SOURCES = \ @@ -27,6 +27,7 @@ SOURCES = \ Escape \ Fade \ Flags \ + Font \ Game \ Generic \ GenericLoad \ diff --git a/build/data/font.ttf b/build/data/cour.ttf similarity index 100% rename from build/data/font.ttf rename to build/data/cour.ttf diff --git a/build/data/msgothic.ttc b/build/data/msgothic.ttc new file mode 100644 index 00000000..7a187412 Binary files /dev/null and b/build/data/msgothic.ttc differ diff --git a/src/Draw.cpp b/src/Draw.cpp index 7cde7289..08592788 100644 --- a/src/Draw.cpp +++ b/src/Draw.cpp @@ -7,10 +7,11 @@ #include #include #include -#include + #include "WindowsWrapper.h" #include "Draw.h" +#include "Font.h" #include "Tags.h" #include "Resource.h" @@ -19,8 +20,7 @@ RECT grcFull = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}; SURFACE surf[SURFACE_ID_MAX]; -TTF_Font *gFont; -double gFontXStretch; //This is something normally done by DirectX, but... well... we're not using DirectX. +FontObject *gFont; #define FRAMERATE 20 @@ -206,7 +206,7 @@ bool MakeSurface_Generic(int bxsize, int bysize, int surf_no) ReleaseSurface(surf_no); //Create surface - surf[surf_no].texture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, bxsize * gWindowScale, bysize * gWindowScale); + surf[surf_no].texture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, bxsize * gWindowScale, bysize * gWindowScale); if (!surf[surf_no].texture) { @@ -231,7 +231,7 @@ void BackupSurface(int surf_no, RECT *rect) SDL_GetRendererOutputSize(gRenderer, &w, &h); //Get texture of what's currently rendered on screen - SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(SDL_TEXTUREACCESS_TARGET, w, h, 0, SDL_PIXELFORMAT_RGB888); + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 0, SDL_PIXELFORMAT_RGBA32); SDL_RenderReadPixels(gRenderer, nullptr, SDL_PIXELFORMAT_RGBA32, surface->pixels, surface->pitch); SDL_Texture *screenTexture = SDL_CreateTextureFromSurface(gRenderer, surface); @@ -247,6 +247,8 @@ void BackupSurface(int surf_no, RECT *rect) SDL_SetRenderTarget(gRenderer, surf[surf_no].texture); SDL_RenderCopy(gRenderer, screenTexture, &frameRect, &destRect); SDL_SetRenderTarget(gRenderer, NULL); + + SDL_DestroyTexture(screenTexture); } void PutBitmap3(RECT *rcView, int x, int y, RECT *rect, int surf_no) //Transparency @@ -369,140 +371,43 @@ void CortBox2(RECT *rect, uint32_t col, int surf_no) void InitTextObject() { - //Initialize SDL_TTF - if(!TTF_WasInit() && TTF_Init() < 0) - { - printf("TTF_Init: %s\n", TTF_GetError()); - return; - } - - //If font already exists, delete - if (gFont) - TTF_CloseFont(gFont); - //Get font size unsigned int fontWidth, fontHeight; if (gWindowScale == 1) { - fontWidth = 5; - fontHeight = 10; + fontWidth = 6; + fontHeight = 12; } else { fontWidth = 5 * gWindowScale; - fontHeight = 8 * gWindowScale + (gWindowScale >> 1); + fontHeight = 10 * gWindowScale; } //Open Font.ttf char path[PATH_LENGTH]; - sprintf(path, "%s/Font.ttf", gDataPath); - - gFont = TTF_OpenFont(path, fontHeight); - if(!gFont) - { - printf("TTF_OpenFont: %s\n", TTF_GetError()); - return; - } - - //Get the average width of the font, and make it so the average width is the font width above. - char string[0xE1]; - for (int i = 0; i < 0xE0; i++) - string[i] = i + 0x20; - string[0xE1] = 0; - - int width, height; - if (TTF_SizeText(gFont, string, &width, &height)) - { - printf("TTF_SizeText: %s\n", TTF_GetError()); - return; - } - - gFontXStretch = (double)fontWidth / ((double)width / (double)0xE0); +#ifdef JAPANESE + sprintf(path, "%s/msgothic.ttc", gDataPath); +#else + sprintf(path, "%s/cour.ttf", gDataPath); +#endif + + gFont = LoadFont(fontWidth, fontHeight, path); } void PutText(int x, int y, const char *text, uint32_t color) { - SDL_Color textColor = {(uint8_t)((color & 0xFF0000) >> 16), (uint8_t)((color & 0xFF00) >> 8), (uint8_t)(color & 0xFF)}; - SDL_Color backColor = {0, 0, 0}; - - //Draw text - SDL_Surface *textSurface = TTF_RenderText_Shaded(gFont, text, textColor, backColor); - - if (!textSurface) - { - printf("TTF_RenderText_Shaded: %s\n", TTF_GetError()); - return; - } - - SDL_SetColorKey(textSurface, SDL_TRUE, 0x000000); - - //Convert to texture - SDL_Texture *textTexture = SDL_CreateTextureFromSurface(gRenderer, textSurface); - - if (!textTexture) - { - printf("Failed to convert SDL_Surface to SDL_Texture to draw text: %s\nTTF Error: %s\n", text, TTF_GetError()); - return; - } - - //Draw to screen - SDL_Rect destRect = {x * gWindowScale, y * gWindowScale, (int)(textSurface->w * gFontXStretch), textSurface->h}; - SDL_RenderCopy(gRenderer, textTexture, NULL, &destRect); - - //Destroy surface and texture - SDL_FreeSurface(textSurface); - SDL_DestroyTexture(textTexture); + DrawText(gFont, gRenderer, NULL, x * gWindowScale, y * gWindowScale, color, text, strlen(text)); } void PutText2(int x, int y, const char *text, uint32_t color, int surf_no) { - SDL_Color textColor = {(uint8_t)((color & 0xFF0000) >> 16), (uint8_t)((color & 0xFF00) >> 8), (uint8_t)(color & 0xFF)}; - SDL_Color backColor = {0, 0, 0}; - - //Draw text - SDL_Surface *textSurface = TTF_RenderText_Shaded(gFont, text, textColor, backColor); - - if (!textSurface) - { - printf("TTF_RenderText_Shaded: %s\n", TTF_GetError()); - return; - } - - SDL_SetColorKey(textSurface, SDL_TRUE, 0x000000); - - //Convert to texture - SDL_Texture *textTexture = SDL_CreateTextureFromSurface(gRenderer, textSurface); - - if (!textTexture) - { - printf("Failed to convert SDL_Surface to SDL_Texture to draw text: %s\nTTF Error: %s\n", text, TTF_GetError()); - return; - } - - //Target surface - if (!surf[surf_no].texture) - { - printf("Tried to draw text to surface %s, which doesn't exist\n", surf_no); - return; - } - - SDL_SetRenderTarget(gRenderer, surf[surf_no].texture); - - //Draw to screen - SDL_Rect destRect = {x * gWindowScale, y * gWindowScale, (int)(textSurface->w * gFontXStretch), textSurface->h}; - SDL_RenderCopy(gRenderer, textTexture, NULL, &destRect); - - //Destroy surface and texture - SDL_FreeSurface(textSurface); - SDL_DestroyTexture(textTexture); - - //Stop targetting surface - SDL_SetRenderTarget(gRenderer, NULL); + DrawText(gFont, gRenderer, surf[surf_no].texture, x * gWindowScale, y * gWindowScale, color, text, strlen(text)); } void EndTextObject() { //Destroy font - TTF_CloseFont(gFont); + UnloadFont(gFont); gFont = nullptr; } diff --git a/src/Font.cpp b/src/Font.cpp new file mode 100644 index 00000000..3061a13f --- /dev/null +++ b/src/Font.cpp @@ -0,0 +1,258 @@ +#include "Font.h" + +#include +#include +#include + +#include + +#include +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H +#include FT_BITMAP_H + +#include "SDL.h" + +// Uncomment for that authentic pre-Windows Vista feel +//#define DISABLE_FONT_ANTIALIASING + +#undef MIN +#undef MAX +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +static unsigned long UTF8ToCode(const unsigned char *string, unsigned int *bytes_read) +{ + unsigned int length; + unsigned long charcode; + + if ((string[0] & 0x80) == 0) + { + length = 1; + charcode = string[0] & 0x7F; + } + else if ((string[0] & 0xE0) == 0xC0) + { + length = 2; + charcode = ((string[0] & ~0xE0) << 6) | (string[1] & 0x3F); + } + else if ((string[0] & 0xF0) == 0xE0) + { + length = 3; + charcode = ((string[0] & ~0xF0) << (6 * 2)) | ((string[1] & 0x3F) << 6) | (string[2] & 0x3F); + } + else //if (string[0] & 0xF8 == 0xF0) + { + length = 4; + charcode = ((string[0] & ~0xF8) << (6 * 3)) | ((string[1] & 0x3F) << (6 * 2)) | ((string[2] & 0x3F) << 6) | (string[3] & 0x3F); + } + + if (bytes_read) + *bytes_read = length; + + return charcode; +} + +FontObject* LoadFont(unsigned int cell_width, unsigned int cell_height, char *font_filename) +{ + FontObject *font_object = (FontObject*)malloc(sizeof(FontObject)); + + FT_Init_FreeType(&font_object->library); + +#ifndef DISABLE_FONT_ANTIALIASING + font_object->lcd_mode = FT_Library_SetLcdFilter(font_object->library, FT_LCD_FILTER_DEFAULT) != FT_Err_Unimplemented_Feature; +#endif + + FT_New_Face(font_object->library, font_filename, 0, &font_object->face); + + unsigned int best_cell_width = 0; + unsigned int best_cell_height = 0; + unsigned int best_pixel_width = 0; + unsigned int best_pixel_height = 0; + + for (unsigned int i = 0;; ++i) + { + FT_Set_Pixel_Sizes(font_object->face, i, i); + + const unsigned int current_cell_width = font_object->face->size->metrics.max_advance / 64; + const unsigned int current_cell_height = font_object->face->size->metrics.height / 64; + + if (current_cell_width > cell_width && current_cell_height > cell_height) + { + break; + } + else + { + if (current_cell_width <= cell_width) + { + best_pixel_width = i; + best_cell_width = current_cell_width; + } + if (current_cell_height <= cell_height) + { + best_pixel_height = i; + best_cell_height = current_cell_height; + } + } + } + +#ifdef JAPANESE + best_pixel_width = 0; // Cheap hack to make the font square +#endif + + FT_Set_Pixel_Sizes(font_object->face, best_pixel_width, best_pixel_height); + +#ifdef JAPANESE + font_object->conv = iconv_open("UTF-8", "SHIFT-JIS"); +#endif + + return font_object; +} + +void DrawText(FontObject *font_object, SDL_Renderer *renderer, SDL_Texture *texture, int x, int y, unsigned long colour, const char *string, size_t string_length) +{ + const unsigned char colours[3] = {(unsigned char)(colour >> 16), (unsigned char)(colour >> 8), (unsigned char)colour}; + + SDL_Texture *old_render_target = SDL_GetRenderTarget(renderer); + SDL_SetRenderTarget(renderer, texture); + + int surface_width, surface_height; + SDL_GetRendererOutputSize(renderer, &surface_width, &surface_height); + + SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, surface_width, surface_height, 0, SDL_PIXELFORMAT_RGBA32); + SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_RGBA32, surface->pixels, surface->pitch); + unsigned char (*surface_buffer)[surface->pitch / 4][4] = (unsigned char (*)[surface->pitch / 4][4])surface->pixels; + + FT_Face face = font_object->face; + + unsigned int pen_x = 0; + + const unsigned char *string_pointer = (unsigned char*)string; + const unsigned char *string_end = (unsigned char*)string + string_length; + + while (string_pointer != string_end) + { +#ifdef JAPANESE + size_t out_size = 4; + unsigned char out_buffer[4]; // Max UTF-8 length is four bytes + unsigned char *out_pointer = out_buffer; + + size_t in_size = ((*string_pointer >= 0x81 && *string_pointer <= 0x9F) || (*string_pointer >= 0xE0 && *string_pointer <= 0xEF)) ? 2 : 1; + unsigned char in_buffer[2]; + unsigned char *in_pointer = in_buffer; + + for (size_t i = 0; i < in_size; ++i) + in_buffer[i] = string_pointer[i]; + + string_pointer += in_size; + + iconv(font_object->conv, (char**)&in_pointer, &in_size, (char**)&out_pointer, &out_size); + + const unsigned long val = UTF8ToCode(out_buffer, NULL); +#else + unsigned int bytes_read; + const unsigned long val = UTF8ToCode(string_pointer, &bytes_read); + string_pointer += bytes_read; +#endif + + unsigned int glyph_index = FT_Get_Char_Index(face, val); + +#ifndef DISABLE_FONT_ANTIALIASING + FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | (font_object->lcd_mode ? FT_LOAD_TARGET_LCD : 0)); +#else + FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_LOAD_MONOCHROME); +#endif + + FT_Bitmap converted; + FT_Bitmap_New(&converted); + FT_Bitmap_Convert(font_object->library, &face->glyph->bitmap, &converted, 1); + + const int letter_x = x + pen_x + face->glyph->bitmap_left; + const int letter_y = y + ((FT_MulFix(face->ascender, face->size->metrics.y_scale) + (64 - 1)) / 64) - (face->glyph->metrics.horiBearingY / 64); + + for (int iy = MAX(-letter_y, 0); letter_y + iy < MIN(letter_y + converted.rows, surface_height); ++iy) + { + if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) + { + for (int ix = MAX(-letter_x, 0); letter_x + ix < MIN(letter_x + (int)converted.width / 3, surface_width); ++ix) + { + unsigned char (*font_buffer)[converted.pitch / 3][3] = (unsigned char (*)[converted.pitch / 3][3])converted.buffer; + + unsigned char *font_pixel = font_buffer[iy][ix]; + unsigned char *surface_pixel = surface_buffer[letter_y + iy][letter_x + ix]; + + if (font_pixel[0] || font_pixel[1] || font_pixel[2]) + { + for (unsigned int j = 0; j < 3; ++j) + { + unsigned char colour_accumulator = surface_pixel[j]; + + colour_accumulator = ((colours[j] * font_pixel[j]) / 0xFF) + ((colour_accumulator * (0xFF - font_pixel[j])) / 0xFF); // Alpha blending + colour_accumulator = 255.0 * pow((colour_accumulator / 255.0), 1.0 / 1.8); // Gamma correction + + surface_pixel[j] = colour_accumulator; + } + + surface_pixel[3] = 0xFF; + } + } + } + else if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) + { + for (int ix = MAX(-letter_x, 0); letter_x + ix < MIN(letter_x + (int)converted.width, surface_width); ++ix) + { + unsigned char (*font_buffer)[converted.pitch] = (unsigned char (*)[converted.pitch])converted.buffer; + + const double raw_alpha = (double)font_buffer[iy][ix] / (converted.num_grays - 1); + const double alpha = pow(raw_alpha, 1.0 / 1.8); // Gamma-corrected + + unsigned char *surface_pixel = surface_buffer[letter_y + iy][letter_x + ix]; + + if (alpha) + { + for (unsigned int j = 0; j < 3; ++j) + surface_pixel[j] = (colours[j] * alpha) + (surface_pixel[j] * (1.0 - alpha)); // Alpha blending + + surface_pixel[3] = 0xFF; + } + } + } + else if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + for (int ix = MAX(-letter_x, 0); letter_x + ix < MIN(letter_x + (int)converted.width, surface_width); ++ix) + { + unsigned char (*font_buffer)[converted.pitch] = (unsigned char (*)[converted.pitch])converted.buffer; + + unsigned char *surface_pixel = surface_buffer[letter_y + iy][letter_x + ix]; + + if (font_buffer[iy][ix]) + { + for (unsigned int j = 0; j < 3; ++j) + surface_pixel[j] = colours[j]; + + surface_pixel[3] = 0xFF; + } + } + } + } + + FT_Bitmap_Done(font_object->library, &converted); + + pen_x += face->glyph->advance.x / 64; + } + + SDL_Texture *screen_texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + SDL_RenderCopy(renderer, screen_texture, NULL, NULL); + SDL_DestroyTexture(screen_texture); + SDL_SetRenderTarget(renderer, old_render_target); +} + +void UnloadFont(FontObject *font_object) +{ +#ifdef JAPANESE + iconv_close(font_object->conv); +#endif + FT_Done_Face(font_object->face); + FT_Done_FreeType(font_object->library); +} diff --git a/src/Font.h b/src/Font.h new file mode 100644 index 00000000..57953a3a --- /dev/null +++ b/src/Font.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include FT_FREETYPE_H + +#include "SDL.h" + +typedef struct FontObject +{ + FT_Library library; + FT_Face face; +#ifndef DISABLE_FONT_ANTIALIASING + bool lcd_mode; +#endif +#ifdef JAPANESE + iconv_t conv; +#endif +} FontObject; + +FontObject* LoadFont(unsigned int cell_width, unsigned int cell_height, char *font_filename); +void DrawText(FontObject *font_object, SDL_Renderer *renderer, SDL_Texture *texture, int x, int y, unsigned long colour, const char *string, size_t string_length); +void UnloadFont(FontObject *font_object); diff --git a/src/MapName.cpp b/src/MapName.cpp index a6d6e9e5..e8d70e9a 100644 --- a/src/MapName.cpp +++ b/src/MapName.cpp @@ -16,7 +16,7 @@ void ReadyMapName(char *str) //Handle "Studio Pixel presents" text in the intro #ifdef JAPANESE - char presentText[24] = "開発室Pixel presents\x00\x01\x01\x01"; + char presentText[24] = "ŠJ”­ŽºPixel presents"; #else char presentText[24] = " Studio Pixel presents"; #endif