diff --git a/CMakeLists.txt b/CMakeLists.txt index 7250130..01bffcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ add_executable(${PROJECT_NAME} src/gui/GuiImage.cpp src/gui/GuiImage.h src/gui/sigslot.h - src/CVideo.cpp - src/CVideo.h + src/system/SDLSystem.cpp + src/system/SDLSystem.h src/gui/GuiElement.cpp src/gui/GuiText.cpp src/gui/GuiText.h @@ -25,13 +25,14 @@ add_executable(${PROJECT_NAME} src/gui/GuiButton.h - src/gui/SDLController.h src/MainWindow.cpp src/MainWindow.h src/gui/SDLControllerJoystick.h src/gui/SDLControllerMouse.h - src/gui/SDLControllerWiiUGamepad.h - src/gui/SDLControllerWiiUProContoller.h + src/input/SDLController.h src/menu/MainWindow.cpp src/menu/MainWindow.h src/input/SDLControllerJoystick.h src/input/SDLControllerMouse.h + src/input/SDLControllerWiiUGamepad.h + src/input/SDLControllerWiiUProContoller.h src/gui/GuiTexture.cpp src/gui/GuiTexture.h - src/gui/SDL_FontCache.h src/gui/SDL_FontCache.c + src/system/video/SDL_FontCache.h + src/system/video/SDL_FontCache.cpp - ) + src/system/video/Renderer.h) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/sdl2) diff --git a/Makefile.pc-win b/Makefile.pc-win index 3062acc..64944a1 100644 --- a/Makefile.pc-win +++ b/Makefile.pc-win @@ -28,7 +28,12 @@ endif TARGET := SDL2_Playground BUILD := build-pc-win SOURCES := src \ - src/gui + src/gui \ + src/input \ + src/menu \ + src/system \ + src/system/video \ + src/utils DATA := data INCLUDES := source diff --git a/Makefile.wiiu b/Makefile.wiiu index 2c70735..a5e2d3c 100644 --- a/Makefile.wiiu +++ b/Makefile.wiiu @@ -21,7 +21,12 @@ WUMS_ROOT := $(DEVKITPRO)/wums TARGET := SDL2_Playground BUILD := build SOURCES := src \ - src/gui + src/gui \ + src/input \ + src/menu \ + src/system \ + src/system/video \ + src/utils DATA := data INCLUDES := source diff --git a/src/gui/GuiButton.cpp b/src/gui/GuiButton.cpp index d3af71f..dd79357 100644 --- a/src/gui/GuiButton.cpp +++ b/src/gui/GuiButton.cpp @@ -133,7 +133,7 @@ void GuiButton::setTrigger(GuiTrigger *t, int32_t idx) { } } -void GuiButton::resetState(void) { +void GuiButton::resetState() { clickedTrigger = NULL; heldTrigger = NULL; GuiElement::resetState(); @@ -142,7 +142,7 @@ void GuiButton::resetState(void) { /** * Draw the button on screen */ -void GuiButton::draw(CVideo *v) { +void GuiButton::draw(Renderer *v) { if (!this->isVisible()) { return; } diff --git a/src/gui/GuiButton.h b/src/gui/GuiButton.h index 2bcbd73..6a4390a 100644 --- a/src/gui/GuiButton.h +++ b/src/gui/GuiButton.h @@ -20,7 +20,7 @@ #include "GuiText.h" #include "GuiSound.h" #include "GuiTrigger.h" -#include "../CVideo.h" +#include "../system/SDLSystem.h" //!Display, manage, and manipulate buttons in the GUI. Buttons can have images, icons, text, and sound set (all of which are optional) class GuiButton : public GuiElement { @@ -94,7 +94,7 @@ public: void resetState(void) override; //!Constantly called to draw the GuiButton - void draw(CVideo *video) override; + void draw(Renderer *video) override; //!Constantly called to allow the GuiButton to respond to updated input data //!\param t Pointer to a GuiTrigger, containing the current input data from PAD/WPAD diff --git a/src/gui/GuiElement.h b/src/gui/GuiElement.h index 8d95e97..e258679 100644 --- a/src/gui/GuiElement.h +++ b/src/gui/GuiElement.h @@ -20,14 +20,15 @@ #include #include -#include -#include -#include +#include +#include +#include #include -#include -#include +#include +#include #include #include "sigslot.h" +#include "../system/video/Renderer.h" enum { EFFECT_NONE = 0x00, @@ -59,7 +60,7 @@ enum { //!Forward declaration class GuiController; -class CVideo; +class SDLSystem; //!Primary GUI class. Most other classes inherit from this class. class GuiElement { @@ -524,7 +525,7 @@ public: virtual void update(GuiController *t) {} //!Called constantly to redraw the element - virtual void draw(CVideo *v) {} + virtual void draw(Renderer * v) {} //!Called constantly to process stuff in the element virtual void process() {} diff --git a/src/gui/GuiFrame.cpp b/src/gui/GuiFrame.cpp index 7b259eb..e699946 100644 --- a/src/gui/GuiFrame.cpp +++ b/src/gui/GuiFrame.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see . ****************************************************************************/ #include "GuiFrame.h" +#include "../system/video/Renderer.h" GuiFrame::GuiFrame(GuiFrame *p) { parent = p; @@ -156,12 +157,12 @@ int32_t GuiFrame::getSelected() { return found; } -void GuiFrame::draw(CVideo *v) { +void GuiFrame::draw(Renderer *v) { if (!this->isVisible() && parentElement) { return; } - if (parentElement && dim == true) { + if (parentElement && dim) { //GXColor dimColor = (GXColor){0, 0, 0, 0x70}; //Menu_DrawRectangle(0, 0, GetZPosition(), screenwidth,screenheight, &dimColor, false, true); } diff --git a/src/gui/GuiFrame.h b/src/gui/GuiFrame.h index ef97816..6297660 100644 --- a/src/gui/GuiFrame.h +++ b/src/gui/GuiFrame.h @@ -20,6 +20,7 @@ #include #include "GuiElement.h" #include "sigslot.h" +#include "../system/video/Renderer.h" //!Allows GuiElements to be grouped together into a "window" class GuiFrame : public GuiElement { @@ -33,7 +34,7 @@ public: GuiFrame(float w, float h, GuiFrame *parent = 0); //!Destructor - virtual ~GuiFrame(); + ~GuiFrame() override; //!Appends a GuiElement to the GuiFrame //!\param e The GuiElement to append. If it is already in the GuiFrame, it is removed first @@ -84,7 +85,7 @@ public: int32_t getSelected() override; //!Draws all the elements in this GuiFrame - void draw(CVideo *v) override; + void draw(Renderer *v) override; //!Updates the window and all elements contains within //!Allows the GuiFrame and all elements to respond to the input data specified diff --git a/src/gui/GuiImage.cpp b/src/gui/GuiImage.cpp index 038a30e..36ff4ff 100644 --- a/src/gui/GuiImage.cpp +++ b/src/gui/GuiImage.cpp @@ -17,7 +17,7 @@ #include #include #include "GuiImage.h" -#include "../CVideo.h" +#include "../system/SDLSystem.h" GuiImage::GuiImage(const std::string& path) : GuiTexture(path){ } diff --git a/src/gui/GuiSound.cpp b/src/gui/GuiSound.cpp index db3323b..3419333 100644 --- a/src/gui/GuiSound.cpp +++ b/src/gui/GuiSound.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . ****************************************************************************/ #include "GuiSound.h" -#include "../logger.h" +#include "../utils/logger.h" GuiSound::GuiSound(const char *filepath) { Load(filepath); diff --git a/src/gui/GuiText.cpp b/src/gui/GuiText.cpp index aa2d483..e54ceef 100644 --- a/src/gui/GuiText.cpp +++ b/src/gui/GuiText.cpp @@ -17,23 +17,19 @@ #include #include #include "GuiText.h" -#include "../CVideo.h" -#include "../logger.h" -#include "SDL_FontCache.h" - /** * Constructor for the GuiText class. */ -GuiText::GuiText(const std::string& text, int32_t s, SDL_Color c, TTF_Font* gFont) { +GuiText::GuiText(const std::string& text, SDL_Color c, FC_Font* gFont) { this->text = text; - this->size = s; this->color = c; - this->ttf_font = gFont; - this->invalid = true; - this->updateText = true; - this->maxWidth = 200; + this->fc_font = gFont; + this->updateText = false; + + updateSize(); + updateTexture(); } GuiText::~GuiText(){ @@ -44,45 +40,13 @@ GuiText::~GuiText(){ delete texture; } -void GuiText::draw(CVideo *pVideo) { +void GuiText::draw(Renderer *renderer) { if (!this->isVisible()) { return; } - if(invalid){ - if(fc_font){ - FC_FreeFont(fc_font); - fc_font = nullptr; - } - fc_font = FC_CreateFont(); - invalid = !FC_LoadFontFromTTF(fc_font, pVideo->getRenderer(), this->ttf_font, color); - DEBUG_FUNCTION_LINE("FC_CACHE init done"); - } - - if(updateText || !texture){ - delete texture; - // Create the intermediate texture target - SDL_Texture* temp = SDL_CreateTexture(pVideo->getRenderer(), pVideo->getPixelFormat(), SDL_TEXTUREACCESS_TARGET, width, height); - if(temp){ - texture = new GuiTexture(temp); - texture->setParent(this); - texture->setBlendMode(SDL_BLENDMODE_BLEND); - - // Draw the text onto it - SDL_SetRenderTarget(pVideo->getRenderer(), temp); - // make sure the texture is clean. - SDL_RenderClear(pVideo->getRenderer()); - FC_DrawColumn(fc_font, pVideo->getRenderer(), 0, 0, maxWidth, text.c_str()); - SDL_SetRenderTarget(pVideo->getRenderer(), NULL); - - updateText = false; - }else{ - DEBUG_FUNCTION_LINE("Failed to create texture"); - } - - } if(texture){ - texture->draw(pVideo); + texture->draw(renderer); } } @@ -90,15 +54,37 @@ void GuiText::process() { GuiElement::process(); if(updateText && fc_font){ - auto height = FC_GetColumnHeight(fc_font, maxWidth, text.c_str()); - auto width = FC_GetWidth(fc_font, text.c_str()); - width = width > maxWidth ? maxWidth : width; - this->setSize(width, height); + updateTexture(); + updateText = false; } } void GuiText::setMaxWidth(float width) { this->maxWidth = width; + // Rebuild the texture cache. updateText = true; } + +void GuiText::updateSize() { + auto height = FC_GetColumnHeight(fc_font, maxWidth, text.c_str()); + auto width = FC_GetWidth(fc_font, text.c_str()); + width = width > maxWidth ? maxWidth : width; + this->setSize(width, height); +} + +void GuiText::updateTexture() { + updateSize(); + SDL_Texture* temp = FC_CreateTexture(fc_font, SDL_PIXELFORMAT_RGBA8888, width, height); + + if(temp){ + delete texture; + texture = new GuiTexture(temp); + texture->setParent(this); + texture->setBlendMode(SDL_BLENDMODE_BLEND); + + FC_DrawColumnToTexture(fc_font, temp, 0,0, maxWidth, text.c_str()); + }else{ + DEBUG_FUNCTION_LINE("Failed to create texture"); + } +} diff --git a/src/gui/GuiText.h b/src/gui/GuiText.h index 9e7b40b..25fa7ac 100644 --- a/src/gui/GuiText.h +++ b/src/gui/GuiText.h @@ -17,12 +17,11 @@ #pragma once #include "GuiElement.h" -#include "SDL_FontCache.h" #include "GuiTexture.h" +#include "../system/video/SDL_FontCache.h" #include #include - //!Display, manage, and manipulate text in the GUI class GuiText : public GuiElement { public: @@ -30,10 +29,10 @@ public: //!\param t Text //!\param s Font size //!\param c Font color - GuiText(const std::string &t, int32_t s, SDL_Color c, TTF_Font *gFont); + GuiText(const std::string &t, SDL_Color c, FC_Font *font); ~GuiText() override; - void draw(CVideo *pVideo) override; + void draw(Renderer *pVideo) override; void process() override; @@ -42,12 +41,13 @@ public: protected: GuiTexture* texture = nullptr; std::string text; - int32_t size; SDL_Color color; - TTF_Font *ttf_font = nullptr; FC_Font *fc_font = nullptr; - bool invalid = true; bool updateText = true; uint16_t maxWidth = 0xFFFF; + + void updateSize(); + + void updateTexture(); }; diff --git a/src/gui/GuiTexture.cpp b/src/gui/GuiTexture.cpp index 52cc8da..db2061e 100644 --- a/src/gui/GuiTexture.cpp +++ b/src/gui/GuiTexture.cpp @@ -1,7 +1,7 @@ #include #include "GuiTexture.h" -#include "../CVideo.h" -#include "../logger.h" +#include "../system/SDLSystem.h" +#include "../utils/logger.h" GuiTexture::GuiTexture(const std::string& path) { imgSurface = IMG_Load( path.c_str() ); @@ -49,20 +49,20 @@ GuiTexture::~GuiTexture() { } } -void GuiTexture::draw(CVideo *pVideo) { +void GuiTexture::draw(Renderer *renderer) { if (!this->isVisible()) { DEBUG_FUNCTION_LINE("not visible!"); return; } if (texture == NULL && imgSurface) { - SDL_Surface *optimizedSurface = SDL_ConvertSurfaceFormat(imgSurface, pVideo->getPixelFormat(), 0); + SDL_Surface *optimizedSurface = SDL_ConvertSurfaceFormat(imgSurface, renderer->getPixelFormat(), 0); if (optimizedSurface != NULL) { SDL_FreeSurface(imgSurface); imgSurface = optimizedSurface; DEBUG_FUNCTION_LINE("Optimized surface"); } - texture = SDL_CreateTextureFromSurface(pVideo->getRenderer(), imgSurface); + texture = SDL_CreateTextureFromSurface(renderer->getRenderer(), imgSurface); } if (!texture) { DEBUG_FUNCTION_LINE("no texture!"); @@ -79,9 +79,9 @@ void GuiTexture::draw(CVideo *pVideo) { rect.h = currScaleY * getHeight(); if (getAngle() == 0) { - SDL_RenderCopy(pVideo->getRenderer(), texture, nullptr, &rect); + SDL_RenderCopy(renderer->getRenderer(), texture, nullptr, &rect); } else { - SDL_RenderCopyEx(pVideo->getRenderer(), texture, nullptr, &rect, getAngle(), nullptr, SDL_FLIP_NONE); + SDL_RenderCopyEx(renderer->getRenderer(), texture, nullptr, &rect, getAngle(), nullptr, SDL_FLIP_NONE); } } diff --git a/src/gui/GuiTexture.h b/src/gui/GuiTexture.h index 027dcce..36b1d9c 100644 --- a/src/gui/GuiTexture.h +++ b/src/gui/GuiTexture.h @@ -12,7 +12,7 @@ public: ~GuiTexture() override; //!Constantly called to draw the image - void draw(CVideo *pVideo) override; + void draw(Renderer *pVideo) override; int setBlendMode(SDL_BlendMode blendMode); diff --git a/src/gui/SDLController.h b/src/input/SDLController.h similarity index 95% rename from src/gui/SDLController.h rename to src/input/SDLController.h index d0a0b35..dba5bc7 100644 --- a/src/gui/SDLController.h +++ b/src/input/SDLController.h @@ -1,60 +1,60 @@ -#pragma once - -#include -#include -#include -#include "GuiController.h" -#include "../logger.h" - -#define printButton(chan, x) if(data.buttons_d & x) DEBUG_FUNCTION_LINE("Controller #%d %s", chan, #x) - -class SDLController : public GuiController { -public: - explicit SDLController(int32_t channel) : GuiController(channel) { - - } - - virtual bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) = 0; - - - virtual void before() { - lastData = data; - - data.buttons_d = 0; - data.buttons_r = 0; - } - - virtual void after() { - data.buttons_d |= (data.buttons_h & (~(lastData.buttons_h))); - data.buttons_r |= ((lastData.buttons_h) & (~data.buttons_h)); - if (data.buttons_h != 0 || data.buttons_d != 0 || data.buttons_r != 0) { - // DEBUG_FUNCTION_LINE("Controller #%d: h %08X d %08X r %08X", chanIdx, data.buttons_h, data.buttons_d, data.buttons_r); - - printButton(chanIdx, GuiTrigger::BUTTON_A); - printButton(chanIdx, GuiTrigger::BUTTON_B); - printButton(chanIdx, GuiTrigger::BUTTON_X); - printButton(chanIdx, GuiTrigger::BUTTON_Y); - printButton(chanIdx, GuiTrigger::BUTTON_STICK_L); - printButton(chanIdx, GuiTrigger::BUTTON_STICK_R); - printButton(chanIdx, GuiTrigger::BUTTON_L); - printButton(chanIdx, GuiTrigger::BUTTON_R); - printButton(chanIdx, GuiTrigger::BUTTON_ZL); - printButton(chanIdx, GuiTrigger::BUTTON_ZR); - printButton(chanIdx, GuiTrigger::BUTTON_PLUS); - printButton(chanIdx, GuiTrigger::BUTTON_MINUS); - printButton(chanIdx, GuiTrigger::BUTTON_LEFT); - printButton(chanIdx, GuiTrigger::BUTTON_UP); - printButton(chanIdx, GuiTrigger::BUTTON_RIGHT); - printButton(chanIdx, GuiTrigger::BUTTON_DOWN); - printButton(chanIdx, GuiTrigger::STICK_L_LEFT); - printButton(chanIdx, GuiTrigger::STICK_L_UP); - printButton(chanIdx, GuiTrigger::STICK_L_RIGHT); - printButton(chanIdx, GuiTrigger::STICK_L_DOWN); - printButton(chanIdx, GuiTrigger::STICK_R_LEFT); - printButton(chanIdx, GuiTrigger::STICK_R_UP); - printButton(chanIdx, GuiTrigger::STICK_R_RIGHT); - printButton(chanIdx, GuiTrigger::STICK_R_DOWN); - printButton(chanIdx, GuiTrigger::TOUCHED); - } - } -}; +#pragma once + +#include +#include +#include +#include "../gui/GuiController.h" +#include "../utils/logger.h" + +#define printButton(chan, x) if(data.buttons_d & x) DEBUG_FUNCTION_LINE("Controller #%d %s", chan, #x) + +class SDLController : public GuiController { +public: + explicit SDLController(int32_t channel) : GuiController(channel) { + + } + + virtual bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) = 0; + + + virtual void before() { + lastData = data; + + data.buttons_d = 0; + data.buttons_r = 0; + } + + virtual void after() { + data.buttons_d |= (data.buttons_h & (~(lastData.buttons_h))); + data.buttons_r |= ((lastData.buttons_h) & (~data.buttons_h)); + if (data.buttons_h != 0 || data.buttons_d != 0 || data.buttons_r != 0) { + // DEBUG_FUNCTION_LINE("Controller #%d: h %08X d %08X r %08X", chanIdx, data.buttons_h, data.buttons_d, data.buttons_r); + + printButton(chanIdx, GuiTrigger::BUTTON_A); + printButton(chanIdx, GuiTrigger::BUTTON_B); + printButton(chanIdx, GuiTrigger::BUTTON_X); + printButton(chanIdx, GuiTrigger::BUTTON_Y); + printButton(chanIdx, GuiTrigger::BUTTON_STICK_L); + printButton(chanIdx, GuiTrigger::BUTTON_STICK_R); + printButton(chanIdx, GuiTrigger::BUTTON_L); + printButton(chanIdx, GuiTrigger::BUTTON_R); + printButton(chanIdx, GuiTrigger::BUTTON_ZL); + printButton(chanIdx, GuiTrigger::BUTTON_ZR); + printButton(chanIdx, GuiTrigger::BUTTON_PLUS); + printButton(chanIdx, GuiTrigger::BUTTON_MINUS); + printButton(chanIdx, GuiTrigger::BUTTON_LEFT); + printButton(chanIdx, GuiTrigger::BUTTON_UP); + printButton(chanIdx, GuiTrigger::BUTTON_RIGHT); + printButton(chanIdx, GuiTrigger::BUTTON_DOWN); + printButton(chanIdx, GuiTrigger::STICK_L_LEFT); + printButton(chanIdx, GuiTrigger::STICK_L_UP); + printButton(chanIdx, GuiTrigger::STICK_L_RIGHT); + printButton(chanIdx, GuiTrigger::STICK_L_DOWN); + printButton(chanIdx, GuiTrigger::STICK_R_LEFT); + printButton(chanIdx, GuiTrigger::STICK_R_UP); + printButton(chanIdx, GuiTrigger::STICK_R_RIGHT); + printButton(chanIdx, GuiTrigger::STICK_R_DOWN); + printButton(chanIdx, GuiTrigger::TOUCHED); + } + } +}; diff --git a/src/gui/SDLControllerJoystick.h b/src/input/SDLControllerJoystick.h similarity index 97% rename from src/gui/SDLControllerJoystick.h rename to src/input/SDLControllerJoystick.h index 419171b..4d3651d 100644 --- a/src/gui/SDLControllerJoystick.h +++ b/src/input/SDLControllerJoystick.h @@ -1,58 +1,58 @@ -#pragma once - -class SDLControllerJoystick : public SDLController { -public: - SDLControllerJoystick(int32_t channel, SDL_JoystickID joystickId) : SDLController(channel) { - - } - - bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) override { - if (e->type == SDL_JOYBUTTONDOWN) { - data.buttons_h |= (1 << e->jbutton.button); - } else if (e->type == SDL_JOYBUTTONUP) { - data.buttons_h &= ~(1 << e->jbutton.button); - } else if (e->type == SDL_JOYHATMOTION) { - auto val = e->jhat.value; - - auto hatMask = (GuiTrigger::BUTTON_LEFT | GuiTrigger::BUTTON_UP | GuiTrigger::BUTTON_DOWN | GuiTrigger::BUTTON_RIGHT); - - // Remove hat values so we can add the new value. - data.buttons_h &= ~hatMask; - - switch (val) { - case SDL_HAT_LEFTUP: - data.buttons_h |= GuiTrigger::BUTTON_LEFT; - data.buttons_h |= GuiTrigger::BUTTON_UP; - break; - case SDL_HAT_LEFT: - data.buttons_h |= GuiTrigger::BUTTON_LEFT; - break; - case SDL_HAT_LEFTDOWN: - data.buttons_h |= GuiTrigger::BUTTON_LEFT; - data.buttons_h |= GuiTrigger::BUTTON_DOWN; - break; - case SDL_HAT_UP: - data.buttons_h |= GuiTrigger::BUTTON_UP; - break; - case SDL_HAT_DOWN: - data.buttons_h |= GuiTrigger::BUTTON_DOWN; - break; - case SDL_HAT_RIGHTUP: - data.buttons_h |= GuiTrigger::BUTTON_RIGHT; - data.buttons_h |= GuiTrigger::BUTTON_UP; - break; - case SDL_HAT_RIGHT: - data.buttons_h |= GuiTrigger::BUTTON_RIGHT; - break; - case SDL_HAT_RIGHTDOWN: - data.buttons_h |= GuiTrigger::BUTTON_RIGHT; - data.buttons_h |= GuiTrigger::BUTTON_DOWN; - break; - } - } else if (e->type == SDL_JOYAXISMOTION) { - // - } - return true; - } -}; - +#pragma once + +class SDLControllerJoystick : public SDLController { +public: + SDLControllerJoystick(int32_t channel, SDL_JoystickID joystickId) : SDLController(channel) { + + } + + bool update(SDL_Event *e, int32_t screenWidth, int32_t screenHeight) override { + if (e->type == SDL_JOYBUTTONDOWN) { + data.buttons_h |= (1 << e->jbutton.button); + } else if (e->type == SDL_JOYBUTTONUP) { + data.buttons_h &= ~(1 << e->jbutton.button); + } else if (e->type == SDL_JOYHATMOTION) { + auto val = e->jhat.value; + + auto hatMask = (GuiTrigger::BUTTON_LEFT | GuiTrigger::BUTTON_UP | GuiTrigger::BUTTON_DOWN | GuiTrigger::BUTTON_RIGHT); + + // Remove hat values so we can add the new value. + data.buttons_h &= ~hatMask; + + switch (val) { + case SDL_HAT_LEFTUP: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_LEFT: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + break; + case SDL_HAT_LEFTDOWN: + data.buttons_h |= GuiTrigger::BUTTON_LEFT; + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + case SDL_HAT_UP: + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_DOWN: + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + case SDL_HAT_RIGHTUP: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + data.buttons_h |= GuiTrigger::BUTTON_UP; + break; + case SDL_HAT_RIGHT: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + break; + case SDL_HAT_RIGHTDOWN: + data.buttons_h |= GuiTrigger::BUTTON_RIGHT; + data.buttons_h |= GuiTrigger::BUTTON_DOWN; + break; + } + } else if (e->type == SDL_JOYAXISMOTION) { + // + } + return true; + } +}; + diff --git a/src/gui/SDLControllerMouse.h b/src/input/SDLControllerMouse.h similarity index 100% rename from src/gui/SDLControllerMouse.h rename to src/input/SDLControllerMouse.h diff --git a/src/gui/SDLControllerWiiUGamepad.h b/src/input/SDLControllerWiiUGamepad.h similarity index 100% rename from src/gui/SDLControllerWiiUGamepad.h rename to src/input/SDLControllerWiiUGamepad.h diff --git a/src/gui/SDLControllerWiiUProContoller.h b/src/input/SDLControllerWiiUProContoller.h similarity index 100% rename from src/gui/SDLControllerWiiUProContoller.h rename to src/input/SDLControllerWiiUProContoller.h diff --git a/src/gui/SDLControllerXboxOne.h b/src/input/SDLControllerXboxOne.h similarity index 100% rename from src/gui/SDLControllerXboxOne.h rename to src/input/SDLControllerXboxOne.h diff --git a/src/main.cpp b/src/main.cpp index 3e0ff33..c40b787 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,17 @@ #include -#include "CVideo.h" +#include "system/SDLSystem.h" #include "gui/GuiFrame.h" #include "gui/GuiImage.h" #include "gui/GuiButton.h" #include "gui/GuiController.h" -#include "gui/SDLController.h" -#include "MainWindow.h" -#include "logger.h" -#include "gui/SDLControllerJoystick.h" -#include "gui/SDLControllerMouse.h" -#include "gui/SDLControllerWiiUGamepad.h" -#include "gui/SDLControllerXboxOne.h" -#include "gui/SDLControllerWiiUProContoller.h" +#include "menu/MainWindow.h" +#include "utils/logger.h" +#include "input/SDLController.h" +#include "input/SDLControllerMouse.h" +#include "input/SDLControllerWiiUGamepad.h" +#include "input/SDLControllerXboxOne.h" +#include "input/SDLControllerWiiUProContoller.h" +#include "input/SDLControllerJoystick.h" #include #include @@ -58,7 +58,7 @@ GuiTrigger::eChannels increaseChannel(GuiTrigger::eChannels channel); void removeJoystick(int32_t instanceId, std::map &controllerList, std::map& joystickToChannel); int main(int argc, char *args[]) { - auto *video = new CVideo(); + auto *video = new SDLSystem(); #if defined _WIN32 // Create the Console @@ -82,7 +82,7 @@ int main(int argc, char *args[]) { WHBLogUdpInit(); #endif - GuiFrame *frame = new MainWindow(video->getWidth(), video->getHeight()); + GuiFrame *frame = new MainWindow(video->getWidth(), video->getHeight(), video->getRenderer()); std::map controllerList; std::map joystickToChannel; @@ -99,7 +99,6 @@ int main(int argc, char *args[]) { break; } #endif - //! Read out inputs for( auto const& [channel, controller] : controllerList ){ controller->before(); @@ -156,15 +155,25 @@ int main(int argc, char *args[]) { frame->process(); // clear the screen - SDL_RenderClear(video->getRenderer()); + SDL_RenderClear(video->getRenderer()->getRenderer()); - frame->draw(video); + frame->draw(video->getRenderer()); frame->updateEffects(); // flip the backbuffer // this means that everything that we prepared behind the screens is actually shown - SDL_RenderPresent(video->getRenderer()); + + DEBUG_FUNCTION_LINE("%08X", video); + if(video){ + DEBUG_FUNCTION_LINE("%08X", video->getRenderer()); + if(video->getRenderer()){ + DEBUG_FUNCTION_LINE("%08X", video->getRenderer()->getRenderer()); + if(video->getRenderer()->getRenderer()) { + SDL_RenderPresent(video->getRenderer()->getRenderer()); + } + } + } } diff --git a/src/MainWindow.cpp b/src/menu/MainWindow.cpp similarity index 79% rename from src/MainWindow.cpp rename to src/menu/MainWindow.cpp index e1ec0ea..ed976e2 100644 --- a/src/MainWindow.cpp +++ b/src/menu/MainWindow.cpp @@ -1,5 +1,4 @@ #include "MainWindow.h" -#include "gui/SDL_FontCache.h" MainWindow::~MainWindow() { delete label;; @@ -16,7 +15,7 @@ MainWindow::~MainWindow() { delete bgMusic;; } -MainWindow::MainWindow(int32_t w, int32_t h) : GuiFrame(w, h) { +MainWindow::MainWindow(int32_t w, int32_t h, Renderer* renderer) : GuiFrame(w, h) { #if defined _WIN32 auto picture_path = "test.png"; auto font_path = "FreeSans.ttf"; @@ -34,7 +33,14 @@ MainWindow::MainWindow(int32_t w, int32_t h) : GuiFrame(w, h) { font = TTF_OpenFont(font_path, 28); - label = new GuiText("This is a test.This is a test. This is a test.This is a test.This is a test.This is a test.", 25, {255, 255, 0, 255}, font); + FC_Font* fc_font = FC_CreateFont(); + if(!fc_font){ + DEBUG_FUNCTION_LINE("Failed to create font"); + } + + FC_LoadFontFromTTF(fc_font, renderer->getRenderer(), font, {255, 255, 255, 255}); + + label = new GuiText("This is a test.This is a test. This is a test.This is a test.This is a test.This is a test.", {255, 255, 0, 255}, fc_font); bgMusic = new GuiSound(bgMusic_path); bgMusic->SetLoop(true); @@ -87,3 +93,7 @@ void MainWindow::process() { } button->setAngle(res); } + +void MainWindow::test(GuiButton *, const GuiController *, GuiTrigger *) { + DEBUG_FUNCTION_LINE("Hello, you have clicked the button"); +} diff --git a/src/MainWindow.h b/src/menu/MainWindow.h similarity index 61% rename from src/MainWindow.h rename to src/menu/MainWindow.h index 36cd390..0389a69 100644 --- a/src/MainWindow.h +++ b/src/menu/MainWindow.h @@ -1,19 +1,17 @@ #pragma once #include -#include "gui/GuiFrame.h" -#include "gui/GuiButton.h" -#include "logger.h" +#include "../gui/GuiFrame.h" +#include "../gui/GuiButton.h" +#include "../utils/logger.h" class MainWindow : public GuiFrame, public sigslot::has_slots<> { public: - void test(GuiButton *, const GuiController *, GuiTrigger *) { - DEBUG_FUNCTION_LINE("Hello, you have clicked the button"); - } + void test(GuiButton *, const GuiController *, GuiTrigger *); - ~MainWindow(); + ~MainWindow() override; - MainWindow(int32_t w, int32_t h); + MainWindow(int32_t w, int32_t h, Renderer* renderer); void process() override; private: GuiText *label = nullptr; diff --git a/src/CVideo.cpp b/src/system/SDLSystem.cpp similarity index 65% rename from src/CVideo.cpp rename to src/system/SDLSystem.cpp index f2647d1..b0b771c 100644 --- a/src/CVideo.cpp +++ b/src/system/SDLSystem.cpp @@ -1,74 +1,81 @@ -/**************************************************************************** - * Copyright (C) 2015 Dimok - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -#include "CVideo.h" -#include "logger.h" -#include -#include - -CVideo::CVideo() { - SDL_Init(SDL_INIT_EVERYTHING); - - auto SDLFlags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC; - - //Setup window - window = SDL_CreateWindow(nullptr, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, 0); - if (!window) { return; } - renderer = SDL_CreateRenderer(window, -1, SDLFlags); - if (!renderer) { return; } - SDL_SetRenderTarget(renderer, NULL); - - if (SDL_Init(SDL_INIT_AUDIO) != 0) { - DEBUG_FUNCTION_LINE("SDL init error: %s\n", SDL_GetError()); - return; - } - - int flags = 0; - int result = 0; - if (flags != (result = Mix_Init(flags))) { - DEBUG_FUNCTION_LINE("Could not initialize mixer (result: %d).\n", result); - DEBUG_FUNCTION_LINE("Mix_Init: %s\n", Mix_GetError()); - } - - auto dev = Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640); - SDL_PauseAudioDevice(dev, 0); -} - -CVideo::~CVideo() { - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); -} - -SDL_Renderer *CVideo::getRenderer() { - return renderer; -} - -float CVideo::getHeight() { - int h = 0; - SDL_GetWindowSize(window, NULL, &h); - return h; -} - -float CVideo::getWidth() { - int w = 0; - SDL_GetWindowSize(window, &w, NULL); - return w; -} - -unsigned int CVideo::getPixelFormat() { - return SDL_GetWindowPixelFormat(window); -} +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#include "SDLSystem.h" +#include "../utils/logger.h" +#include +#include + +SDLSystem::SDLSystem() { + SDL_Init(SDL_INIT_EVERYTHING); + + auto SDLFlags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC; + + //Setup window + window = SDL_CreateWindow(nullptr, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, 0); + if (!window) { + DEBUG_FUNCTION_LINE("Failed to create window"); + return; + } + auto sdl_renderer = SDL_CreateRenderer(window, -1, SDLFlags); + if (!sdl_renderer) { + DEBUG_FUNCTION_LINE("Failed to init sdl renderer"); + return; + } + SDL_SetRenderTarget(sdl_renderer, nullptr); + this->renderer = new Renderer(sdl_renderer, SDL_GetWindowPixelFormat(window)); + if (!renderer) { + DEBUG_FUNCTION_LINE("Failed to init renderer"); + return; + } + + if (SDL_Init(SDL_INIT_AUDIO) != 0) { + DEBUG_FUNCTION_LINE("SDL init error: %s\n", SDL_GetError()); + return; + } + + int flags = 0; + int result = 0; + if (flags != (result = Mix_Init(flags))) { + DEBUG_FUNCTION_LINE("Could not initialize mixer (result: %d).\n", result); + DEBUG_FUNCTION_LINE("Mix_Init: %s\n", Mix_GetError()); + } + + auto dev = Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640); + SDL_PauseAudioDevice(dev, 0); +} + +SDLSystem::~SDLSystem() { + SDL_DestroyWindow(window); + delete renderer; + SDL_Quit(); +} + +float SDLSystem::getHeight() { + int h = 0; + SDL_GetWindowSize(window, nullptr, &h); + return h; +} + +float SDLSystem::getWidth() { + int w = 0; + SDL_GetWindowSize(window, &w, nullptr); + return w; +} + +Renderer *SDLSystem::getRenderer() { + return renderer; +} diff --git a/src/CVideo.h b/src/system/SDLSystem.h similarity index 84% rename from src/CVideo.h rename to src/system/SDLSystem.h index 0dbf068..1110415 100644 --- a/src/CVideo.h +++ b/src/system/SDLSystem.h @@ -1,36 +1,35 @@ -/**************************************************************************** - * Copyright (C) 2015 Dimok - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -#pragma once -#include - -class CVideo { -public: - CVideo(); - - virtual ~CVideo(); - - SDL_Renderer *getRenderer(); - - float getHeight(); - float getWidth(); - - unsigned int getPixelFormat(); - -private: - SDL_Window *window = NULL; - SDL_Renderer *renderer = NULL; +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#pragma once +#include +#include "video/Renderer.h" + +class SDLSystem { +public: + SDLSystem(); + + virtual ~SDLSystem(); + + Renderer *getRenderer(); + + float getHeight(); + float getWidth(); + +private: + SDL_Window *window = NULL; + Renderer *renderer = NULL; }; \ No newline at end of file diff --git a/src/system/video/Renderer.h b/src/system/video/Renderer.h new file mode 100644 index 0000000..4617250 --- /dev/null +++ b/src/system/video/Renderer.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class Renderer { +public: + Renderer(SDL_Renderer *renderer, uint32_t pixelFormat) : sdl_renderer(renderer), pixelFormat(pixelFormat){ + + } + + virtual ~Renderer() { + if(sdl_renderer){ + SDL_DestroyRenderer(sdl_renderer); + } + } + + SDL_Renderer *getRenderer(){ + return sdl_renderer; + } + + uint32_t getPixelFormat(){ + return pixelFormat; + } + +private: + SDL_Renderer *sdl_renderer = NULL; + uint32_t pixelFormat = SDL_PIXELFORMAT_RGBA8888; +}; \ No newline at end of file diff --git a/src/gui/SDL_FontCache.c b/src/system/video/SDL_FontCache.cpp similarity index 92% rename from src/gui/SDL_FontCache.c rename to src/system/video/SDL_FontCache.cpp index 9ca8d55..10b6e92 100644 --- a/src/gui/SDL_FontCache.c +++ b/src/system/video/SDL_FontCache.cpp @@ -1,2927 +1,3038 @@ -/* -SDL_FontCache: A font cache for SDL and SDL_ttf -by Jonathan Dearborn - -See SDL_FontCache.h for license info. -*/ - -#include "SDL_FontCache.h" - -#include -#include -#include - -// Visual C does not support static inline -#ifndef static_inline - #ifdef _MSC_VER - #define static_inline static - #else - #define static_inline static inline - #endif -#endif - -#if SDL_VERSION_ATLEAST(2,0,0) - #define FC_GET_ALPHA(sdl_color) ((sdl_color).a) -#else - #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused) -#endif - -// Need SDL_RenderIsClipEnabled() for proper clipping support -#if SDL_VERSION_ATLEAST(2,0,4) - #define ENABLE_SDL_CLIPPING -#endif - -#define FC_MIN(a,b) ((a) < (b)? (a) : (b)) -#define FC_MAX(a,b) ((a) > (b)? (a) : (b)) - - -// vsnprintf replacement from Valentin Milea: -// http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 -#if defined(_MSC_VER) && _MSC_VER < 1900 - -#define snprintf c99_snprintf -#define vsnprintf c99_vsnprintf - -__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) -{ - int count = -1; - - if (size != 0) - count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); - if (count == -1) - count = _vscprintf(format, ap); - - return count; -} - -__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) -{ - int count; - va_list ap; - - va_start(ap, format); - count = c99_vsnprintf(outBuf, size, format, ap); - va_end(ap); - - return count; -} - -#endif - - -#define FC_EXTRACT_VARARGS(buffer, start_args) \ -{ \ - va_list lst; \ - va_start(lst, start_args); \ - vsnprintf(buffer, fc_buffer_size, start_args, lst); \ - va_end(lst); \ -} - -// Extra pixels of padding around each glyph to avoid linear filtering artifacts -#define FC_CACHE_PADDING 1 - - - -static Uint8 has_clip(FC_Target* dest) -{ - #ifdef FC_USE_SDL_GPU - return dest->use_clip_rect; - #elif defined(ENABLE_SDL_CLIPPING) - return SDL_RenderIsClipEnabled(dest); - #else - return 0; - #endif -} - -static FC_Rect get_clip(FC_Target* dest) -{ - #ifdef FC_USE_SDL_GPU - return dest->clip_rect; - #elif defined(ENABLE_SDL_CLIPPING) - SDL_Rect r; - SDL_RenderGetClipRect(dest, &r); - return r; - #else - SDL_Rect r = {0, 0, 0, 0}; - return r; - #endif -} - -static void set_clip(FC_Target* dest, FC_Rect* rect) -{ - #ifdef FC_USE_SDL_GPU - if(rect != NULL) - GPU_SetClipRect(dest, *rect); - else - GPU_UnsetClip(dest); - #elif defined(ENABLE_SDL_CLIPPING) - SDL_RenderSetClipRect(dest, rect); - #endif -} - -static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a) -{ - #ifdef FC_USE_SDL_GPU - GPU_SetRGBA(src, r, g, b, a); - #else - SDL_SetTextureColorMod(src, r, g, b); - SDL_SetTextureAlphaMod(src, a); - #endif -} - - - -static char* new_concat(const char* a, const char* b) -{ - // Create new buffer - unsigned int size = strlen(a) + strlen(b); - char* new_string = (char*)malloc(size+1); - - // Concatenate strings in the new buffer - strcpy(new_string, a); - strcat(new_string, b); - - return new_string; -} - -static char* replace_concat(char** a, const char* b) -{ - char* new_string = new_concat(*a, b); - free(*a); - *a = new_string; - return *a; -} - - -// Width of a tab in units of the space width (sorry, no tab alignment!) -static unsigned int fc_tab_width = 4; - -// 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; - -// The number of fonts that has been created but not freed -static int NUM_EXISTING_FONTS = 0; - -// Globals for GetString functions -static char* ASCII_STRING = NULL; -static char* LATIN_1_STRING = NULL; -static char* ASCII_LATIN_1_STRING = NULL; - -char* FC_GetStringASCII(void) -{ - if(ASCII_STRING == NULL) - { - int i; - char c; - ASCII_STRING = (char*)malloc(512); - memset(ASCII_STRING, 0, 512); - i = 0; - c = 32; - while(1) - { - ASCII_STRING[i] = c; - if(c == 126) - break; - ++i; - ++c; - } - } - return U8_strdup(ASCII_STRING); -} - -char* FC_GetStringLatin1(void) -{ - if(LATIN_1_STRING == NULL) - { - int i; - unsigned char c; - LATIN_1_STRING = (char*)malloc(512); - memset(LATIN_1_STRING, 0, 512); - i = 0; - c = 0xA0; - while(1) - { - LATIN_1_STRING[i] = 0xC2; - LATIN_1_STRING[i+1] = c; - if(c == 0xBF) - break; - i += 2; - ++c; - } - i += 2; - c = 0x80; - while(1) - { - LATIN_1_STRING[i] = 0xC3; - LATIN_1_STRING[i+1] = c; - if(c == 0xBF) - break; - i += 2; - ++c; - } - } - return U8_strdup(LATIN_1_STRING); -} - -char* FC_GetStringASCII_Latin1(void) -{ - if(ASCII_LATIN_1_STRING == NULL) - ASCII_LATIN_1_STRING = new_concat(FC_GetStringASCII(), FC_GetStringLatin1()); - - return U8_strdup(ASCII_LATIN_1_STRING); -} - -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_u8char; - int len; - int add_len; - int ulen; - const char* string_start = string; - - 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_u8char = 0; - while(*string != '\0' && pos_u8char < position) - { - string = (char*)U8_next(string); - ++pos_u8char; - } - - // Move the rest of the string out of the way - memmove(string + add_len, string, len - (string - string_start) + 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); -} - - -unsigned int FC_GetTabWidth(void) -{ - return fc_tab_width; -} - -void FC_SetTabWidth(unsigned int width_in_spaces) -{ - fc_tab_width = width_in_spaces; -} - - - - - -// 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 = 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); - GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different - #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; - } - // bug: we do not have the correct color here, this might be the wrong color! - // , most functions use set_color_for_all_caches() - // - for evading this bug, you must use FC_SetDefaultColor(), before using any draw functions - set_color(new_level, font->default_color.r, font->default_color.g, font->default_color.b, FC_GET_ALPHA(font->default_color)); -#ifndef FC_USE_SDL_GPU - { - Uint8 r, g, b, a; - SDL_Texture* prev_target = SDL_GetRenderTarget(font->renderer); - SDL_Rect prev_clip, prev_viewport; - int prev_logicalw, prev_logicalh; - Uint8 prev_clip_enabled; - float prev_scalex, prev_scaley; - // only backup if previous target existed (SDL will preserve them for the default target) - if (prev_target) { - prev_clip_enabled = has_clip(font->renderer); - if (prev_clip_enabled) - prev_clip = get_clip(font->renderer); - SDL_RenderGetViewport(font->renderer, &prev_viewport); - SDL_RenderGetScale(font->renderer, &prev_scalex, &prev_scaley); - SDL_RenderGetLogicalSize(font->renderer, &prev_logicalw, &prev_logicalh); - } - SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); - SDL_SetRenderTarget(font->renderer, new_level); - SDL_GetRenderDrawColor(font->renderer, &r, &g, &b, &a); - SDL_SetRenderDrawColor(font->renderer, 0, 0, 0, 0); - SDL_RenderClear(font->renderer); - SDL_SetRenderDrawColor(font->renderer, r, g, b, a); - SDL_SetRenderTarget(font->renderer, prev_target); - if (prev_target) { - if (prev_clip_enabled) - set_clip(font->renderer, &prev_clip); - if (prev_logicalw && prev_logicalh) - SDL_RenderSetLogicalSize(font->renderer, prev_logicalw, prev_logicalh); - else { - SDL_RenderSetViewport(font->renderer, &prev_viewport); - SDL_RenderSetScale(font->renderer, prev_scalex, prev_scaley); - } - } - } -#endif - 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); - GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different - 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 - const char* old_filter_hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); - if(!old_filter_hint) - old_filter_hint = "nearest"; - snprintf(old_filter_mode, 16, "%s", old_filter_hint); - - 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_Texture* prev_target = SDL_GetRenderTarget(renderer); - SDL_Rect prev_clip, prev_viewport; - int prev_logicalw, prev_logicalh; - Uint8 prev_clip_enabled; - float prev_scalex, prev_scaley; - // only backup if previous target existed (SDL will preserve them for the default target) - if (prev_target) { - prev_clip_enabled = has_clip(renderer); - if (prev_clip_enabled) - prev_clip = get_clip(renderer); - SDL_RenderGetViewport(renderer, &prev_viewport); - SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); - SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); - } - 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, prev_target); - if (prev_target) { - if (prev_clip_enabled) - set_clip(renderer, &prev_clip); - if (prev_logicalw && prev_logicalh) - SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); - else { - SDL_RenderSetViewport(renderer, &prev_viewport); - SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); - } - } - - 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; - - // TAB is special! - if(codepoint == '\t') - { - FC_GlyphData spaceGlyph; - FC_GetGlyphData(font, &spaceGlyph, ' '); - width = fc_tab_width * spaceGlyph.rect.w; - } - - 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); - ++NUM_EXISTING_FONTS; - - 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); - - // Some bug for certain fonts can result in an incorrect height. - if(font->height < font->ascent - font->descent) - font->height = font->ascent - font->descent; - - 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; - - source_string = font->loading_string; - for(; *source_string != '\0'; source_string = U8_next(source_string)) - { - memset(buff, 0, 5); - 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; -} - - -#ifndef FC_USE_SDL_GPU -void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType) -{ - TTF_Font* ttf; - SDL_Color col; - Uint8 owns_ttf; - if (font == NULL) - return; - - // Destroy glyph cache - if (evType == SDL_RENDER_TARGETS_RESET) { - int i; - for (i = 0; i < font->glyph_cache_count; ++i) - SDL_DestroyTexture(font->glyph_cache[i]); - } - free(font->glyph_cache); - - ttf = font->ttf_source; - col = font->default_color; - owns_ttf = font->owns_ttf_source; - FC_Init(font); - - // Can only reload glyphs if we own the SDL_RWops. - if (owns_ttf) - FC_LoadFontFromTTF(font, renderer, ttf, col); - font->owns_ttf_source = owns_ttf; -} -#endif - -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); - - // If the last font has been freed; assume shutdown and free the global variables - if (--NUM_EXISTING_FONTS <= 0) - { - free(ASCII_STRING); - ASCII_STRING = NULL; - - free(LATIN_1_STRING); - LATIN_1_STRING = NULL; - - free(ASCII_LATIN_1_STRING); - ASCII_LATIN_1_STRING = NULL; - - free(fc_buffer); - fc_buffer = NULL; - } -} - -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_SetAnchor(img, 0.5f, 0.5f); // Just in case the default is different - 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; - SDL_Texture* img; - SDL_Rect destrect; - SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); - SDL_Rect prev_clip, prev_viewport; - int prev_logicalw, prev_logicalh; - Uint8 prev_clip_enabled; - float prev_scalex, prev_scaley; - // only backup if previous target existed (SDL will preserve them for the default target) - if (prev_target) { - prev_clip_enabled = has_clip(renderer); - if (prev_clip_enabled) - prev_clip = get_clip(renderer); - SDL_RenderGetViewport(renderer, &prev_viewport); - SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); - SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); - } - - img = SDL_CreateTextureFromSurface(renderer, glyph_surface); - - destrect = font->last_glyph.rect; - SDL_SetRenderTarget(renderer, dest); - SDL_RenderCopy(renderer, img, NULL, &destrect); - SDL_SetRenderTarget(renderer, prev_target); - if (prev_target) { - if (prev_clip_enabled) - set_clip(renderer, &prev_clip); - if (prev_logicalw && prev_logicalh) - SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); - else { - SDL_RenderSetViewport(renderer, &prev_viewport); - SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); - } - } - - SDL_DestroyTexture(img); - } - #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) -{ - if(node == NULL) - { - return NULL; - } - - // Get to the last node - while(*node != NULL) - { - node = &(*node)->next; - } - - *node = (FC_StringList*)malloc(sizeof(FC_StringList)); - - (*node)->value = (copy? U8_strdup(value) : value); - (*node)->next = NULL; - - return node; -} - -FC_StringList** FC_StringListPushBackBytes(FC_StringList** node, const char* data, int num_bytes) -{ - if(node == NULL) - { - return node; - } - - // Get to the last node - while(*node != NULL) - { - node = &(*node)->next; - } - - *node = (FC_StringList*)malloc(sizeof(FC_StringList)); - - (*node)->value = (char*)malloc(num_bytes + 1); - memcpy((*node)->value, data, num_bytes); - (*node)->value[num_bytes] = '\0'; - (*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_ExplodeBreakingSpace(const char* text, FC_StringList** spaces) -{ - FC_StringList* head; - FC_StringList** node; - const char* start; - const char* end; - unsigned int size; - if(text == NULL) - return NULL; - - head = NULL; - node = &head; - - // Warning: spaces must not be initialized before this function - *spaces = NULL; - - // Doesn't technically support UTF-8, but it's probably fine, right? - size = 0; - start = end = text; - while(1) - { - // Add any characters here that should make separate words (except for \n?) - if(*end == ' ' || *end == '\t' || *end == '\0') - { - FC_StringListPushBackBytes(node, start, size); - FC_StringListPushBackBytes(spaces, end, 1); - - 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** 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') - { - FC_StringListPushBackBytes(node, start, size); - - 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, *spaces, *spaces_iter; - - words = FC_ExplodeBreakingSpace(line, &spaces); - // Skip the first word for the iterator, so there will always be at least one word per line - line = new_concat(words->value, spaces->value); - for(word_iter = words->next, spaces_iter = spaces->next; word_iter != NULL && spaces_iter != NULL; word_iter = word_iter->next, spaces_iter = spaces_iter->next) - { - char* line_plus_word = new_concat(line, word_iter->value); - char* word_plus_space = new_concat(word_iter->value, spaces_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); - FC_StringListFree(spaces); - } - 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); - int8_t onFirst = 1; - for(iter = ls; iter != NULL; iter = iter->next) - { - FC_RenderAlign(font, dest, box.x, y, box.w, scale, onFirst ? FC_ALIGN_LEFT : align, iter->value); - y += FC_GetLineHeight(font); - onFirst = 0; - } - 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_CENTER); - - 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; -} - -FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...) -{ - FC_Rect result = {x, y, 0, 0}; - - if(formatted_text == NULL) - return result; - - // Create a temp buffer while GetWidth and GetHeight use fc_buffer. - char* temp = (char*)malloc(fc_buffer_size); - FC_EXTRACT_VARARGS(temp, formatted_text); - - result.w = FC_GetWidth(font, "%s", temp) * scale.x; - result.h = FC_GetHeight(font, "%s", temp) * scale.y; - - switch(align) - { - case FC_ALIGN_LEFT: - break; - case FC_ALIGN_CENTER: - result.x -= result.w/2; - break; - case FC_ALIGN_RIGHT: - result.x -= result.w; - break; - default: - break; - } - - free(temp); - - return result; -} - -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; -} - -int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...) -{ - FC_StringList *ls, *iter; - - if(font == NULL) - return 0; - - if(formatted_text == NULL || width == 0) - return 0; - - FC_EXTRACT_VARARGS(fc_buffer, formatted_text); - - ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); - int size_so_far = 0; - int size_remaining = max_result_size-1; // reserve for \0 - for(iter = ls; iter != NULL && size_remaining > 0; iter = iter->next) - { - // Copy as much of this line as we can - int len = strlen(iter->value); - int num_bytes = FC_MIN(len, size_remaining); - memcpy(&result[size_so_far], iter->value, num_bytes); - size_so_far += num_bytes; - - // If there's another line, add newline character - if(size_remaining > 0 && iter->next != NULL) - { - --size_remaining; - result[size_so_far] = '\n'; - ++size_so_far; - } - } - FC_StringListFree(ls); - - result[size_so_far] = '\0'; - - return size_so_far; -} - - - -// 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; -} +/* +SDL_FontCache: A font cache for SDL and SDL_ttf +by Jonathan Dearborn + +See SDL_FontCache.h for license info. +*/ + +#include "SDL_FontCache.h" + +#include +#include +#include +#include + +// Visual C does not support static inline +#ifndef static_inline + #ifdef _MSC_VER + #define static_inline static + #else + #define static_inline static inline + #endif +#endif + +#if SDL_VERSION_ATLEAST(2,0,0) + #define FC_GET_ALPHA(sdl_color) ((sdl_color).a) +#else + #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused) +#endif + +// Need SDL_RenderIsClipEnabled() for proper clipping support +#if SDL_VERSION_ATLEAST(2,0,4) + #define ENABLE_SDL_CLIPPING +#endif + +#define FC_MIN(a,b) ((a) < (b)? (a) : (b)) +#define FC_MAX(a,b) ((a) > (b)? (a) : (b)) + + +// vsnprintf replacement from Valentin Milea: +// http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +#if defined(_MSC_VER) && _MSC_VER < 1900 + +#define snprintf c99_snprintf +#define vsnprintf c99_vsnprintf + +__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + + return count; +} + +#endif + + +#define FC_EXTRACT_VARARGS(buffer, start_args) \ +{ \ + va_list lst; \ + va_start(lst, start_args); \ + vsnprintf(buffer, fc_buffer_size, start_args, lst); \ + va_end(lst); \ +} + +// Extra pixels of padding around each glyph to avoid linear filtering artifacts +#define FC_CACHE_PADDING 1 + + +static Uint8 has_clip(FC_Target* dest) +{ + #ifdef FC_USE_SDL_GPU + return dest->use_clip_rect; + #elif defined(ENABLE_SDL_CLIPPING) + return SDL_RenderIsClipEnabled(dest); + #else + return 0; + #endif +} + +static FC_Rect get_clip(FC_Target* dest) +{ + #ifdef FC_USE_SDL_GPU + return dest->clip_rect; + #elif defined(ENABLE_SDL_CLIPPING) + SDL_Rect r; + SDL_RenderGetClipRect(dest, &r); + return r; + #else + SDL_Rect r = {0, 0, 0, 0}; + return r; + #endif +} + +static void set_clip(FC_Target* dest, FC_Rect* rect) +{ + #ifdef FC_USE_SDL_GPU + if(rect != NULL) + GPU_SetClipRect(dest, *rect); + else + GPU_UnsetClip(dest); + #elif defined(ENABLE_SDL_CLIPPING) + SDL_RenderSetClipRect(dest, rect); + #endif +} + +static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + #ifdef FC_USE_SDL_GPU + GPU_SetRGBA(src, r, g, b, a); + #else + SDL_SetTextureColorMod(src, r, g, b); + SDL_SetTextureAlphaMod(src, a); + #endif +} + + + +static char* new_concat(const char* a, const char* b) +{ + // Create new buffer + unsigned int size = strlen(a) + strlen(b); + char* new_string = (char*)malloc(size+1); + + // Concatenate strings in the new buffer + strcpy(new_string, a); + strcat(new_string, b); + + return new_string; +} + +static char* replace_concat(char** a, const char* b) +{ + char* new_string = new_concat(*a, b); + free(*a); + *a = new_string; + return *a; +} + + +// Width of a tab in units of the space width (sorry, no tab alignment!) +static unsigned int fc_tab_width = 4; + +// 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; + +// The number of fonts that has been created but not freed +static int NUM_EXISTING_FONTS = 0; + +// Globals for GetString functions +static char* ASCII_STRING = NULL; +static char* LATIN_1_STRING = NULL; +static char* ASCII_LATIN_1_STRING = NULL; + +char* FC_GetStringASCII(void) +{ + if(ASCII_STRING == NULL) + { + int i; + char c; + ASCII_STRING = (char*)malloc(512); + memset(ASCII_STRING, 0, 512); + i = 0; + c = 32; + while(1) + { + ASCII_STRING[i] = c; + if(c == 126) + break; + ++i; + ++c; + } + } + return U8_strdup(ASCII_STRING); +} + +char* FC_GetStringLatin1(void) +{ + if(LATIN_1_STRING == NULL) + { + int i; + unsigned char c; + LATIN_1_STRING = (char*)malloc(512); + memset(LATIN_1_STRING, 0, 512); + i = 0; + c = 0xA0; + while(1) + { + LATIN_1_STRING[i] = 0xC2; + LATIN_1_STRING[i+1] = c; + if(c == 0xBF) + break; + i += 2; + ++c; + } + i += 2; + c = 0x80; + while(1) + { + LATIN_1_STRING[i] = 0xC3; + LATIN_1_STRING[i+1] = c; + if(c == 0xBF) + break; + i += 2; + ++c; + } + } + return U8_strdup(LATIN_1_STRING); +} + +char* FC_GetStringASCII_Latin1(void) +{ + if(ASCII_LATIN_1_STRING == NULL) + ASCII_LATIN_1_STRING = new_concat(FC_GetStringASCII(), FC_GetStringLatin1()); + + return U8_strdup(ASCII_LATIN_1_STRING); +} + +FC_Rect FC_MakeRect(float x, float y, float w, float h) +{ + FC_Rect r = {(int) x, (int) y, (int) w, (int) 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; +}; + +std::recursive_mutex mutex; + +SDL_Texture* FC_CreateTexture(FC_Font *pFont, uint32_t pixelFormat, float width, float height) { + return SDL_CreateTexture(pFont->renderer, pixelFormat, SDL_TEXTUREACCESS_TARGET, width, height); +} + +// 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_u8char; + int len; + int add_len; + int ulen; + const char* string_start = string; + + 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_u8char = 0; + while(*string != '\0' && pos_u8char < position) + { + string = (char*)U8_next(string); + ++pos_u8char; + } + + // Move the rest of the string out of the way + memmove(string + add_len, string, len - (string - string_start) + 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 = {(int) x, (int) y, (int) FC_MAX(0, x2 - x), (int) 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); +} + + +unsigned int FC_GetTabWidth(void) +{ + return fc_tab_width; +} + +void FC_SetTabWidth(unsigned int width_in_spaces) +{ + fc_tab_width = width_in_spaces; +} + + + + + +// 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 = 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); + GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different + #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; + } + // bug: we do not have the correct color here, this might be the wrong color! + // , most functions use set_color_for_all_caches() + // - for evading this bug, you must use FC_SetDefaultColor(), before using any draw functions + set_color(new_level, font->default_color.r, font->default_color.g, font->default_color.b, FC_GET_ALPHA(font->default_color)); +#ifndef FC_USE_SDL_GPU + { + Uint8 r, g, b, a; + SDL_Texture* prev_target = SDL_GetRenderTarget(font->renderer); + SDL_Rect prev_clip, prev_viewport; + int prev_logicalw, prev_logicalh; + Uint8 prev_clip_enabled; + float prev_scalex, prev_scaley; + // only backup if previous target existed (SDL will preserve them for the default target) + if (prev_target) { + prev_clip_enabled = has_clip(font->renderer); + if (prev_clip_enabled) + prev_clip = get_clip(font->renderer); + SDL_RenderGetViewport(font->renderer, &prev_viewport); + SDL_RenderGetScale(font->renderer, &prev_scalex, &prev_scaley); + SDL_RenderGetLogicalSize(font->renderer, &prev_logicalw, &prev_logicalh); + } + SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); + SDL_SetRenderTarget(font->renderer, new_level); + SDL_GetRenderDrawColor(font->renderer, &r, &g, &b, &a); + SDL_SetRenderDrawColor(font->renderer, 0, 0, 0, 0); + SDL_RenderClear(font->renderer); + SDL_SetRenderDrawColor(font->renderer, r, g, b, a); + SDL_SetRenderTarget(font->renderer, prev_target); + if (prev_target) { + if (prev_clip_enabled) + set_clip(font->renderer, &prev_clip); + if (prev_logicalw && prev_logicalh) + SDL_RenderSetLogicalSize(font->renderer, prev_logicalw, prev_logicalh); + else { + SDL_RenderSetViewport(font->renderer, &prev_viewport); + SDL_RenderSetScale(font->renderer, prev_scalex, prev_scaley); + } + } + } +#endif + 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); + GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different + 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 + const char* old_filter_hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); + if(!old_filter_hint) + old_filter_hint = "nearest"; + snprintf(old_filter_mode, 16, "%s", old_filter_hint); + + 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_Texture* prev_target = SDL_GetRenderTarget(renderer); + SDL_Rect prev_clip, prev_viewport; + int prev_logicalw, prev_logicalh; + Uint8 prev_clip_enabled; + float prev_scalex, prev_scaley; + // only backup if previous target existed (SDL will preserve them for the default target) + if (prev_target) { + prev_clip_enabled = has_clip(renderer); + if (prev_clip_enabled) + prev_clip = get_clip(renderer); + SDL_RenderGetViewport(renderer, &prev_viewport); + SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); + SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); + } + 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, prev_target); + if (prev_target) { + if (prev_clip_enabled) + set_clip(renderer, &prev_clip); + if (prev_logicalw && prev_logicalh) + SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); + else { + SDL_RenderSetViewport(renderer, &prev_viewport); + SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); + } + } + + 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; + + // TAB is special! + if(codepoint == '\t') + { + FC_GlyphData spaceGlyph; + FC_GetGlyphData(font, &spaceGlyph, ' '); + width = fc_tab_width * spaceGlyph.rect.w; + } + + 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); + ++NUM_EXISTING_FONTS; + + 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); + std::scoped_lock lock(mutex); + + // 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); + + // Some bug for certain fonts can result in an incorrect height. + if(font->height < font->ascent - font->descent) + font->height = font->ascent - font->descent; + + 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; + + source_string = font->loading_string; + for(; *source_string != '\0'; source_string = U8_next(source_string)) + { + memset(buff, 0, 5); + 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; +} + + +#ifndef FC_USE_SDL_GPU +void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType) +{ + TTF_Font* ttf; + SDL_Color col; + Uint8 owns_ttf; + if (font == NULL) + return; + + // Destroy glyph cache + if (evType == SDL_RENDER_TARGETS_RESET) { + int i; + for (i = 0; i < font->glyph_cache_count; ++i) + SDL_DestroyTexture(font->glyph_cache[i]); + } + free(font->glyph_cache); + + ttf = font->ttf_source; + col = font->default_color; + owns_ttf = font->owns_ttf_source; + FC_Init(font); + + // Can only reload glyphs if we own the SDL_RWops. + if (owns_ttf) + FC_LoadFontFromTTF(font, renderer, ttf, col); + font->owns_ttf_source = owns_ttf; +} +#endif + +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); + + // If the last font has been freed; assume shutdown and free the global variables + if (--NUM_EXISTING_FONTS <= 0) + { + free(ASCII_STRING); + ASCII_STRING = NULL; + + free(LATIN_1_STRING); + LATIN_1_STRING = NULL; + + free(ASCII_LATIN_1_STRING); + ASCII_LATIN_1_STRING = NULL; + + free(fc_buffer); + fc_buffer = NULL; + } +} + +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_SetAnchor(img, 0.5f, 0.5f); // Just in case the default is different + 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; + SDL_Texture* img; + SDL_Rect destrect; + SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); + SDL_Rect prev_clip, prev_viewport; + int prev_logicalw, prev_logicalh; + Uint8 prev_clip_enabled; + float prev_scalex, prev_scaley; + // only backup if previous target existed (SDL will preserve them for the default target) + if (prev_target) { + prev_clip_enabled = has_clip(renderer); + if (prev_clip_enabled) + prev_clip = get_clip(renderer); + SDL_RenderGetViewport(renderer, &prev_viewport); + SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); + SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); + } + + img = SDL_CreateTextureFromSurface(renderer, glyph_surface); + + destrect = font->last_glyph.rect; + SDL_SetRenderTarget(renderer, dest); + SDL_RenderCopy(renderer, img, NULL, &destrect); + SDL_SetRenderTarget(renderer, prev_target); + if (prev_target) { + if (prev_clip_enabled) + set_clip(renderer, &prev_clip); + if (prev_logicalw && prev_logicalh) + SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); + else { + SDL_RenderSetViewport(renderer, &prev_viewport); + SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); + } + } + + SDL_DestroyTexture(img); + } + #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); + + std::scoped_lock lock(mutex); + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + set_color_for_all_caches(font, font->default_color); + + auto res = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + + return res; +} + +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) +{ + if(node == NULL) + { + return NULL; + } + + // Get to the last node + while(*node != NULL) + { + node = &(*node)->next; + } + + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + + (*node)->value = (copy? U8_strdup(value) : value); + (*node)->next = NULL; + + return node; +} + +FC_StringList** FC_StringListPushBackBytes(FC_StringList** node, const char* data, int num_bytes) +{ + if(node == NULL) + { + return node; + } + + // Get to the last node + while(*node != NULL) + { + node = &(*node)->next; + } + + *node = (FC_StringList*)malloc(sizeof(FC_StringList)); + + (*node)->value = (char*)malloc(num_bytes + 1); + memcpy((*node)->value, data, num_bytes); + (*node)->value[num_bytes] = '\0'; + (*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_ExplodeBreakingSpace(const char* text, FC_StringList** spaces) +{ + FC_StringList* head; + FC_StringList** node; + const char* start; + const char* end; + unsigned int size; + if(text == NULL) + return NULL; + + head = NULL; + node = &head; + + // Warning: spaces must not be initialized before this function + *spaces = NULL; + + // Doesn't technically support UTF-8, but it's probably fine, right? + size = 0; + start = end = text; + while(1) + { + // Add any characters here that should make separate words (except for \n?) + if(*end == ' ' || *end == '\t' || *end == '\0') + { + FC_StringListPushBackBytes(node, start, size); + FC_StringListPushBackBytes(spaces, end, 1); + + 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** 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') + { + FC_StringListPushBackBytes(node, start, size); + + 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, *spaces, *spaces_iter; + + words = FC_ExplodeBreakingSpace(line, &spaces); + // Skip the first word for the iterator, so there will always be at least one word per line + line = new_concat(words->value, spaces->value); + for(word_iter = words->next, spaces_iter = spaces->next; word_iter != NULL && spaces_iter != NULL; word_iter = word_iter->next, spaces_iter = spaces_iter->next) + { + char* line_plus_word = new_concat(line, word_iter->value); + char* word_plus_space = new_concat(word_iter->value, spaces_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); + FC_StringListFree(spaces); + } + 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); + int8_t onFirst = 1; + for(iter = ls; iter != NULL; iter = iter->next) + { + FC_RenderAlign(font, dest, box.x, y, box.w, scale, onFirst ? FC_ALIGN_LEFT : align, iter->value); + y += FC_GetLineHeight(font); + onFirst = 0; + } + 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); + + std::scoped_lock lock(mutex); + + 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); + + std::scoped_lock lock(mutex); + + 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); + + std::scoped_lock lock(mutex); + + 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); + + std::scoped_lock lock(mutex); + + 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); + + std::scoped_lock lock(mutex); + + 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 = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(mutex); + + 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); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + return res; +} + +FC_Rect FC_DrawColumnToTexture(FC_Font* font, SDL_Texture* target, float x, float y, Uint16 width, const char* text) +{ + std::scoped_lock lock(mutex); + + // Draw the text onto it + SDL_SetRenderTarget(font->renderer, target); + // make sure the texture is clean. + SDL_RenderClear(font->renderer); + FC_Rect res = FC_DrawColumn(font, font->renderer, x, y, width, text); + SDL_SetRenderTarget(font->renderer, NULL); + + return res; +} + +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 = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(mutex); + + 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); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + return res; +} + +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 = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(mutex); + + 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_CENTER); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + + + return res; +} + +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 = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(mutex); + + 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); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + + + return res; +} + +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 = {(int) x, (int) y, (int) width, (int) 0}; + int total_height; + + if(formatted_text == NULL || font == NULL) + return FC_MakeRect(x, y, 0, 0); + + std::scoped_lock lock(mutex); + + 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); + + auto res = FC_MakeRect(box.x, box.y, width, total_height); + + + + return res; +} + +static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) +{ + FC_Rect result = {(int) x, (int) y, (int) 0, (int) 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 = {(int) x, (int) y, (int) 0, (int) 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); + + std::scoped_lock lock(mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, font->default_color); + + auto res = FC_RenderLeft(font, dest, x, y, scale, fc_buffer); + + + + return res; +} + +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); + + std::scoped_lock lock(mutex); + + 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); + + std::scoped_lock lock(mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + set_color_for_all_caches(font, color); + + auto res = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); + + + + return res; +} + + +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); + + std::scoped_lock lock(mutex); + + 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; + + std::scoped_lock lock(mutex); + + 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 + auto res = font->height*numLines + font->lineSpacing*(numLines - 1); //height*numLines; + + + + return res; +} + +Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...) +{ + if(formatted_text == NULL || font == NULL) + return 0; + + std::scoped_lock lock(mutex); + + 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; + + std::scoped_lock lock(mutex); + + 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; + + std::scoped_lock lock(mutex); + + 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; + + std::scoped_lock lock(mutex); + + 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; + + std::scoped_lock lock(mutex); + + 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; +} + +FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...) +{ + FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; + + if(formatted_text == NULL) + return result; + + // Create a temp buffer while GetWidth and GetHeight use fc_buffer. + char* temp = (char*)malloc(fc_buffer_size); + FC_EXTRACT_VARARGS(temp, formatted_text); + + result.w = FC_GetWidth(font, "%s", temp) * scale.x; + result.h = FC_GetHeight(font, "%s", temp) * scale.y; + + switch(align) + { + case FC_ALIGN_LEFT: + break; + case FC_ALIGN_CENTER: + result.x -= result.w/2; + break; + case FC_ALIGN_RIGHT: + result.x -= result.w; + break; + default: + break; + } + + free(temp); + + return result; +} + +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; + + std::scoped_lock lock(mutex); + + 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; +} + +int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...) +{ + FC_StringList *ls, *iter; + + if(font == NULL) + return 0; + + if(formatted_text == NULL || width == 0) + return 0; + + std::scoped_lock lock(mutex); + + FC_EXTRACT_VARARGS(fc_buffer, formatted_text); + + ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); + int size_so_far = 0; + int size_remaining = max_result_size-1; // reserve for \0 + for(iter = ls; iter != NULL && size_remaining > 0; iter = iter->next) + { + // Copy as much of this line as we can + int len = strlen(iter->value); + int num_bytes = FC_MIN(len, size_remaining); + memcpy(&result[size_so_far], iter->value, num_bytes); + size_so_far += num_bytes; + + // If there's another line, add newline character + if(size_remaining > 0 && iter->next != NULL) + { + --size_remaining; + result[size_so_far] = '\n'; + ++size_so_far; + } + } + FC_StringListFree(ls); + + result[size_so_far] = '\0'; + + + + return size_so_far; +} + + + +// 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/gui/SDL_FontCache.h b/src/system/video/SDL_FontCache.h similarity index 95% rename from src/gui/SDL_FontCache.h rename to src/system/video/SDL_FontCache.h index a8cdfa1..315e4c9 100644 --- a/src/gui/SDL_FontCache.h +++ b/src/system/video/SDL_FontCache.h @@ -1,328 +1,322 @@ -/* -SDL_FontCache v0.10.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) 2019 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 -#include -#include "../logger.h" - -#ifdef FC_USE_SDL_GPU - #include "SDL_gpu.h" -#endif - - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Let's pretend this exists... -#define TTF_STYLE_OUTLINE 16 - - - -// Differences between SDL_Renderer and SDL_gpu -#ifdef FC_USE_SDL_GPU -#define FC_Rect GPU_Rect -#define FC_Target GPU_Target -#define FC_Image GPU_Image -#define FC_Log GPU_LogError -#else -#define FC_Rect SDL_Rect -#define FC_Target SDL_Renderer -#define FC_Image SDL_Texture -#define FC_Log DEBUG_FUNCTION_LINE -#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 - -#ifndef FC_USE_SDL_GPU -// note: handle SDL event types SDL_RENDER_TARGETS_RESET(>= SDL 2.0.2) and SDL_RENDER_DEVICE_RESET(>= SDL 2.0.4) -void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType); -#endif - -void FC_ClearFont(FC_Font* font); - -void FC_FreeFont(FC_Font* font); - - - -// Built-in loading strings - -char* FC_GetStringASCII(void); - -char* FC_GetStringLatin1(void); - -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); - -/*! Returns the width of a single horizontal tab in multiples of the width of a space (default: 4) */ -unsigned int FC_GetTabWidth(void); - -/*! Changes the width of a horizontal tab in multiples of the width of a space (default: 4) */ -void FC_SetTabWidth(unsigned int width_in_spaces); - -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); - -FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...); - -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, ...); - -// Returns the number of characters in the new wrapped text written into `result`. -int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, 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 +/* +SDL_FontCache v0.10.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) 2019 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 +#include +#include +#include "../../utils/logger.h" + +#ifdef FC_USE_SDL_GPU + #include "SDL_gpu.h" +#endif + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Let's pretend this exists... +#define TTF_STYLE_OUTLINE 16 + + + +// Differences between SDL_Renderer and SDL_gpu +#ifdef FC_USE_SDL_GPU +#define FC_Rect GPU_Rect +#define FC_Target GPU_Target +#define FC_Image GPU_Image +#define FC_Log GPU_LogError +#else +#define FC_Rect SDL_Rect +#define FC_Target SDL_Renderer +#define FC_Image SDL_Texture +#define FC_Log DEBUG_FUNCTION_LINE +#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); + +SDL_Texture* FC_CreateTexture(FC_Font *pFont, uint32_t pixelFormat, float width, float height); + +// 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 + +#ifndef FC_USE_SDL_GPU +// note: handle SDL event types SDL_RENDER_TARGETS_RESET(>= SDL 2.0.2) and SDL_RENDER_DEVICE_RESET(>= SDL 2.0.4) +void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType); +#endif + +void FC_ClearFont(FC_Font* font); + +void FC_FreeFont(FC_Font* font); + + + +// Built-in loading strings + +char* FC_GetStringASCII(void); + +char* FC_GetStringLatin1(void); + +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); + +/*! Returns the width of a single horizontal tab in multiples of the width of a space (default: 4) */ +unsigned int FC_GetTabWidth(void); + +/*! Changes the width of a horizontal tab in multiples of the width of a space (default: 4) */ +void FC_SetTabWidth(unsigned int width_in_spaces); + +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_DrawColumnToTexture(FC_Font* font, SDL_Texture* target, float x, float y, Uint16 width, const char* 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); + +FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...); + +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, ...); + +// Returns the number of characters in the new wrapped text written into `result`. +int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, 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/logger.h b/src/utils/logger.h similarity index 100% rename from src/logger.h rename to src/utils/logger.h