git repos / saland

commit c9352480

sago007 · 2017-04-24 21:32
c9352480bf6d13d0f1550fce697ddfa6b799c177 patch · browse files

Initial commit

Changed files

A .gitignore
A CMakeLists.txt
A src/Libs/.editorconfig
A src/Libs/NFont.cpp
A src/Libs/NFont.h
A src/Libs/SDL_FontCache.c
A src/Libs/SDL_FontCache.h
A src/cmake/FindSDL2.cmake
A src/cmake/LibFindMacros.cmake
A src/sago/GameStateInterface.hpp
A src/sago/LICENSE
A src/sago/Makefile.sago
A src/sago/SagoDataHolder.cpp
A src/sago/SagoDataHolder.hpp
A src/sago/SagoMisc.cpp
A src/sago/SagoMisc.hpp
A src/sago/SagoMiscSdl2.cpp
A src/sago/SagoMiscSdl2.hpp
A src/sago/SagoSprite.cpp
A src/sago/SagoSprite.hpp
A src/sago/SagoSpriteHolder.cpp
A src/sago/SagoSpriteHolder.hpp
A src/sago/platform_folders.cpp
A src/sago/platform_folders.h
A src/saland.cpp
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 <cmath>
+#include <cstdio>
+#ifndef M_PI
+ #define M_PI 3.14159265358979323846
+#endif
+
+#include <string>
+#include <cstring>
+#include <list>
+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<string> explode(const string& str, char delimiter)
+{
+ list<string> 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// 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 <stdarg.h>
+
+#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 <tronic>
+# 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(<prefix> <another 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(<prefix> <pkg-config args> FIND_PATH <name> [other args] FIND_LIBRARY <name> [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 <PREFIX>_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} \"<version>\" 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 "<not found>")
+ 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 <iostream>
+#include <mutex>
+#include <map>
+#include <vector>
+#include <physfs.h>
+#include <memory>
+#include <SDL_mixer.h>
+#include "SagoMiscSdl2.hpp"
+
+namespace sago {
+
+struct SagoDataHolder::SagoDataHolderData {
+ std::map<std::string, SDL_Texture*> textures;
+ std::map<std::string, std::map<int, TTF_Font*> > fonts; //font, ptsize
+ std::map<std::string, Mix_Music*> music;
+ std::map<std::string, Mix_Chunk*> sounds;
+ std::vector<SDL_RWops*> rwOpsToFree;
+ std::vector<std::unique_ptr<char[]>> 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<char[]> 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<char[]> 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<char[]> 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<char[]> 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 <SDL_mixer.h> //Used for sound & music
+#include <SDL_image.h> //To load PNG images!
+#include <SDL_ttf.h>
+#include <physfs.h> //Abstract file system. To use containers
+#include <string>
+
+#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 <physfs.h>
+#include <iostream>
+#include <iconv.h>
+#include <string.h>
+#include <memory>
+
+using std::string;
+using std::cerr;
+using std::vector;
+
+namespace sago {
+
+
+std::vector<std::string> GetFileList(const char* dir) {
+ vector<string> 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<char[]> 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 <vector>
+#include <string>
+
+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<std::string> 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 <iostream>
+
+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 <memory>
+#include <unordered_map>
+#include "rapidjson/document.h"
+#include <iostream>
+#include <string.h>
+#include <boost/algorithm/string/predicate.hpp>
+
+using std::string;
+using std::cerr;
+using std::cout;
+using std::vector;
+
+namespace sago {
+
+struct SagoSpriteHolder::SagoSpriteHolderData {
+ const sago::SagoDataHolder* tex;
+ std::unordered_map<std::string,std::shared_ptr<sago::SagoSprite>> 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<sago::SagoSprite> 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<std::string> 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<std::string,std::shared_ptr<sago::SagoSprite>>::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 <iostream>
+#include <stdexcept>
+#include <string.h>
+#include <stdio.h>
+#include <cstdlib>
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <shlobj.h>
+
+#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<char> 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 <CoreServices/CoreServices.h>
+
+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 <map>
+#include <fstream>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/types.h>
+//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<std::string>& folders) {
+ std::vector<char> 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string, std::string> folders;
+};
+
+static void PlatformFoldersAddFromFile(const std::string& filename, std::map<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>::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 <vector>
+#include <string>
+
+/**
+ * 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<string> 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<std::string>& 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<std::string> 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<std::string>& 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 <iostream>
+#include <boost/program_options.hpp>
+#include <SDL_mixer.h>
+#include "sago/SagoDataHolder.hpp"
+#include "sago/SagoSpriteHolder.hpp"
+#include <sstream>
+#include <SDL_events.h>
+#include <SDL_render.h>
+#include <SDL_rect.h>
+#include <SDL2/SDL_render.h>
+
+#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;
+}