commit
4fee0ce823
@ -0,0 +1,509 @@ |
||||
#ifndef OLC_PGEX_TTF_H |
||||
#define OLC_PGEX_TTF_H |
||||
|
||||
#include "olcPixelGameEngine.h" |
||||
|
||||
#ifdef WIN32 |
||||
#include <ft2build.h> |
||||
#pragma comment(lib, "freetype.lib") |
||||
#else |
||||
#include <freetype2/ft2build.h> |
||||
#endif |
||||
|
||||
#include <iostream> |
||||
#include <limits> |
||||
#include <string> |
||||
#include <vector> |
||||
#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<int>::max(); |
||||
constexpr int intMin = std::numeric_limits<int>::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<Font> fallbacks; |
||||
int fontSize; |
||||
|
||||
static FT_Library library; |
||||
}; |
||||
} // namespace olc
|
||||
|
||||
#ifdef OLC_PGEX_TTF |
||||
#undef OLC_PGEX_TTF |
||||
|
||||
FT_Library olc::Font::library; |
||||
|
||||
#endif |
||||
|
||||
#endif |
Loading…
Reference in new issue