#ifndef OLC_PGEX_TTF_H #define OLC_PGEX_TTF_H #include "olcPixelGameEngine.h" #ifdef WIN32 #include #pragma comment(lib, "freetype.lib") #else #include #endif #include #include #include #include #include FT_FREETYPE_H #include FT_GLYPH_H namespace olc { struct FontRect { olc::vi2d offset; olc::vi2d size; }; class Font : public olc::PGEX { public: Font() = default; ~Font() { if (fontFace != nullptr) { FT_Done_Face(fontFace); } } Font(std::string path, int fontSize) : fontSize(fontSize) { FT_Error error = FT_New_Face(library, path.c_str(), 0, &fontFace); if (error) { const char *errorString = FT_Error_String(error); if (errorString == nullptr) { std::cerr << "An unknown error occured while loading the font! Error code: " << error << "\n"; } else { std::cerr << errorString << "\n"; } } error = FT_Set_Pixel_Sizes(fontFace, 0, fontSize); if (error) { const char *errorString = FT_Error_String(error); if (errorString == nullptr) { std::cerr << "An unknown error occured while loading the font!Error Code: " << error << "\n"; } else { std::cerr << errorString << "\n"; } } } Font(const Font &other) = delete; Font(Font &&other) { fontFace = other.fontFace; fontSize = other.fontSize; fallbacks = std::move(other.fallbacks); other.fontFace = nullptr; other.fallbacks.clear(); } Font &operator=(const Font &other) = delete; Font &operator=(Font &&other) { fontFace = other.fontFace; fontSize = other.fontSize; fallbacks = std::move(other.fallbacks); other.fontFace = nullptr; other.fallbacks.clear(); return *this; } void DrawString(std::u32string string, int x, int y, olc::Pixel color = olc::BLACK, float angle = 0.0f) { FT_Matrix rotMat; rotMat.xx = (FT_Fixed)(std::cos(angle) * 0x10000L); rotMat.xy = (FT_Fixed)(-std::sin(angle) * 0x10000L); rotMat.yx = (FT_Fixed)(std::sin(angle) * 0x10000L); rotMat.yy = (FT_Fixed)(std::cos(angle) * 0x10000L); FT_Vector pen; pen.x = x * 64; pen.y = (pge->ScreenHeight() - y) * 64; olc::Pixel::Mode prevMode = pge->GetPixelMode(); pge->SetPixelMode(olc::Pixel::ALPHA); Font *prevToUse = nullptr; for (size_t i = 0; i < string.size(); i++) { char32_t chr = string[i]; Font *toUse = this; FT_UInt chrIndex = GetCharIndex(chr); if (chrIndex == 0) { for (auto &font : fallbacks) { FT_UInt fbChr = font.GetCharIndex(chr); if (fbChr != 0) { chrIndex = fbChr; toUse = &font; } } } if (prevToUse == toUse) { FT_Vector kern; FT_Get_Kerning(fontFace, string[i - 1], chr, FT_KERNING_DEFAULT, &kern); pen.x += kern.x; pen.y += kern.y; } FT_Set_Transform(toUse->fontFace, &rotMat, &pen); FT_Error error = FT_Load_Char(toUse->fontFace, chr, FT_LOAD_RENDER | FT_LOAD_COLOR); if (error) { const char *errorString = FT_Error_String(error); if (errorString == nullptr) { std::cerr << "An unknown error occured while rendering a glyph! Error code: " << error << "\n"; } else { std::cerr << errorString << "\n"; } return; } FT_Bitmap bmp = toUse->fontFace->glyph->bitmap; FT_GlyphSlot slot = toUse->fontFace->glyph; pen.x += toUse->fontFace->glyph->advance.x; pen.y += toUse->fontFace->glyph->advance.y; if(chr=='\n'){ int scaled_line_spacing = fontFace->size->metrics.height; pen.y -=scaled_line_spacing; pen.x=x*64; } else { DrawBitmap(slot->bitmap_left, pge->ScreenHeight() - slot->bitmap_top, bmp, color); } prevToUse = toUse; } pge->SetPixelMode(prevMode); } void DrawString(std::u32string string, olc::vi2d pos, olc::Pixel color = olc::BLACK, float angle = 0.0f) { DrawString(string, pos.x, pos.y, color, angle); } FontRect GetStringBounds(std::u32string string, float angle = 0.0f) { FT_Matrix rotMat; rotMat.xx = (FT_Fixed)(std::cos(angle) * 0x10000L); rotMat.xy = (FT_Fixed)(-std::sin(angle) * 0x10000L); rotMat.yx = (FT_Fixed)(std::sin(angle) * 0x10000L); rotMat.yy = (FT_Fixed)(std::cos(angle) * 0x10000L); FT_Vector pen; pen.x = 0; pen.y = 0; olc::FontRect rect; constexpr int intMax = std::numeric_limits::max(); constexpr int intMin = std::numeric_limits::min(); int minX = 0; int minY = 0; int maxX = 0; int maxY = 0; Font *prevToUse = nullptr; for (size_t i = 0; i < string.size(); i++) { char32_t chr = string[i]; Font *toUse = this; FT_UInt chrIndex = GetCharIndex(chr); if (chrIndex == 0) { for (auto &font : fallbacks) { FT_UInt fbChr = font.GetCharIndex(chr); if (fbChr != 0) { chrIndex = fbChr; toUse = &font; } } } if (prevToUse == toUse) { FT_Vector kern; FT_Get_Kerning(fontFace, string[i - 1], chr, FT_KERNING_DEFAULT, &kern); pen.x += kern.x; pen.y += kern.y; } FT_Set_Transform(toUse->fontFace, &rotMat, &pen); FT_Error error = FT_Load_Char(toUse->fontFace, chr, FT_LOAD_BITMAP_METRICS_ONLY); if (error) { const char *errorString = FT_Error_String(error); if (errorString == nullptr) { std::cerr << "An unknown error occured while loading a glyph! Error code: " << error << "\n"; } else { std::cerr << errorString << "\n"; } return olc::FontRect{{0, 0}, {0, 0}}; } FT_GlyphSlot slot = toUse->fontFace->glyph; FT_Glyph glyph; FT_Get_Glyph(slot, &glyph); FT_BBox bbox; FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox); if (bbox.xMin < minX) { minX = bbox.xMin; } if (bbox.xMax > maxX) { maxX = bbox.xMax; } if (bbox.yMin < minY) { minY = bbox.yMin; } if (bbox.yMax > maxY) { maxY = bbox.yMax; } pen.x += slot->advance.x; pen.y += slot->advance.y; if(chr=='\n'){ int scaled_line_spacing = fontFace->size->metrics.height; pen.y-=scaled_line_spacing; pen.x=minX; } FT_Done_Glyph(glyph); } minX /= 64; minY /= 64; maxX /= 64; maxY /= 64; return olc::FontRect{{minX, -maxY}, {maxX - minX, maxY - minY}}; } olc::Sprite *RenderStringToSprite(std::u32string string, olc::Pixel color) { olc::FontRect rect = GetStringBounds(string); olc::Sprite *sprite = new olc::Sprite{rect.size.x, rect.size.y}; for (int x = 0; x < rect.size.x; x++) { for (int y = 0; y < rect.size.y; y++) { sprite->SetPixel(x, y, olc::BLANK); } } FT_Vector pen; pen.x = -rect.offset.x; pen.y = (pge->ScreenHeight() + rect.offset.y) * 64; olc::Pixel::Mode prevMode = pge->GetPixelMode(); pge->SetPixelMode(olc::Pixel::ALPHA); olc::Font *prevToUse = nullptr; for (size_t i = 0; i < string.size(); i++) { char32_t chr = string[i]; Font *toUse = this; FT_UInt chrIndex = GetCharIndex(chr); if (chrIndex == 0) { for (auto &font : fallbacks) { FT_UInt fbChr = font.GetCharIndex(chr); if (fbChr != 0) { chrIndex = fbChr; toUse = &font; } } } if (prevToUse == toUse) { FT_Vector kern; FT_Get_Kerning(fontFace, string[i - 1], chr, FT_KERNING_DEFAULT, &kern); pen.x += kern.x; pen.y += kern.y; } FT_Set_Transform(toUse->fontFace, nullptr, &pen); FT_Error error = FT_Load_Char(toUse->fontFace, chr, FT_LOAD_RENDER | FT_LOAD_COLOR); if (error) { const char *errorString = FT_Error_String(error); if (errorString == nullptr) { std::cerr << "An unknown error occured while rendering a glyph! Error code: " << error << "\n"; } else { std::cerr << errorString << "\n"; } return nullptr; } FT_Bitmap bmp = toUse->fontFace->glyph->bitmap; FT_GlyphSlot slot = toUse->fontFace->glyph; pen.x += toUse->fontFace->glyph->advance.x; pen.y += toUse->fontFace->glyph->advance.y; if(chr=='\n'){ int scaled_line_spacing = fontFace->size->metrics.height; pen.y -=scaled_line_spacing; pen.x=rect.offset.x; } else { DrawBitmapTo(slot->bitmap_left, pge->ScreenHeight() - slot->bitmap_top, bmp, color, sprite); } } pge->SetPixelMode(prevMode); return sprite; } olc::Decal *RenderStringToDecal(std::u32string string, olc::Pixel color) { Sprite *sprite = RenderStringToSprite(string, color); olc::Decal *decal = new olc::Decal{sprite}; return decal; } olc::Renderable RenderStringToRenderable(std::u32string string, olc::Pixel color) { Sprite *sprite = RenderStringToSprite(string, color); olc::Renderable renderable; renderable.Create(sprite->width, sprite->height); for (int x = 0; x < sprite->width; x++) { for (int y = 0; y < sprite->height; y++) { renderable.Sprite()->SetPixel(x, y, sprite->GetPixel(x, y)); } } delete sprite; renderable.Decal()->Update(); return renderable; } void AddFallbackFont(std::string path) { fallbacks.emplace_back(path, fontSize); } static bool init() { FT_Error error = FT_Init_FreeType(&library); if (error) { const char *errorString = FT_Error_String(error); if (errorString == nullptr) { std::cerr << "An unknown error occured while loading the font library! " "Error code: " << error << "\n"; } else { std::cerr << errorString << "\n"; } return false; } return true; } private: void DrawBitmap(int x, int y, FT_Bitmap bmp, olc::Pixel color) { switch (bmp.pixel_mode) { case FT_PIXEL_MODE_MONO: for (size_t bx = 0; bx < bmp.width; bx++) { for (size_t by = 0; by < bmp.rows; by++) { int byteOffset = bx / 8; char byte = bmp.buffer[by * bmp.pitch + byteOffset]; bool val = (byte >> (7 - bx % 8)) & 1; if (val) { pge->Draw(x + bx, y + by, color); } } } break; case FT_PIXEL_MODE_GRAY: for (size_t bx = 0; bx < bmp.width; bx++) { for (size_t by = 0; by < bmp.rows; by++) { uint8_t byte = bmp.buffer[by * bmp.pitch + bx]; if (byte == 0) { continue; } color.a = byte; pge->Draw(x + bx, y + by, color); } } break; case FT_PIXEL_MODE_GRAY2: break; case FT_PIXEL_MODE_GRAY4: break; case FT_PIXEL_MODE_LCD: break; case FT_PIXEL_MODE_LCD_V: break; case FT_PIXEL_MODE_BGRA: for (size_t bx = 0; bx < bmp.width; bx++) { for (size_t by = 0; by < bmp.rows; by++) { olc::Pixel pixel{ bmp.buffer[by * bmp.pitch + bx * 4 + 2], bmp.buffer[by * bmp.pitch + bx * 4 + 1], bmp.buffer[by * bmp.pitch + bx * 4 + 0], bmp.buffer[by * bmp.pitch + bx * 4 + 3], }; pge->Draw(x + bx, y + by, pixel); } } break; } } void DrawBitmapTo(int x, int y, FT_Bitmap bmp, olc::Pixel color, olc::Sprite *sprite) { switch (bmp.pixel_mode) { case FT_PIXEL_MODE_MONO: for (size_t bx = 0; bx < bmp.width; bx++) { for (size_t by = 0; by < bmp.rows; by++) { int byteOffset = bx / 8; char byte = bmp.buffer[by * bmp.pitch + byteOffset]; bool val = (byte >> (7 - bx % 8)) & 1; if (val) { sprite->SetPixel(x + bx, y + by, color); } } } break; case FT_PIXEL_MODE_GRAY: for (size_t bx = 0; bx < bmp.width; bx++) { for (size_t by = 0; by < bmp.rows; by++) { uint8_t byte = bmp.buffer[by * bmp.pitch + bx]; if (byte == 0) { continue; } color.a = byte; sprite->SetPixel(x + bx, y + by, color); } } break; case FT_PIXEL_MODE_GRAY2: break; case FT_PIXEL_MODE_GRAY4: break; case FT_PIXEL_MODE_LCD: break; case FT_PIXEL_MODE_LCD_V: break; case FT_PIXEL_MODE_BGRA: for (size_t bx = 0; bx < bmp.width; bx++) { for (size_t by = 0; by < bmp.rows; by++) { olc::Pixel pixel{ bmp.buffer[by * bmp.pitch + bx * 4 + 2], bmp.buffer[by * bmp.pitch + bx * 4 + 1], bmp.buffer[by * bmp.pitch + bx * 4 + 0], bmp.buffer[by * bmp.pitch + bx * 4 + 3], }; sprite->SetPixel(x + bx, y + by, pixel); } } break; } } FT_UInt GetCharIndex(char32_t charCode) { return FT_Get_Char_Index(fontFace, charCode); } FT_Face fontFace = nullptr; std::vector fallbacks; int fontSize; static FT_Library library; }; } // namespace olc #ifdef OLC_PGEX_TTF #undef OLC_PGEX_TTF FT_Library olc::Font::library; #endif #endif