The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
494 lines
17 KiB
494 lines
17 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2018 - 2023 OneLoneCoder.com
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
Portions of this software are copyright © 2023 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
#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);
|
|
|
|
error = FT_Set_Pixel_Sizes(fontFace, 0, fontSize*4);
|
|
}
|
|
|
|
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];
|
|
if(chr=='\r')continue;
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
private:
|
|
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;
|
|
int intMax = std::numeric_limits<int>::max();
|
|
int intMin = std::numeric_limits<int>::min();
|
|
|
|
int minX = intMax;
|
|
int minY = intMax;
|
|
int maxX = intMin;
|
|
int maxY = intMin;
|
|
|
|
Font *prevToUse = nullptr;
|
|
|
|
for (size_t i = 0; i < string.size(); i++) {
|
|
char32_t chr = string[i];
|
|
if(chr=='\r')continue;
|
|
|
|
int prevmaxX=maxX;
|
|
|
|
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_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;
|
|
maxX=prevmaxX; //Since this character is getting ignored, we need to revert back to the previous max X.
|
|
pen.x=minX;
|
|
}
|
|
FT_Done_Glyph(glyph);
|
|
}
|
|
|
|
minX /= 64;
|
|
minY /= 64;
|
|
maxX /= 64;
|
|
maxY /= 64;
|
|
return olc::FontRect{{minX, -maxY}, {maxX - minX, maxY - minY}};
|
|
}
|
|
|
|
public:
|
|
FontRect GetStringBounds(std::u32string string, float angle = 0.0f) {
|
|
FontRect temp=_GetStringBounds(string,angle);
|
|
return {temp.offset/4,{temp.size/4}};
|
|
}
|
|
|
|
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];
|
|
if(chr=='\r')continue;
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
std::u32string GetFontName(){
|
|
std::string name=fontFace->family_name;
|
|
return std::u32string(name.begin(),name.end());
|
|
}
|
|
|
|
static bool init() {
|
|
FT_Error error = FT_Init_FreeType(&library);
|
|
|
|
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 = 255;
|
|
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 |