commit 4fee0ce823c0d9bc27057154eff33cf94a134815 Author: sigonasr2 Date: Tue Jul 4 16:41:07 2023 -0700 Include newlines and empty string fix diff --git a/olcPGEX_TTF.h b/olcPGEX_TTF.h new file mode 100644 index 0000000..897106c --- /dev/null +++ b/olcPGEX_TTF.h @@ -0,0 +1,509 @@ +#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 \ No newline at end of file