ref: 17b0b503a940ef0cfb70ed1506a3132c0b6146d6
dir: /src/Font.cpp/
#include "Font.h" #include <stdbool.h> #include <stddef.h> #include <stdlib.h> #ifdef JAPANESE #include <iconv.h> #endif #include <ft2build.h> #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)) typedef struct FontObject { FT_Library library; FT_Face face; #ifndef DISABLE_FONT_ANTIALIASING bool lcd_mode; #endif #ifdef JAPANESE iconv_t conv; #endif } FontObject; static unsigned long UTF8ToCode(const unsigned char *string, unsigned int *bytes_read) { unsigned int length; unsigned long charcode; unsigned int zero_bit = 0; for (unsigned char lead_byte = string[0]; zero_bit < 5 && (lead_byte & 0x80); ++zero_bit, lead_byte <<= 1); switch (zero_bit) { case 0: // Single-byte character length = 1; charcode = string[0]; break; case 2: case 3: case 4: length = zero_bit; charcode = string[0] & (1 << (8 - zero_bit)) - 1; for (unsigned int i = 1; i < zero_bit; ++i) { if ((string[i] & 0xC0) == 0x80) { charcode <<= 6; charcode |= string[i] & ~0xC0; } else { // Error: Invalid continuation byte length = 1; charcode = 0xFFFD; break; } } break; default: // Error: Invalid lead byte length = 1; charcode = 0xFFFD; break; } 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) { const unsigned char (*font_buffer)[converted.pitch / 3][3] = (unsigned char (*)[converted.pitch / 3][3])converted.buffer; const 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) { const double alpha = pow((font_pixel[j] / 255.0), 1.0 / 1.8); // Gamma correction 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_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 alpha = pow((double)font_buffer[iy][ix] / (converted.num_grays - 1), 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); }