// Copyright (c) 2022 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #include "TrueTypeFont.hpp" #ifndef STB_TRUETYPE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION #include #endif using namespace Dawn; void TrueTypeFont::bakeQuad(truetypequad_t *quad,float_t *x,float_t *y,char c){ assertNotNull(quad); assertNotNull(x); assertNotNull(y); assertTrue(c >= TRUETYPE_FIRST_CHAR); assertTrue(c < (TRUETYPE_FIRST_CHAR+TRUETYPE_NUM_CHARS)); 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) { assertTrue(scale > 0); return scale / this->fontSize; } float_t TrueTypeFont::getSpaceSize(float_t fontSize) { assertTrue(fontSize > 0); return this->getScale(fontSize) * 48; } float_t TrueTypeFont::getInitialLineHeight(float_t fontSize) { assertTrue(fontSize > 0); return 42.0f * this->getScale(fontSize); } float_t TrueTypeFont::getLineHeight(float_t fontSize) { assertTrue(fontSize > 0); return 128.0f * this->getScale(fontSize); } void TrueTypeFont::buffer( std::string text, float_t fontSize, float_t maxWidth, Mesh *mesh, struct FontMeasure *info ) { assertNotNull(mesh); assertNotNull(info); assertTrue(fontSize > 0); assertTrue(maxWidth == -1 || maxWidth > 0); assertTrue(this->isReady()); auto stringLength = text.length(); if(stringLength == 0) { info->length = 0; info->realLength = 0; info->lines.clear(); info->height = 0; info->width = 0; info->height = 0.0f; info->lineHeight = 0.0f; mesh->createBuffers(0, 0); return; } auto quads = new truetypequad_t[stringLength]; assertNotNull(quads); // Get the font scale auto scale = this->getScale(fontSize); assertTrue(scale > 0); // 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->lines.clear(); info->lineHeight = this->getLineHeight(fontSize) * scale; // Prepare the line counters info->addLine(0, 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 = 0; // Bake the first quad. this->bakeQuad(quads, &x, &y, 'D'); y = -(quads->y0); x = 0; // y = this->getInitialLineHeight(fontSize) / scale; 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) / scale; // Did this space cause a newline? if(x > maxWidth) { info->addLine(info->realLength, 0); y += this->getLineHeight(fontSize) / scale; x = 0; } wordX = x; wordStart = info->realLength; continue; } // New line. if(c == FONT_NEWLINE) { info->addLine(info->realLength, 0); wordStart = info->realLength; y += this->getLineHeight(fontSize) / scale; 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) / scale; quad->y1 += this->getLineHeight(fontSize) / scale; } // Go back to the previous (still current) line and remove the chars info->lines[info->lines.size() - 1].length -= info->realLength - wordStart; // Next line begins with this word y += this->getLineHeight(fontSize) / scale; info->addLine(wordStart, info->realLength-wordStart); wordX = 0; } info->lines[info->lines.size() - 1].length++; info->realLength++; } // 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; } bool_t TrueTypeFont::isReady() { return this->texture.isReady(); } Texture * TrueTypeFont::getTexture() { return &this->texture; } void TrueTypeFont::draw(Mesh *mesh, int32_t startchar, int32_t length) { assertNotNull(mesh); mesh->draw( MESH_DRAW_MODE_TRIANGLES, startchar * QUAD_INDICE_COUNT, length == -1 ? length : length * QUAD_INDICE_COUNT ); } float_t TrueTypeFont::getDefaultFontSize() { return (float_t)this->fontSize; }