diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..625fde3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +CMakeCache.txt +CMakeFiles +cmake_install.cmake +*~ +*.bak +*.exe +*.so +*.dll +Makefile +saland +nbproject diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f6fda5f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 2.8.9) +project (saland) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${saland_SOURCE_DIR}/src/cmake") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -std=c++11 -g") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -g") + +find_package(Boost COMPONENTS program_options REQUIRED) + +#Setup SDL2 +find_package(SDL2 REQUIRED) +include_directories(${SDL2_INCLUDE_DIR}) + +#Setup things that use pkg-config +find_package(PkgConfig REQUIRED) +pkg_search_module(SDL2MIXER REQUIRED SDL2_mixer) +pkg_search_module(SDL2IMAGE REQUIRED SDL2_image) +pkg_search_module(SDL2TTF REQUIRED SDL2_ttf) + +pkg_search_module(XML2 REQUIRED libxml-2.0) +include_directories(${XML2_INCLUDE_DIRS}) + +file(GLOB SOURCES "src/*.cpp" "src/*/*.cpp" "src/Libs/*/*.c" "src/Libs/*/*.cpp") + + +add_executable(saland src/saland.cpp) +TARGET_LINK_LIBRARIES( saland ${Boost_LIBRARIES} ) +target_link_libraries( saland ${SDL2_LIBRARY}) +target_link_libraries( saland physfs z b64) +target_link_libraries( saland ${XML2_LIBRARIES}) +target_link_libraries( saland ${SDL2MIXER_LIBRARIES} ${SDL2IMAGE_LIBRARIES} ${SDL2TTF_LIBRARIES}) diff --git a/src/Libs/.editorconfig b/src/Libs/.editorconfig new file mode 100644 index 0000000..aed8500 --- /dev/null +++ b/src/Libs/.editorconfig @@ -0,0 +1,11 @@ +#See http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines +[*] +end_of_line = lf + +[*.cpp,*.hpp,*.h] +indent_style = spaces diff --git a/src/Libs/NFont.cpp b/src/Libs/NFont.cpp new file mode 100644 index 0000000..7449efc --- /dev/null +++ b/src/Libs/NFont.cpp @@ -0,0 +1,1076 @@ +/* +NFont: A font class for SDL and SDL_Renderer +by Jonathan Dearborn + +See NFont.h for license info. +*/ + +#include "NFont.h" +#include "SDL_FontCache.h" + +#include +#include +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +#include +#include +#include +using std::string; +using std::list; + +#ifdef NFONT_USE_SDL_GPU +#define NFont_Target GPU_Target +#define NFont_Image GPU_Image +#define NFont_Log GPU_LogError +#else +#define NFont_Target SDL_Renderer +#define NFont_Image SDL_Texture +#define NFont_Log SDL_Log +#endif + +#define MIN(a,b) ((a) < (b)? (a) : (b)) +#define MAX(a,b) ((a) > (b)? (a) : (b)) + +#define NFONT_BUFFER_SIZE 1024 + +// vsnprintf replacement adapted from Valentin Milea: +// http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +#if defined(_MSC_VER) && _MSC_VER < 1900 + +#define vsnprintf c99_vsnprintf + +static int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +#endif + + + +static inline SDL_Surface* createSurface24(Uint32 width, Uint32 height) +{ + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 24, 0xFF0000, 0x00FF00, 0x0000FF, 0); + #else + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0); + #endif +} + +static inline SDL_Surface* createSurface32(Uint32 width, Uint32 height) +{ + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); + #else + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); + #endif +} + +static inline char* copyString(const char* c) +{ + if(c == NULL) + return NULL; + + char* result = new char[strlen(c)+1]; + strcpy(result, c); + + return result; +} + +static inline Uint32 getPixel(SDL_Surface *Surface, int x, int y) +{ + Uint8* bits; + Uint32 bpp; + + if(x < 0 || x >= Surface->w) + return 0; // Best I could do for errors + + bpp = Surface->format->BytesPerPixel; + bits = ((Uint8*)Surface->pixels) + y*Surface->pitch + x*bpp; + + switch (bpp) + { + case 1: + return *((Uint8*)Surface->pixels + y * Surface->pitch + x); + break; + case 2: + return *((Uint16*)Surface->pixels + y * Surface->pitch/2 + x); + break; + case 3: + // Endian-correct, but slower + Uint8 r, g, b; + r = *((bits)+Surface->format->Rshift/8); + g = *((bits)+Surface->format->Gshift/8); + b = *((bits)+Surface->format->Bshift/8); + return SDL_MapRGB(Surface->format, r, g, b); + break; + case 4: + return *((Uint32*)Surface->pixels + y * Surface->pitch/4 + x); + break; + } + + return 0; // FIXME: Handle errors better +} + +static inline void setPixel(SDL_Surface* surface, int x, int y, Uint32 color) +{ + int bpp = surface->format->BytesPerPixel; + Uint8* bits = ((Uint8 *)surface->pixels) + y*surface->pitch + x*bpp; + + /* Set the pixel */ + switch(bpp) + { + case 1: + *((Uint8 *)(bits)) = (Uint8)color; + break; + case 2: + *((Uint16 *)(bits)) = (Uint16)color; + break; + case 3: { /* Format/endian independent */ + Uint8 r,g,b; + r = (color >> surface->format->Rshift) & 0xFF; + g = (color >> surface->format->Gshift) & 0xFF; + b = (color >> surface->format->Bshift) & 0xFF; + *((bits)+surface->format->Rshift/8) = r; + *((bits)+surface->format->Gshift/8) = g; + *((bits)+surface->format->Bshift/8) = b; + } + break; + case 4: + *((Uint32 *)(bits)) = (Uint32)color; + break; + } +} + +static inline void drawPixel(SDL_Surface *surface, Sint16 x, Sint16 y, Uint32 color, Uint8 alpha) +{ + if(x > surface->clip_rect.x + surface->clip_rect.w || x < surface->clip_rect.x || y > surface->clip_rect.y + surface->clip_rect.h || y < surface->clip_rect.y) + return; + + switch (surface->format->BytesPerPixel) + { + case 1: { /* Assuming 8-bpp */ + + Uint8 *pixel = (Uint8 *)surface->pixels + y*surface->pitch + x; + + Uint8 dR = surface->format->palette->colors[*pixel].r; + Uint8 dG = surface->format->palette->colors[*pixel].g; + Uint8 dB = surface->format->palette->colors[*pixel].b; + Uint8 sR = surface->format->palette->colors[color].r; + Uint8 sG = surface->format->palette->colors[color].g; + Uint8 sB = surface->format->palette->colors[color].b; + + dR = dR + ((sR-dR)*alpha >> 8); + dG = dG + ((sG-dG)*alpha >> 8); + dB = dB + ((sB-dB)*alpha >> 8); + + *pixel = SDL_MapRGB(surface->format, dR, dG, dB); + + } + break; + + case 2: { /* Probably 15-bpp or 16-bpp */ + + Uint32 Rmask = surface->format->Rmask, Gmask = surface->format->Gmask, Bmask = surface->format->Bmask, Amask = surface->format->Amask; + Uint16 *pixel = (Uint16 *)surface->pixels + y*surface->pitch/2 + x; + Uint32 dc = *pixel; + Uint32 R,G,B,A=0; + + R = ((dc & Rmask) + (( (color & Rmask) - (dc & Rmask) ) * alpha >> 8)) & Rmask; + G = ((dc & Gmask) + (( (color & Gmask) - (dc & Gmask) ) * alpha >> 8)) & Gmask; + B = ((dc & Bmask) + (( (color & Bmask) - (dc & Bmask) ) * alpha >> 8)) & Bmask; + if( Amask ) + A = ((dc & Amask) + (( (color & Amask) - (dc & Amask) ) * alpha >> 8)) & Amask; + + *pixel= R | G | B | A; + + } + break; + + case 3: { /* Slow 24-bpp mode, usually not used */ + Uint8 *pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3; + Uint8 rshift8=surface->format->Rshift/8; + Uint8 gshift8=surface->format->Gshift/8; + Uint8 bshift8=surface->format->Bshift/8; + Uint8 ashift8=surface->format->Ashift/8; + + + + Uint8 dR, dG, dB, dA; + Uint8 sR, sG, sB, sA; + + pix = (Uint8 *)surface->pixels + y * surface->pitch + x*3; + + dR = *((pix)+rshift8); + dG = *((pix)+gshift8); + dB = *((pix)+bshift8); + dA = *((pix)+ashift8); + + sR = (color>>surface->format->Rshift)&0xff; + sG = (color>>surface->format->Gshift)&0xff; + sB = (color>>surface->format->Bshift)&0xff; + sA = (color>>surface->format->Ashift)&0xff; + + dR = dR + ((sR-dR)*alpha >> 8); + dG = dG + ((sG-dG)*alpha >> 8); + dB = dB + ((sB-dB)*alpha >> 8); + dA = dA + ((sA-dA)*alpha >> 8); + + *((pix)+rshift8) = dR; + *((pix)+gshift8) = dG; + *((pix)+bshift8) = dB; + *((pix)+ashift8) = dA; + + } + break; + + case 4: { /* Probably 32-bpp */ + Uint32 Rmask = surface->format->Rmask, Gmask = surface->format->Gmask, Bmask = surface->format->Bmask, Amask = surface->format->Amask; + Uint32* pixel = (Uint32*)surface->pixels + y*surface->pitch/4 + x; + Uint32 source = *pixel; + Uint32 R,G,B,A; + R = color & Rmask; + G = color & Gmask; + B = color & Bmask; + A = 0; // keep this as 0 to avoid corruption of non-alpha surfaces + + // Blend and keep dest alpha + if( alpha != SDL_ALPHA_OPAQUE ){ + R = ((source & Rmask) + (( R - (source & Rmask) ) * alpha >> 8)) & Rmask; + G = ((source & Gmask) + (( G - (source & Gmask) ) * alpha >> 8)) & Gmask; + B = ((source & Bmask) + (( B - (source & Bmask) ) * alpha >> 8)) & Bmask; + } + if(Amask) + A = (source & Amask); + + *pixel = R | G | B | A; + } + break; + } +} + +static inline NFont::Rectf rectUnion(const NFont::Rectf& A, const NFont::Rectf& B) +{ + float x,x2,y,y2; + x = MIN(A.x, B.x); + y = MIN(A.y, B.y); + x2 = MAX(A.x+A.w, B.x+B.w); + y2 = MAX(A.y+A.h, B.y+B.h); + NFont::Rectf result(x, y, MAX(0, x2 - x), MAX(0, y2 - y)); + return result; +} + +// Adapted from SDL_IntersectRect +static inline NFont::Rectf rectIntersect(const NFont::Rectf& A, const NFont::Rectf& B) +{ + NFont::Rectf result; + float Amin, Amax, Bmin, Bmax; + + // Horizontal intersection + Amin = A.x; + Amax = Amin + A.w; + Bmin = B.x; + Bmax = Bmin + B.w; + if(Bmin > Amin) + Amin = Bmin; + result.x = Amin; + if(Bmax < Amax) + Amax = Bmax; + result.w = Amax - Amin > 0 ? Amax - Amin : 0; + + // Vertical intersection + Amin = A.y; + Amax = Amin + A.h; + Bmin = B.y; + Bmax = Bmin + B.h; + if(Bmin > Amin) + Amin = Bmin; + result.y = Amin; + if(Bmax < Amax) + Amax = Bmax; + result.h = Amax - Amin > 0 ? Amax - Amin : 0; + + return result; +} + +static inline SDL_Surface* copySurface(SDL_Surface *Surface) +{ + return SDL_ConvertSurface(Surface, Surface->format, Surface->flags); +} + + + + + + + + + +NFont::Color::Color() + : r(0), g(0), b(0), a(255) +{} +NFont::Color::Color(Uint8 r, Uint8 g, Uint8 b) + : r(r), g(g), b(b), a(255) +{} +NFont::Color::Color(Uint8 r, Uint8 g, Uint8 b, Uint8 a) + : r(r), g(g), b(b), a(a) +{} +NFont::Color::Color(const SDL_Color& color) + : r(color.r), g(color.g), b(color.b), a(color.a) +{} + +NFont::Color& NFont::Color::rgb(Uint8 R, Uint8 G, Uint8 B) +{ + r = R; + g = G; + b = B; + + return *this; +} + +NFont::Color& NFont::Color::rgba(Uint8 R, Uint8 G, Uint8 B, Uint8 A) +{ + r = R; + g = G; + b = B; + a = A; + + return *this; +} + +NFont::Color& NFont::Color::color(const SDL_Color& color) +{ + r = color.r; + g = color.g; + b = color.b; + a = color.a; + + return *this; +} + +SDL_Color NFont::Color::to_SDL_Color() const +{ + SDL_Color c = {r, g, b, a}; + return c; +} + + + + +NFont::Rectf::Rectf() + : x(0), y(0), w(0), h(0) +{} + +NFont::Rectf::Rectf(float x, float y) + : x(x), y(y), w(0), h(0) +{} + +NFont::Rectf::Rectf(float x, float y, float w, float h) + : x(x), y(y), w(w), h(h) +{} + +NFont::Rectf::Rectf(const SDL_Rect& rect) + : x(rect.x), y(rect.y), w(rect.w), h(rect.h) +{} + +#ifdef NFONT_USE_SDL_GPU +NFont::Rectf::Rectf(const GPU_Rect& rect) + : x(rect.x), y(rect.y), w(rect.w), h(rect.h) +{} +#endif + +SDL_Rect NFont::Rectf::to_SDL_Rect() const +{ + SDL_Rect r = {int(x), int(y), int(w), int(h)}; + return r; +} + +#ifdef NFONT_USE_SDL_GPU +GPU_Rect NFont::Rectf::to_GPU_Rect() const +{ + return GPU_MakeRect(x, y, w, h); +} +#endif + + + + + + + +char* NFont::buffer = NULL; // Shared buffer for efficient drawing + + +// Constructors +NFont::NFont() +{ + init(); +} + + +#ifdef NFONT_USE_SDL_GPU +NFont::NFont(TTF_Font* ttf) +{ + init(); + load(ttf, FC_GetDefaultColor(font)); +} +NFont::NFont(TTF_Font* ttf, const NFont::Color& color) +{ + init(); + load(ttf, color); +} +NFont::NFont(const char* filename_ttf, Uint32 pointSize) +{ + init(); + load(filename_ttf, pointSize); +} +NFont::NFont(const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style) +{ + init(); + load(filename_ttf, pointSize, color, style); +} +NFont::NFont(SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style) +{ + init(); + load(file_rwops_ttf, own_rwops, pointSize, color, style); +} + +#else + +NFont::NFont(NFont_Target* renderer, TTF_Font* ttf) +{ + init(); + load(renderer, ttf, FC_GetDefaultColor(font)); +} +NFont::NFont(NFont_Target* renderer, TTF_Font* ttf, const NFont::Color& color) +{ + init(); + load(renderer, ttf, color); +} +NFont::NFont(NFont_Target* renderer, const char* filename_ttf, Uint32 pointSize) +{ + init(); + load(renderer, filename_ttf, pointSize); +} +NFont::NFont(NFont_Target* renderer, const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style) +{ + init(); + load(renderer, filename_ttf, pointSize, color, style); +} +NFont::NFont(NFont_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style) +{ + init(); + load(renderer, file_rwops_ttf, own_rwops, pointSize, color, style); +} +#endif + + +NFont::~NFont() +{ + FC_FreeFont(font); +} + +void NFont::init() +{ + font = FC_CreateFont(); + + if(buffer == NULL) + buffer = new char[NFONT_BUFFER_SIZE]; +} + + + + + + +void NFont::setLoadingString(const char* str) +{ + FC_SetLoadingString(font, str); +} + +#ifdef NFONT_USE_SDL_GPU +bool NFont::load(TTF_Font* ttf) +#else +bool NFont::load(NFont_Target* renderer, TTF_Font* ttf) +#endif +{ + #ifdef NFONT_USE_SDL_GPU + return load(ttf, FC_GetDefaultColor(font)); + #else + return load(renderer, ttf, Color(0,0,0,255)); + #endif +} + +#ifdef NFONT_USE_SDL_GPU +bool NFont::load(TTF_Font* ttf, const NFont::Color& color) +#else +bool NFont::load(NFont_Target* renderer, TTF_Font* ttf, const NFont::Color& color) +#endif +{ + if(ttf == NULL) + return false; + + #ifndef NFONT_USE_SDL_GPU + if(renderer == NULL) + return false; + #endif + + FC_ClearFont(font); + #ifdef NFONT_USE_SDL_GPU + return FC_LoadFontFromTTF(font, ttf, color.to_SDL_Color()); + #else + return FC_LoadFontFromTTF(font, renderer, ttf, color.to_SDL_Color()); + #endif +} + +#ifdef NFONT_USE_SDL_GPU +bool NFont::load(const char* filename_ttf, Uint32 pointSize) +#else +bool NFont::load(NFont_Target* renderer, const char* filename_ttf, Uint32 pointSize) +#endif +{ + #ifdef NFONT_USE_SDL_GPU + return load(filename_ttf, pointSize, Color(0,0,0,255)); + #else + return load(renderer, filename_ttf, pointSize, Color(0,0,0,255)); + #endif +} + +#ifdef NFONT_USE_SDL_GPU +bool NFont::load(const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style) +#else +bool NFont::load(NFont_Target* renderer, const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style) +#endif +{ + FC_ClearFont(font); + #ifdef NFONT_USE_SDL_GPU + return FC_LoadFont(font, filename_ttf, pointSize, color.to_SDL_Color(), style); + #else + return FC_LoadFont(font, renderer, filename_ttf, pointSize, color.to_SDL_Color(), style); + #endif +} + +#ifdef NFONT_USE_SDL_GPU +bool NFont::load(SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style) +#else +bool NFont::load(NFont_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style) +#endif +{ + FC_ClearFont(font); + #ifdef NFONT_USE_SDL_GPU + return FC_LoadFont_RW(font, file_rwops_ttf, own_rwops, pointSize, color.to_SDL_Color(), style); + #else + return FC_LoadFont_RW(font, renderer, file_rwops_ttf, own_rwops, pointSize, color.to_SDL_Color(), style); + #endif +} + + + +void NFont::free() +{ + FC_ClearFont(font); +} + + + +NFont::Rectf NFont::draw(NFont_Target* dest, float x, float y, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_Draw(font, dest, x, y, "%s", buffer); +} + +/*static int getIndexPastWidth(const char* text, int width, const int* charWidth) +{ + int charnum; + int len = strlen(text); + + for (int index = 0; index < len; index++) + { + char c = text[index]; + charnum = (unsigned char)(c) - 33; + + // spaces and nonprintable characters + if (c == ' ' || charnum > 222) + { + width -= charWidth[0]; + } + else + width -= charWidth[charnum]; + + if(width <= 0) + return index; + } + return 0; +}*/ + + + +/*static list explode(const string& str, char delimiter) +{ + list result; + + size_t oldPos = 0; + size_t pos = str.find_first_of(delimiter); + while(pos != string::npos) + { + result.push_back(str.substr(oldPos, pos - oldPos)); + oldPos = pos+1; + pos = str.find_first_of(delimiter, oldPos); + } + + result.push_back(str.substr(oldPos, string::npos)); + + return result; +}*/ + +NFont::Rectf NFont::drawBox(NFont_Target* dest, const Rectf& box, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(box.x, box.y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + #ifdef NFONT_USE_SDL_GPU + return FC_DrawBox(font, dest, box.to_GPU_Rect(), "%s", buffer); + #else + return FC_DrawBox(font, dest, box.to_SDL_Rect(), "%s", buffer); + #endif +} + +static FC_AlignEnum translate_enum_NFont_to_FC(NFont::AlignEnum align) +{ + switch(align) + { + case NFont::LEFT: + return FC_ALIGN_LEFT; + case NFont::CENTER: + return FC_ALIGN_CENTER; + case NFont::RIGHT: + return FC_ALIGN_RIGHT; + default: + return FC_ALIGN_LEFT; + } +} + +NFont::Rectf NFont::drawBox(NFont_Target* dest, const Rectf& box, AlignEnum align, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(box.x, box.y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + #ifdef NFONT_USE_SDL_GPU + return FC_DrawBoxAlign(font, dest, box.to_GPU_Rect(), translate_enum_NFont_to_FC(align), "%s", buffer); + #else + return FC_DrawBoxAlign(font, dest, box.to_SDL_Rect(), translate_enum_NFont_to_FC(align), "%s", buffer); + #endif +} + +NFont::Rectf NFont::drawBox(NFont_Target* dest, const Rectf& box, const Scale& scale, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(box.x, box.y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + #ifdef NFONT_USE_SDL_GPU + return FC_DrawBoxScale(font, dest, box.to_GPU_Rect(), FC_MakeScale(scale.x, scale.y), "%s", buffer); + #else + return FC_DrawBoxScale(font, dest, box.to_SDL_Rect(), FC_MakeScale(scale.x, scale.y), "%s", buffer); + #endif +} + +NFont::Rectf NFont::drawBox(NFont_Target* dest, const Rectf& box, const Color& color, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(box.x, box.y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + #ifdef NFONT_USE_SDL_GPU + return FC_DrawBoxColor(font, dest, box.to_GPU_Rect(), color.to_SDL_Color(), "%s", buffer); + #else + return FC_DrawBoxColor(font, dest, box.to_SDL_Rect(), color.to_SDL_Color(), "%s", buffer); + #endif +} + +NFont::Rectf NFont::drawBox(NFont_Target* dest, const Rectf& box, const Effect& effect, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(box.x, box.y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + #ifdef NFONT_USE_SDL_GPU + return FC_DrawBoxEffect(font, dest, box.to_GPU_Rect(), FC_MakeEffect(translate_enum_NFont_to_FC(effect.alignment), FC_MakeScale(effect.scale.x, effect.scale.y), effect.color.to_SDL_Color()), "%s", buffer); + #else + return FC_DrawBoxEffect(font, dest, box.to_SDL_Rect(), FC_MakeEffect(translate_enum_NFont_to_FC(effect.alignment), FC_MakeScale(effect.scale.x, effect.scale.y), effect.color.to_SDL_Color()), "%s", buffer); + #endif +} + +NFont::Rectf NFont::drawColumn(NFont_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawColumn(font, dest, x, y, width, "%s", buffer); +} + +NFont::Rectf NFont::drawColumn(NFont_Target* dest, float x, float y, Uint16 width, AlignEnum align, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawColumnAlign(font, dest, x, y, width, translate_enum_NFont_to_FC(align), "%s", buffer); +} + +NFont::Rectf NFont::drawColumn(NFont_Target* dest, float x, float y, Uint16 width, const Scale& scale, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawColumnScale(font, dest, x, y, width, FC_MakeScale(scale.x, scale.y), "%s", buffer); +} + +NFont::Rectf NFont::drawColumn(NFont_Target* dest, float x, float y, Uint16 width, const Color& color, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawColumnColor(font, dest, x, y, width, color.to_SDL_Color(), "%s", buffer); +} + +NFont::Rectf NFont::drawColumn(NFont_Target* dest, float x, float y, Uint16 width, const Effect& effect, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + #ifdef NFONT_USE_SDL_GPU + return FC_DrawColumnEffect(font, dest, x, y, width, FC_MakeEffect(translate_enum_NFont_to_FC(effect.alignment), FC_MakeScale(effect.scale.x, effect.scale.y), effect.color.to_SDL_Color()), "%s", buffer); + #else + return FC_DrawColumnEffect(font, dest, x, y, width, FC_MakeEffect(translate_enum_NFont_to_FC(effect.alignment), FC_MakeScale(effect.scale.x, effect.scale.y), effect.color.to_SDL_Color()), "%s", buffer); + #endif +} + + + +NFont::Rectf NFont::draw(NFont_Target* dest, float x, float y, const Scale& scale, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawScale(font, dest, x, y, FC_MakeScale(scale.x, scale.y), "%s", buffer); +} + +NFont::Rectf NFont::draw(NFont_Target* dest, float x, float y, AlignEnum align, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawAlign(font, dest, x, y, translate_enum_NFont_to_FC(align), "%s", buffer); +} + +NFont::Rectf NFont::draw(NFont_Target* dest, float x, float y, const Color& color, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawColor(font, dest, x, y, color.to_SDL_Color(), "%s", buffer); +} + + +NFont::Rectf NFont::draw(NFont_Target* dest, float x, float y, const Effect& effect, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(x, y, 0, 0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_DrawEffect(font, dest, x, y, FC_MakeEffect(translate_enum_NFont_to_FC(effect.alignment), FC_MakeScale(effect.scale.x, effect.scale.y), effect.color.to_SDL_Color()), "%s", buffer); +} + + + + +// Getters + + +NFont::FilterEnum NFont::getFilterMode() const +{ + FC_FilterEnum f = FC_GetFilterMode(font); + if(f == FC_FILTER_LINEAR) + return NFont::LINEAR; + return NFont::NEAREST; +} + +Uint16 NFont::getHeight() const +{ + return FC_GetLineHeight(font); +} + +Uint16 NFont::getHeight(const char* formatted_text, ...) const +{ + if(formatted_text == NULL) + return 0; + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetHeight(font, "%s", buffer); +} + +Uint16 NFont::getWidth(const char* formatted_text, ...) +{ + if (formatted_text == NULL) + return 0; + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetWidth(font, "%s", buffer); +} + + +NFont::Rectf NFont::getCharacterOffset(Uint16 position_index, int column_width, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return Rectf(0,0,0,0); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetCharacterOffset(font, position_index, column_width, "%s", buffer); +} + +// Given an offset (x,y) from the text draw position (the upper-left corner), returns the character position (UTF-8 index) +Uint16 NFont::getPositionFromOffset(float x, float y, int column_width, NFont::AlignEnum align, const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return 0; + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetPositionFromOffset(font, x, y, column_width, translate_enum_NFont_to_FC(align), "%s", buffer); +} + + +Uint16 NFont::getColumnHeight(Uint16 width, const char* formatted_text, ...) +{ + if(formatted_text == NULL || width == 0) + return 0; + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetColumnHeight(font, width, "%s", buffer); +} + +int NFont::getAscent(const char character) +{ + return FC_GetAscent(font, "%c", character); +} + +int NFont::getAscent() const +{ + return FC_GetAscent(font, NULL); +} + +int NFont::getAscent(const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return FC_GetAscent(font, NULL); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetAscent(font, "%s", buffer); +} + +int NFont::getDescent(const char character) +{ + return FC_GetDescent(font, "%c", character); +} + +int NFont::getDescent() const +{ + return FC_GetDescent(font, NULL); +} + +int NFont::getDescent(const char* formatted_text, ...) +{ + if(formatted_text == NULL) + return FC_GetDescent(font, NULL); + + va_list lst; + va_start(lst, formatted_text); + vsnprintf(buffer, NFONT_BUFFER_SIZE, formatted_text, lst); + va_end(lst); + + return FC_GetDescent(font, "%s", buffer); +} + +int NFont::getSpacing() const +{ + return FC_GetSpacing(font); +} + +int NFont::getLineSpacing() const +{ + return FC_GetLineSpacing(font); +} + +Uint16 NFont::getBaseline() const +{ + return FC_GetBaseline(font); +} + +Uint16 NFont::getMaxWidth() const +{ + return FC_GetMaxWidth(font); +} + +NFont::Color NFont::getDefaultColor() const +{ + return FC_GetDefaultColor(font); +} + + +int NFont::getNumCacheLevels() const +{ + return FC_GetNumCacheLevels(font); +} + +NFont_Image* NFont::getCacheLevel(int level) const +{ + return FC_GetGlyphCacheLevel(font, level); +} + + + + + +// Setters + +void NFont::setFilterMode(NFont::FilterEnum filter) +{ + if(filter == NFont::LINEAR) + FC_SetFilterMode(font, FC_FILTER_LINEAR); + else + FC_SetFilterMode(font, FC_FILTER_NEAREST); +} + +void NFont::setSpacing(int LetterSpacing) +{ + FC_SetSpacing(font, LetterSpacing); +} + +void NFont::setLineSpacing(int LineSpacing) +{ + FC_SetLineSpacing(font, LineSpacing); +} + +void NFont::setBaseline() +{ + +} + +void NFont::setDefaultColor(const Color& color) +{ + FC_SetDefaultColor(font, color.to_SDL_Color()); +} + +void NFont::enableTTFOwnership() +{ + +} + + + + diff --git a/src/Libs/NFont.h b/src/Libs/NFont.h new file mode 100644 index 0000000..27a3f3f --- /dev/null +++ b/src/Libs/NFont.h @@ -0,0 +1,320 @@ +/* +NFont v5.0.0: A font class for SDL and SDL_Renderer +by Jonathan Dearborn +Dedicated to the memory of Florian Hufsky + +License: + The short: + Use it however you'd like, but keep the copyright and license notice + whenever these files or parts of them are distributed in uncompiled form. + + The long: +Copyright (c) 2016 Jonathan Dearborn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef _NFONT_H__ +#define _NFONT_H__ + +#include "SDL.h" + +#if defined(FC_USE_SDL_GPU) && !defined(NFONT_USE_SDL_GPU) +#define NFONT_USE_SDL_GPU +#endif + +#ifdef NFONT_USE_SDL_GPU + #include "SDL_gpu.h" +#endif + +#ifndef NFONT_FORMAT + +#if ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define NFONT_FORMAT(X) __attribute__ ((format (printf, X, X+1))) +#else +#define NFONT_FORMAT(X) +#endif + +#endif + +#include "stdarg.h" + +// Let's pretend this exists... +#ifndef TTF_STYLE_OUTLINE + #define TTF_STYLE_OUTLINE 16 +#endif + +struct FC_Font; + +typedef struct _TTF_Font TTF_Font; + +// Differences between SDL_Renderer and SDL_gpu +#ifdef NFONT_USE_SDL_GPU +#define NFont_Image GPU_Image +#else +#define NFont_Image SDL_Texture +#endif + +#if defined(NFONT_DLL) || defined(NFONT_DLL_EXPORT) + #ifdef NFONT_DLL_EXPORT + #define NFONT_EXPORT __declspec(dllexport) + #else + #define NFONT_EXPORT __declspec(dllimport) + #endif +#else + #define NFONT_EXPORT +#endif + +class NFONT_EXPORT NFont +{ + public: + + class NFONT_EXPORT Color + { + public: + + Uint8 r, g, b, a; + + Color(); + Color(Uint8 r, Uint8 g, Uint8 b); + Color(Uint8 r, Uint8 g, Uint8 b, Uint8 a); + Color(const SDL_Color& color); + + Color& rgb(Uint8 R, Uint8 G, Uint8 B); + Color& rgba(Uint8 R, Uint8 G, Uint8 B, Uint8 A); + Color& color(const SDL_Color& color); + + SDL_Color to_SDL_Color() const; + }; + + class NFONT_EXPORT Rectf + { + public: + float x, y; + float w, h; + + Rectf(); + Rectf(float x, float y); + Rectf(float x, float y, float w, float h); + Rectf(const SDL_Rect& rect); + + SDL_Rect to_SDL_Rect() const; + + #ifdef NFONT_USE_SDL_GPU + Rectf(const GPU_Rect& rect); + GPU_Rect to_GPU_Rect() const; + #endif + }; + + + enum AlignEnum {LEFT, CENTER, RIGHT}; + enum FilterEnum {NEAREST, LINEAR}; + + class NFONT_EXPORT Scale + { + public: + + float x; + float y; + + enum ScaleTypeEnum {NEAREST}; + ScaleTypeEnum type; + + Scale() + : x(1.0f), y(1.0f), type(NEAREST) + {} + Scale(float xy) + : x(xy), y(xy), type(NEAREST) + {} + Scale(float xy, ScaleTypeEnum type) + : x(xy), y(xy), type(type) + {} + Scale(float x, float y) + : x(x), y(y), type(NEAREST) + {} + Scale(float x, float y, ScaleTypeEnum type) + : x(x), y(y), type(type) + {} + }; + + class NFONT_EXPORT Effect + { + public: + AlignEnum alignment; + Scale scale; + bool use_color; + Color color; + + Effect() + : alignment(LEFT), use_color(false), color(255, 255, 255, 255) + {} + + Effect(const Scale& scale) + : alignment(LEFT), scale(scale), use_color(false), color(255, 255, 255, 255) + {} + Effect(AlignEnum alignment) + : alignment(alignment), use_color(false), color(255, 255, 255, 255) + {} + Effect(const Color& color) + : alignment(LEFT), use_color(true), color(color) + {} + + Effect(AlignEnum alignment, const Scale& scale) + : alignment(alignment), scale(scale), use_color(false), color(255, 255, 255, 255) + {} + Effect(AlignEnum alignment, const Color& color) + : alignment(alignment), use_color(true), color(color) + {} + Effect(const Scale& scale, const Color& color) + : alignment(LEFT), scale(scale), use_color(true), color(color) + {} + Effect(AlignEnum alignment, const Scale& scale, const Color& color) + : alignment(alignment), scale(scale), use_color(true), color(color) + {} + }; + + + // Constructors + NFont(); + NFont(const NFont& font); + #ifdef NFONT_USE_SDL_GPU + NFont(SDL_Surface* src); + NFont(TTF_Font* ttf); + NFont(TTF_Font* ttf, const NFont::Color& color); + NFont(const char* filename_ttf, Uint32 pointSize); + NFont(const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style = 0); + NFont(SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style = 0); + #else + NFont(SDL_Renderer* renderer, SDL_Surface* src); + NFont(SDL_Renderer* renderer, TTF_Font* ttf); + NFont(SDL_Renderer* renderer, TTF_Font* ttf, const NFont::Color& color); + NFont(SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize); + NFont(SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style = 0); + NFont(SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style = 0); + #endif + + ~NFont(); + + NFont& operator=(const NFont& font); + + // Loading + void setLoadingString(const char* str); + + #ifdef NFONT_USE_SDL_GPU + bool load(SDL_Surface* FontSurface); + bool load(TTF_Font* ttf); + bool load(TTF_Font* ttf, const NFont::Color& color); + bool load(const char* filename_ttf, Uint32 pointSize); + bool load(const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style = 0); + bool load(SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style = 0); + #else + bool load(SDL_Renderer* renderer, SDL_Surface* FontSurface); + bool load(SDL_Renderer* renderer, TTF_Font* ttf); + bool load(SDL_Renderer* renderer, TTF_Font* ttf, const NFont::Color& color); + bool load(SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize); + bool load(SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize, const NFont::Color& color, int style = 0); + bool load(SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, const NFont::Color& color, int style = 0); + #endif + + void free(); + + // Drawing + #ifdef NFONT_USE_SDL_GPU + Rectf draw(GPU_Target* dest, float x, float y, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf draw(GPU_Target* dest, float x, float y, AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf draw(GPU_Target* dest, float x, float y, const Scale& scale, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf draw(GPU_Target* dest, float x, float y, const Color& color, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf draw(GPU_Target* dest, float x, float y, const Effect& effect, const char* formatted_text, ...) NFONT_FORMAT(6); + + Rectf drawBox(GPU_Target* dest, const Rectf& box, const char* formatted_text, ...) NFONT_FORMAT(4); + Rectf drawBox(GPU_Target* dest, const Rectf& box, AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf drawBox(GPU_Target* dest, const Rectf& box, const Scale& scale, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf drawBox(GPU_Target* dest, const Rectf& box, const Color& color, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf drawBox(GPU_Target* dest, const Rectf& box, const Effect& effect, const char* formatted_text, ...) NFONT_FORMAT(5); + + Rectf drawColumn(GPU_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf drawColumn(GPU_Target* dest, float x, float y, Uint16 width, AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(7); + Rectf drawColumn(GPU_Target* dest, float x, float y, Uint16 width, const Scale& scale, const char* formatted_text, ...) NFONT_FORMAT(7); + Rectf drawColumn(GPU_Target* dest, float x, float y, Uint16 width, const Color& color, const char* formatted_text, ...) NFONT_FORMAT(7); + Rectf drawColumn(GPU_Target* dest, float x, float y, Uint16 width, const Effect& effect, const char* formatted_text, ...) NFONT_FORMAT(7); + #else + Rectf draw(SDL_Renderer* dest, float x, float y, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf draw(SDL_Renderer* dest, float x, float y, AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf draw(SDL_Renderer* dest, float x, float y, const Scale& scale, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf draw(SDL_Renderer* dest, float x, float y, const Color& color, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf draw(SDL_Renderer* dest, float x, float y, const Effect& effect, const char* formatted_text, ...) NFONT_FORMAT(6); + + Rectf drawBox(SDL_Renderer* dest, const Rectf& box, const char* formatted_text, ...) NFONT_FORMAT(4); + Rectf drawBox(SDL_Renderer* dest, const Rectf& box, AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf drawBox(SDL_Renderer* dest, const Rectf& box, const Scale& scale, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf drawBox(SDL_Renderer* dest, const Rectf& box, const Color& color, const char* formatted_text, ...) NFONT_FORMAT(5); + Rectf drawBox(SDL_Renderer* dest, const Rectf& box, const Effect& effect, const char* formatted_text, ...) NFONT_FORMAT(5); + + Rectf drawColumn(SDL_Renderer* dest, float x, float y, Uint16 width, const char* formatted_text, ...) NFONT_FORMAT(6); + Rectf drawColumn(SDL_Renderer* dest, float x, float y, Uint16 width, AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(7); + Rectf drawColumn(SDL_Renderer* dest, float x, float y, Uint16 width, const Scale& scale, const char* formatted_text, ...) NFONT_FORMAT(7); + Rectf drawColumn(SDL_Renderer* dest, float x, float y, Uint16 width, const Color& color, const char* formatted_text, ...) NFONT_FORMAT(7); + Rectf drawColumn(SDL_Renderer* dest, float x, float y, Uint16 width, const Effect& effect, const char* formatted_text, ...) NFONT_FORMAT(7); + #endif + + // Getters + FilterEnum getFilterMode() const; + Uint16 getHeight() const; + Uint16 getHeight(const char* formatted_text, ...) const NFONT_FORMAT(2); + Uint16 getWidth(const char* formatted_text, ...) NFONT_FORMAT(2); + Rectf getCharacterOffset(Uint16 position_index, int column_width, const char* formatted_text, ...) NFONT_FORMAT(4); + Uint16 getPositionFromOffset(float x, float y, int column_width, NFont::AlignEnum align, const char* formatted_text, ...) NFONT_FORMAT(6); + Uint16 getColumnHeight(Uint16 width, const char* formatted_text, ...) NFONT_FORMAT(3); + int getSpacing() const; + int getLineSpacing() const; + Uint16 getBaseline() const; + int getAscent() const; + int getAscent(const char character); + int getAscent(const char* formatted_text, ...) NFONT_FORMAT(2); + int getDescent() const; + int getDescent(const char character); + int getDescent(const char* formatted_text, ...) NFONT_FORMAT(2); + Uint16 getMaxWidth() const; + Color getDefaultColor() const; + + int getNumCacheLevels() const; + NFont_Image* getCacheLevel(int level) const; + + // Setters + void setFilterMode(FilterEnum filter); + void setSpacing(int LetterSpacing); + void setLineSpacing(int LineSpacing); + void setBaseline(); + void setBaseline(Uint16 Baseline); + void setDefaultColor(const Color& color); + + void enableTTFOwnership(); + + private: + + static char* buffer; + FC_Font* font; + + void init(); // Common constructor + +}; + + + +#endif // _NFONT_H__ diff --git a/src/Libs/SDL_FontCache.c b/src/Libs/SDL_FontCache.c new file mode 100644 index 0000000..16b0925 --- /dev/null +++ b/src/Libs/SDL_FontCache.c @@ -0,0 +1,2640 @@ +/* +SDL_FontCache: A font cache for SDL and SDL_ttf +by Jonathan Dearborn + +See SDL_FontCache.h for license info. +*/ + +#include "SDL_FontCache.h" + +#include +#include +#include + +// Visual C does not support static inline +#ifndef static_inline + #ifdef _MSC_VER + #define static_inline static + #else + #define static_inline static inline + #endif +#endif + +#if SDL_VERSION_ATLEAST(2,0,0) + #define FC_GET_ALPHA(sdl_color) ((sdl_color).a) +#else + #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused) +#endif + +// Need SDL_RenderIsClipEnabled() for proper clipping support +#if SDL_VERSION_ATLEAST(2,0,4) + #define ENABLE_SDL_CLIPPING +#endif + +#define FC_MIN(a,b) ((a) < (b)? (a) : (b)) +#define FC_MAX(a,b) ((a) > (b)? (a) : (b)) + + +// vsnprintf replacement from Valentin Milea: +// http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +#if defined(_MSC_VER) && _MSC_VER < 1900 + +#define snprintf c99_snprintf +#define vsnprintf c99_vsnprintf + +__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + + return count; +} + +#endif + + +#define FC_EXTRACT_VARARGS(buffer, start_args) \ +{ \ + va_list lst; \ + va_start(lst, start_args); \ + vsnprintf(buffer, fc_buffer_size, start_args, lst); \ + va_end(lst); \ +} + +// Extra pixels of padding around each glyph to avoid linear filtering artifacts +#define FC_CACHE_PADDING 1 + + + +static Uint8 has_clip(FC_Target* dest) +{ + #ifdef FC_USE_SDL_GPU + return dest->use_clip_rect; + #elif defined(ENABLE_SDL_CLIPPING) + return SDL_RenderIsClipEnabled(dest); + #else + return 0; + #endif +} + +static FC_Rect get_clip(FC_Target* dest) +{ + #ifdef FC_USE_SDL_GPU + return dest->clip_rect; + #elif defined(ENABLE_SDL_CLIPPING) + SDL_Rect r; + SDL_RenderGetClipRect(dest, &r); + return r; + #else + SDL_Rect r = {0, 0, 0, 0}; + return r; + #endif +} + +static void set_clip(FC_Target* dest, FC_Rect* rect) +{ + #ifdef FC_USE_SDL_GPU + if(rect != NULL) + GPU_SetClipRect(dest, *rect); + else + GPU_UnsetClip(dest); + #elif defined(ENABLE_SDL_CLIPPING) + SDL_RenderSetClipRect(dest, rect); + #endif +} + +static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + #ifdef FC_USE_SDL_GPU + GPU_SetRGBA(src, r, g, b, a); + #else + SDL_SetTextureColorMod(src, r, g, b); + SDL_SetTextureAlphaMod(src, a); + #endif +} + + + +static char* new_concat(const char* a, const char* b) +{ + // Create new buffer + unsigned int size = strlen(a) + strlen(b); + char* new_string = (char*)malloc(size+1); + + // Concatenate strings in the new buffer + strcpy(new_string, a); + strcat(new_string, b); + + return new_string; +} + +static char* replace_concat(char** a, const char* b) +{ + char* new_string = new_concat(*a, b); + free(*a); + *a = new_string; + return *a; +} + + + + + +// Shared buffer for variadic text +static char* fc_buffer = NULL; +static unsigned int fc_buffer_size = 1024; + +static Uint8 fc_has_render_target_support = 0; + +const char* FC_GetStringASCII(void) +{ + static char* buffer = NULL; + if(buffer == NULL) + { + int i; + char c; + buffer = (char*)malloc(512); + memset(buffer, 0, 512); + i = 0; + c = 32; + while(1) + { + buffer[i] = c; + if(c == 126) + break; + ++i; + ++c; + } + } + return buffer; +} + +const char* FC_GetStringLatin1(void) +{ + static char* buffer = NULL; + if(buffer == NULL) + { + int i; + unsigned char c; + buffer = (char*)malloc(512); + memset(buffer, 0, 512); + i = 0; + c = 0xA0; + while(1) + { + buffer[i] = 0xC2; + buffer[i+1] = c; + if(c == 0xBF) + break; + i += 2; + ++c; + } + i += 2; + c = 0x80; + while(1) + { + buffer[i] = 0xC3; + buffer[i+1] = c; + if(c == 0xBF) + break; + i += 2; + ++c; + } + } + return buffer; +} + +const char* FC_GetStringASCII_Latin1(void) +{ + static char* buffer = NULL; + if(buffer == NULL) + buffer = new_concat(FC_GetStringASCII(), FC_GetStringLatin1()); + + return buffer; +} + +FC_Rect FC_MakeRect(float x, float y, float w, float h) +{ + FC_Rect r = {x, y, w, h}; + return r; +} + +FC_Scale FC_MakeScale(float x, float y) +{ + FC_Scale s = {x, y}; + + return s; +} + +SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + SDL_Color c = {r, g, b, a}; + + return c; +} + +FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color) +{ + FC_Effect e; + + e.alignment = alignment; + e.scale = scale; + e.color = color; + + return e; +} + +FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h) +{ + FC_GlyphData gd; + + gd.rect.x = x; + gd.rect.y = y; + gd.rect.w = w; + gd.rect.h = h; + gd.cache_level = cache_level; + + return gd; +} + +// Enough to hold all of the ascii characters and some. +#define FC_DEFAULT_NUM_BUCKETS 300 + +typedef struct FC_MapNode +{ + Uint32 key; + FC_GlyphData value; + struct FC_MapNode* next; + +} FC_MapNode; + +typedef struct FC_Map +{ + int num_buckets; + FC_MapNode** buckets; +} FC_Map; + + + +static FC_Map* FC_MapCreate(int num_buckets) +{ + int i; + FC_Map* map = (FC_Map*)malloc(sizeof(FC_Map)); + + map->num_buckets = num_buckets; + map->buckets = (FC_MapNode**)malloc(num_buckets * sizeof(FC_MapNode*)); + + for(i = 0; i < num_buckets; ++i) + { + map->buckets[i] = NULL; + } + + return map; +} + +/*static void FC_MapClear(FC_Map* map) +{ + int i; + if(map == NULL) + return; + + // Go through each bucket + for(i = 0; i < map->num_buckets; ++i) + { + // Delete the nodes in order + FC_MapNode* node = map->buckets[i]; + while(node != NULL) + { + FC_MapNode* last = node; + node = node->next; + free(last); + } + // Set the bucket to empty + map->buckets[i] = NULL; + } +}*/ + +static void FC_MapFree(FC_Map* map) +{ + int i; + if(map == NULL) + return; + + // Go through each bucket + for(i = 0; i < map->num_buckets; ++i) + { + // Delete the nodes in order + FC_MapNode* node = map->buckets[i]; + while(node != NULL) + { + FC_MapNode* last = node; + node = node->next; + free(last); + } + } + + free(map->buckets); + free(map); +} + +// Note: Does not handle duplicates in any special way. +static FC_GlyphData* FC_MapInsert(FC_Map* map, Uint32 codepoint, FC_GlyphData glyph) +{ + Uint32 index; + FC_MapNode* node; + if(map == NULL) + return NULL; + + // Get index for bucket + index = codepoint % map->num_buckets; + + // If this bucket is empty, create a node and return its value + if(map->buckets[index] == NULL) + { + node = map->buckets[index] = (FC_MapNode*)malloc(sizeof(FC_MapNode)); + node->key = codepoint; + node->value = glyph; + node->next = NULL; + return &node->value; + } + + for(node = map->buckets[index]; node != NULL; node = node->next) + { + // Find empty node and add a new one on. + if(node->next == NULL) + { + node->next = (FC_MapNode*)malloc(sizeof(FC_MapNode)); + node = node->next; + + node->key = codepoint; + node->value = glyph; + node->next = NULL; + return &node->value; + } + } + + return NULL; +} + +static FC_GlyphData* FC_MapFind(FC_Map* map, Uint32 codepoint) +{ + Uint32 index; + FC_MapNode* node; + if(map == NULL) + return NULL; + + // Get index for bucket + index = codepoint % map->num_buckets; + + // Go through list until we find a match + for(node = map->buckets[index]; node != NULL; node = node->next) + { + if(node->key == codepoint) + return &node->value; + } + + return NULL; +} + + + +struct FC_Font +{ + #ifndef FC_USE_SDL_GPU + SDL_Renderer* renderer; + #endif + + TTF_Font* ttf_source; // TTF_Font source of characters + Uint8 owns_ttf_source; // Can we delete the TTF_Font ourselves? + + FC_FilterEnum filter; + + SDL_Color default_color; + Uint16 height; + + Uint16 maxWidth; + Uint16 baseline; + int ascent; + int descent; + + int lineSpacing; + int letterSpacing; + + // Uses 32-bit (4-byte) Unicode codepoints to refer to each glyph + // Codepoints are little endian (reversed from UTF-8) so that something like 0x00000005 is ASCII 5 and the map can be indexed by ASCII values + FC_Map* glyphs; + + FC_GlyphData last_glyph; // Texture packing cursor + int glyph_cache_size; + int glyph_cache_count; + FC_Image** glyph_cache; + + char* loading_string; + +}; + +// Private +static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight); + + +static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); +static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); +static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); + + +static_inline SDL_Surface* FC_CreateSurface32(Uint32 width, Uint32 height) +{ + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); + #else + return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); + #endif +} + + +char* U8_alloc(unsigned int size) +{ + char* result; + if(size == 0) + return NULL; + + result = (char*)malloc(size); + result[0] = '\0'; + + return result; +} + +void U8_free(char* string) +{ + free(string); +} + +char* U8_strdup(const char* string) +{ + char* result; + if(string == NULL) + return NULL; + + result = (char*)malloc(strlen(string)+1); + strcpy(result, string); + + return result; +} + +int U8_strlen(const char* string) +{ + int length = 0; + if(string == NULL) + return 0; + + while(*string != '\0') + { + string = U8_next(string); + ++length; + } + + return length; +} + +int U8_charsize(const char* character) +{ + if(character == NULL) + return 0; + + if((unsigned char)*character <= 0x7F) + return 1; + else if((unsigned char)*character < 0xE0) + return 2; + else if((unsigned char)*character < 0xF0) + return 3; + else + return 4; + return 1; +} + +int U8_charcpy(char* buffer, const char* source, int buffer_size) +{ + int charsize; + if(buffer == NULL || source == NULL || buffer_size < 1) + return 0; + + charsize = U8_charsize(source); + if(charsize > buffer_size) + return 0; + + memcpy(buffer, source, charsize); + return charsize; +} + +const char* U8_next(const char* string) +{ + return string + U8_charsize(string); +} + +int U8_strinsert(char* string, int position, const char* source, int max_bytes) +{ + int pos_bytes; + int len; + int add_len; + int ulen; + + if(string == NULL || source == NULL) + return 0; + + len = strlen(string); + add_len = strlen(source); + ulen = U8_strlen(string); + + if(position == -1) + position = ulen; + + if(position < 0 || position > ulen || len + add_len + 1 > max_bytes) + return 0; + + // Move string pointer to the proper position + pos_bytes = 0; + while(*string != '\0' && pos_bytes < position) + { + string = (char*)U8_next(string); + ++pos_bytes; + } + + // Move the rest of the string out of the way + memmove(string + add_len, string, len - pos_bytes + 1); + + // Copy in the new characters + memcpy(string, source, add_len); + + return 1; +} + +void U8_strdel(char* string, int position) +{ + if(string == NULL || position < 0) + return; + + while(*string != '\0') + { + if(position == 0) + { + int chars_to_erase = U8_charsize(string); + int remaining_bytes = strlen(string) + 1; + memmove(string, string + chars_to_erase, remaining_bytes); + break; + } + + string = (char*)U8_next(string); + --position; + } +} + + + + + +static_inline FC_Rect FC_RectUnion(FC_Rect A, FC_Rect B) +{ + float x,x2,y,y2; + x = FC_MIN(A.x, B.x); + y = FC_MIN(A.y, B.y); + x2 = FC_MAX(A.x+A.w, B.x+B.w); + y2 = FC_MAX(A.y+A.h, B.y+B.h); + { + FC_Rect result = {x, y, FC_MAX(0, x2 - x), FC_MAX(0, y2 - y)}; + return result; + } +} + +// Adapted from SDL_IntersectRect +static_inline FC_Rect FC_RectIntersect(FC_Rect A, FC_Rect B) +{ + FC_Rect result; + float Amin, Amax, Bmin, Bmax; + + // Horizontal intersection + Amin = A.x; + Amax = Amin + A.w; + Bmin = B.x; + Bmax = Bmin + B.w; + if(Bmin > Amin) + Amin = Bmin; + result.x = Amin; + if(Bmax < Amax) + Amax = Bmax; + result.w = Amax - Amin > 0 ? Amax - Amin : 0; + + // Vertical intersection + Amin = A.y; + Amax = Amin + A.h; + Bmin = B.y; + Bmax = Bmin + B.h; + if(Bmin > Amin) + Amin = Bmin; + result.y = Amin; + if(Bmax < Amax) + Amax = Bmax; + result.h = Amax - Amin > 0 ? Amax - Amin : 0; + + return result; +} + + + + + + + + + + + + + + +FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) +{ + float w = srcrect->w * xscale; + float h = srcrect->h * yscale; + FC_Rect result; + + // FIXME: Why does the scaled offset look so wrong? + #ifdef FC_USE_SDL_GPU + { + GPU_Rect r = *srcrect; + GPU_BlitScale(src, &r, dest, x + xscale*r.w/2.0f, y + r.h/2.0f, xscale, yscale); + } + #else + { + SDL_RendererFlip flip = SDL_FLIP_NONE; + if(xscale < 0) + { + xscale = -xscale; + flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_HORIZONTAL); + } + if(yscale < 0) + { + yscale = -yscale; + flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_VERTICAL); + } + + SDL_Rect r = *srcrect; + SDL_Rect dr = {(int)x, (int)y, (int)(xscale*r.w), (int)(yscale*r.h)}; + SDL_RenderCopyEx(dest, src, &r, &dr, 0, NULL, flip); + } + #endif + + result.x = x; + result.y = y; + result.w = w; + result.h = h; + return result; +} + +static FC_Rect (*fc_render_callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) = &FC_DefaultRenderCallback; + +void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)) +{ + if(callback == NULL) + fc_render_callback = &FC_DefaultRenderCallback; + else + fc_render_callback = callback; +} + +void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint) +{ + char a, b, c, d; + + if(result == NULL) + return; + + a = (codepoint >> 24) & 0xFF; + b = (codepoint >> 16) & 0xFF; + c = (codepoint >> 8) & 0xFF; + d = codepoint & 0xFF; + + if(a == 0) + { + if(b == 0) + { + if(c == 0) + { + result[0] = d; + result[1] = '\0'; + } + else + { + result[0] = c; + result[1] = d; + result[2] = '\0'; + } + } + else + { + result[0] = b; + result[1] = c; + result[2] = d; + result[3] = '\0'; + } + } + else + { + result[0] = a; + result[1] = b; + result[2] = c; + result[3] = d; + result[4] = '\0'; + } +} + +Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer) +{ + Uint32 result = 0; + const char* str; + if(c == NULL || *c == NULL) + return 0; + + str = *c; + if((unsigned char)*str <= 0x7F) + result = *str; + else if((unsigned char)*str < 0xE0) + { + result |= (unsigned char)(*str) << 8; + result |= (unsigned char)(*(str+1)); + if(advance_pointer) + *c += 1; + } + else if((unsigned char)*str < 0xF0) + { + result |= (unsigned char)(*str) << 16; + result |= (unsigned char)(*(str+1)) << 8; + result |= (unsigned char)(*(str+2)); + if(advance_pointer) + *c += 2; + } + else + { + result |= (unsigned char)(*str) << 24; + result |= (unsigned char)(*(str+1)) << 16; + result |= (unsigned char)(*(str+2)) << 8; + result |= (unsigned char)(*(str+3)); + if(advance_pointer) + *c += 3; + } + return result; +} + + +void FC_SetLoadingString(FC_Font* font, const char* string) +{ + if(font == NULL) + return; + + free(font->loading_string); + font->loading_string = U8_strdup(string); +} + + +unsigned int FC_GetBufferSize(void) +{ + return fc_buffer_size; +} + +void FC_SetBufferSize(unsigned int size) +{ + free(fc_buffer); + if(size > 0) + { + fc_buffer_size = size; + fc_buffer = (char*)malloc(fc_buffer_size); + } + else + fc_buffer = (char*)malloc(fc_buffer_size); +} + + + + + +// Constructors + +static void FC_Init(FC_Font* font) +{ + if(font == NULL) + return; + + #ifndef FC_USE_SDL_GPU + font->renderer = NULL; + #endif + + font->ttf_source = NULL; + font->owns_ttf_source = 0; + + font->filter = FC_FILTER_NEAREST; + + font->default_color.r = 0; + font->default_color.g = 0; + font->default_color.b = 0; + FC_GET_ALPHA(font->default_color) = 255; + + font->height = 0; // ascent+descent + + font->maxWidth = 0; + font->baseline = 0; + font->ascent = 0; + font->descent = 0; + + font->lineSpacing = 0; + font->letterSpacing = 0; + + // Give a little offset for when filtering/mipmaps are used. Depending on mipmap level, this will still not be enough. + font->last_glyph.rect.x = FC_CACHE_PADDING; + font->last_glyph.rect.y = FC_CACHE_PADDING; + font->last_glyph.rect.w = 0; + font->last_glyph.rect.h = 0; + font->last_glyph.cache_level = 0; + + if(font->glyphs != NULL) + FC_MapFree(font->glyphs); + + font->glyphs = FC_MapCreate(FC_DEFAULT_NUM_BUCKETS); + + font->glyph_cache_size = 3; + font->glyph_cache_count = 0; + + + font->glyph_cache = (FC_Image**)malloc(font->glyph_cache_size * sizeof(FC_Image*)); + + if(font->loading_string == NULL) + font->loading_string = U8_strdup(FC_GetStringASCII()); + + if(fc_buffer == NULL) + fc_buffer = (char*)malloc(fc_buffer_size); +} + +static Uint8 FC_GrowGlyphCache(FC_Font* font) +{ + if(font == NULL) + return 0; + #ifdef FC_USE_SDL_GPU + GPU_Image* new_level = GPU_CreateImage(font->height * 12, font->height * 12, GPU_FORMAT_RGBA); + #else + SDL_Texture* new_level = SDL_CreateTexture(font->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, font->height * 12, font->height * 12); + #endif + if(new_level == NULL || !FC_SetGlyphCacheLevel(font, font->glyph_cache_count, new_level)) + { + FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(new_level); + #else + SDL_DestroyTexture(new_level); + #endif + return 0; + } + return 1; +} + +Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface) +{ + if(font == NULL || data_surface == NULL) + return 0; + #ifdef FC_USE_SDL_GPU + GPU_Image* new_level = GPU_CopyImageFromSurface(data_surface); + if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) + GPU_SetImageFilter(new_level, GPU_FILTER_LINEAR); + else + GPU_SetImageFilter(new_level, GPU_FILTER_NEAREST); + #else + SDL_Texture* new_level; + if(!fc_has_render_target_support) + new_level = SDL_CreateTextureFromSurface(font->renderer, data_surface); + else + { + // Must upload with render target enabled so we can put more glyphs on later + SDL_Renderer* renderer = font->renderer; + + // Set filter mode for new texture + char old_filter_mode[16]; // Save it so we can change the hint value in the meantime + snprintf(old_filter_mode, 16, "%s", SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY)); + + if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + else + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + new_level = SDL_CreateTexture(renderer, data_surface->format->format, SDL_TEXTUREACCESS_TARGET, data_surface->w, data_surface->h); + SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); + + // Reset filter mode for the temp texture + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + { + Uint8 r, g, b, a; + SDL_Texture* temp = SDL_CreateTextureFromSurface(renderer, data_surface); + SDL_SetTextureBlendMode(temp, SDL_BLENDMODE_NONE); + SDL_SetRenderTarget(renderer, new_level); + + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + SDL_SetRenderDrawColor(renderer, r, g, b, a); + + SDL_RenderCopy(renderer, temp, NULL, NULL); + SDL_SetRenderTarget(renderer, NULL); + + SDL_DestroyTexture(temp); + } + + // Reset to the old filter value + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, old_filter_mode); + + } + #endif + if(new_level == NULL || !FC_SetGlyphCacheLevel(font, cache_level, new_level)) + { + FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(new_level); + #else + SDL_DestroyTexture(new_level); + #endif + return 0; + } + return 1; +} + +static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight) +{ + FC_Map* glyphs = font->glyphs; + FC_GlyphData* last_glyph = &font->last_glyph; + Uint16 height = font->height + FC_CACHE_PADDING; + + if(last_glyph->rect.x + last_glyph->rect.w + width >= maxWidth - FC_CACHE_PADDING) + { + if(last_glyph->rect.y + height + height >= maxHeight - FC_CACHE_PADDING) + { + // Get ready to pack on the next cache level when it is ready + last_glyph->cache_level = font->glyph_cache_count; + last_glyph->rect.x = FC_CACHE_PADDING; + last_glyph->rect.y = FC_CACHE_PADDING; + last_glyph->rect.w = 0; + return NULL; + } + else + { + // Go to next row + last_glyph->rect.x = FC_CACHE_PADDING; + last_glyph->rect.y += height; + last_glyph->rect.w = 0; + } + } + + // Move to next space + last_glyph->rect.x += last_glyph->rect.w + 1 + FC_CACHE_PADDING; + last_glyph->rect.w = width; + + return FC_MapInsert(glyphs, codepoint, FC_MakeGlyphData(last_glyph->cache_level, last_glyph->rect.x, last_glyph->rect.y, last_glyph->rect.w, last_glyph->rect.h)); +} + + +FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level) +{ + if(font == NULL || cache_level < 0 || cache_level > font->glyph_cache_count) + return NULL; + + return font->glyph_cache[cache_level]; +} + +Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture) +{ + if(font == NULL || cache_level < 0) + return 0; + + // Must be sequentially added + if(cache_level > font->glyph_cache_count + 1) + return 0; + + if(cache_level == font->glyph_cache_count) + { + font->glyph_cache_count++; + + // Grow cache? + if(font->glyph_cache_count > font->glyph_cache_size) + { + // Copy old cache to new one + int i; + FC_Image** new_cache; + new_cache = (FC_Image**)malloc(font->glyph_cache_count * sizeof(FC_Image*)); + for(i = 0; i < font->glyph_cache_size; ++i) + new_cache[i] = font->glyph_cache[i]; + + // Save new cache + free(font->glyph_cache); + font->glyph_cache_size = font->glyph_cache_count; + font->glyph_cache = new_cache; + } + } + + font->glyph_cache[cache_level] = cache_texture; + return 1; +} + + +FC_Font* FC_CreateFont(void) +{ + FC_Font* font; + + font = (FC_Font*)malloc(sizeof(FC_Font)); + memset(font, 0, sizeof(FC_Font)); + + FC_Init(font); + + return font; +} + + +// Assume this many will be enough... +#define FC_LOAD_MAX_SURFACES 10 + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color) +#else +Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color) +#endif +{ + if(font == NULL || ttf == NULL) + return 0; + #ifndef FC_USE_SDL_GPU + if(renderer == NULL) + return 0; + #endif + + FC_ClearFont(font); + + + // Might as well check render target support here + #ifdef FC_USE_SDL_GPU + fc_has_render_target_support = GPU_IsFeatureEnabled(GPU_FEATURE_RENDER_TARGETS); + #else + SDL_RendererInfo info; + SDL_GetRendererInfo(renderer, &info); + fc_has_render_target_support = (info.flags & SDL_RENDERER_TARGETTEXTURE); + + font->renderer = renderer; + #endif + + font->ttf_source = ttf; + + //font->line_height = TTF_FontLineSkip(ttf); + font->height = TTF_FontHeight(ttf); + font->ascent = TTF_FontAscent(ttf); + font->descent = -TTF_FontDescent(ttf); + + font->baseline = font->height - font->descent; + + font->default_color = color; + + { + SDL_Color white = {255, 255, 255, 255}; + SDL_Surface* glyph_surf; + char buff[5]; + const char* buff_ptr = buff; + const char* source_string; + Uint8 packed = 0; + + // Copy glyphs from the surface to the font texture and store the position data + // Pack row by row into a square texture + // Try figuring out dimensions that make sense for the font size. + unsigned int w = font->height*12; + unsigned int h = font->height*12; + SDL_Surface* surfaces[FC_LOAD_MAX_SURFACES]; + int num_surfaces = 1; + surfaces[0] = FC_CreateSurface32(w, h); + font->last_glyph.rect.x = FC_CACHE_PADDING; + font->last_glyph.rect.y = FC_CACHE_PADDING; + font->last_glyph.rect.w = 0; + font->last_glyph.rect.h = font->height; + + memset(buff, 0, 5); + source_string = font->loading_string; + for(; *source_string != '\0'; source_string = U8_next(source_string)) + { + if(!U8_charcpy(buff, source_string, 5)) + continue; + glyph_surf = TTF_RenderUTF8_Blended(ttf, buff, white); + if(glyph_surf == NULL) + continue; + + // Try packing. If it fails, create a new surface for the next cache level. + packed = (FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL); + if(!packed) + { + int i = num_surfaces-1; + if(num_surfaces >= FC_LOAD_MAX_SURFACES) + { + // Can't do any more! + FC_Log("SDL_FontCache error: Could not create enough cache surfaces to fit all of the loading string!\n"); + SDL_FreeSurface(glyph_surf); + break; + } + + // Upload the current surface to the glyph cache now so we can keep the cache level packing cursor up to date as we go. + FC_UploadGlyphCache(font, i, surfaces[i]); + SDL_FreeSurface(surfaces[i]); + #ifndef FC_USE_SDL_GPU + SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); + #endif + // Update the glyph cursor to the new cache level. We need to do this here because the actual cache lags behind our use of the packing above. + font->last_glyph.cache_level = num_surfaces; + + + surfaces[num_surfaces] = FC_CreateSurface32(w, h); + num_surfaces++; + } + + // Try packing for the new surface, then blit onto it. + if(packed || FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL) + { + SDL_SetSurfaceBlendMode(glyph_surf, SDL_BLENDMODE_NONE); + SDL_Rect srcRect = {0, 0, glyph_surf->w, glyph_surf->h}; + SDL_Rect destrect = font->last_glyph.rect; + SDL_BlitSurface(glyph_surf, &srcRect, surfaces[num_surfaces-1], &destrect); + } + + SDL_FreeSurface(glyph_surf); + } + + { + int i = num_surfaces-1; + FC_UploadGlyphCache(font, i, surfaces[i]); + SDL_FreeSurface(surfaces[i]); + #ifndef FC_USE_SDL_GPU + SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); + #endif + } + } + + return 1; +} + + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) +#else +Uint8 FC_LoadFont(FC_Font* font, FC_Target* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) +#endif +{ + SDL_RWops* rwops; + + if(font == NULL) + return 0; + + rwops = SDL_RWFromFile(filename_ttf, "rb"); + + if(rwops == NULL) + { + FC_Log("Unable to open file for reading: %s \n", SDL_GetError()); + return 0; + } + + #ifdef FC_USE_SDL_GPU + return FC_LoadFont_RW(font, rwops, 1, pointSize, color, style); + #else + return FC_LoadFont_RW(font, renderer, rwops, 1, pointSize, color, style); + #endif +} + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) +#else +Uint8 FC_LoadFont_RW(FC_Font* font, FC_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) +#endif +{ + Uint8 result; + TTF_Font* ttf; + Uint8 outline; + + if(font == NULL) + return 0; + + if(!TTF_WasInit() && TTF_Init() < 0) + { + FC_Log("Unable to initialize SDL_ttf: %s \n", TTF_GetError()); + if(own_rwops) + SDL_RWclose(file_rwops_ttf); + return 0; + } + + ttf = TTF_OpenFontRW(file_rwops_ttf, own_rwops, pointSize); + + if(ttf == NULL) + { + FC_Log("Unable to load TrueType font: %s \n", TTF_GetError()); + if(own_rwops) + SDL_RWclose(file_rwops_ttf); + return 0; + } + + outline = (style & TTF_STYLE_OUTLINE); + if(outline) + { + style &= ~TTF_STYLE_OUTLINE; + TTF_SetFontOutline(ttf, 1); + } + TTF_SetFontStyle(ttf, style); + + #ifdef FC_USE_SDL_GPU + result = FC_LoadFontFromTTF(font, ttf, color); + #else + result = FC_LoadFontFromTTF(font, renderer, ttf, color); + #endif + + // Can only load new (uncached) glyphs if we can keep the SDL_RWops open. + font->owns_ttf_source = own_rwops; + if(!own_rwops) + { + TTF_CloseFont(font->ttf_source); + font->ttf_source = NULL; + } + + return result; +} + + +void FC_ClearFont(FC_Font* font) +{ + int i; + if(font == NULL) + return; + + // Release resources + if(font->owns_ttf_source) + TTF_CloseFont(font->ttf_source); + + font->owns_ttf_source = 0; + font->ttf_source = NULL; + + // Delete glyph map + FC_MapFree(font->glyphs); + font->glyphs = NULL; + + // Delete glyph cache + for(i = 0; i < font->glyph_cache_count; ++i) + { + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(font->glyph_cache[i]); + #else + SDL_DestroyTexture(font->glyph_cache[i]); + #endif + } + free(font->glyph_cache); + font->glyph_cache = NULL; + + // Reset font + FC_Init(font); +} + + +void FC_FreeFont(FC_Font* font) +{ + int i; + if(font == NULL) + return; + + // Release resources + if(font->owns_ttf_source) + TTF_CloseFont(font->ttf_source); + + // Delete glyph map + FC_MapFree(font->glyphs); + + // Delete glyph cache + for(i = 0; i < font->glyph_cache_count; ++i) + { + #ifdef FC_USE_SDL_GPU + GPU_FreeImage(font->glyph_cache[i]); + #else + SDL_DestroyTexture(font->glyph_cache[i]); + #endif + } + free(font->glyph_cache); + + free(font->loading_string); + + free(font); +} + +int FC_GetNumCacheLevels(FC_Font* font) +{ + return font->glyph_cache_count; +} + +Uint8 FC_AddGlyphToCache(FC_Font* font, SDL_Surface* glyph_surface) +{ + if(font == NULL || glyph_surface == NULL) + return 0; + + SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_NONE); + FC_Image* dest = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); + if(dest == NULL) + return 0; + + #ifdef FC_USE_SDL_GPU + { + GPU_Target* target = GPU_LoadTarget(dest); + if(target == NULL) + return 0; + GPU_Image* img = GPU_CopyImageFromSurface(glyph_surface); + GPU_SetImageFilter(img, GPU_FILTER_NEAREST); + GPU_SetBlendMode(img, GPU_BLEND_SET); + + SDL_Rect destrect = font->last_glyph.rect; + GPU_Blit(img, NULL, target, destrect.x + destrect.w/2, destrect.y + destrect.h/2); + + GPU_FreeImage(img); + GPU_FreeTarget(target); + } + #else + { + SDL_Renderer* renderer = font->renderer; + Uint8 use_clip; + FC_Rect clip_rect; + SDL_Texture* img; + SDL_Rect destrect; + + use_clip = has_clip(renderer); + if(use_clip) + { + clip_rect = get_clip(renderer); + set_clip(renderer, NULL); + } + + img = SDL_CreateTextureFromSurface(renderer, glyph_surface); + + destrect = font->last_glyph.rect; + SDL_SetRenderTarget(renderer, dest); + SDL_RenderCopy(renderer, img, NULL, &destrect); + SDL_SetRenderTarget(renderer, NULL); + SDL_DestroyTexture(img); + + if(use_clip) + set_clip(renderer, &clip_rect); + } + #endif + + return 1; +} + + +unsigned int FC_GetNumCodepoints(FC_Font* font) +{ + FC_Map* glyphs; + int i; + unsigned int result = 0; + if(font == NULL || font->glyphs == NULL) + return 0; + + glyphs = font->glyphs; + + for(i = 0; i < glyphs->num_buckets; ++i) + { + FC_MapNode* node; + for(node = glyphs->buckets[i]; node != NULL; node = node->next) + { + result++; + } + } + + return result; +} + +void FC_GetCodepoints(FC_Font* font, Uint32* result) +{ + FC_Map* glyphs; + int i; + unsigned int count = 0; + if(font == NULL || font->glyphs == NULL) + return; + + glyphs = font->glyphs; + + for(i = 0; i < glyphs->num_buckets; ++i) + { + FC_MapNode* node; + for(node = glyphs->buckets[i]; node != NULL; node = node->next) + { + result[count] = node->key; + count++; + } + } +} + +Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint) +{ + FC_GlyphData* e = FC_MapFind(font->glyphs, codepoint); + if(e == NULL) + { + char buff[5]; + int w, h; + SDL_Color white = {255, 255, 255, 255}; + SDL_Surface* surf; + FC_Image* cache_image; + + if(font->ttf_source == NULL) + return 0; + + FC_GetUTF8FromCodepoint(buff, codepoint); + + cache_image = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); + if(cache_image == NULL) + { + FC_Log("SDL_FontCache: Failed to load cache image, so cannot add new glyphs!\n"); + return 0; + } + + #ifdef FC_USE_SDL_GPU + w = cache_image->w; + h = cache_image->h; + #else + SDL_QueryTexture(cache_image, NULL, NULL, &w, &h); + #endif + + surf = TTF_RenderUTF8_Blended(font->ttf_source, buff, white); + if(surf == NULL) + { + return 0; + } + + e = FC_PackGlyphData(font, codepoint, surf->w, w, h); + if(e == NULL) + { + // Grow the cache + FC_GrowGlyphCache(font); + + // Try packing again + e = FC_PackGlyphData(font, codepoint, surf->w, w, h); + if(e == NULL) + { + SDL_FreeSurface(surf); + return 0; + } + } + + // Render onto the cache texture + FC_AddGlyphToCache(font, surf); + + SDL_FreeSurface(surf); + } + + if(result != NULL && e != NULL) + *result = *e; + + return 1; +} + + +FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data) +{ + return FC_MapInsert(font->glyphs, codepoint, glyph_data); +} + + + +// Drawing +static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + const char* c = text; + FC_Rect srcRect; + FC_Rect dstRect; + FC_Rect dirtyRect = FC_MakeRect(x, y, 0, 0); + + FC_GlyphData glyph; + Uint32 codepoint; + + float destX = x; + float destY = y; + float destH; + float destLineSpacing; + float destLetterSpacing; + + if(font == NULL) + return dirtyRect; + + destH = font->height * scale.y; + destLineSpacing = font->lineSpacing*scale.y; + destLetterSpacing = font->letterSpacing*scale.x; + + if(c == NULL || font->glyph_cache_count == 0 || dest == NULL) + return dirtyRect; + + int newlineX = x; + + for(; *c != '\0'; c++) + { + if(*c == '\n') + { + destX = newlineX; + destY += destH + destLineSpacing; + continue; + } + + codepoint = FC_GetCodepointFromUTF8(&c, 1); // Increments 'c' to skip the extra UTF-8 bytes + if(!FC_GetGlyphData(font, &glyph, codepoint)) + { + codepoint = ' '; + if(!FC_GetGlyphData(font, &glyph, codepoint)) + continue; // Skip bad characters + } + + if (codepoint == ' ') + { + destX += glyph.rect.w*scale.x + destLetterSpacing; + continue; + } + /*if(destX >= dest->w) + continue; + if(destY >= dest->h) + continue;*/ + + #ifdef FC_USE_SDL_GPU + srcRect.x = glyph.rect.x; + srcRect.y = glyph.rect.y; + srcRect.w = glyph.rect.w; + srcRect.h = glyph.rect.h; + #else + srcRect = glyph.rect; + #endif + dstRect = fc_render_callback(FC_GetGlyphCacheLevel(font, glyph.cache_level), &srcRect, dest, destX, destY, scale.x, scale.y); + if(dirtyRect.w == 0 || dirtyRect.h == 0) + dirtyRect = dstRect; + else + dirtyRect = FC_RectUnion(dirtyRect, dstRect); + + destX += glyph.rect.w*scale.x + destLetterSpacing; + } + + return dirtyRect; +} + +static void set_color_for_all_caches(FC_Font* font, SDL_Color color) +{ + // TODO: How can I predict which glyph caches are to be used? + FC_Image* img; + int i; + int num_levels = FC_GetNumCacheLevels(font); + for(i = 0; i < num_levels; ++i) + { + img = FC_GetGlyphCacheLevel(font, i); + set_color(img, color.r, color.g, color.b, FC_GET_ALPHA(color)); + } +} + +FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); +} + + + +typedef struct FC_StringList +{ + char* value; + struct FC_StringList* next; +} FC_StringList; + +void FC_StringListFree(FC_StringList* node) +{ + // Delete the nodes in order + while(node != NULL) + { + FC_StringList* last = node; + node = node->next; + + free(last->value); + free(last); + } +} + +FC_StringList** FC_StringListPushBack(FC_StringList** node, char* value, Uint8 copy) +{ + // Get to the last node + while(node != NULL && *node != NULL) + { + node = &(*node)->next; + } + + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + + (*node)->value = (copy? U8_strdup(value) : value); + (*node)->next = NULL; + + return node; +} + +static FC_StringList* FC_Explode(const char* text, char delimiter) +{ + FC_StringList* head; + FC_StringList* new_node; + FC_StringList** node; + const char* start; + const char* end; + unsigned int size; + if(text == NULL) + return NULL; + + head = NULL; + node = &head; + + // Doesn't technically support UTF-8, but it's probably fine, right? + size = 0; + start = end = text; + while(1) + { + if(*end == delimiter || *end == '\0') + { + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + new_node = *node; + + new_node->value = (char*)malloc(size + 1); + memcpy(new_node->value, start, size); + new_node->value[size] = '\0'; + + new_node->next = NULL; + + if(*end == '\0') + break; + + node = &((*node)->next); + start = end+1; + size = 0; + } + else + ++size; + + ++end; + } + + return head; +} + +static FC_StringList* FC_ExplodeAndKeep(const char* text, char delimiter) +{ + FC_StringList* head; + FC_StringList* new_node; + FC_StringList** node; + const char* start; + const char* end; + unsigned int size; + if(text == NULL) + return NULL; + + head = NULL; + node = &head; + + // Doesn't technically support UTF-8, but it's probably fine, right? + size = 0; + start = end = text; + while(1) + { + if(*end == delimiter || *end == '\0') + { + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + new_node = *node; + + new_node->value = (char*)malloc(size + 1); + memcpy(new_node->value, start, size); + new_node->value[size] = '\0'; + + new_node->next = NULL; + + if(*end == '\0') + break; + + node = &((*node)->next); + start = end; + size = 1; + } + else + ++size; + + ++end; + } + + return head; +} + +static void FC_RenderAlign(FC_Font* font, FC_Target* dest, float x, float y, int width, FC_Scale scale, FC_AlignEnum align, const char* text) +{ + switch(align) + { + case FC_ALIGN_LEFT: + FC_RenderLeft(font, dest, x, y, scale, text); + break; + case FC_ALIGN_CENTER: + FC_RenderCenter(font, dest, x + width/2, y, scale, text); + break; + case FC_ALIGN_RIGHT: + FC_RenderRight(font, dest, x + width, y, scale, text); + break; + } +} + +static FC_StringList* FC_GetBufferFitToColumn(FC_Font* font, int width, FC_Scale scale, Uint8 keep_newlines) +{ + FC_StringList* result = NULL; + FC_StringList** current = &result; + + FC_StringList *ls, *iter; + + ls = (keep_newlines? FC_ExplodeAndKeep(fc_buffer, '\n') : FC_Explode(fc_buffer, '\n')); + for(iter = ls; iter != NULL; iter = iter->next) + { + char* line = iter->value; + + // If line is too long, then add words one at a time until we go over. + if(width > 0 && FC_GetWidth(font, "%s", line) > width) + { + FC_StringList *words, *word_iter; + + words = FC_Explode(line, ' '); + // Skip the first word for the iterator, so there will always be at least one word per line + line = new_concat(words->value, " "); + for(word_iter = words->next; word_iter != NULL; word_iter = word_iter->next) + { + char* line_plus_word = new_concat(line, word_iter->value); + char* word_plus_space = new_concat(word_iter->value, " "); + if(FC_GetWidth(font, "%s", line_plus_word) > width) + { + current = FC_StringListPushBack(current, line, 0); + + line = word_plus_space; + } + else + { + replace_concat(&line, word_plus_space); + free(word_plus_space); + } + free(line_plus_word); + } + current = FC_StringListPushBack(current, line, 0); + FC_StringListFree(words); + } + else + { + current = FC_StringListPushBack(current, line, 0); + iter->value = NULL; + } + } + FC_StringListFree(ls); + + return result; +} + +static void FC_DrawColumnFromBuffer(FC_Font* font, FC_Target* dest, FC_Rect box, int* total_height, FC_Scale scale, FC_AlignEnum align) +{ + int y = box.y; + FC_StringList *ls, *iter; + + ls = FC_GetBufferFitToColumn(font, box.w, scale, 0); + for(iter = ls; iter != NULL; iter = iter->next) + { + FC_RenderAlign(font, dest, box.x, y, box.w, scale, align, iter->value); + y += FC_GetLineHeight(font); + } + FC_StringListFree(ls); + + if(total_height != NULL) + *total_height = y - box.y; +} + +FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + + set_clip(dest, &newclip); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), align); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, scale, FC_ALIGN_LEFT); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...) +{ + Uint8 useClip; + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(box.x, box.y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + useClip = has_clip(dest); + FC_Rect oldclip, newclip; + if(useClip) + { + oldclip = get_clip(dest); + newclip = FC_RectIntersect(oldclip, box); + } + else + newclip = box; + set_clip(dest, &newclip); + + set_color_for_all_caches(font, effect.color); + + FC_DrawColumnFromBuffer(font, dest, box, NULL, effect.scale, effect.alignment); + + if(useClip) + set_clip(dest, &oldclip); + else + set_clip(dest, NULL); + + return box; +} + +FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...) +{ + FC_Rect box = {x, y, width, 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + return FC_MakeRect(box.x, box.y, width, total_height); +} + +FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...) +{ + FC_Rect box = {x, y, width, 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + switch(align) + { + case FC_ALIGN_CENTER: + box.x -= width/2; + break; + case FC_ALIGN_RIGHT: + box.x -= width; + break; + default: + break; + } + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), align); + + return FC_MakeRect(box.x, box.y, width, total_height); +} + +FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...) +{ + FC_Rect box = {x, y, width, 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, scale, FC_ALIGN_LEFT); + + return FC_MakeRect(box.x, box.y, width, total_height); +} + +FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...) +{ + FC_Rect box = {x, y, width, 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, color); + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); + + return FC_MakeRect(box.x, box.y, width, total_height); +} + +FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...) +{ + FC_Rect box = {x, y, width, 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, effect.color); + + switch(effect.alignment) + { + case FC_ALIGN_CENTER: + box.x -= width/2; + break; + case FC_ALIGN_RIGHT: + box.x -= width; + break; + default: + break; + } + + FC_DrawColumnFromBuffer(font, dest, box, &total_height, effect.scale, effect.alignment); + + return FC_MakeRect(box.x, box.y, width, total_height); +} + +static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + FC_Rect result = {x, y, 0, 0}; + if(text == NULL || font == NULL) + return result; + + char* str = U8_strdup(text); + char* del = str; + char* c; + + // Go through str, when you find a \n, replace it with \0 and print it + // then move down, back, and continue. + for(c = str; *c != '\0';) + { + if(*c == '\n') + { + *c = '\0'; + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); + *c = '\n'; + c++; + str = c; + y += scale.y*font->height; + } + else + c++; + } + + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); + + free(del); + return result; +} + +static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + FC_Rect result = {x, y, 0, 0}; + if(text == NULL || font == NULL) + return result; + + char* str = U8_strdup(text); + char* del = str; + char* c; + + for(c = str; *c != '\0';) + { + if(*c == '\n') + { + *c = '\0'; + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); + *c = '\n'; + c++; + str = c; + y += scale.y*font->height; + } + else + c++; + } + + result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); + + free(del); + return result; +} + + + +FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + return FC_RenderLeft(font, dest, x, y, scale, fc_buffer); +} + +FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + FC_Rect result; + switch(align) + { + case FC_ALIGN_LEFT: + result = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + break; + case FC_ALIGN_CENTER: + result = FC_RenderCenter(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + break; + case FC_ALIGN_RIGHT: + result = FC_RenderRight(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + break; + default: + result = FC_MakeRect(x, y, 0, 0); + break; + } + + return result; +} + +FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, color); + + return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); +} + + +FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, effect.color); + + FC_Rect result; + switch(effect.alignment) + { + case FC_ALIGN_LEFT: + result = FC_RenderLeft(font, dest, x, y, effect.scale, fc_buffer); + break; + case FC_ALIGN_CENTER: + result = FC_RenderCenter(font, dest, x, y, effect.scale, fc_buffer); + break; + case FC_ALIGN_RIGHT: + result = FC_RenderRight(font, dest, x, y, effect.scale, fc_buffer); + break; + default: + result = FC_MakeRect(x, y, 0, 0); + break; + } + + return result; +} + + + + +// Getters + + +FC_FilterEnum FC_GetFilterMode(FC_Font* font) +{ + if(font == NULL) + return FC_FILTER_NEAREST; + + return font->filter; +} + +Uint16 FC_GetLineHeight(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->height; +} + +Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return 0; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + Uint16 numLines = 1; + const char* c; + + for (c = fc_buffer; *c != '\0'; c++) + { + if(*c == '\n') + numLines++; + } + + // Actual height of letter region + line spacing + return font->height*numLines + font->lineSpacing*(numLines - 1); //height*numLines; +} + +Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return 0; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + const char* c; + Uint16 width = 0; + Uint16 bigWidth = 0; // Allows for multi-line strings + + for (c = fc_buffer; *c != '\0'; c++) + { + if(*c == '\n') + { + bigWidth = bigWidth >= width? bigWidth : width; + width = 0; + continue; + } + + FC_GlyphData glyph; + Uint32 codepoint = FC_GetCodepointFromUTF8(&c, 1); + if(FC_GetGlyphData(font, &glyph, codepoint) || FC_GetGlyphData(font, &glyph, ' ')) + width += glyph.rect.w; + } + bigWidth = bigWidth >= width? bigWidth : width; + + return bigWidth; +} + +// If width == -1, use no width limit +FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...) +{ + FC_Rect result = {0, 0, 1, FC_GetLineHeight(font)}; + FC_StringList *ls, *iter; + int num_lines = 0; + Uint8 done = 0; + + if(formatted_text == NULL || column_width == 0 || position_index == 0 || font == NULL) + return result; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); + for(iter = ls; iter != NULL;) + { + char* line; + int i = 0; + FC_StringList* next_iter = iter->next; + + ++num_lines; + for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) + { + ++i; + --position_index; + if(position_index == 0) + { + // FIXME: Doesn't handle box-wrapped newlines correctly + line = (char*)U8_next(line); + line[0] = '\0'; + result.x = FC_GetWidth(font, "%s", iter->value); + done = 1; + break; + } + } + if(done) + break; + + // Prevent line wrapping if there are no more lines + if(next_iter == NULL && !done) + result.x = FC_GetWidth(font, "%s", iter->value); + iter = next_iter; + } + FC_StringListFree(ls); + + if(num_lines > 1) + { + result.y = (num_lines - 1) * FC_GetLineHeight(font); + } + + return result; +} + + +Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...) +{ + int y = 0; + + FC_StringList *ls, *iter; + + if(font == NULL) + return 0; + + if(formatted_text == NULL || width == 0) + return font->height; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); + for(iter = ls; iter != NULL; iter = iter->next) + { + y += FC_GetLineHeight(font); + } + FC_StringListFree(ls); + + return y; +} + +static int FC_GetAscentFromCodepoint(FC_Font* font, Uint32 codepoint) +{ + FC_GlyphData glyph; + + if(font == NULL) + return 0; + + // FIXME: Store ascent so we can return it here + FC_GetGlyphData(font, &glyph, codepoint); + return glyph.rect.h; +} + +static int FC_GetDescentFromCodepoint(FC_Font* font, Uint32 codepoint) +{ + FC_GlyphData glyph; + + if(font == NULL) + return 0; + + // FIXME: Store descent so we can return it here + FC_GetGlyphData(font, &glyph, codepoint); + return glyph.rect.h; +} + +int FC_GetAscent(FC_Font* font, const char* formatted_text, ...) +{ + Uint32 codepoint; + int max, ascent; + const char* c; + + if(font == NULL) + return 0; + + if(formatted_text == NULL) + return font->ascent; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + max = 0; + c = fc_buffer; + + while(*c != '\0') + { + codepoint = FC_GetCodepointFromUTF8(&c, 1); + if(codepoint != 0) + { + ascent = FC_GetAscentFromCodepoint(font, codepoint); + if(ascent > max) + max = ascent; + } + ++c; + } + return max; +} + +int FC_GetDescent(FC_Font* font, const char* formatted_text, ...) +{ + Uint32 codepoint; + int max, descent; + const char* c; + + if(font == NULL) + return 0; + + if(formatted_text == NULL) + return font->descent; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + max = 0; + c = fc_buffer; + + while(*c != '\0') + { + codepoint = FC_GetCodepointFromUTF8(&c, 1); + if(codepoint != 0) + { + descent = FC_GetDescentFromCodepoint(font, codepoint); + if(descent > max) + max = descent; + } + ++c; + } + return max; +} + +int FC_GetBaseline(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->baseline; +} + +int FC_GetSpacing(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->letterSpacing; +} + +int FC_GetLineSpacing(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->lineSpacing; +} + +Uint16 FC_GetMaxWidth(FC_Font* font) +{ + if(font == NULL) + return 0; + + return font->maxWidth; +} + +SDL_Color FC_GetDefaultColor(FC_Font* font) +{ + if(font == NULL) + { + SDL_Color c = {0,0,0,255}; + return c; + } + + return font->default_color; +} + + +Uint8 FC_InRect(float x, float y, FC_Rect input_rect) +{ + return (input_rect.x <= x && x <= input_rect.x + input_rect.w && input_rect.y <= y && y <= input_rect.y + input_rect.h); +} + +// TODO: Make it work with alignment +Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...) +{ + FC_StringList *ls, *iter; + Uint8 done = 0; + int height = FC_GetLineHeight(font); + Uint16 position = 0; + int current_x = 0; + int current_y = 0; + FC_GlyphData glyph_data; + + if(formatted_text == NULL || column_width == 0 || font == NULL) + return 0; + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); + for(iter = ls; iter != NULL; iter = iter->next) + { + char* line; + + for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) + { + if(FC_GetGlyphData(font, &glyph_data, FC_GetCodepointFromUTF8((const char**)&line, 0))) + { + if(FC_InRect(x, y, FC_MakeRect(current_x, current_y, glyph_data.rect.w, glyph_data.rect.h))) + { + done = 1; + break; + } + + current_x += glyph_data.rect.w; + } + position++; + } + if(done) + break; + + current_x = 0; + current_y += height; + if(y < current_y) + break; + } + FC_StringListFree(ls); + + return position; +} + + + + +// Setters + + +void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter) +{ + if(font == NULL) + return; + + if(font->filter != filter) + { + font->filter = filter; + + #ifdef FC_USE_SDL_GPU + // Update each texture to use this filter mode + { + int i; + GPU_FilterEnum gpu_filter = GPU_FILTER_NEAREST; + if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) + gpu_filter = GPU_FILTER_LINEAR; + + for(i = 0; i < font->glyph_cache_count; ++i) + { + GPU_SetImageFilter(font->glyph_cache[i], gpu_filter); + } + } + #endif + } +} + + +void FC_SetSpacing(FC_Font* font, int LetterSpacing) +{ + if(font == NULL) + return; + + font->letterSpacing = LetterSpacing; +} + +void FC_SetLineSpacing(FC_Font* font, int LineSpacing) +{ + if(font == NULL) + return; + + font->lineSpacing = LineSpacing; +} + +void FC_SetDefaultColor(FC_Font* font, SDL_Color color) +{ + if(font == NULL) + return; + + font->default_color = color; +} + + + + + + diff --git a/src/Libs/SDL_FontCache.h b/src/Libs/SDL_FontCache.h new file mode 100644 index 0000000..8a200ad --- /dev/null +++ b/src/Libs/SDL_FontCache.h @@ -0,0 +1,311 @@ +/* +SDL_FontCache v0.9.0: A font cache for SDL and SDL_ttf +by Jonathan Dearborn +Dedicated to the memory of Florian Hufsky + +License: + The short: + Use it however you'd like, but keep the copyright and license notice + whenever these files or parts of them are distributed in uncompiled form. + + The long: +Copyright (c) 2016 Jonathan Dearborn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef _SDL_FONTCACHE_H__ +#define _SDL_FONTCACHE_H__ + +#include "SDL.h" +#include "SDL_ttf.h" + +#ifdef FC_USE_SDL_GPU + #include "SDL_gpu.h" +#endif + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Let's pretend this exists... +#define TTF_STYLE_OUTLINE 16 + + + +// Differences between SDL_Renderer and SDL_gpu +#ifdef FC_USE_SDL_GPU +#define FC_Rect GPU_Rect +#define FC_Target GPU_Target +#define FC_Image GPU_Image +#define FC_Log GPU_LogError +#else +#define FC_Rect SDL_Rect +#define FC_Target SDL_Renderer +#define FC_Image SDL_Texture +#define FC_Log SDL_Log +#endif + + +// SDL_FontCache types + +typedef enum +{ + FC_ALIGN_LEFT, + FC_ALIGN_CENTER, + FC_ALIGN_RIGHT +} FC_AlignEnum; + +typedef enum +{ + FC_FILTER_NEAREST, + FC_FILTER_LINEAR +} FC_FilterEnum; + +typedef struct FC_Scale +{ + float x; + float y; + +} FC_Scale; + +typedef struct FC_Effect +{ + FC_AlignEnum alignment; + FC_Scale scale; + SDL_Color color; + +} FC_Effect; + +// Opaque type +typedef struct FC_Font FC_Font; + + +typedef struct FC_GlyphData +{ + SDL_Rect rect; + int cache_level; + +} FC_GlyphData; + + + + +// Object creation + +FC_Rect FC_MakeRect(float x, float y, float w, float h); + +FC_Scale FC_MakeScale(float x, float y); + +SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a); + +FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color); + +FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h); + + + +// Font object + +FC_Font* FC_CreateFont(void); + +#ifdef FC_USE_SDL_GPU +Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style); + +Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color); + +Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style); +#else +Uint8 FC_LoadFont(FC_Font* font, SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style); + +Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color); + +Uint8 FC_LoadFont_RW(FC_Font* font, SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style); +#endif + +void FC_ClearFont(FC_Font* font); + +void FC_FreeFont(FC_Font* font); + + + +// Built-in loading strings + +const char* FC_GetStringASCII(void); + +const char* FC_GetStringLatin1(void); + +const char* FC_GetStringASCII_Latin1(void); + + +// UTF-8 to SDL_FontCache codepoint conversion + +/*! +Returns the Uint32 codepoint (not UTF-32) parsed from the given UTF-8 string. +\param c A pointer to a string of proper UTF-8 character values. +\param advance_pointer If true, the source pointer will be incremented to skip the extra bytes from multibyte codepoints. +*/ +Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer); + +/*! +Parses the given codepoint and stores the UTF-8 bytes in 'result'. The result is NULL terminated. +\param result A memory buffer for the UTF-8 values. Must be at least 5 bytes long. +\param codepoint The Uint32 codepoint to parse (not UTF-32). +*/ +void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint); + + +// UTF-8 string operations + +/*! Allocates a new string of 'size' bytes that is already NULL-terminated. The NULL byte counts toward the size limit, as usual. Returns NULL if size is 0. */ +char* U8_alloc(unsigned int size); + +/*! Deallocates the given string. */ +void U8_free(char* string); + +/*! Allocates a copy of the given string. */ +char* U8_strdup(const char* string); + +/*! Returns the number of UTF-8 characters in the given string. */ +int U8_strlen(const char* string); + +/*! Returns the number of bytes in the UTF-8 multibyte character pointed at by 'character'. */ +int U8_charsize(const char* character); + +/*! Copies the source multibyte character into the given buffer without overrunning it. Returns 0 on failure. */ +int U8_charcpy(char* buffer, const char* source, int buffer_size); + +/*! Returns a pointer to the next UTF-8 character. */ +const char* U8_next(const char* string); + +/*! Inserts a UTF-8 string into 'string' at the given position. Use a position of -1 to append. Returns 0 when unable to insert the string. */ +int U8_strinsert(char* string, int position, const char* source, int max_bytes); + +/*! Erases the UTF-8 character at the given position, moving the subsequent characters down. */ +void U8_strdel(char* string, int position); + + +// Internal settings + +/*! Sets the string from which to load the initial glyphs. Use this if you need upfront loading for any reason (such as lack of render-target support). */ +void FC_SetLoadingString(FC_Font* font, const char* string); + +/*! Returns the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */ +unsigned int FC_GetBufferSize(void); + +/*! Changes the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */ +void FC_SetBufferSize(unsigned int size); + +void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)); + +FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale); + + +// Custom caching + +/*! Returns the number of cache levels that are active. */ +int FC_GetNumCacheLevels(FC_Font* font); + +/*! Returns the cache source texture at the given cache level. */ +FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level); + +// TODO: Specify ownership of the texture (should be shareable) +/*! Sets a cache source texture for rendering. New cache levels must be sequential. */ +Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture); + +/*! Copies the given surface to the given cache level as a texture. New cache levels must be sequential. */ +Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface); + + +/*! Returns the number of codepoints that are stored in the font's glyph data map. */ +unsigned int FC_GetNumCodepoints(FC_Font* font); + +/*! Copies the stored codepoints into the given array. */ +void FC_GetCodepoints(FC_Font* font, Uint32* result); + +/*! Stores the glyph data for the given codepoint in 'result'. Returns 0 if the codepoint was not found in the cache. */ +Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint); + +/*! Sets the glyph data for the given codepoint. Duplicates are not checked. Returns a pointer to the stored data. */ +FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data); + + +// Rendering + +FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...); +FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...); +FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...); +FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...); +FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...); + +FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...); +FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...); +FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...); +FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...); +FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...); + +FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...); +FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...); +FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...); +FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...); +FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...); + + +// Getters + +FC_FilterEnum FC_GetFilterMode(FC_Font* font); +Uint16 FC_GetLineHeight(FC_Font* font); +Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...); +Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...); + +// Returns a 1-pixel wide box in front of the character in the given position (index) +FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...); +Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...); + +int FC_GetAscent(FC_Font* font, const char* formatted_text, ...); +int FC_GetDescent(FC_Font* font, const char* formatted_text, ...); +int FC_GetBaseline(FC_Font* font); +int FC_GetSpacing(FC_Font* font); +int FC_GetLineSpacing(FC_Font* font); +Uint16 FC_GetMaxWidth(FC_Font* font); +SDL_Color FC_GetDefaultColor(FC_Font* font); + +Uint8 FC_InRect(float x, float y, FC_Rect input_rect); +// Given an offset (x,y) from the text draw position (the upper-left corner), returns the character position (UTF-8 index) +Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...); + +// Setters + +void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter); +void FC_SetSpacing(FC_Font* font, int LetterSpacing); +void FC_SetLineSpacing(FC_Font* font, int LineSpacing); +void FC_SetDefaultColor(FC_Font* font, SDL_Color color); + + +#ifdef __cplusplus +} +#endif + + + +#endif diff --git a/src/cmake/FindSDL2.cmake b/src/cmake/FindSDL2.cmake new file mode 100644 index 0000000..9aedb00 --- /dev/null +++ b/src/cmake/FindSDL2.cmake @@ -0,0 +1,46 @@ +# - Try to find SDL2 +# Once done, this will define +# +# SDL2_FOUND - system has SDL2 +# SDL2_INCLUDE_DIRS - the SDL2 include directories +# SDL2_LIBRARIES - link these to use SDL2 +# SDL2_SDL_LIBRARY - only libSDL2 +# SDL2_SDLmain_LIBRARY - only libSDL2main +# SDL2_SOURCES - add this in the source file list of your target (hack for OSX) +# +# See documentation on how to write CMake scripts at +# http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries + +include(LibFindMacros) +libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) + +# Process others than OSX with native SDL normally +if(NOT "${SDL2_SDL_LIBRARY}" MATCHES "framework") + if(MINGW) + set(MINGW32_LIBRARY mingw32) + set(SDL2_PROCESS_LIBS ${SDL2_PROCESS_LIBS} MINGW32_LIBRARY) + endif() + find_library(SDL2_SDLmain_LIBRARY + NAMES libSDL2main.a SDL2main + HINTS ${SDL2_PKGCONF_LIBRARY_DIRS} + ) + if (SDL2_SDLmain_LIBRARY) + set(SDL2_PROCESS_LIBS ${SDL2_PROCESS_LIBS} SDL2_SDLmain_LIBRARY) + endif() + set(SDL2_PROCESS_LIBS ${SDL2_PROCESS_LIBS} SDL2_SDL_LIBRARY) +endif() + +libfind_process(SDL2) + +# Special processing for OSX native SDL +if("${SDL2_SDL_LIBRARY}" MATCHES "SDL.framework") + set(SDL2_SOURCES "osx/SDLmain.m") + set(SDL2_LIBRARIES "-framework SDL2") +endif() + +# All OSX versions need Cocoa +if(APPLE) + set(SDL2_LIBRARIES ${SDL2_LIBRARIES} "-framework Cocoa") +endif(APPLE) + + diff --git a/src/cmake/LibFindMacros.cmake b/src/cmake/LibFindMacros.cmake new file mode 100644 index 0000000..f253ffb --- /dev/null +++ b/src/cmake/LibFindMacros.cmake @@ -0,0 +1,267 @@ +# Version 2.2 +# Public Domain, originally written by Lasse Kärkkäinen +# Maintained at https://github.com/Tronic/cmake-modules +# Please send your improvements as pull requests on Github. + +# Find another package and make it a dependency of the current package. +# This also automatically forwards the "REQUIRED" argument. +# Usage: libfind_package( [extra args to find_package]) +macro (libfind_package PREFIX PKG) + set(${PREFIX}_args ${PKG} ${ARGN}) + if (${PREFIX}_FIND_REQUIRED) + set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) + endif() + find_package(${${PREFIX}_args}) + set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) + unset(${PREFIX}_args) +endmacro() + +# A simple wrapper to make pkg-config searches a bit easier. +# Works the same as CMake's internal pkg_check_modules but is always quiet. +macro (libfind_pkg_check_modules) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${ARGN} QUIET) + endif() +endmacro() + +# Avoid useless copy&pasta by doing what most simple libraries do anyway: +# pkg-config, find headers, find library. +# Usage: libfind_pkg_detect( FIND_PATH [other args] FIND_LIBRARY [other args]) +# E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) +function (libfind_pkg_detect PREFIX) + # Parse arguments + set(argname pkgargs) + foreach (i ${ARGN}) + if ("${i}" STREQUAL "FIND_PATH") + set(argname pathargs) + elseif ("${i}" STREQUAL "FIND_LIBRARY") + set(argname libraryargs) + else() + set(${argname} ${${argname}} ${i}) + endif() + endforeach() + if (NOT pkgargs) + message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") + endif() + # Find library + libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + if (pathargs) + find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) + endif() + if (libraryargs) + find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) + endif() +endfunction() + +# Extracts a version #define from a version.h file, output stored to _VERSION. +# Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) +# Fourth argument "QUIET" may be used for silently testing different define names. +# This function does nothing if the version variable is already defined. +function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) + # Skip processing if we already have a version or if the include dir was not found + if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR) + return() + endif() + set(quiet ${${PREFIX}_FIND_QUIETLY}) + # Process optional arguments + foreach(arg ${ARGN}) + if (arg STREQUAL "QUIET") + set(quiet TRUE) + else() + message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.") + endif() + endforeach() + # Read the header and parse for version number + set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + if (NOT EXISTS ${filename}) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + endif() + return() + endif() + file(READ "${filename}" header) + string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}") + # No regex match? + if (match STREQUAL header) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + endif() + return() + endif() + # Export the version string + set(${PREFIX}_VERSION "${match}" PARENT_SCOPE) +endfunction() + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +function (libfind_process PREFIX) + # Skip processing if already processed during this configuration run + if (${PREFIX}_FOUND) + return() + endif() + + set(found TRUE) # Start with the assumption that the package was found + + # Did we find any files? Did we miss includes? These are for formatting better error messages. + set(some_files FALSE) + set(missing_headers FALSE) + + # Shorthands for some variables that we need often + set(quiet ${${PREFIX}_FIND_QUIETLY}) + set(required ${${PREFIX}_FIND_REQUIRED}) + set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) + set(findver "${${PREFIX}_FIND_VERSION}") + set(version "${${PREFIX}_VERSION}") + + # Lists of config option names (all, includes, libs) + unset(configopts) + set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) + set(libraryopts ${${PREFIX}_PROCESS_LIBS}) + + # Process deps to add to + foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) + if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) + # The package seems to export option lists that we can use, woohoo! + list(APPEND includeopts ${${i}_INCLUDE_OPTS}) + list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) + else() + # If plural forms don't exist or they equal singular forms + if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR + ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) + # Singular forms can be used + if (DEFINED ${i}_INCLUDE_DIR) + list(APPEND includeopts ${i}_INCLUDE_DIR) + endif() + if (DEFINED ${i}_LIBRARY) + list(APPEND libraryopts ${i}_LIBRARY) + endif() + else() + # Oh no, we don't know the option names + message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") + endif() + endif() + endforeach() + + if (includeopts) + list(REMOVE_DUPLICATES includeopts) + endif() + + if (libraryopts) + list(REMOVE_DUPLICATES libraryopts) + endif() + + string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") + if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") + message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") + endif() + + # Include/library names separated by spaces (notice: not CMake lists) + unset(includes) + unset(libs) + + # Process all includes and set found false if any are missing + foreach (i ${includeopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND includes "${${i}}") + else() + set(found FALSE) + set(missing_headers TRUE) + endif() + endforeach() + + # Process all libraries and set found false if any are missing + foreach (i ${libraryopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND libs "${${i}}") + else() + set (found FALSE) + endif() + endforeach() + + # Version checks + if (found AND findver) + if (NOT version) + message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") + elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) + set(found FALSE) + set(version_unsuitable TRUE) + endif() + endif() + + # If all-OK, hide all config options, export variables, print status and exit + if (found) + foreach (i ${configopts}) + mark_as_advanced(${i}) + endforeach() + if (NOT quiet) + message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + if (LIBFIND_DEBUG) + message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") + message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") + message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") + message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") + message(STATUS " ${PREFIX}_LIBRARIES=${libs}") + endif() + set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) + set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) + set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) + set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) + set (${PREFIX}_FOUND TRUE PARENT_SCOPE) + endif() + return() + endif() + + # Format messages for debug info and the type of error + set(vars "Relevant CMake configuration variables:\n") + foreach (i ${configopts}) + mark_as_advanced(CLEAR ${i}) + set(val ${${i}}) + if ("${val}" STREQUAL "${i}-NOTFOUND") + set (val "") + elseif (val AND NOT EXISTS ${val}) + set (val "${val} (does not exist)") + else() + set(some_files TRUE) + endif() + set(vars "${vars} ${i}=${val}\n") + endforeach() + set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") + if (version_unsuitable) + set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") + if (exactver) + set(msg "${msg} only version ${findver} is acceptable.") + else() + set(msg "${msg} version ${findver} is the minimum requirement.") + endif() + else() + if (missing_headers) + set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") + elseif (some_files) + set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") + if(findver) + set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") + endif() + else() + set(msg "We were unable to find package ${PREFIX}.") + endif() + endif() + + # Fatal error out if REQUIRED + if (required) + set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") + message(FATAL_ERROR "${msg}\n${vars}") + endif() + # Otherwise just print a nasty warning + if (NOT quiet) + message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") + endif() +endfunction() + + diff --git a/src/sago/GameStateInterface.hpp b/src/sago/GameStateInterface.hpp new file mode 100644 index 0000000..43b528b --- /dev/null +++ b/src/sago/GameStateInterface.hpp @@ -0,0 +1,54 @@ +/* +Copyright (c) 2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef GAMESTATEINTERFACE_HPP +#define GAMESTATEINTERFACE_HPP + +#include "SDL.h" + +namespace sago { + +class GameStateInterface { +public: + /** + * Is the state active. If this returns false then the State-manager will pop the state object + * @return true if active + */ + virtual bool IsActive() = 0; + + /** + * Tells the state to draw itself to target + * @param target The RenderWindow to draw to + */ + virtual void Draw(SDL_Renderer* target) = 0; + + virtual void ProcessInput(const SDL_Event& event, bool &processed) = 0; + + virtual void Update() {} +}; + +} //sago + +#endif /* GAMESTATEINTERFACE_HPP */ + diff --git a/src/sago/LICENSE b/src/sago/LICENSE new file mode 100644 index 0000000..fe6d772 --- /dev/null +++ b/src/sago/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/sago/Makefile.sago b/src/sago/Makefile.sago new file mode 100644 index 0000000..700f529 --- /dev/null +++ b/src/sago/Makefile.sago @@ -0,0 +1,4 @@ + + +SAGO_BASE_LIBS+= -lphysfs +SAGO_O_FILES+= sago/SagoDataHolder.o sago/SagoSprite.o sago/SagoSpriteHolder.o sago/SagoMisc.o \ No newline at end of file diff --git a/src/sago/SagoDataHolder.cpp b/src/sago/SagoDataHolder.cpp new file mode 100644 index 0000000..4e959e1 --- /dev/null +++ b/src/sago/SagoDataHolder.cpp @@ -0,0 +1,333 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoDataHolder.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "SagoMiscSdl2.hpp" + +namespace sago { + +struct SagoDataHolder::SagoDataHolderData { + std::map textures; + std::map > fonts; //font, ptsize + std::map music; + std::map sounds; + std::vector rwOpsToFree; + std::vector> dataToFree; + bool verbose = false; + Uint64 version = 0; + SDL_Renderer* renderer = nullptr; +}; + +static void printFileWeLoad(const std::string& value) { + std::cout << "Loading " << value << "\n"; +} + +SagoDataHolder::SagoDataHolder() { + data = new SagoDataHolderData(); +} + +SagoDataHolder::SagoDataHolder(SDL_Renderer* renderer) { + data = new SagoDataHolderData(); + data->renderer = renderer; +} + +void SagoDataHolder::invalidateAll(SDL_Renderer* renderer) { + invalidateAll(); + data->renderer = renderer; +} + +void SagoDataHolder::invalidateAll() { + data->version++; + for (auto& item : data->textures) { + SDL_DestroyTexture(item.second); + } + data->textures.clear(); + for (auto& item : data->music) { + Mix_FreeMusic(item.second); + } + data->music.clear(); + for (auto& item : data->sounds) { + Mix_FreeChunk(item.second); + } + data->sounds.clear(); + for (auto& item : data->fonts) { + for (auto& item2 : item.second) { + TTF_CloseFont(item2.second); + } + } + data->fonts.clear(); + for (auto& item : data->rwOpsToFree) { + SDL_FreeRW(item); + } + data->rwOpsToFree.clear(); +} + +SagoDataHolder::~SagoDataHolder() { + invalidateAll(); + delete data; +} + +SDL_Texture* SagoDataHolder::getTexturePtr(const std::string& textureName) const { + if (!data->renderer) { + throw std::runtime_error("SagoDataHolder used before setting the renderer"); + } + SDL_Texture* ret = data->textures[textureName]; + if (ret) { + return ret; + } + std::string path = "textures/"+textureName+".png"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + sago::SagoFatalErrorF("getTextureFailed - Texture does not exist: %s", path.c_str()); + } + PHYSFS_file* myfile = PHYSFS_openRead(path.c_str()); + unsigned int m_size = PHYSFS_fileLength(myfile); + std::unique_ptr m_data(new char[m_size]); + int length_read = PHYSFS_read (myfile, m_data.get(), 1, m_size); + if (length_read != (int)m_size) { + PHYSFS_close(myfile); + std::cerr << "Error: Curropt data file: " << path << "\n"; + return ret; + } + PHYSFS_close(myfile); + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + //The above might fail an return null. + if (!rw) { + PHYSFS_close(myfile); + std::cerr << "Error. Curropt data file!\n"; + return NULL; + } + SDL_Surface* surface = IMG_Load_RW(rw,true); + + ret = SDL_CreateTextureFromSurface(data->renderer, surface); + + if (!ret) { + std::cerr << "getTextureFailed to load " << path << "\n"; + } + SDL_FreeSurface(surface); + data->textures[textureName] = ret; + return ret; +} + +TTF_Font* SagoDataHolder::getFontPtr(const std::string& fontName, int ptsize) const { + TTF_Font* ret = data->fonts[fontName][ptsize]; + if (ret) { + return ret; + } + std::string path = "fonts/"+fontName+".ttf"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + std::cerr << "getFontPtr - Font does not exists: " << path << "\n"; + return ret; + } + PHYSFS_file* myfile = PHYSFS_openRead(path.c_str()); + unsigned int m_size = PHYSFS_fileLength(myfile); + std::unique_ptr m_data(new char[m_size]); + int length_read = PHYSFS_read (myfile, m_data.get(), 1, m_size); + if (length_read != (int)m_size) { + PHYSFS_close(myfile); + std::cerr << "Error: Curropt data file: " << path << "\n"; + return ret; + } + PHYSFS_close(myfile); + + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + + //The above might fail an return null. + if (!rw) { + PHYSFS_close(myfile); + std::cerr << "Error: Curropt data file!\n"; + return ret; + } + + ret = TTF_OpenFontRW(rw, SDL_FALSE, ptsize); + if (!ret) { + std::cerr << "Error openening font: " << fontName << " because: " << TTF_GetError() << "\n"; + } + data->fonts[fontName][ptsize] = ret; + data->dataToFree.push_back(std::move(m_data)); + data->rwOpsToFree.push_back(rw); + return ret; +} + +Mix_Music* SagoDataHolder::getMusicPtr(const std::string& musicName) const { + Mix_Music* ret = data->music[musicName]; + if (ret) { + return ret; + } + std::string path = "music/"+musicName+".ogg"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + std::cerr << "getMusicPtr - Music file does not exists: " << path << "\n"; + return ret; + } + PHYSFS_file* myfile = PHYSFS_openRead(path.c_str()); + unsigned int m_size = PHYSFS_fileLength(myfile); + std::unique_ptr m_data(new char[m_size]); + int length_read = PHYSFS_read (myfile, m_data.get(), 1, m_size); + if (length_read != (int)m_size) { + PHYSFS_close(myfile); + std::cerr << "Error: Curropt data file: " << path << "\n"; + return ret; + } + PHYSFS_close(myfile); + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + + //The above might fail an return null. + if (!rw) { + PHYSFS_close(myfile); + std::cerr << "Error. Curropt data file!\n"; + return NULL; + } + + ret = Mix_LoadMUS_RW(rw, SDL_TRUE); //SDL_TRUE causes rw to be freed + + if (!ret) { + std::cerr << "getMusicPtr to load " << path << " because: " << Mix_GetError() << "\n"; + } + data->music[musicName] = ret; + data->dataToFree.push_back(std::move(m_data)); + return ret; +} + + +Mix_Chunk* SagoDataHolder::getSoundPtr(const std::string& soundName) const { + Mix_Chunk* ret = data->sounds[soundName]; + if (ret) { + return ret; + } + std::string path = "sounds/"+soundName+".ogg"; + if (data->verbose) { + printFileWeLoad(path); + } + if (!PHYSFS_exists(path.c_str())) { + std::cerr << "getSoundPtr - Sound file does not exists: " << path << "\n"; + return ret; + } + PHYSFS_file* myfile = PHYSFS_openRead(path.c_str()); + unsigned int m_size = PHYSFS_fileLength(myfile); + std::unique_ptr m_data(new char[m_size]); + int length_read = PHYSFS_read (myfile, m_data.get(), 1, m_size); + if (length_read != (int)m_size) { + PHYSFS_close(myfile); + std::cerr << "Error: Curropt data file: " << path << "\n"; + return ret; + } + PHYSFS_close(myfile); + SDL_RWops* rw = SDL_RWFromMem (m_data.get(), m_size); + + //The above might fail an return null. + if (!rw) { + PHYSFS_close(myfile); + std::cerr << "Error. Curropt data file!\n"; + return NULL; + } + + ret = Mix_LoadWAV_RW(rw, SDL_TRUE); + data->sounds[soundName] = ret; + data->dataToFree.push_back(std::move(m_data)); + return ret; +} + +void SagoDataHolder::setVerbose(bool value) { + data->verbose = value; +} + +Uint64 SagoDataHolder::getVersion() const { + return data->version; +} + +TextureHandler::TextureHandler(const SagoDataHolder* holder, const std::string &textureName) { + this->holder = holder; + this->version = this->holder->getVersion(); + this->textureName = textureName; + this->data = this->holder->getTexturePtr(this->textureName); +} + +SDL_Texture* TextureHandler::get() { + if (version != holder->getVersion()) { + //The holder has been invalidated + this->data = this->holder->getTexturePtr(textureName); + } + return data; +} + + +MusicHandler::MusicHandler(const SagoDataHolder* holder, const std::string& musicName) { + this->holder = holder; + this->version = this->holder->getVersion(); + this->musicName = musicName; + this->data = this->holder->getMusicPtr(this->musicName); +} + +Mix_Music* MusicHandler::get() { + if (version != holder->getVersion()) { + //The holder has been invalidated + this->data = this->holder->getMusicPtr(musicName); + } + return data; +} + +SoundHandler::SoundHandler(const SagoDataHolder* holder, const std::string& soundName) { + this->holder = holder; + this->version = this->holder->getVersion(); + this->soundName = soundName; + this->data = this->holder->getSoundPtr(this->soundName); +} + +Mix_Chunk* SoundHandler::get() { + if (version != holder->getVersion()) { + //The holder has been invalidated + this->data = this->holder->getSoundPtr(soundName); + } + return data; +} + + +TextureHandler SagoDataHolder::getTextureHandler(const std::string &textureName) const { + return TextureHandler(this, textureName); +} + +MusicHandler SagoDataHolder::getMusicHandler(const std::string &musicName) const { + return MusicHandler(this, musicName); +} + +SoundHandler SagoDataHolder::getSoundHandler(const std::string &soundName) const { + return SoundHandler(this, soundName); +} + +} //name space sago diff --git a/src/sago/SagoDataHolder.hpp b/src/sago/SagoDataHolder.hpp new file mode 100644 index 0000000..4cc0185 --- /dev/null +++ b/src/sago/SagoDataHolder.hpp @@ -0,0 +1,121 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SDL.h" +#include //Used for sound & music +#include //To load PNG images! +#include +#include //Abstract file system. To use containers +#include + +#ifndef TEXTUREHOLDER_HPP +#define TEXTUREHOLDER_HPP + +namespace sago { + +class SagoDataHolder; + +class TextureHandler { +public: + TextureHandler() {}; + TextureHandler(const SagoDataHolder* holder, const std::string &textureName); + SDL_Texture* get(); +private: + std::string textureName; + const SagoDataHolder* holder = nullptr; + SDL_Texture* data = nullptr; + Uint64 version = 0; +}; + + +class MusicHandler { +public: + MusicHandler() {}; + MusicHandler(const SagoDataHolder* holder, const std::string &musicName); + Mix_Music* get(); +private: + std::string musicName; + const SagoDataHolder* holder = nullptr; + Mix_Music* data = nullptr; + Uint64 version = 0; +}; + + +class SoundHandler { +public: + SoundHandler() {}; + SoundHandler(const SagoDataHolder* holder, const std::string &soundName); + Mix_Chunk* get(); +private: + std::string soundName; + const SagoDataHolder* holder = nullptr; + Mix_Chunk* data = nullptr; + Uint64 version = 0; +}; + +class SagoDataHolder { +public: + /** + * The renderer must be set before requesting a texture. + * If the constructor without elements is used then invalidateAll(SDL_Renderer*) must be called before getTexturePtr + */ + SagoDataHolder(); + SagoDataHolder(SDL_Renderer* renderer); + /** + * Return a pointer to the given texture. The pointer is valid for the lifetime of SagoDataHolder object it was taken from or invalidateAll is called. + * @param textureName Name of the texture + * @return Pointer to the loaded texture + */ + SDL_Texture* getTexturePtr(const std::string &textureName) const; + TextureHandler getTextureHandler(const std::string &textureName) const; + TTF_Font* getFontPtr(const std::string &fontName, int ptsize) const; + Mix_Music* getMusicPtr(const std::string &musicName) const; + MusicHandler getMusicHandler(const std::string &musicName) const; + Mix_Chunk* getSoundPtr(const std::string &soundName) const; + SoundHandler getSoundHandler(const std::string &soundName) const; + void setVerbose(bool value); + /** + * Invalidates all pointers returned by any of the get variables + */ + void invalidateAll(); + /** + * Invalidates all pointers returned by any of the get variables. + * Also sets a new renderer. + * + * Setting a new renderer might cause all old textures to no longer match the renderer format. + */ + void invalidateAll(SDL_Renderer* renderer); + Uint64 getVersion() const; + virtual ~SagoDataHolder(); +private: + SagoDataHolder(const SagoDataHolder& base) = delete; + SagoDataHolder& operator=(const SagoDataHolder& base) = delete; + struct SagoDataHolderData; + mutable SagoDataHolderData *data; +}; + +} //namespace sago + +#endif /* TEXTUREHOLDER_HPP */ + diff --git a/src/sago/SagoMisc.cpp b/src/sago/SagoMisc.cpp new file mode 100644 index 0000000..da58bd8 --- /dev/null +++ b/src/sago/SagoMisc.cpp @@ -0,0 +1,89 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoMisc.hpp" +#include +#include +#include +#include +#include + +using std::string; +using std::cerr; +using std::vector; + +namespace sago { + + +std::vector GetFileList(const char* dir) { + vector ret; + char** rc = PHYSFS_enumerateFiles(dir); + for (char** i = rc; *i != NULL; i++) { + ret.push_back(*i); + } + PHYSFS_freeList(rc); + return ret; +} + +bool FileExists(const char* filename) { + return PHYSFS_exists(filename); +} + +std::string GetFileContent(const char* filename) { + string ret; + if (!PHYSFS_exists(filename)) { + cerr << "GetFileContent - File does not exists: " << filename << "\n"; + return ret; + } + PHYSFS_file* myfile = PHYSFS_openRead(filename); + unsigned int m_size = PHYSFS_fileLength(myfile); + std::unique_ptr m_data(new char[m_size]); + int length_read = PHYSFS_read (myfile, m_data.get(), 1, m_size); + if (length_read != (int)m_size) { + PHYSFS_close(myfile); + cerr << "Error: Curropt data file: " << filename << "\n"; + return ret; + } + PHYSFS_close(myfile); + //Now create a std::string + ret = string(m_data.get(), m_data.get()+m_size); + return ret; +} + +void WriteFileContent(const char* filename, const std::string& content) { + PHYSFS_file* myfile = PHYSFS_openWrite(filename); + if (!myfile) { + cerr << "Failed to open file for writing, " << PHYSFS_getLastError() << "\n"; + return; + } + PHYSFS_write(myfile, content.c_str(), sizeof(char), content.length()); + PHYSFS_close(myfile); +} + +long int StrToLong(const char* c_string) { + auto ret = strtol(c_string, nullptr, 10); + return ret; +} + +} diff --git a/src/sago/SagoMisc.hpp b/src/sago/SagoMisc.hpp new file mode 100644 index 0000000..55bc097 --- /dev/null +++ b/src/sago/SagoMisc.hpp @@ -0,0 +1,72 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOMISC_HPP +#define SAGOMISC_HPP + +#include +#include + +namespace sago { + + /** + * Returns a vector with all filenames in a given directory. + * PHYSFS must be setup before hand. The directory is relative to the PHYSFS base + * @param dir The directory to list + * @return A vector with the filenames in the given directory. If empty the directory was empty or did not exist + */ + std::vector GetFileList(const char* dir); + + /** + * Reads an entire file into memory. + * PHYSFS must be setup before hand + * @param filename The file to read + * @return The content of the file. If empty either the file was empty, did not exist or could not be opened + */ + std::string GetFileContent(const char* filename); + + /** + * Reads an entire file into memory. + * PHYSFS must be setup before hand + * @param filename The file to read + * @return The content of the file. If empty either the file was empty, did not exist or could not be opened + */ + inline std::string GetFileContent(const std::string& filename) { return GetFileContent(filename.c_str()); }; + + bool FileExists(const char* filename); + + void WriteFileContent(const char* filename, const std::string& content); + + /** + * This functions convers a string on a best effort basis + * Unlike atol this does NOT cause undefined behavior if out of range + * @param c_string A string that may contain a number + * @return A number between LONG_MIN and LONG_MAX (both inclusive) + */ + long int StrToLong(const char* c_string); + +} //namespace sago + +#endif /* SAGOMISC_HPP */ + diff --git a/src/sago/SagoMiscSdl2.cpp b/src/sago/SagoMiscSdl2.cpp new file mode 100644 index 0000000..8a3d294 --- /dev/null +++ b/src/sago/SagoMiscSdl2.cpp @@ -0,0 +1,54 @@ +/* +Copyright (c) 2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoMiscSdl2.hpp" +#include "SDL.h" + +void sago::SagoFatalError(const char* errorMsg) { + const SDL_MessageBoxButtonData buttons[] = { + { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Ok" }, + }; + const SDL_MessageBoxData messageboxdata = { + SDL_MESSAGEBOX_INFORMATION, /* .flags */ + nullptr, /* .window */ + "Fatal error", /* .title */ + errorMsg, /* .message */ + SDL_arraysize(buttons), /* .numbuttons */ + buttons, /* .buttons */ + nullptr /* .colorScheme */ + }; + int buttonid; + SDL_ShowMessageBox(&messageboxdata, &buttonid); + abort(); +} + +void sago::SagoFatalErrorF(const char* fmt, ...) { + char buffer[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + SagoFatalError(buffer); + va_end(args); +} + diff --git a/src/sago/SagoMiscSdl2.hpp b/src/sago/SagoMiscSdl2.hpp new file mode 100644 index 0000000..1bc115b --- /dev/null +++ b/src/sago/SagoMiscSdl2.hpp @@ -0,0 +1,46 @@ +/* +Copyright (c) 2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOMISCSDL2_HPP +#define SAGOMISCSDL2_HPP + +namespace sago { + + /** + * Writes an error message to the screen and aborts the program + * @param errorMsg The message displayed in a pop-up box to the user. + */ + void SagoFatalError(const char* errorMsg); + + /** + * Writes an error message to the screen and aborts the program + * @param fmt A printf-style format string + * @param ... Parameters to the format string + */ + void SagoFatalErrorF(const char* fmt, ...) __attribute__ ((format (printf, 1, 2))); + +} + +#endif /* SAGOMISCSDL2_HPP */ + diff --git a/src/sago/SagoSprite.cpp b/src/sago/SagoSprite.cpp new file mode 100644 index 0000000..2ddcd14 --- /dev/null +++ b/src/sago/SagoSprite.cpp @@ -0,0 +1,155 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoSprite.hpp" +#include + +namespace sago { + +struct SagoSprite::SagoSpriteData { + TextureHandler tex; + SDL_Rect imgCord{}; + SDL_Rect origin{}; + int aniFrames = 0; + int aniFrameTime = 0; +}; + +SagoSprite::SagoSprite() { + data = new SagoSpriteData(); +} + +SagoSprite::SagoSprite(const SagoDataHolder& texHolder, const std::string& texture,const SDL_Rect& initImage,const int animationFrames, const int animationFrameLength) { + data = new SagoSpriteData(); + data->tex = texHolder.getTextureHandler(texture); + data->imgCord = initImage; + data->aniFrames = animationFrames; + data->aniFrameTime = animationFrameLength; +} + +SagoSprite::SagoSprite(const SagoSprite& base) : data(new SagoSpriteData(*base.data)) { + +} + +SagoSprite& SagoSprite::operator=(const SagoSprite& base) { + *data = *base.data; + return *this; +} + +SagoSprite::~SagoSprite() { + delete data; +} + +void SagoSprite::Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y) const { + DrawScaled(target, frameTime, x, y, data->imgCord.w, data->imgCord.h); +} + +void SagoSprite::DrawScaled(SDL_Renderer* target, Sint32 frameTime, int x, int y, int w, int h) const { + if (!data->tex.get()) { + std::cerr << "Texture is null!\n"; + } + SDL_Rect rect = data->imgCord; + rect.x+=rect.w*((frameTime/data->aniFrameTime)%data->aniFrames); + SDL_Rect pos = rect; + pos.x = x; + pos.y = y; + if (w > 0) { + pos.w = w; + } + if (h > 0) { + pos.h = h; + } + SDL_RenderCopy(target, data->tex.get(), &rect, &pos); +} + +void SagoSprite::Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& part) const { + SDL_Rect rect = data->imgCord; + rect.x+=rect.w*((frameTime/data->aniFrameTime)%data->aniFrames); + rect.x += part.x; + rect.y += part.y; + rect.w = part.w; + rect.h = part.h; + SDL_Rect pos = rect; + pos.x = x; + pos.y = y; + SDL_RenderCopy(target, data->tex.get(), &rect, &pos); +} + +void SagoSprite::DrawBounded(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& bounds) const { + SDL_Rect rect = data->imgCord; + rect.x+=rect.w*((frameTime/data->aniFrameTime)%data->aniFrames); + SDL_Rect pos = rect; + pos.x = x; + pos.y = y; + if (pos.x > bounds.x+bounds.w) { + return; + } + if (pos.y > bounds.y+bounds.h) { + return; + } + if (pos.x+pos.w < bounds.x) { + return; + } + if (pos.y+pos.h < bounds.y) { + return; + } + if (pos.x < bounds.x) { + Sint16 absDiff = bounds.x-pos.x; + pos.x+=absDiff; + rect.x+=absDiff; + pos.w-=absDiff; + rect.w-=absDiff; + } + if (pos.y < bounds.y) { + Sint16 absDiff = bounds.y-pos.y; + pos.y+=absDiff; + rect.y+=absDiff; + pos.h-=absDiff; + rect.h-=absDiff; + } + if (pos.x+pos.w > bounds.x+bounds.w) { + Sint16 absDiff = pos.x+pos.w-(bounds.x+bounds.w); + pos.w -= absDiff; + rect.w -= absDiff; + } + if (pos.y+pos.h > bounds.y+bounds.h) { + Sint16 absDiff = pos.y+pos.h-(bounds.y+bounds.h); + pos.h -= absDiff; + rect.h -= absDiff; + } + + SDL_RenderCopy(target, data->tex.get(), &rect, &pos); +} + +void SagoSprite::SetOrigin(const SDL_Rect& newOrigin) { + data->origin = newOrigin; +} + +int SagoSprite::GetWidth() const { + return data->imgCord.w; +} +int SagoSprite::GetHeight() const { + return data->imgCord.h; +} + +} //namespace sago \ No newline at end of file diff --git a/src/sago/SagoSprite.hpp b/src/sago/SagoSprite.hpp new file mode 100644 index 0000000..6a19c3f --- /dev/null +++ b/src/sago/SagoSprite.hpp @@ -0,0 +1,81 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOSPRITE_HPP +#define SAGOSPRITE_HPP + +#include "SagoDataHolder.hpp" + +namespace sago { + +class SagoSprite { +public: + SagoSprite(); + SagoSprite(const SagoDataHolder &texHolder, const std::string &texture,const SDL_Rect& initImage,const int animationFrames, const int animationFrameLength); + /** + * Draws the sprite to a given render window + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + */ + void Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y) const; + /** + * Draws part of the sprite to a given render window + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + * @param part the part of the sprite that should be drawn. + */ + void Draw(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& part) const; + /** + * Draws the wprite to the given renderer but makes sure to not draw outside th bounds given + * @param target The render window to draw on + * @param frameTime The time in milliseonds since gamestart. Used to determen the place in the animation + * @param x Place to draw the sprite + * @param y Place to draw the sprite + * @param bounds A recagular area that we must not draw outside. + */ + void DrawBounded(SDL_Renderer* target, Sint32 frameTime, int x, int y, const SDL_Rect& bounds) const; + void DrawScaled(SDL_Renderer* target, Sint32 frameTime, int x, int y, int w, int h) const; + /** + * Set a different origin. Normally it is the top left cornor. But in some cases you might want to center the origin or tranform it for other reasons + * @param newOrigin the coordinates that should be the new origin. Call with {0,0} to reset to default + */ + void SetOrigin(const SDL_Rect& newOrigin); + SagoSprite(const SagoSprite& base); + SagoSprite& operator=(const SagoSprite& base); + int GetWidth() const; + int GetHeight() const; + virtual ~SagoSprite(); +private: + struct SagoSpriteData; + SagoSpriteData *data; +}; + +} + +#endif /* SAGOSPRITE_HPP */ + diff --git a/src/sago/SagoSpriteHolder.cpp b/src/sago/SagoSpriteHolder.cpp new file mode 100644 index 0000000..138b3e0 --- /dev/null +++ b/src/sago/SagoSpriteHolder.cpp @@ -0,0 +1,151 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "SagoSpriteHolder.hpp" +#include "SagoMisc.hpp" +#include +#include +#include "rapidjson/document.h" +#include +#include +#include + +using std::string; +using std::cerr; +using std::cout; +using std::vector; + +namespace sago { + +struct SagoSpriteHolder::SagoSpriteHolderData { + const sago::SagoDataHolder* tex; + std::unordered_map> sprites; + const sago::SagoSprite* defaultSprite; + bool verbose = false; +}; + +SagoSpriteHolder::SagoSpriteHolder(const SagoDataHolder& texHolder) { + data = new SagoSpriteHolderData(); + try { + data->tex = &texHolder; + ReadSprites(); + data->defaultSprite = new sago::SagoSprite(texHolder,"fallback", {0,0,64,64},1,100); + } + catch (...) { + delete data; + } +} + +SagoSpriteHolder::~SagoSpriteHolder() { + delete data; +} + +static int getDefaultValue(const rapidjson::Value& value, const char* name, int defaultValue) { + assert(value.IsObject()); + const auto& t = value.GetObject().FindMember(name); + if (t->value.IsInt()) { + return t->value.GetInt(); + } + return defaultValue; +} + +static std::string getDefaultValue(const rapidjson::Value& value, const char* name, std::string defaultValue) { + assert(value.IsObject()); + const auto& t = value.GetObject().FindMember(name); + if (t->value.IsString()) { + defaultValue = t->value.GetString(); + } + return defaultValue; +} + +void SagoSpriteHolder::ReadSpriteFile(const std::string& filename) { + string fullfile = "sprites/"+filename; + string content = sago::GetFileContent(fullfile.c_str()); + rapidjson::Document document; + document.Parse(content.c_str()); + if ( !document.IsObject() ) { + cerr << "Failed to parse: " << fullfile << "\n"; + return; + } + for (auto& m : document.GetObject()) { + const std::string& spriteName = m.name.GetString(); + if (!m.value.IsObject()) { + if (spriteName[0] != '_') { + std::cerr << "Invalid sprite: " << spriteName << "\n"; + } + continue; + } + string textureName = getDefaultValue(m.value, "texture", "fallback"); + int topx = getDefaultValue(m.value, "topx", 0); + int topy = getDefaultValue(m.value, "topy",0); + int height = getDefaultValue(m.value, "height",0); + int width = getDefaultValue(m.value, "width",0); + int number_of_frames = getDefaultValue(m.value, "number_of_frames",1); + int frame_time = getDefaultValue(m.value, "frame_time",1); + int originx = getDefaultValue(m.value, "originx",0); + int originy = getDefaultValue(m.value, "originy",0); + if (number_of_frames < 1) { + number_of_frames = 1; + } + if (frame_time < 1) { + frame_time = 1; + } + std::shared_ptr ptr(new SagoSprite(*(data->tex),textureName, {topx,topy,width,height},number_of_frames,frame_time)); + ptr->SetOrigin({originx,originy, 0, 0}); + this->data->sprites[std::string(spriteName)] = ptr; + } +} + +void SagoSpriteHolder::ReadSprites() { + std::vector spritefiles = GetFileList("sprites"); + for (std::string& item : spritefiles ) { + if (boost::algorithm::ends_with(item,".sprite")) { + if (data->verbose) { + cout << "Found " << item << "\n"; + } + ReadSpriteFile(item); + } + else { + if (data->verbose) { + cout << "Ignoreing " << item << "\n"; + } + } + } +} + +const sago::SagoSprite& SagoSpriteHolder::GetSprite(const std::string& spritename) const { + std::unordered_map>::const_iterator got = data->sprites.find (spritename); + if ( got == data->sprites.end() ) { + return *data->defaultSprite; + } + else { + return *(got->second); + } +} + +const SagoDataHolder& SagoSpriteHolder::GetDataHolder() const { + return *data->tex; +} + +} diff --git a/src/sago/SagoSpriteHolder.hpp b/src/sago/SagoSpriteHolder.hpp new file mode 100644 index 0000000..2e88528 --- /dev/null +++ b/src/sago/SagoSpriteHolder.hpp @@ -0,0 +1,51 @@ +/* +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGOSPRITEHOLDER_HPP +#define SAGOSPRITEHOLDER_HPP + +#include "SagoDataHolder.hpp" +#include "SagoSprite.hpp" + +namespace sago { + +class SagoSpriteHolder { +public: + SagoSpriteHolder(const SagoDataHolder &texHolder); + virtual ~SagoSpriteHolder(); + void ReadSprites(); + const sago::SagoSprite& GetSprite(const std::string &spritename) const; + const SagoDataHolder& GetDataHolder() const; +private: + SagoSpriteHolder(const SagoSpriteHolder& base) = delete; + SagoSpriteHolder& operator=(const SagoSpriteHolder& base) = delete; + void ReadSpriteFile(const std::string &filename); + struct SagoSpriteHolderData; + SagoSpriteHolderData *data; +}; + +} + +#endif /* SAGOSPRITEHOLDER_HPP */ + diff --git a/src/sago/platform_folders.cpp b/src/sago/platform_folders.cpp new file mode 100644 index 0000000..34513bf --- /dev/null +++ b/src/sago/platform_folders.cpp @@ -0,0 +1,369 @@ +/* +Its is under the MIT license, to encourage reuse by cut-and-paste. + +The original files are hosted here: https://github.com/sago007/PlatformFolders + +Copyright (c) 2015-2016 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "platform_folders.h" +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#include + +#define strtok_r strtok_s + +static std::string win32_utf16_to_utf8(const wchar_t* wstr) +{ + std::string res; + // If the 6th parameter is 0 then WideCharToMultiByte returns the number of bytes needed to store the result. + int actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (actualSize > 0) { + //If the converted UTF-8 string could not be in the initial buffer. Allocate one that can hold it. + std::vector buffer(actualSize); + actualSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], buffer.size(), NULL, NULL); + res = buffer.data(); + } + if (actualSize == 0) { + // WideCharToMultiByte return 0 for errors. + std::string errorMsg = "UTF16 to UTF8 failed with error code: " + GetLastError(); + throw std::runtime_error(errorMsg.c_str()); + } + return res; +} + +static std::string GetWindowsFolder(int folderId, const char* errorMsg) { + wchar_t szPath[MAX_PATH]; + szPath[0] = 0; + if ( !SUCCEEDED( SHGetFolderPathW( NULL, folderId, NULL, 0, szPath ) ) ) + { + throw std::runtime_error(errorMsg); + } + return win32_utf16_to_utf8(szPath); +} + +static std::string GetAppData() { + return GetWindowsFolder(CSIDL_APPDATA, "RoamingAppData could not be found"); +} + +static std::string GetAppDataCommon() { + return GetWindowsFolder(CSIDL_COMMON_APPDATA, "Common appdata could not be found"); +} + +static std::string GetAppDataLocal() { + return GetWindowsFolder(CSIDL_LOCAL_APPDATA, "LocalAppData could not be found"); +} +#elif defined(__APPLE__) +#include + +static std::string GetMacFolder(OSType folderType, const char* errorMsg) { + std::string ret; + FSRef ref; + char path[PATH_MAX]; + OSStatus err = FSFindFolder( kUserDomain, folderType, kCreateFolder, &ref ); + if (err != noErr) { + throw std::runtime_error(errorMsg); + } + FSRefMakePath( &ref, (UInt8*)&path, PATH_MAX ); + ret = path; + return ret; +} + +#else +#include +#include +#include +#include +#include +//Typically Linux. For easy reading the comments will just say Linux but should work with most *nixes + +static void throwOnRelative(const char* envName, const char* envValue) { + if (envValue[0] != '/') { + char buffer[200]; + snprintf(buffer, sizeof(buffer), "Environment \"%s\" does not start with an '/'. XDG specifies that the value must be absolute. The current value is: \"%s\"", envName, envValue); + throw std::runtime_error(buffer); + } +} + +/** + * Retrives the effective user's home dir. + * If the user is running as root we ignore the HOME environment. It works badly with sudo. + * Writing to $HOME as root implies security concerns that a multiplatform program cannot be assumed to handle. + * @return The home directory. HOME environment is respected for non-root users if it exists. + */ +static std::string getHome() { + std::string res; + int uid = getuid(); + const char* homeEnv = getenv("HOME"); + if ( uid != 0 && homeEnv) { + //We only acknowlegde HOME if not root. + res = homeEnv; + return res; + } + struct passwd *pw = getpwuid(uid); + if (!pw) { + throw std::runtime_error("Unable to get passwd struct."); + } + const char* tempRes = pw->pw_dir; + if (!tempRes) { + throw std::runtime_error("User has no home directory"); + } + res = tempRes; + return res; +} + +static std::string getLinuxFolderDefault(const char* envName, const char* defaultRelativePath) { + std::string res; + const char* tempRes = getenv(envName); + if (tempRes) { + throwOnRelative(envName, tempRes); + res = tempRes; + return res; + } + res = getHome() + "/" + defaultRelativePath; + return res; +} + +static void appendExtraFoldersTokenizer(const char* envName, const char* envValue, std::vector& folders) { + std::vector buffer(envValue, envValue + strlen(envValue) + 1); + char *saveptr; + const char* p = strtok_r ( &buffer[0], ":", &saveptr); + while (p != NULL) { + if (p[0] == '/') { + folders.push_back(p); + } + else { + //Unless the system is wrongly configured this should never happen... But of course some systems will be incorectly configured. + //The XDG documentation indicates that the folder should be ignored but that the program should continue. + std::cerr << "Skipping path \"" << p << "\" in \"" << envName << "\" because it does not start with a \"/\"\n"; + } + p = strtok_r (NULL, ":", &saveptr); + } +} + +static void appendExtraFolders(const char* envName, const char* defaultValue, std::vector& folders) { + const char* envValue = getenv(envName); + if (!envValue) { + envValue = defaultValue; + } + appendExtraFoldersTokenizer(envName, envValue, folders); +} + +#endif + + +namespace sago { + +std::string getDataHome() { +#if defined(_WIN32) + return GetAppData(); +#elif defined(__APPLE__) + return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); +#else + return getLinuxFolderDefault("XDG_DATA_HOME", ".local/share"); +#endif +} + +std::string getConfigHome() { +#if defined(_WIN32) + return GetAppData(); +#elif defined(__APPLE__) + return GetMacFolder(kApplicationSupportFolderType, "Failed to find the Application Support Folder"); +#else + return getLinuxFolderDefault("XDG_CONFIG_HOME", ".config"); +#endif +} + +std::string getCacheDir() { +#if defined(_WIN32) + return GetAppDataLocal(); +#elif defined(__APPLE__) + return GetMacFolder(kCachedDataFolderType, "Failed to find the Application Support Folder"); +#else + return getLinuxFolderDefault("XDG_CONFIG_HOME", ".cache"); +#endif +} + +void appendAdditionalDataDirectories(std::vector& homes) { +#if defined(_WIN32) + homes.push_back(GetAppDataCommon()); +#elif defined(__APPLE__) +#else + appendExtraFolders("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/", homes); +#endif +} + +void appendAdditionalConfigDirectories(std::vector& homes) { +#if defined(_WIN32) + homes.push_back(GetAppDataCommon()); +#elif defined(__APPLE__) +#else + appendExtraFolders("XDG_CONFIG_DIRS", "/etc/xdg", homes); +#endif +} + +#if defined(_WIN32) +#elif defined(__APPLE__) +#else +struct PlatformFolders::PlatformFoldersData { + std::map folders; +}; + +static void PlatformFoldersAddFromFile(const std::string& filename, std::map& folders) { + std::ifstream infile(filename.c_str()); + std::string line; + while (std::getline(infile, line)) { + if (line.length() == 0 || line.at(0) == '#') { + continue; + } + std::size_t splitPos = line.find("="); + std::string key = line.substr(0, splitPos); + std::string value = line.substr(splitPos+2, line.length()-splitPos-3); + folders[key] = value; + //std::cout << key << " : " << value << "\n"; + } +} + +static void PlatformFoldersFillData(std::map& folders) { + folders["XDG_DOCUMENTS_DIR"] = "$HOME/Documents"; + folders["XDG_DESKTOP_DIR"] = "$HOME/Desktop"; + folders["XDG_DOWNLOAD_DIR"] = "$HOME/Downloads"; + folders["XDG_MUSIC_DIR"] = "$HOME/Music"; + folders["XDG_PICTURES_DIR"] = "$HOME/Pictures"; + folders["XDG_PUBLICSHARE_DIR"] = "$HOME/Public"; + folders["XDG_TEMPLATES_DIR"] = "$HOME/.Templates"; + folders["XDG_VIDEOS_DIR"] = "$HOME/Videos"; + PlatformFoldersAddFromFile( getConfigHome()+"/user-dirs.dirs", folders); + for (std::map::iterator itr = folders.begin() ; itr != folders.end() ; itr ++ ) { + std::string& value = itr->second; + if (value.compare(0, 5, "$HOME") == 0) { + value = getHome() + value.substr(5, std::string::npos); + } + } +} +#endif + +PlatformFolders::PlatformFolders() { +#if defined(_WIN32) +#elif defined(__APPLE__) +#else + this->data = new PlatformFolders::PlatformFoldersData(); + try { + PlatformFoldersFillData(data->folders); + } catch (...) { + delete this->data; + throw; + } +#endif +} + +PlatformFolders::~PlatformFolders() { +#if defined(_WIN32) +#elif defined(__APPLE__) +#else + delete this->data; +#endif +} + +std::string PlatformFolders::getDocumentsFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder"); +#elif defined(__APPLE__) + return GetMacFolder(kDocumentsFolderType, "Failed to find Documents Folder"); +#else + return data->folders["XDG_DOCUMENTS_DIR"]; +#endif +} + +std::string PlatformFolders::getDesktopFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find Desktop folder"); +#elif defined(__APPLE__) + return GetMacFolder(kDesktopFolderType, "Failed to find Desktop folder"); +#else + return data->folders["XDG_DESKTOP_DIR"]; +#endif +} + +std::string PlatformFolders::getPicturesFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_MYPICTURES, "Failed to find My Pictures folder"); +#elif defined(__APPLE__) + return GetMacFolder(kPictureDocumentsFolderType, "Failed to find Picture folder"); +#else + return data->folders["XDG_PICTURES_DIR"]; +#endif +} + +std::string PlatformFolders::getDownloadFolder1() const { +#if defined(_WIN32) + //Pre Vista. Files was downloaded to the desktop + return GetWindowsFolder(CSIDL_DESKTOP, "Failed to find My Downloads (Desktop) folder"); +#elif defined(__APPLE__) + return GetMacFolder(kDownloadsFolderType, "Failed to find Download folder"); +#else + return data->folders["XDG_DOWNLOAD_DIR"]; +#endif +} + +std::string PlatformFolders::getMusicFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_MYMUSIC, "Failed to find My Music folder"); +#elif defined(__APPLE__) + return GetMacFolder(kMusicDocumentsFolderType, "Failed to find Music folder"); +#else + return data->folders["XDG_MUSIC_DIR"]; +#endif +} + +std::string PlatformFolders::getVideoFolder() const { +#if defined(_WIN32) + return GetWindowsFolder(CSIDL_MYVIDEO, "Failed to find My Video folder"); +#elif defined(__APPLE__) + return GetMacFolder(kMovieDocumentsFolderType, "Failed to find Movie folder"); +#else + return data->folders["XDG_VIDEOS_DIR"]; +#endif +} + +std::string PlatformFolders::getSaveGamesFolder1() const { +#if defined(_WIN32) + //A dedicated Save Games folder was not introduced until Vista. For XP and older save games are most often saved in a normal folder named "My Games". + //Data that should not be user accessible should be placed under GetDataHome() instead + return GetWindowsFolder(CSIDL_PERSONAL, "Failed to find My Documents folder")+"\\My Games"; +#elif defined(__APPLE__) + return GetMacFolder(kApplicationSupportFolderType, "Failed to find Application Support Folder"); +#else + return getDataHome(); +#endif +} + + + +} //namespace sago diff --git a/src/sago/platform_folders.h b/src/sago/platform_folders.h new file mode 100644 index 0000000..0e3ef4a --- /dev/null +++ b/src/sago/platform_folders.h @@ -0,0 +1,172 @@ +/* +Its is under the MIT license, to encourage reuse by cut-and-paste. + +The original files are hosted here: https://github.com/sago007/PlatformFolders + +Copyright (c) 2015 Poul Sander + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SAGO_PLATFORM_FOLDERS_H +#define SAGO_PLATFORM_FOLDERS_H + +#include +#include + +/** + * The namespace I use for common function. Nothing special about it. + */ +namespace sago { + +/** + * Retrives the base folder for storring data files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getDataHome()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATA% (Roaming profile) + * On Linux this defaults to ~/.local/share but can be configured + * @return The base folder for storring program data. + */ +std::string getDataHome(); +/** + * Retrives the base folder for storring config files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getConfigHome()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATA% (Roaming profile) + * On Linux this defaults to ~/.config but can be configured + * @return The base folder for storring config data. + */ +std::string getConfigHome(); +/** + * Retrives the base folder for storring cache files. + * You must add the program name yourself like this: + * @code{.cpp} + * string data_home = getCacheDir()+"/My Program Name/"; + * @endcode + * On Windows this defaults to %APPDATALOCAL% + * On Linux this defaults to ~/.cache but can be configured + * @return The base folder for storring data that do not need to be backed up. + */ +std::string getCacheDir(); +/** + * This will append extra folders that your program should be looking for data files in. + * This does not normally include the path returned by GetDataHome(). + * If you want all the folders you should do something like: + * @code{.cpp} + * vector folders; + * folders.push_back(getDataHome()); + * appendAdditionalDataDirectories(folders); + * for (string s& : folders) { + * s+="/My Program Name/"; + * } + * @endcode + * You must apply "/My Program Name/" to all the strings. + * The string at the lowest index has the highest priority. + * @param homes A vector that extra folders will be appended to. + */ +void appendAdditionalDataDirectories(std::vector& homes); +/** + * This will append extra folders that your program should be looking for config files in. + * This does not normally include the path returned by GetConfigHome(). + * If you want all the folders you should do something like: + * @code{.cpp} + * std::vector folders; + * folders.push_back(sago::getConfigHome()); + * sago::appendAdditionalConfigDirectories(folders); + * for (std::string s& : folders) { + * s+="/My Program Name/"; + * } + * @endcode + * You must apply "/My Program Name/" to all the strings. + * The string at the lowest index has the highest priority. + * @param homes A vector that extra folders will be appended to. + */ +void appendAdditionalConfigDirectories(std::vector& homes); + +/** + * This class contains methods for finding the system depended special folders. + * For Windows these folders are either by convention or given by CSIDL. + * For Linux XDG convention is used. + * The Linux version has very little error checking and assumes that the config is correct + */ +class PlatformFolders { +public: + PlatformFolders(); + ~PlatformFolders(); + /** + * The folder that represents the desktop. + * Normally you should try not to use this folder. + * @return Absolute path to the user's desktop + */ + std::string getDesktopFolder() const; + /** + * The folder to store user documents to + * @return Absolute path to the "Documents" folder + */ + std::string getDocumentsFolder() const; + /** + * The folder for storring the user's pictures. + * @return Absolute path to the "Picture" folder + */ + std::string getPicturesFolder() const; + /** + * The folder where files are downloaded. + * @note Windows: This version is XP compatible and returns the Desktop. Vista and later has a dedicated folder. + * @return Absolute path to the folder where files are downloaded to. + */ + std::string getDownloadFolder1() const; + /** + * The folder where music is stored + * @return Absolute path to the music folder + */ + std::string getMusicFolder() const; + /** + * The folder where video is stored + * @return Absolute path to the video folder + */ + std::string getVideoFolder() const; + /** + * The base folder for storring saved games. + * You must add the program name to it like this: + * @code{.cpp} + * PlatformFolders pf; + * string saved_games_folder = pf.getSaveGamesFolder1()+"/My Program Name/"; + * @endcode + * @note Windows: This is an XP compatible version and returns the path to "My Games" in Documents. Vista and later has an official folder. + * @note Linux: XDF does not define a folder for saved games. This will just return the same as GetDataHome() + * @return The folder base folder for storring save games. + */ + std::string getSaveGamesFolder1() const; +private: + PlatformFolders(const PlatformFolders&); + PlatformFolders& operator=(const PlatformFolders&); + struct PlatformFoldersData; + PlatformFoldersData *data; +}; + +} //namespace sago + +#endif /* PLATFORM_FOLDERS_H */ + diff --git a/src/saland.cpp b/src/saland.cpp new file mode 100644 index 0000000..9bca2ae --- /dev/null +++ b/src/saland.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include "sago/SagoDataHolder.hpp" +#include "sago/SagoSpriteHolder.hpp" +#include +#include +#include +#include +#include + +#include "sago/SagoMisc.hpp" + +#ifndef VERSIONNUMBER +#define VERSIONNUMBER "0.1.0" +#endif + +int main(int argc, const char* argv[]) { + PHYSFS_init(argv[0]); + PHYSFS_addToSearchPath((std::string(PHYSFS_getBaseDir())+"/data").c_str(), 1); + PHYSFS_setWriteDir( (std::string(PHYSFS_getBaseDir())+"/writedir").c_str()); + boost::program_options::options_description desc("Options"); + desc.add_options() + ("version", "Print version information and quit") + ("help,h", "Print basic usage information to stdout and quit") + ; + boost::program_options::variables_map vm; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + boost::program_options::notify(vm); + if (vm.count("help")) { + std::cout << desc << "\n"; + return 0; + } + if (vm.count("version")) { + std::cout << "saland " << VERSIONNUMBER << "\n"; + return 0; + } + return 0; +}