| 1 | /** | 
|---|
| 2 | * Copyright (c) 2006-2023 LOVE Development Team | 
|---|
| 3 | * | 
|---|
| 4 | * This software is provided 'as-is', without any express or implied | 
|---|
| 5 | * warranty.  In no event will the authors be held liable for any damages | 
|---|
| 6 | * arising from the use of this software. | 
|---|
| 7 | * | 
|---|
| 8 | * Permission is granted to anyone to use this software for any purpose, | 
|---|
| 9 | * including commercial applications, and to alter it and redistribute it | 
|---|
| 10 | * freely, subject to the following restrictions: | 
|---|
| 11 | * | 
|---|
| 12 | * 1. The origin of this software must not be misrepresented; you must not | 
|---|
| 13 | *    claim that you wrote the original software. If you use this software | 
|---|
| 14 | *    in a product, an acknowledgment in the product documentation would be | 
|---|
| 15 | *    appreciated but is not required. | 
|---|
| 16 | * 2. Altered source versions must be plainly marked as such, and must not be | 
|---|
| 17 | *    misrepresented as being the original software. | 
|---|
| 18 | * 3. This notice may not be removed or altered from any source distribution. | 
|---|
| 19 | **/ | 
|---|
| 20 |  | 
|---|
| 21 | #include "Text.h" | 
|---|
| 22 | #include "Graphics.h" | 
|---|
| 23 |  | 
|---|
| 24 | #include <algorithm> | 
|---|
| 25 |  | 
|---|
| 26 | namespace love | 
|---|
| 27 | { | 
|---|
| 28 | namespace graphics | 
|---|
| 29 | { | 
|---|
| 30 |  | 
|---|
| 31 | love::Type Text::type( "Text", &Drawable::type); | 
|---|
| 32 |  | 
|---|
| 33 | Text::Text(Font *font, const std::vector<Font::ColoredString> &text) | 
|---|
| 34 | : font(font) | 
|---|
| 35 | , vertexAttributes(Font::vertexFormat, 0) | 
|---|
| 36 | , vertex_buffer(nullptr) | 
|---|
| 37 | , vert_offset(0) | 
|---|
| 38 | , texture_cache_id((uint32) -1) | 
|---|
| 39 | { | 
|---|
| 40 | set(text); | 
|---|
| 41 | } | 
|---|
| 42 |  | 
|---|
| 43 | Text::~Text() | 
|---|
| 44 | { | 
|---|
| 45 | delete vertex_buffer; | 
|---|
| 46 | } | 
|---|
| 47 |  | 
|---|
| 48 | void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset) | 
|---|
| 49 | { | 
|---|
| 50 | size_t offset = vertoffset * sizeof(Font::GlyphVertex); | 
|---|
| 51 | size_t datasize = vertices.size() * sizeof(Font::GlyphVertex); | 
|---|
| 52 |  | 
|---|
| 53 | // If we haven't created a VBO or the vertices are too big, make a new one. | 
|---|
| 54 | if (datasize > 0 && (!vertex_buffer || (offset + datasize) > vertex_buffer->getSize())) | 
|---|
| 55 | { | 
|---|
| 56 | // Make it bigger than necessary to reduce potential future allocations. | 
|---|
| 57 | size_t newsize = size_t((offset + datasize) * 1.5); | 
|---|
| 58 |  | 
|---|
| 59 | if (vertex_buffer != nullptr) | 
|---|
| 60 | newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize); | 
|---|
| 61 |  | 
|---|
| 62 | auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS); | 
|---|
| 63 | Buffer *new_buffer = gfx->newBuffer(newsize, nullptr, BUFFER_VERTEX, vertex::USAGE_DYNAMIC, 0); | 
|---|
| 64 |  | 
|---|
| 65 | if (vertex_buffer != nullptr) | 
|---|
| 66 | vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0); | 
|---|
| 67 |  | 
|---|
| 68 | delete vertex_buffer; | 
|---|
| 69 | vertex_buffer = new_buffer; | 
|---|
| 70 |  | 
|---|
| 71 | vertexBuffers.set(0, vertex_buffer, 0); | 
|---|
| 72 | } | 
|---|
| 73 |  | 
|---|
| 74 | if (vertex_buffer != nullptr && datasize > 0) | 
|---|
| 75 | { | 
|---|
| 76 | uint8 *bufferdata = (uint8 *) vertex_buffer->map(); | 
|---|
| 77 | memcpy(bufferdata + offset, &vertices[0], datasize); | 
|---|
| 78 | // We unmap when we draw, to avoid unnecessary full map()/unmap() calls. | 
|---|
| 79 | } | 
|---|
| 80 | } | 
|---|
| 81 |  | 
|---|
| 82 | void Text::regenerateVertices() | 
|---|
| 83 | { | 
|---|
| 84 | // If the font's texture cache was invalidated then we need to recreate the | 
|---|
| 85 | // text's vertices, since glyph texcoords might have changed. | 
|---|
| 86 | if (font->getTextureCacheID() != texture_cache_id) | 
|---|
| 87 | { | 
|---|
| 88 | std::vector<TextData> textdata = text_data; | 
|---|
| 89 |  | 
|---|
| 90 | clear(); | 
|---|
| 91 |  | 
|---|
| 92 | for (const TextData &t : textdata) | 
|---|
| 93 | addTextData(t); | 
|---|
| 94 |  | 
|---|
| 95 | texture_cache_id = font->getTextureCacheID(); | 
|---|
| 96 | } | 
|---|
| 97 | } | 
|---|
| 98 |  | 
|---|
| 99 | void Text::addTextData(const TextData &t) | 
|---|
| 100 | { | 
|---|
| 101 | std::vector<Font::GlyphVertex> vertices; | 
|---|
| 102 | std::vector<Font::DrawCommand> new_commands; | 
|---|
| 103 |  | 
|---|
| 104 | Font::TextInfo text_info; | 
|---|
| 105 |  | 
|---|
| 106 | Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f); | 
|---|
| 107 |  | 
|---|
| 108 | // We only have formatted text if the align mode is valid. | 
|---|
| 109 | if (t.align == Font::ALIGN_MAX_ENUM) | 
|---|
| 110 | new_commands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &text_info); | 
|---|
| 111 | else | 
|---|
| 112 | new_commands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &text_info); | 
|---|
| 113 |  | 
|---|
| 114 | size_t voffset = vert_offset; | 
|---|
| 115 |  | 
|---|
| 116 | if (!t.append_vertices) | 
|---|
| 117 | { | 
|---|
| 118 | voffset = 0; | 
|---|
| 119 | vert_offset = 0; | 
|---|
| 120 | draw_commands.clear(); | 
|---|
| 121 | text_data.clear(); | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | if (t.use_matrix && !vertices.empty()) | 
|---|
| 125 | t.matrix.transformXY(vertices.data(), vertices.data(), (int) vertices.size()); | 
|---|
| 126 |  | 
|---|
| 127 | uploadVertices(vertices, voffset); | 
|---|
| 128 |  | 
|---|
| 129 | if (!new_commands.empty()) | 
|---|
| 130 | { | 
|---|
| 131 | // The start vertex should be adjusted to account for the vertex offset. | 
|---|
| 132 | for (Font::DrawCommand &cmd : new_commands) | 
|---|
| 133 | cmd.startvertex += (int) voffset; | 
|---|
| 134 |  | 
|---|
| 135 | auto firstcmd = new_commands.begin(); | 
|---|
| 136 |  | 
|---|
| 137 | // If the first draw command in the new list has the same texture as the | 
|---|
| 138 | // last one in the existing list we're building and its vertices are | 
|---|
| 139 | // in-order, we can combine them (saving a draw call.) | 
|---|
| 140 | if (!draw_commands.empty()) | 
|---|
| 141 | { | 
|---|
| 142 | auto prevcmd = draw_commands.back(); | 
|---|
| 143 | if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex) | 
|---|
| 144 | { | 
|---|
| 145 | draw_commands.back().vertexcount += firstcmd->vertexcount; | 
|---|
| 146 | ++firstcmd; | 
|---|
| 147 | } | 
|---|
| 148 | } | 
|---|
| 149 |  | 
|---|
| 150 | // Append the new draw commands to the list we're building. | 
|---|
| 151 | draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end()); | 
|---|
| 152 | } | 
|---|
| 153 |  | 
|---|
| 154 | vert_offset = voffset + vertices.size(); | 
|---|
| 155 |  | 
|---|
| 156 | text_data.push_back(t); | 
|---|
| 157 | text_data.back().text_info = text_info; | 
|---|
| 158 |  | 
|---|
| 159 | // Font::generateVertices can invalidate the font's texture cache. | 
|---|
| 160 | if (font->getTextureCacheID() != texture_cache_id) | 
|---|
| 161 | regenerateVertices(); | 
|---|
| 162 | } | 
|---|
| 163 |  | 
|---|
| 164 | void Text::set(const std::vector<Font::ColoredString> &text) | 
|---|
| 165 | { | 
|---|
| 166 | return set(text, -1.0f, Font::ALIGN_MAX_ENUM); | 
|---|
| 167 | } | 
|---|
| 168 |  | 
|---|
| 169 | void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align) | 
|---|
| 170 | { | 
|---|
| 171 | if (text.empty() || (text.size() == 1 && text[0].str.empty())) | 
|---|
| 172 | return clear(); | 
|---|
| 173 |  | 
|---|
| 174 | Font::ColoredCodepoints codepoints; | 
|---|
| 175 | Font::getCodepointsFromString(text, codepoints); | 
|---|
| 176 |  | 
|---|
| 177 | addTextData({codepoints, wrap, align, {}, false, false, Matrix4()}); | 
|---|
| 178 | } | 
|---|
| 179 |  | 
|---|
| 180 | int Text::add(const std::vector<Font::ColoredString> &text, const Matrix4 &m) | 
|---|
| 181 | { | 
|---|
| 182 | return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m); | 
|---|
| 183 | } | 
|---|
| 184 |  | 
|---|
| 185 | int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m) | 
|---|
| 186 | { | 
|---|
| 187 | Font::ColoredCodepoints codepoints; | 
|---|
| 188 | Font::getCodepointsFromString(text, codepoints); | 
|---|
| 189 |  | 
|---|
| 190 | addTextData({codepoints, wrap, align, {}, true, true, m}); | 
|---|
| 191 |  | 
|---|
| 192 | return (int) text_data.size() - 1; | 
|---|
| 193 | } | 
|---|
| 194 |  | 
|---|
| 195 | void Text::clear() | 
|---|
| 196 | { | 
|---|
| 197 | text_data.clear(); | 
|---|
| 198 | draw_commands.clear(); | 
|---|
| 199 | texture_cache_id = font->getTextureCacheID(); | 
|---|
| 200 | vert_offset = 0; | 
|---|
| 201 | } | 
|---|
| 202 |  | 
|---|
| 203 | void Text::setFont(Font *f) | 
|---|
| 204 | { | 
|---|
| 205 | font.set(f); | 
|---|
| 206 |  | 
|---|
| 207 | // Invalidate the texture cache ID since the font is different. We also have | 
|---|
| 208 | // to re-upload all the vertices based on the new font's textures. | 
|---|
| 209 | texture_cache_id = (uint32) -1; | 
|---|
| 210 | regenerateVertices(); | 
|---|
| 211 | } | 
|---|
| 212 |  | 
|---|
| 213 | Font *Text::getFont() const | 
|---|
| 214 | { | 
|---|
| 215 | return font.get(); | 
|---|
| 216 | } | 
|---|
| 217 |  | 
|---|
| 218 | int Text::getWidth(int index) const | 
|---|
| 219 | { | 
|---|
| 220 | if (index < 0) | 
|---|
| 221 | index = std::max((int) text_data.size() - 1, 0); | 
|---|
| 222 |  | 
|---|
| 223 | if (index >= (int) text_data.size()) | 
|---|
| 224 | return 0; | 
|---|
| 225 |  | 
|---|
| 226 | return text_data[index].text_info.width; | 
|---|
| 227 | } | 
|---|
| 228 |  | 
|---|
| 229 | int Text::getHeight(int index) const | 
|---|
| 230 | { | 
|---|
| 231 | if (index < 0) | 
|---|
| 232 | index = std::max((int) text_data.size() - 1, 0); | 
|---|
| 233 |  | 
|---|
| 234 | if (index >= (int) text_data.size()) | 
|---|
| 235 | return 0; | 
|---|
| 236 |  | 
|---|
| 237 | return text_data[index].text_info.height; | 
|---|
| 238 | } | 
|---|
| 239 |  | 
|---|
| 240 | void Text::draw(Graphics *gfx, const Matrix4 &m) | 
|---|
| 241 | { | 
|---|
| 242 | if (vertex_buffer == nullptr || draw_commands.empty()) | 
|---|
| 243 | return; | 
|---|
| 244 |  | 
|---|
| 245 | gfx->flushStreamDraws(); | 
|---|
| 246 |  | 
|---|
| 247 | if (Shader::isDefaultActive()) | 
|---|
| 248 | Shader::attachDefault(Shader::STANDARD_DEFAULT); | 
|---|
| 249 |  | 
|---|
| 250 | if (Shader::current) | 
|---|
| 251 | Shader::current->checkMainTextureType(TEXTURE_2D, false); | 
|---|
| 252 |  | 
|---|
| 253 | // Re-generate the text if the Font's texture cache was invalidated. | 
|---|
| 254 | if (font->getTextureCacheID() != texture_cache_id) | 
|---|
| 255 | regenerateVertices(); | 
|---|
| 256 |  | 
|---|
| 257 | int totalverts = 0; | 
|---|
| 258 | for (const Font::DrawCommand &cmd : draw_commands) | 
|---|
| 259 | totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts); | 
|---|
| 260 |  | 
|---|
| 261 | vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU. | 
|---|
| 262 |  | 
|---|
| 263 | Graphics::TempTransform transform(gfx, m); | 
|---|
| 264 |  | 
|---|
| 265 | for (const Font::DrawCommand &cmd : draw_commands) | 
|---|
| 266 | gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture); | 
|---|
| 267 | } | 
|---|
| 268 |  | 
|---|
| 269 | } // graphics | 
|---|
| 270 | } // love | 
|---|
| 271 |  | 
|---|