diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..06f8ea8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: Microsoft +UseTab: Never +IndentWidth: 4 +AllowShortFunctionsOnASingleLine: InlineOnly +ColumnLimit: 100 +BreakBeforeBraces: Linux +PointerAlignment: Left \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e4c28ea --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.ttf filter=lfs diff=lfs merge=lfs -text diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/SDL_DrawText.iml b/.idea/SDL_DrawText.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/SDL_DrawText.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..79b3c94 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a310a14 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f8c222..846d61e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(BIN_DIR ${test_SOURCE_DIR}/bin) # Bump up warning levels appropriately for clang, gcc & msvc and build in debug mode if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -std=c++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -std=c++14") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -g") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O2") elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") diff --git a/build/fonts/OfenbacherSchwabCAT.ttf b/build/fonts/OfenbacherSchwabCAT.ttf index 7596111..25ce53b 100644 Binary files a/build/fonts/OfenbacherSchwabCAT.ttf and b/build/fonts/OfenbacherSchwabCAT.ttf differ diff --git a/src/drawtext.cpp b/src/drawtext.cpp index 2a813a4..6a41f4c 100644 --- a/src/drawtext.cpp +++ b/src/drawtext.cpp @@ -4,132 +4,93 @@ #include #include #include +#include -DrawText::DrawText(const char* fontPath, const int fontSize, const SDL_Color& fontColor) +DrawText::DrawText(const char* fontPath, int fontSize, const SDL_Color& fontColor, + uint16_t initialCharacter, uint16_t finalCharacter, bool throwExceptions) + : throwExceptions_{throwExceptions} { + if (finalCharacter - initialCharacter <= 0) { + throw std::logic_error("The final character must be bigger than the initial character."); + } + TTF_Font* font = TTF_OpenFont(fontPath, fontSize); if (!font) { std::stringstream message; message << "DrawText::DrawText: " << TTF_GetError(); throw std::runtime_error(message.str()); } - createAlphabet(font, fontColor); + createAlphabet(font, fontColor, initialCharacter, finalCharacter); TTF_CloseFont(font); } DrawText::~DrawText() { - for (auto& i : alphabet) { - SDL_FreeSurface(i); + for (std::map::iterator it = alphabet_.begin(); it != alphabet_.end(); + it++) { + SDL_FreeSurface(it->second); } } -void DrawText::createAlphabet(TTF_Font* font, const SDL_Color& fontColor) +void DrawText::createAlphabet(TTF_Font* font, const SDL_Color& fontColor, uint16_t initialCharacter, + uint16_t finalCharacter) { - for (Uint16 c = INITIAL_CHARACTER; c <= FINAL_CHARACTER; c++) { - alphabet[c - INITIAL_CHARACTER] = TTF_RenderGlyph_Blended(font, c, fontColor); + for (uint16_t c = initialCharacter; c <= finalCharacter; c++) { + auto* glyph = TTF_RenderGlyph_Blended(font, c, fontColor); + if (glyph == nullptr) { + throw std::runtime_error("Error creating alphabet"); + } + alphabet_[c] = glyph; } } -void DrawText::drawGlyph(SDL_Surface* destinationSurface, const unsigned char character, int& x, - int& y) +static int calculateIncrement(int x0, int w0, int x1, int w1) { - if (character == '\n') { - SDL_Surface* glyph = alphabet[0]; - y = y + glyph->h; - newLine = true; - return; + int dx = 0; + if (w0 == 0) { + dx = w1; + } else { + dx = (x0 + w0) > (x1 + w1) ? w1 : w1 - (x1 + w1 - x0 - w0); } - - unsigned char i = character - INITIAL_CHARACTER; - SDL_Surface* glyph = alphabet[i]; - - SDL_Rect dst_rect; - dst_rect.x = x; - dst_rect.y = y; - dst_rect.w = glyph->w; - dst_rect.h = glyph->h; - - x = x + glyph->w; - SDL_BlitSurface(glyph, NULL, destinationSurface, &dst_rect); + return dx; } -void DrawText::print(SDL_Surface* destinationSurface, const std::string& text, int x, int y) +void DrawText::drawGlyph(SDL_Surface* destinationSurface, uint16_t character, int& x, int& y, + Constrain constrain) { - auto tmp_x = x; - auto tmp_y = y; - - for (const char* c = text.c_str(); *c != '\0'; c++) { - if (newLine) { - tmp_x = x; - newLine = false; - } - drawGlyph(destinationSurface, *c, tmp_x, tmp_y); + if (y > (constrain.x0 + constrain.height) && constrain.height != 0) { + return; } -} + auto get_glyph_of_character = [&](uint16_t c) -> SDL_Surface* { + try { + auto* glyph = alphabet_.at(c); + return glyph; + } catch (std::out_of_range& e) { + if (throwExceptions_) { + throw std::logic_error("Trying to print character not defined in alphabet."); + } + } + return nullptr; + }; -void DrawText::drawGlyphW(SDL_Surface* destinationSurface, const wchar_t character, int& x, int& y) -{ - if (character == '\n') { - SDL_Surface* glyph = alphabet[0]; + // New line + if (character == static_cast('\n')) { + auto* glyph = get_glyph_of_character(alphabet_.begin()->first); y = y + glyph->h; - newLine = true; + newLine_ = true; return; } - unsigned char i = (unsigned char)character - INITIAL_CHARACTER; - if (i >= FINAL_CHARACTER) { + + // Normal character + auto* glyph = get_glyph_of_character(character); + if (glyph == nullptr) { return; } - SDL_Surface* glyph = alphabet[i]; - - SDL_Rect dst_rect; - dst_rect.x = x; - dst_rect.y = y; - dst_rect.w = glyph->w; - dst_rect.h = glyph->h; + const int dx = calculateIncrement(constrain.x0, constrain.width, x, glyph->w); + const int dy = calculateIncrement(constrain.y0, constrain.height, y, glyph->h); + SDL_Rect src_rect = {0, 0, dx, dy}; + SDL_Rect dst_rect = {x, y, dx, dy}; x = x + glyph->w; - SDL_BlitSurface(glyph, NULL, destinationSurface, &dst_rect); -} - -void DrawText::print(SDL_Surface* destinationSurface, const std::wstring& text, int x, int y) -{ - auto tmp_x = x; - auto tmp_y = y; - - for (auto character = text.c_str(); *character != '\0'; character++) { - if (newLine) { - tmp_x = x; - newLine = false; - } - drawGlyphW(destinationSurface, *character, tmp_x, tmp_y); - } -} -std::string DrawText::format(const std::string text, ...) -{ - std::string a; - - char buffer[BUFFER_SIZE] = {}; - { - va_list list; - va_start(list, text); - vsnprintf(buffer, BUFFER_SIZE, text.c_str(), list); - va_end(list); - }; - a = buffer; - return a; -} - -std::wstring DrawText::format(const std::wstring text, ...) -{ - std::wstring out; - wchar_t buffer[BUFFER_SIZE] = {}; - { - va_list list; - va_start(list, text); - vswprintf(buffer, BUFFER_SIZE, text.c_str(), list); - va_end(list); - }; - out = buffer; - return out; + SDL_BlitSurface(glyph, &src_rect, destinationSurface, &dst_rect); } \ No newline at end of file diff --git a/src/drawtext.h b/src/drawtext.h index cb10028..69c3e69 100644 --- a/src/drawtext.h +++ b/src/drawtext.h @@ -3,98 +3,128 @@ #include #include -#include - -class DrawText { -public: - /** - * @brief Defines the properties of the text to draw on the screen. - * @param fontPath Path to the TTF file to be used. - * @param fontSize Size of the font to write on screen. - * @param fontColor Color of the text to write. - */ - DrawText(const char *fontPath, int fontSize, const SDL_Color &fontColor); - - /** - * @brief Destroys the alphabet and free memory. - */ - ~DrawText(); - /** - * @brief Print text on a given surface - * @param destinationSurface Surface where the text will be written. - * @param text Text to write on the screen. - * @param x Horizontal position of the text. - * @param y Vertical position of the text. - * @param ... Optional variables for the special characters in the string. - */ - void print(SDL_Surface *destinationSurface, const std::string& text, int x, int y); - - /** - * @brief Print text on a given surface - * @param destinationSurface Surface where the text will be written. - * @param text Unicode text to write on the screen. - * @param x Horizontal position of the text. - * @param y Vertical position of the text. - */ - void print(SDL_Surface *destinationSurface, const std::wstring& text, int x, int y); +#include +#include +#include +#include +#include +#include - /** - * @brief Formats a string - * @param text The text or format specifier to convert - * @param ... Extra parameters to replace by the format specifier - * @return Resulting string after applying the format specifier - */ - static std::string format(const std::string text, ...); +class DrawText +{ + public: + /** + * @brief Defines the properties of the text to draw on the screen. + * @param fontPath Path to the TTF file to be used. + * @param fontSize Size of the font to write on screen. + * @param fontColor Color of the text to write. + */ + DrawText(const char* fontPath, int fontSize, const SDL_Color& fontColor, + uint16_t initialCharacter = 5, uint16_t finalCharacter = 255, + bool throwExceptions = false); - /** - * @brief Formats a string - * @param text The text or format specifier to convert - * @param ... Extra parameters to replace by the format specifier - * @return Resulting string after applying the format specifier - */ - static std::wstring format(const std::wstring string, ...); -private: - /** - * @brief Creates the glyphs that composes the alphabet. - * @param font Pointer to the TTF_Font that contains a valid font. - * @param fontColor Color for the glyphs. - */ - void createAlphabet(TTF_Font *font, const SDL_Color &fontColor); + /** + * @brief Destroys the alphabet and free memory. + */ + virtual ~DrawText(); - /** - * @brief Draw a glyph on the screen - * @param destinationSurface Surface where the character will be written. - * @param character Character to write on the screen. - * @param x Horizontal position for the glyph. - * @param y Vertical position for the glyph. - */ - void drawGlyph(SDL_Surface *destinationSurface, unsigned char character, - int &x, int &y); - /** - * @brief Draw a glyph on the screen - * @param destinationSurface Surface where the character will be written. - * @param character Unicode character to write on the screen. - * @param x Horizontal position for the glyph. - * @param y Vertical position for the glyph. - */ - void drawGlyphW(SDL_Surface *destinationSurface, wchar_t character, int &x, - int &y); + /** + * @brief Print text on a given surface + * @param destinationSurface Surface where the text will be written. + * @param text Unicode text to write on the screen. + * @param x Horizontal position of the text. + * @param y Vertical position of the text. + * @param width Sets a maximum width for the text. 0 for no limitations. + * @param height Sets a maxium height for the text. 0 for no limitations. + * @throw Out of range if the string contains characters not contained in the alphabet + */ + template ::value, std::string>, + typename = std::enable_if::value, std::wstring>> + void print(SDL_Surface* destinationSurface, T const& text, int x, int y, int width = 0, + int height = 0) + { + auto tmp_x = x; + auto tmp_y = y; + for (auto c = std::begin(text); c != std::end(text) && *c != '\0'; ++c) { + if (newLine_) { + tmp_x = x; + newLine_ = false; + } + drawGlyph(destinationSurface, *c, tmp_x, tmp_y, Constrain{x, y, width, height}); + } + } - /// @brief Maximum size for the buffer for the message on screen. - static constexpr int BUFFER_SIZE = 256; + /** + * @brief Formats a string + * @param text The text or format specifier to convert + * @param ... Extra parameters to replace by the format specifier + * @return Resulting string after applying the format specifier + * @throw Fails if the resulting string has a size of zero + */ + template static std::string format(const std::string& text, Args&&... args) + { + size_t n = std::snprintf(nullptr, 0, text.c_str(), std::forward(args)...); + if (n < 0) { + throw std::runtime_error("The formated string has a size of zero."); + } + if (n == 0) { + return {}; + } + auto buffer = new char[n+1]; + std::snprintf(buffer, n, text.c_str(), std::forward(args)...); + std::string result = buffer; + delete[] buffer; + return result; + } + template static std::wstring format(const std::wstring& text, Args&&... args) + { + int n = std::swprintf(nullptr, 0, text.c_str(), std::forward(args)...); + if (n < 0) { + throw std::runtime_error("The formated string has a size of zero."); + } + if (n == 0) { + return {}; + } + auto buffer = new wchar_t[n+1]; + swprintf(buffer, n, text.c_str(), std::forward(args)...); + std::wstring result = buffer; + delete[] buffer; + return result; + } - /// @brief Initial character for the glyph alphabet. - static constexpr int INITIAL_CHARACTER = 32; + private: + struct Constrain { + int x0{0}; + int y0{0}; + int width{0}; + int height{0}; + }; + /** + * @brief Creates the glyphs that composes the alphabet. + * @param font Pointer to the TTF_Font that contains a valid font. + * @param fontColor Color for the glyphs. + */ + void createAlphabet(TTF_Font* font, const SDL_Color& fontColor, uint16_t initialCharacter, + uint16_t finalCharacter); - /// @brief Final character for the glyph alphabet. - static constexpr int FINAL_CHARACTER = 255; + /** + * @brief Draw a glyph on the screen + * @param destinationSurface Surface where the character will be written. + * @param character Unicode character to write on the screen. + * @param x Horizontal position for the glyph. + * @param y Vertical position for the glyph. + * @param Constrain sets the drawing limitations. + * + * If the width or height in the constrain struct are zero the dimension is ignored. + */ + void drawGlyph(SDL_Surface* destinationSurface, uint16_t character, int& x, int& y, + Constrain constrain = Constrain{0, 0, 0, 0}); - ///@brief Total number of characters in the glyph alphabet. - static constexpr int TOTAL_CHARACTERS = FINAL_CHARACTER - INITIAL_CHARACTER; + std::map alphabet_; - SDL_Surface *alphabet[TOTAL_CHARACTERS]{}; - bool newLine{false}; + bool newLine_{false}; + bool throwExceptions_{false}; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 7a45d82..d0570b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,118 +1,126 @@ +#include "drawtext.h" + #include #include -#include -#include "drawtext.h" +#include +#include SDL_Renderer* renderer; SDL_Window* window; SDL_Surface* screen; SDL_Texture* texture; -int init() +void init() { - static constexpr int WINDOW_WIDTH = 800; - static constexpr int WINDOW_HEIGHT = 600; - static constexpr int WINDOW_BPP = 32; - - SDL_Init(SDL_INIT_VIDEO); - - window = SDL_CreateWindow("Examples", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN); - if (window == NULL) { - std::cout << "Could not create window: " << SDL_GetError() << std::endl; - return -1; - } - - renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); - if (renderer == NULL) { - std::cout << "Renderer could not be created! SDL Error: " << SDL_GetError() << std::endl; - return -1; - } + static constexpr int WINDOW_WIDTH = 800; + static constexpr int WINDOW_HEIGHT = 600; + static constexpr int WINDOW_BPP = 32; + + SDL_Init(SDL_INIT_VIDEO); + + window = SDL_CreateWindow("Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); + if (window == NULL) { + std::stringstream out; + out << "Can't create window: " << SDL_GetError(); + throw std::runtime_error(out.str()); + } + + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) { + std::stringstream out; + out << "Can't create render: " << SDL_GetError(); + throw std::runtime_error(out.str()); + } #if SDL_BYTEORDER == SDL_BIG_ENDIAN - screen = SDL_CreateRGBSurface(0, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_BPP, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff); + screen = SDL_CreateRGBSurface(0, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_BPP, 0xff000000, + 0x00ff0000, 0x0000ff00, 0x000000ff); #else - screen = SDL_CreateRGBSurface(0, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_BPP, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + screen = SDL_CreateRGBSurface(0, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_BPP, 0, 0, 0, 0); #endif - texture = SDL_CreateTextureFromSurface(renderer, screen); + texture = SDL_CreateTextureFromSurface(renderer, screen); - if (TTF_Init() < 0) { - std::cout << "Can't init TTF: " << SDL_GetError() << std::endl; - return -1; - } - return 0; + if (TTF_Init() < 0) { + std::stringstream out; + out << "Can't init TTF: " << SDL_GetError(); + throw std::runtime_error(out.str()); + } } void updateScreen() { - SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); } void mainLoop() { - static constexpr unsigned int MINIMUM_FRAME_TIME = 10; - - SDL_Event event; - bool quit = false; - unsigned int initialTime = 0; - - while (!quit) { - SDL_PollEvent(&event); - if (event.type == SDL_QUIT) { - quit = true; - } - - initialTime = SDL_GetTicks(); - updateScreen(); - - auto delta_time = SDL_GetTicks() - initialTime; - if (delta_time < MINIMUM_FRAME_TIME) { - SDL_Delay(MINIMUM_FRAME_TIME - delta_time); - } - } + static constexpr unsigned int MINIMUM_FRAME_TIME = 10; + + SDL_Event event; + auto quit = false; + unsigned int initialTime = 0; + + while (!quit) { + SDL_PollEvent(&event); + if (event.type == SDL_QUIT) { + quit = true; + } + + initialTime = SDL_GetTicks(); + updateScreen(); + + auto delta_time = SDL_GetTicks() - initialTime; + if (delta_time < MINIMUM_FRAME_TIME) { + SDL_Delay(MINIMUM_FRAME_TIME - delta_time); + } + } } void quit() { - SDL_FreeSurface(screen); - SDL_DestroyTexture(texture); - SDL_DestroyWindow(window); - TTF_Quit(); - SDL_Quit(); + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyWindow(window); + TTF_Quit(); + SDL_Quit(); } int main(int argc, char* argv[]) { - init(); + init(); + + // Init DrawText object + SDL_Color white = {255, 255, 255, 0}; + SDL_Color red = {255, 0, 0, 0}; - // Init DrawText object - SDL_Color white = { 255, 255, 255, 0 }; - SDL_Color red = { 255, 0, 0, 0 }; + auto* text = new DrawText("fonts/OfenbacherSchwabCAT.ttf", 25, white); + auto* redText = new DrawText("fonts/OfenbacherSchwabCAT.ttf", 20, red); - auto* text = new DrawText("fonts/OfenbacherSchwabCAT.ttf", 25, white); - auto* redText = new DrawText("fonts/OfenbacherSchwabCAT.ttf", 20, red); + // Print some text + text->print(screen, + DrawText::format(L"Uicode string:\nEn un lugar de la Mancha," + "de cuyo nombre no quiero acordarme,\nno ha mucho tiempo" + "que vivía un hidalgo de los de lanza en astillero," + "\nadarga antigua, rocín flaco y galgo corredor."), + 10, 50); - // Print some text - text->print(screen, L"Uicode string:\nEn un lugar de la Mancha," - "de cuyo nombre no quiero acordarme,\nno ha mucho tiempo" - "que vivía un hidalgo de los de lanza en astillero," - "\nadarga antigua, rocín flaco y galgo corredor.", - 10, 50); + text->print(screen, + DrawText::format("Non-uicode string:\nEn un lugar de la Mancha," + "de cuyo nombre no quiero acordarme,\nno ha mucho tiempo" + "que vivía un hidalgo de los de lanza en astillero," + "\nadarga antigua, rocín flaco y galgo corredor."), + 10, 200); - text->print(screen, - "Non-uicode string:\nEn un lugar de la Mancha," - "de cuyo nombre no quiero acordarme,\nno ha mucho tiempo" - "que vivía un hidalgo de los de lanza en astillero," - "\nadarga antigua, rocín flaco y galgo corredor.", - 10, 200); + redText->print(screen, DrawText::format("Pi=%f", 3.141592), 10, 350); - redText->print(screen, DrawText::format("Pi=%f", 3.141592), 10, 350); + mainLoop(); - mainLoop(); + quit(); - quit(); - return 0; + return 0; }