diff --git a/src/dawn/CMakeLists.txt b/src/dawn/CMakeLists.txt index 2a079d8e..82074828 100644 --- a/src/dawn/CMakeLists.txt +++ b/src/dawn/CMakeLists.txt @@ -7,6 +7,7 @@ target_link_libraries(${DAWN_TARGET_NAME} PUBLIC glm + stb ) # Includes diff --git a/src/dawn/dawnlibs.hpp b/src/dawn/dawnlibs.hpp index 6b354dde..669efec9 100644 --- a/src/dawn/dawnlibs.hpp +++ b/src/dawn/dawnlibs.hpp @@ -28,6 +28,7 @@ extern "C" { #include #include #include +#include // #include // #include // #include diff --git a/src/dawn/display/font/CMakeLists.txt b/src/dawn/display/font/CMakeLists.txt index 841a5b48..0a1bfd16 100644 --- a/src/dawn/display/font/CMakeLists.txt +++ b/src/dawn/display/font/CMakeLists.txt @@ -6,5 +6,5 @@ # Sources target_sources(${DAWN_TARGET_NAME} PRIVATE - Font.cpp + TrueTypeFont.cpp ) \ No newline at end of file diff --git a/src/dawn/display/font/Font.cpp b/src/dawn/display/font/Font.cpp deleted file mode 100644 index 64ad528a..00000000 --- a/src/dawn/display/font/Font.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2022 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#include "Font.hpp" - -using namespace Dawn; \ No newline at end of file diff --git a/src/dawn/display/font/Font.hpp b/src/dawn/display/font/Font.hpp index fb167403..2aa993e8 100644 --- a/src/dawn/display/font/Font.hpp +++ b/src/dawn/display/font/Font.hpp @@ -7,7 +7,12 @@ #pragma once #include "display/mesh/Mesh.hpp" +#include "util/mathutils.hpp" #include "display/Texture.hpp" +#include "display/mesh/QuadMesh.hpp" + +#define FONT_NEWLINE '\n' +#define FONT_SPACE ' ' namespace Dawn { class FontMeasure { diff --git a/src/dawn/display/font/TrueTypeFont.cpp b/src/dawn/display/font/TrueTypeFont.cpp new file mode 100644 index 00000000..11216b73 --- /dev/null +++ b/src/dawn/display/font/TrueTypeFont.cpp @@ -0,0 +1,196 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "TrueTypeFont.hpp" + +using namespace Dawn; + +void TrueTypeFont::bakeQuad(truetypequad_t *quad,float_t *x,float_t *y,char c){ + stbtt_GetBakedQuad( + this->characterData, + this->texture.getWidth(), this->texture.getHeight(), + ((int32_t)c) - TRUETYPE_FIRST_CHAR, + x, y, quad, + TRUETYPE_FILL_MODE + ); +} + +float_t TrueTypeFont::getScale(float_t scale) { + return scale / this->fontSize; +} + +float_t TrueTypeFont::getSpaceSize(float_t fontSize) { + return mathRoundFloat(this->fontSize * 0.3f); +} + +float_t TrueTypeFont::getInitialLineHeight(float_t fontSize) { + return mathRoundFloat(this->getScale(fontSize) * 11.0f); +} + +void TrueTypeFont::buffer( + std::string text, + float_t fontSize, + float_t maxWidth, + Mesh &mesh, + FontMeasure &measure +) { + // truetypequad_t *quads; + // int32_t stringLength, wordStart, i, j; + // float_t x, y, wordX; + // float_t scale; + // char c; + // truetypequad_t *quad; + + auto stringLength = text.length(); + + if(stringLength == 0) { + info->length = 0; + info->realLength = 0; + info->lineCount = 0; + info->lines[0].start = 0; + info->lines[0].length = 0; + info->height = 0; + info->width = 0; + mesh.createBuffers(0, 0); + return; + } + + auto quads = new truetypequad_t[stringLength]; + + // Get the font scale + auto scale = this->getScale(fontSize); + + // Adjust the max width to match the scale, and allow "no max width". + maxWidth = maxWidth == -1 ? 9999999 : maxWidth * (1 / scale); + + // Which index in the original text var is the current word from + int32_t wordStart = 0; + + // Setup Scales + info->length = 0; + info->realLength = 0; + info->lineCount = 0; + + // Prepare the line counters + info->lines[0].start = 0; + info->lines[0].length = 0; + + // Reset Dimensions + info->width = info->height = 0; + + // Setup the initial loop state, and X/Y coords for the quad. + int32_t i = 0; + float_t x = 0; + float_t y = this->getInitialLineHeight(fontSize); + float_t wordX = 0; + char c; + while(c = text[i++]) { + info->length++; + + // When space, start of new word about to begin + if(c == FONT_SPACE) { + x += this->getSpaceSize(fontSize); + + // Did this space cause a newline? + if(x > maxWidth) { + trueTypeTextBufferAddLine(info, info->realLength, 0); + y += this->getLineHeight(fontSize); + x = 0; + } + wordX = x; + wordStart = info->realLength; + continue; + } + + // New line. + if(c == FONT_NEWLINE) { + trueTypeTextBufferAddLine(info, info->realLength, 0); + wordStart = info->realLength; + y += this->getLineHeight(fontSize); + x = 0; + continue; + } + + // Generate the quad. + auto quad = quads + info->realLength; + this->bakeQuad(quad, &x, &y, c); + + // Now measure the width of the line (take the right side of that quad) + if(quad->x1 > maxWidth) { + // We've exceeded the edge, go back to the start of the word and newline. + x = quad->x1 - wordX; + for(auto j = wordStart; j <= info->realLength; j++) { + quad = quads + j; + quad->x0 -= wordX; + quad->x1 -= wordX; + quad->y0 += this->getLineHeight(fontSize); + quad->y1 += this->getLineHeight(fontSize); + } + + // Go back to the previous (still current) line and remove the chars + info->lines[info->lineCount].length -= info->realLength - wordStart; + + // Next line begins with this word + y += this->getLineHeight(fontSize); + trueTypeTextBufferAddLine(info, wordStart, info->realLength-wordStart); + wordX = 0; + } + + info->lines[info->lineCount].length++; + info->realLength++; + } + + info->lineCount++; + + // Initialize primitive + mesh.createBuffers( + QUAD_VERTICE_COUNT * info->realLength, + QUAD_INDICE_COUNT * info->realLength + ); + for(auto j = 0; j < info->realLength; j++) { + auto quad = quads + j; + + // Scale the Quad + if(scale != 1.0) { + quad->x0 *= scale; + quad->x1 *= scale; + quad->y0 *= scale; + quad->y1 *= scale; + } + + // Update the dimensions. + info->width = mathMax(info->width, quad->x1); + info->height = mathMax(info->height, quad->y1); + + // Buffer the quad. + QuadMesh::bufferQuadMesh(mesh, + glm::vec2(quad->x0, quad->y0), glm::vec2(quad->s0, quad->t0), + glm::vec2(quad->x1, quad->y1), glm::vec2(quad->s1, quad->t1), + j * QUAD_VERTICE_COUNT, j * QUAD_INDICE_COUNT + ); + } + + delete quads; +} + +Texture & TrueTypeFont::getTexture() { + return this->texture; +} + +void TrueTypeFont::draw(Mesh &mesh, int32_t startchar, int32_t length) { + mesh.draw( + MESH_DRAW_MODE_TRIANGLES, + startchar * QUAD_INDICE_COUNT, + length == -1 ? length : length * QUAD_INDICE_COUNT + ); +} + +float_t TrueTypeFont::getLineHeight(float_t fontSize) { + return mathRoundFloat(this->getScale(fontSize) * 14.0f); +} + +float_t TrueTypeFont::getDefaultFontSize() { + return this->fontSize; +} \ No newline at end of file diff --git a/src/dawn/display/font/TrueTypeFont.hpp b/src/dawn/display/font/TrueTypeFont.hpp new file mode 100644 index 00000000..39914eb2 --- /dev/null +++ b/src/dawn/display/font/TrueTypeFont.hpp @@ -0,0 +1,117 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "Font.hpp" + +#ifndef STB_TRUETYPE_IMPLEMENTATION + #define STB_TRUETYPE_IMPLEMENTATION + #include +#endif + +namespace Dawn { + /** Which character (ASCII) to start the font from */ + #define TRUETYPE_FIRST_CHAR 32 + + /** How many characters (after the first char) to generate */ + #define TRUETYPE_NUM_CHARS 96 + + /** Refer to STBTT docs for OpenGL Fill Mode v d3d Fill Modes */ + #define TRUETYPE_FILL_MODE 1 + + typedef stbtt_bakedchar truetypechar_t; + typedef stbtt_aligned_quad truetypequad_t; + + class TrueTypeFontMeasure : public FontMeasure { + public: + // typedef struct { + // /** What (real character) index the line starts at */ + // int32_t start; + // /** How many (real) characters the line is in length */ + // int32_t length; + // } truetypefontinfoline_t; + + // typedef struct { + // /** How many raw chars are in the string */ + // int32_t length; + + // /** How many real characters (non whitespace) are in the string */ + // int32_t realLength; + + // /** How many lines is the string? Trailing newlines will count */ + // int32_t lineCount; + + // /** The real character info for each line */ + // truetypefontinfoline_t lines[256]; + + // /** Dimensions of the string */ + // float_t width, height; + // } truetypefontinfo_t; + }; + + class TrueTypeFont : public Font { + protected: + /** + * Calculate the quad information for a given character. + * + * @param font Font to get the character from. + * @param quad Pointer to the quad to store the quad information in. + * @param x Pointer to the X position for the quad. + * @param y Pointer to the Y position for the quad. + * @param c Character to get the quad and position information for. + */ + void bakeQuad(truetypequad_t *quad, float_t *x, float_t *y, char c); + + /** + * Returns the font scale to use for rendering your desired font size at a + * font size that is different than the precompiled font size for this + * true type font. For example, let's say you render your font size at 36 + * when you are precompiling it, but rather than creating a new font just + * to add a size 24, you can instead use this method to get the scale to + * use to downscale your font to match. + * + * @param font TrueType font to get the scale of. + * @param fontSize Font size you desire. + * @return The scale used to get the font size that will match. + */ + float_t getScale(float_t fontSize); + + /** + * Returns the size of a space character for a given font. + * + * @param fontSize Font size of the font to get the space size for. + * @return The size of the space character. + */ + float_t getSpaceSize(float_t fontSize); + + /** + * Returns the initial line height of a font. + * + * @param fontSize Font size for the font to get the line height of. + * @return The line height initial value. + */ + float_t getInitialLineHeight(float_t fontSize); + + public: + Texture texture; + float_t fontSize; + truetypechar_t characterData[TRUETYPE_NUM_CHARS]; + + void init() override; + + void buffer( + std::string text, + float_t fontSize, + float_t maxWidth, + Mesh &mesh, + FontMeasure &measure + ) override; + + Texture & getTexture() override; + void draw(Mesh &mesh, int32_t startCharacter, int32_t length) override; + float_t getLineHeight(float_t fontSize) override; + float_t getDefaultFontSize() override; + }; +} \ No newline at end of file diff --git a/src/dawn/util/mathutils.hpp b/src/dawn/util/mathutils.hpp new file mode 100644 index 00000000..639d1103 --- /dev/null +++ b/src/dawn/util/mathutils.hpp @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dawnlibs.hpp" + +#define MATH_PI 3.1415926535897f + +/** + * Returns the largest of the two provided int32 numbers. + * + * @param left Left number to get the largest of. + * @param right Right number to get the largest of. + * @return The larger of the two numbers + */ +template +static inline T mathMax(T left, T right) { + return left < right ? right : left; +} + +/** + * Returns the smallest of two provided int32 numbers. + * + * @param left Left number to get the smallest of. + * @param right Right number to get the smallest of. + * @return Smaller of the two numbers. + */ +template +static inline T mathMin(T left, T right) { + return left < right ? left : right; +} + +/** + * Returns the input value, constrained between the min and max values, so that + * the value cannot underceed the min, and cannot exceed the max. + * + * @param val Value to get the clamp for. + * @param min Minimum clamping value. + * @param max Maximum clamping value. + * @return The value, or the closest clamped value. + */ +template +static inline T mathClamp(T val, T min, T max) { + return mathMin(mathMax(val, min), max); +} + +/** + * Returns the absolute value (the non-negative representation of) for the given + * int32 number.Abs values will be -value if value < 0. + * + * @param value Value to get the absolute value for. + * @return The absolute value (-value if value < 0) + */ +template +static inline T mathAbs(T value) { + return value < 0 ? -value : value; +} + +template +static inline int32_t mathMod(T value, T modulo) { + return ((value % modulo) + modulo) % modulo; +} + +// Float-specific + +/** + * Convert degrees to radians. + * + * @param n Degrees to convert. + * @returns The number in radians. + */ +static inline float_t mathDeg2Rad(float_t degrees) { + return degrees * (MATH_PI / 180.0f); +} + +/** + * Convert radians to degrees. + * @param n Radians to convert. + * @returns The number in degrees. + */ +static inline float_t mathRad2Deg(float_t n) { + return (n * 180.0f) / MATH_PI; +} + +/** + * Round a number to the nearest whole number. + * @param n Number to round. + * @return Rounded number. + */ +static inline float_t mathRoundFloat(float_t n) { + return roundf(n); +} + +/** + * Rounds the number down to the nearest whole number. + * @param n Number to round down. + * @return Rounded number. + */ +static inline float_t mathFloorFloat(float_t n) { + return floorf(n); +} \ No newline at end of file