diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5662b85..b8d6b9c5d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -468,6 +468,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/Color.h src/modules/graphics/Drawable.cpp src/modules/graphics/Drawable.h + src/modules/graphics/Font.cpp + src/modules/graphics/Font.h src/modules/graphics/Graphics.cpp src/modules/graphics/Graphics.h src/modules/graphics/ParticleSystem.cpp @@ -484,6 +486,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/vertex.h src/modules/graphics/Volatile.cpp src/modules/graphics/Volatile.h + src/modules/graphics/wrap_Font.cpp + src/modules/graphics/wrap_Font.h src/modules/graphics/wrap_Quad.cpp src/modules/graphics/wrap_Quad.h src/modules/graphics/wrap_Texture.cpp @@ -517,8 +521,6 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/Video.h src/modules/graphics/opengl/wrap_Canvas.cpp src/modules/graphics/opengl/wrap_Canvas.h - src/modules/graphics/opengl/wrap_Font.cpp - src/modules/graphics/opengl/wrap_Font.h src/modules/graphics/opengl/wrap_Graphics.cpp src/modules/graphics/opengl/wrap_Graphics.h src/modules/graphics/opengl/wrap_Image.cpp diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index b0f6766a5..b8de5df40 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -459,9 +459,6 @@ FA0B7D581A95902C000E1D17 /* wrap_Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BA61A95902C000E1D17 /* wrap_Canvas.cpp */; }; FA0B7D591A95902C000E1D17 /* wrap_Canvas.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BA61A95902C000E1D17 /* wrap_Canvas.cpp */; }; FA0B7D5A1A95902C000E1D17 /* wrap_Canvas.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BA71A95902C000E1D17 /* wrap_Canvas.h */; }; - FA0B7D5B1A95902C000E1D17 /* wrap_Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BA81A95902C000E1D17 /* wrap_Font.cpp */; }; - FA0B7D5C1A95902C000E1D17 /* wrap_Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BA81A95902C000E1D17 /* wrap_Font.cpp */; }; - FA0B7D5D1A95902C000E1D17 /* wrap_Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BA91A95902C000E1D17 /* wrap_Font.h */; }; FA0B7D5E1A95902C000E1D17 /* wrap_Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BAA1A95902C000E1D17 /* wrap_Graphics.cpp */; }; FA0B7D5F1A95902C000E1D17 /* wrap_Graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0B7BAA1A95902C000E1D17 /* wrap_Graphics.cpp */; }; FA0B7D601A95902C000E1D17 /* wrap_Graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0B7BAB1A95902C000E1D17 /* wrap_Graphics.h */; }; @@ -848,6 +845,12 @@ FA19C4C51B4B0BD50059B0B3 /* wrap_Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA19C4C21B4B0BD50059B0B3 /* wrap_Video.cpp */; }; FA19C4C61B4B0BD50059B0B3 /* wrap_Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA19C4C21B4B0BD50059B0B3 /* wrap_Video.cpp */; }; FA19C4C71B4B0BD50059B0B3 /* wrap_Video.h in Headers */ = {isa = PBXBuildFile; fileRef = FA19C4C31B4B0BD50059B0B3 /* wrap_Video.h */; }; + FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA09B1E16CFCE00AA2803 /* Font.cpp */; }; + FA1BA09E1E16CFCE00AA2803 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA09B1E16CFCE00AA2803 /* Font.cpp */; }; + FA1BA09F1E16CFCE00AA2803 /* Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA09C1E16CFCE00AA2803 /* Font.h */; }; + FA1BA0A21E16D97500AA2803 /* wrap_Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */; }; + FA1BA0A31E16D97500AA2803 /* wrap_Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */; }; + FA1BA0A41E16D97500AA2803 /* wrap_Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA0A11E16D97500AA2803 /* wrap_Font.h */; }; FA1DC2631C5D9555008F99A0 /* HashFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1DC2611C5D9555008F99A0 /* HashFunction.cpp */; }; FA1DC2641C5D9555008F99A0 /* HashFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1DC2621C5D9555008F99A0 /* HashFunction.h */; }; FA1E887E1DF363CD00E808AA /* Filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1E887C1DF363CD00E808AA /* Filter.cpp */; }; @@ -1343,8 +1346,6 @@ FA0B7BA51A95902C000E1D17 /* GLBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLBuffer.h; sourceTree = ""; }; FA0B7BA61A95902C000E1D17 /* wrap_Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Canvas.cpp; sourceTree = ""; }; FA0B7BA71A95902C000E1D17 /* wrap_Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Canvas.h; sourceTree = ""; }; - FA0B7BA81A95902C000E1D17 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = ""; }; - FA0B7BA91A95902C000E1D17 /* wrap_Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Font.h; sourceTree = ""; }; FA0B7BAA1A95902C000E1D17 /* wrap_Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Graphics.cpp; sourceTree = ""; }; FA0B7BAB1A95902C000E1D17 /* wrap_Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Graphics.h; sourceTree = ""; }; FA0B7BAC1A95902C000E1D17 /* wrap_Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Image.cpp; sourceTree = ""; }; @@ -1608,6 +1609,10 @@ FA19C4C21B4B0BD50059B0B3 /* wrap_Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Video.cpp; sourceTree = ""; }; FA19C4C31B4B0BD50059B0B3 /* wrap_Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Video.h; sourceTree = ""; }; FA19C4C41B4B0BD50059B0B3 /* wrap_Video.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Video.lua; sourceTree = ""; }; + FA1BA09B1E16CFCE00AA2803 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = ""; }; + FA1BA09C1E16CFCE00AA2803 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = ""; }; + FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = ""; }; + FA1BA0A11E16D97500AA2803 /* wrap_Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Font.h; sourceTree = ""; }; FA1DC2611C5D9555008F99A0 /* HashFunction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HashFunction.cpp; sourceTree = ""; }; FA1DC2621C5D9555008F99A0 /* HashFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HashFunction.h; sourceTree = ""; }; FA1E887C1DF363CD00E808AA /* Filter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Filter.cpp; sourceTree = ""; }; @@ -2432,6 +2437,8 @@ FA0B7B881A95902C000E1D17 /* Color.h */, FA9D8DDC1DEF842A002CD881 /* Drawable.cpp */, FA0B7B891A95902C000E1D17 /* Drawable.h */, + FA1BA09B1E16CFCE00AA2803 /* Font.cpp */, + FA1BA09C1E16CFCE00AA2803 /* Font.h */, FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */, FA0B7B8B1A95902C000E1D17 /* Graphics.h */, FA0B7B8C1A95902C000E1D17 /* opengl */, @@ -2449,6 +2456,8 @@ FA2AF6711DAC76FF0032B62C /* vertex.h */, FA0B7BC01A95902C000E1D17 /* Volatile.cpp */, FA0B7BC11A95902C000E1D17 /* Volatile.h */, + FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */, + FA1BA0A11E16D97500AA2803 /* wrap_Font.h */, FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */, FA620A2F1AA2F8DB005DB4C2 /* wrap_Quad.h */, FA620A301AA2F8DB005DB4C2 /* wrap_Texture.cpp */, @@ -2486,8 +2495,6 @@ FA27B3C41B4985D8008A9DCE /* Video.h */, FA0B7BA61A95902C000E1D17 /* wrap_Canvas.cpp */, FA0B7BA71A95902C000E1D17 /* wrap_Canvas.h */, - FA0B7BA81A95902C000E1D17 /* wrap_Font.cpp */, - FA0B7BA91A95902C000E1D17 /* wrap_Font.h */, FA0B7BAA1A95902C000E1D17 /* wrap_Graphics.cpp */, FA0B7BAB1A95902C000E1D17 /* wrap_Graphics.h */, FA6AE6041B3335EC00583D5C /* wrap_Graphics.lua */, @@ -3194,6 +3201,7 @@ FAB17BF71ABFC4B100F9BA27 /* lz4hc.h in Headers */, FA0B7E831A95902C000E1D17 /* Shape.h in Headers */, FAE272531C05A15B00A67640 /* ParticleSystem.h in Headers */, + FA1BA09F1E16CFCE00AA2803 /* Font.h in Headers */, FA0B7EDD1A95902D000E1D17 /* Touch.h in Headers */, FA0B7EDE1A95902D000E1D17 /* Touch.h in Headers */, FA0B7A541A958EA3000E1D17 /* b2Math.h in Headers */, @@ -3230,6 +3238,7 @@ FA0B7DF01A95902C000E1D17 /* Mouse.h in Headers */, 217DFBDC1D9F6D490055D849 /* buffer.h in Headers */, FA0B7DAD1A95902C000E1D17 /* STBHandler.h in Headers */, + FA1BA0A41E16D97500AA2803 /* wrap_Font.h in Headers */, FA0B7DE11A95902C000E1D17 /* wrap_Math.h in Headers */, FA0B7AAF1A958EA3000E1D17 /* b2WheelJoint.h in Headers */, FA0B7D1A1A95902C000E1D17 /* TrueTypeRasterizer.h in Headers */, @@ -3460,7 +3469,6 @@ FA0B793F1A958E3B000E1D17 /* types.h in Headers */, FA0B7AB31A958EA3000E1D17 /* b2Rope.h in Headers */, FA41A3CA1C0A1F950084430C /* ASTCHandler.h in Headers */, - FA0B7D5D1A95902C000E1D17 /* wrap_Font.h in Headers */, FA0B7ED31A95902C000E1D17 /* wrap_ThreadModule.h in Headers */, FA0B7A511A958EA3000E1D17 /* b2GrowableStack.h in Headers */, FA0B7D921A95902C000E1D17 /* FormatHandler.h in Headers */, @@ -3585,6 +3593,7 @@ FA0B7D221A95902C000E1D17 /* Rasterizer.cpp in Sources */, FA0B7A4F1A958EA3000E1D17 /* b2Draw.cpp in Sources */, FA0B7D7D1A95902C000E1D17 /* Texture.cpp in Sources */, + FA1BA09E1E16CFCE00AA2803 /* Font.cpp in Sources */, FA0B7ECC1A95902C000E1D17 /* wrap_Channel.cpp in Sources */, FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */, FA0B7A5F1A958EA3000E1D17 /* b2Body.cpp in Sources */, @@ -3634,6 +3643,7 @@ FA4F2BB51DE1E4C300CA37D7 /* wrap_RecordingDevice.cpp in Sources */, FA0B7A311A958EA3000E1D17 /* b2CollidePolygon.cpp in Sources */, FA4F2C111DE936FE00CA37D7 /* unix.c in Sources */, + FA1BA0A31E16D97500AA2803 /* wrap_Font.cpp in Sources */, FA0B7A931A958EA3000E1D17 /* b2GearJoint.cpp in Sources */, FA0B7E0D1A95902C000E1D17 /* Fixture.cpp in Sources */, FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */, @@ -3796,7 +3806,6 @@ FA0B7E341A95902C000E1D17 /* WeldJoint.cpp in Sources */, FA4F2C091DE936E200CA37D7 /* luasocket.c in Sources */, FA9D8DD21DEB56C3002CD881 /* pixelformat.cpp in Sources */, - FA0B7D5C1A95902C000E1D17 /* wrap_Font.cpp in Sources */, FA0B7B221A958EA3000E1D17 /* luasocket.cpp in Sources */, FA0B7D311A95902C000E1D17 /* Graphics.cpp in Sources */, FA0B7E9E1A95902C000E1D17 /* WaveDecoder.cpp in Sources */, @@ -3901,6 +3910,7 @@ FA0B7D211A95902C000E1D17 /* Rasterizer.cpp in Sources */, FA0B7D7C1A95902C000E1D17 /* Texture.cpp in Sources */, FA0B7ECB1A95902C000E1D17 /* wrap_Channel.cpp in Sources */, + FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */, FA0B7E6C1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */, FA0B7A5E1A958EA3000E1D17 /* b2Body.cpp in Sources */, FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */, @@ -3950,6 +3960,7 @@ FA0B7E0C1A95902C000E1D17 /* Fixture.cpp in Sources */, FA0B7D181A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */, FA0B7CFA1A95902C000E1D17 /* Filesystem.cpp in Sources */, + FA1BA0A21E16D97500AA2803 /* wrap_Font.cpp in Sources */, FA0B7D3C1A95902C000E1D17 /* Image.cpp in Sources */, FA0B7A8C1A958EA3000E1D17 /* b2DistanceJoint.cpp in Sources */, 217DFBE91D9F6D490055D849 /* io.c in Sources */, @@ -4104,7 +4115,6 @@ FA0B7E1B1A95902C000E1D17 /* MouseJoint.cpp in Sources */, FA0B7CF41A95902C000E1D17 /* File.cpp in Sources */, FA0B7E331A95902C000E1D17 /* WeldJoint.cpp in Sources */, - FA0B7D5B1A95902C000E1D17 /* wrap_Font.cpp in Sources */, FA0B7D301A95902C000E1D17 /* Graphics.cpp in Sources */, FA0B7E9D1A95902C000E1D17 /* WaveDecoder.cpp in Sources */, FA29C0051E12355B00268CD8 /* StreamBuffer.cpp in Sources */, diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp new file mode 100644 index 000000000..95b893044 --- /dev/null +++ b/src/modules/graphics/Font.cpp @@ -0,0 +1,919 @@ +/** +* Copyright (c) 2006-2016 LOVE Development Team +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +**/ +#include "common/config.h" +#include "Font.h" +#include "font/GlyphData.h" + +#include "libraries/utf8/utf8.h" + +#include "common/math.h" +#include "common/Matrix.h" +#include "Graphics.h" + +#include +#include +#include // for max +#include + +namespace love +{ +namespace graphics +{ + +static inline uint16 normToUint16(double n) +{ + return (uint16) (n * LOVE_UINT16_MAX); +} + +love::Type Font::type("Font", &Object::type); +int Font::fontCount = 0; + +Font::Font(love::font::Rasterizer *r, const Texture::Filter &f) + : rasterizers({r}) + , height(r->getHeight()) + , lineHeight(1) + , textureWidth(128) + , textureHeight(128) + , filter(f) + , pixelDensity(r->getPixelDensity()) + , useSpacesAsTab(false) + , textureCacheID(0) +{ + filter.mipmap = Texture::FILTER_NONE; + + // Try to find the best texture size match for the font size. default to the + // largest texture size if no rough match is found. + while (true) + { + if ((height * 0.8) * height * 30 <= textureWidth * textureHeight) + break; + + TextureSize nextsize = getNextTextureSize(); + + if (nextsize.width <= textureWidth && nextsize.height <= textureHeight) + break; + + textureWidth = nextsize.width; + textureHeight = nextsize.height; + } + + love::font::GlyphData *gd = r->getGlyphData(32); // Space character. + pixelFormat = gd->getFormat(); + gd->release(); + + if (!r->hasGlyph(9)) // No tab character in the Rasterizer. + useSpacesAsTab = true; + + ++fontCount; +} + +Font::~Font() +{ + --fontCount; +} + +Font::TextureSize Font::getNextTextureSize() const +{ + TextureSize size = {textureWidth, textureHeight}; + + int maxsize = 2048; + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr) + maxsize = (int) gfx->getSystemLimit(Graphics::LIMIT_TEXTURE_SIZE); + + int maxwidth = std::min(8192, maxsize); + int maxheight = std::min(4096, maxsize); + + if (size.width * 2 <= maxwidth || size.height * 2 <= maxheight) + { + // {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc. + if (size.width == size.height) + size.width *= 2; + else + size.height *= 2; + } + + return size; +} + +love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph) +{ + // Use spaces for the tab 'glyph'. + if (glyph == 9 && useSpacesAsTab) + { + love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32); + PixelFormat fmt = spacegd->getFormat(); + + love::font::GlyphMetrics gm = {}; + gm.advance = spacegd->getAdvance() * SPACES_PER_TAB; + gm.bearingX = spacegd->getBearingX(); + gm.bearingY = spacegd->getBearingY(); + + spacegd->release(); + + return new love::font::GlyphData(glyph, gm, fmt); + } + + for (const StrongRef &r : rasterizers) + { + if (r->hasGlyph(glyph)) + return r->getGlyphData(glyph); + } + + return rasterizers[0]->getGlyphData(glyph); +} + +const Font::Glyph &Font::addGlyph(uint32 glyph) +{ + StrongRef gd(getRasterizerGlyphData(glyph), Acquire::NORETAIN); + + int w = gd->getWidth(); + int h = gd->getHeight(); + + if (w + TEXTURE_PADDING * 2 < textureWidth && h + TEXTURE_PADDING * 2 < textureHeight) + { + if (textureX + w + TEXTURE_PADDING > textureWidth) + { + // Out of space - new row! + textureX = TEXTURE_PADDING; + textureY += rowHeight; + rowHeight = TEXTURE_PADDING; + } + + if (textureY + h + TEXTURE_PADDING > textureHeight) + { + // Totally out of space - new texture! + createTexture(); + + // Makes sure the above code for checking if the glyph can fit at + // the current position in the texture is run again for this glyph. + return addGlyph(glyph); + } + } + + Glyph g; + + g.texture = 0; + g.spacing = floorf(gd->getAdvance() / pixelDensity + 0.5f); + + memset(g.vertices, 0, sizeof(GlyphVertex) * 4); + + // Don't waste space for empty glyphs. + if (w > 0 && h > 0) + { + uploadGlyphToTexture(gd, g); + + double tX = (double) textureX, tY = (double) textureY; + double tWidth = (double) textureWidth, tHeight = (double) textureHeight; + + Color c(255, 255, 255, 255); + + // 0---2 + // | / | + // 1---3 + const GlyphVertex verts[4] = + { + {0.0f, 0.0f, normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c}, + {0.0f, h/pixelDensity, normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c}, + {w/pixelDensity, 0.0f, normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c}, + {w/pixelDensity, h/pixelDensity, normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c} + }; + + // Copy vertex data to the glyph and set proper bearing. + for (int i = 0; i < 4; i++) + { + g.vertices[i] = verts[i]; + g.vertices[i].x += gd->getBearingX() / pixelDensity; + g.vertices[i].y -= gd->getBearingY() / pixelDensity; + } + + textureX += w + TEXTURE_PADDING; + rowHeight = std::max(rowHeight, h + TEXTURE_PADDING); + } + + glyphs[glyph] = g; + return glyphs[glyph]; +} + +const Font::Glyph &Font::findGlyph(uint32 glyph) +{ + const auto it = glyphs.find(glyph); + + if (it != glyphs.end()) + return it->second; + + return addGlyph(glyph); +} + +float Font::getKerning(uint32 leftglyph, uint32 rightglyph) +{ + uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph; + + const auto it = kerning.find(packedglyphs); + if (it != kerning.end()) + return it->second; + + float k = rasterizers[0]->getKerning(leftglyph, rightglyph); + + for (const auto &r : rasterizers) + { + if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) + { + k = floorf(r->getKerning(leftglyph, rightglyph) / pixelDensity + 0.5f); + break; + } + } + + kerning[packedglyphs] = k; + return k; +} + +void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints) +{ + codepoints.reserve(text.size()); + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (i != end) + { + uint32 g = *i++; + codepoints.push_back(g); + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } +} + +void Font::getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) +{ + if (strs.empty()) + return; + + codepoints.cps.reserve(strs[0].str.size()); + + for (const ColoredString &cstr : strs) + { + // No need to add the color if the string is empty anyway, and the code + // further on assumes no two colors share the same starting position. + if (cstr.str.size() == 0) + continue; + + IndexedColor c = {cstr.color, (int) codepoints.cps.size()}; + codepoints.colors.push_back(c); + + getCodepointsFromString(cstr.str, codepoints.cps); + } + + if (codepoints.colors.size() == 1) + { + IndexedColor c = codepoints.colors[0]; + + if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f)) + codepoints.colors.pop_back(); + } +} + +float Font::getHeight() const +{ + return (float) floorf(height / pixelDensity + 0.5f); +} + +std::vector Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector offset, TextInfo *info) +{ + // Spacing counter and newline handling. + float dx = offset.x; + float dy = offset.y; + + float lineheight = getBaseline(); + int maxwidth = 0; + + // Keeps track of when we need to switch textures in our vertex array. + std::vector commands; + + // Pre-allocate space for the maximum possible number of vertices. + size_t vertstartsize = vertices.size(); + vertices.reserve(vertstartsize + codepoints.cps.size() * 4); + + uint32 prevglyph = 0; + + Colorf linearconstantcolor = gammaCorrectColor(constantcolor); + + Color curcolor = toColor(constantcolor); + int curcolori = -1; + int ncolors = (int) codepoints.colors.size(); + + for (int i = 0; i < (int) codepoints.cps.size(); i++) + { + uint32 g = codepoints.cps[i]; + + if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i) + { + Colorf color = gammaCorrectColor(codepoints.colors[++curcolori].color); + color *= linearconstantcolor; + unGammaCorrectColor(color); + + curcolor = toColor(color); + } + + if (g == '\n') + { + if (dx > maxwidth) + maxwidth = (int) dx; + + // Wrap newline, but do not print it. + dy += floorf(getHeight() * getLineHeight() + 0.5f); + dx = offset.x; + continue; + } + + // Ignore carriage returns + if (g == '\r') + continue; + + uint32 cacheid = textureCacheID; + + const Glyph &glyph = findGlyph(g); + + // If findGlyph invalidates the texture cache, re-start the loop. + if (cacheid != textureCacheID) + { + i = 0; + maxwidth = 0; + dx = offset.x; + dy = offset.y; + commands.clear(); + vertices.resize(vertstartsize); + prevglyph = 0; + curcolori = -1; + curcolor = toColor(constantcolor); + continue; + } + + // Add kerning to the current horizontal offset. + dx += getKerning(prevglyph, g); + + if (glyph.texture != 0) + { + // Copy the vertices and set their colors and relative positions. + for (int j = 0; j < 4; j++) + { + vertices.push_back(glyph.vertices[j]); + vertices.back().x += dx; + vertices.back().y += dy + lineheight; + vertices.back().color = curcolor; + } + + // Check if glyph texture has changed since the last iteration. + if (commands.empty() || commands.back().texture != glyph.texture) + { + // Add a new draw command if the texture has changed. + DrawCommand cmd; + cmd.startvertex = (int) vertices.size() - 4; + cmd.vertexcount = 0; + cmd.texture = glyph.texture; + commands.push_back(cmd); + } + + commands.back().vertexcount += 4; + } + + // Advance the x position for the next glyph. + dx += glyph.spacing; + + // Account for extra spacing given to space characters. + if (g == ' ' && extra_spacing != 0.0f) + dx = floorf(dx + extra_spacing); + + prevglyph = g; + } + + const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool + { + // Texture binds are expensive, so we should sort by that first. + if (a.texture != b.texture) + return a.texture < b.texture; + else + return a.startvertex < b.startvertex; + }; + + std::sort(commands.begin(), commands.end(), drawsort); + + if (dx > maxwidth) + maxwidth = (int) dx; + + if (info != nullptr) + { + info->width = maxwidth - offset.x; + info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y; + } + + return commands; +} + +std::vector Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, TextInfo *info) +{ + wrap = std::max(wrap, 0.0f); + + uint32 cacheid = textureCacheID; + + std::vector drawcommands; + vertices.reserve(text.cps.size() * 4); + + std::vector widths; + std::vector lines; + + getWrap(text, wrap, lines, &widths); + + float y = 0.0f; + float maxwidth = 0.0f; + + for (int i = 0; i < (int) lines.size(); i++) + { + const auto &line = lines[i]; + + float width = (float) widths[i]; + love::Vector offset(0.0f, floorf(y)); + float extraspacing = 0.0f; + + maxwidth = std::max(width, maxwidth); + + switch (align) + { + case ALIGN_RIGHT: + offset.x = floorf(wrap - width); + break; + case ALIGN_CENTER: + offset.x = floorf((wrap - width) / 2.0f); + break; + case ALIGN_JUSTIFY: + { + float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' '); + if (width < wrap && numspaces >= 1) + extraspacing = (wrap - width) / numspaces; + else + extraspacing = 0.0f; + break; + } + case ALIGN_LEFT: + default: + break; + } + + std::vector newcommands = generateVertices(line, constantcolor, vertices, extraspacing, offset); + + if (!newcommands.empty()) + { + auto firstcmd = newcommands.begin(); + + // If the first draw command in the new list has the same texture + // as the last one in the existing list we're building and its + // vertices are in-order, we can combine them (saving a draw call.) + if (!drawcommands.empty()) + { + auto prevcmd = drawcommands.back(); + if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex) + { + drawcommands.back().vertexcount += firstcmd->vertexcount; + ++firstcmd; + } + } + + // Append the new draw commands to the list we're building. + drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end()); + } + + y += getHeight() * getLineHeight(); + } + + if (info != nullptr) + { + info->width = (int) maxwidth; + info->height = (int) y; + } + + if (cacheid != textureCacheID) + { + vertices.clear(); + drawcommands = generateVerticesFormatted(text, constantcolor, wrap, align, vertices); + } + + return drawcommands; +} + +void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices) +{ + if (vertices.empty() || drawcommands.empty()) + return; + + Matrix4 m(gfx->getTransform(), t); + + for (const DrawCommand &cmd : drawcommands) + { + Graphics::StreamDrawRequest req; + req.formats[0] = vertex::CommonFormat::XYf_STus_RGBAub; + req.indexMode = vertex::TriangleIndexMode::QUADS; + req.vertexCount = cmd.vertexcount; + req.textureHandle = cmd.texture; + + Graphics::StreamVertexData data = gfx->requestStreamDraw(req); + GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0]; + + memcpy(vertexdata, &vertices[cmd.startvertex], sizeof(GlyphVertex) * cmd.vertexcount); + m.transform(vertexdata, &vertices[cmd.startvertex], cmd.vertexcount); + } +} + +void Font::print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) +{ + ColoredCodepoints codepoints; + getCodepointsFromString(text, codepoints); + + std::vector vertices; + std::vector drawcommands = generateVertices(codepoints, constantcolor, vertices); + + printv(gfx, m, drawcommands, vertices); +} + +void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) +{ + ColoredCodepoints codepoints; + getCodepointsFromString(text, codepoints); + + std::vector vertices; + std::vector drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices); + + printv(gfx, m, drawcommands, vertices); +} + +int Font::getWidth(const std::string &str) +{ + if (str.size() == 0) return 0; + + std::istringstream iss(str); + std::string line; + int max_width = 0; + + while (getline(iss, line, '\n')) + { + int width = 0; + uint32 prevglyph = 0; + try + { + utf8::iterator i(line.begin(), line.begin(), line.end()); + utf8::iterator end(line.end(), line.begin(), line.end()); + + while (i != end) + { + uint32 c = *i++; + + // Ignore carriage returns + if (c == '\r') + continue; + + const Glyph &g = findGlyph(c); + width += g.spacing + getKerning(prevglyph, c); + + prevglyph = c; + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + max_width = std::max(max_width, width); + } + + return max_width; +} + +int Font::getWidth(char character) +{ + const Glyph &g = findGlyph(character); + return g.spacing; +} + +void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *linewidths) +{ + // Per-line info. + float width = 0.0f; + float widthbeforelastspace = 0.0f; + float widthoftrailingspace = 0.0f; + uint32 prevglyph = 0; + + int lastspaceindex = -1; + + // Keeping the indexed colors "in sync" is a bit tricky, since we split + // things up and we might skip some glyphs but we don't want to skip any + // color which starts at those indices. + Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f); + bool addcurcolor = false; + int curcolori = -1; + int endcolori = (int) codepoints.colors.size() - 1; + + // A wrapped line of text. + ColoredCodepoints wline; + + int i = 0; + while (i < (int) codepoints.cps.size()) + { + uint32 c = codepoints.cps[i]; + + // Determine the current color before doing anything else, to make sure + // it's still applied to future glyphs even if this one is skipped. + if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i) + { + curcolor = codepoints.colors[curcolori + 1].color; + curcolori++; + addcurcolor = true; + } + + // Split text at newlines. + if (c == '\n') + { + lines.push_back(wline); + + // Ignore the width of any trailing spaces, for individual lines. + if (linewidths) + linewidths->push_back(width - widthoftrailingspace); + + // Make sure the new line keeps any color that was set previously. + addcurcolor = true; + + width = widthbeforelastspace = widthoftrailingspace = 0.0f; + prevglyph = 0; // Reset kerning information. + lastspaceindex = -1; + wline.cps.clear(); + wline.colors.clear(); + i++; + + continue; + } + + // Ignore carriage returns + if (c == '\r') + { + i++; + continue; + } + + const Glyph &g = findGlyph(c); + float charwidth = g.spacing + getKerning(prevglyph, c); + float newwidth = width + charwidth; + + // Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're + // processing a newline character, though. + if (c != ' ' && newwidth > wraplimit) + { + // If this is the first character in the line and it exceeds the + // limit, skip it completely. + if (wline.cps.empty()) + i++; + else if (lastspaceindex != -1) + { + // 'Rewind' to the last seen space, if the line has one. + // FIXME: This could be more efficient... + while (!wline.cps.empty() && wline.cps.back() != ' ') + wline.cps.pop_back(); + + while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size()) + wline.colors.pop_back(); + + // Also 'rewind' to the color that the last character is using. + for (int colori = curcolori; colori >= 0; colori--) + { + if (codepoints.colors[colori].index <= lastspaceindex) + { + curcolor = codepoints.colors[colori].color; + curcolori = colori; + break; + } + } + + // Ignore the width of trailing spaces in wrapped lines. + width = widthbeforelastspace; + + i = lastspaceindex; + i++; // Start the next line after the space. + } + + lines.push_back(wline); + + if (linewidths) + linewidths->push_back(width); + + addcurcolor = true; + + prevglyph = 0; + width = widthbeforelastspace = widthoftrailingspace = 0.0f; + wline.cps.clear(); + wline.colors.clear(); + lastspaceindex = -1; + + continue; + } + + if (prevglyph != ' ' && c == ' ') + widthbeforelastspace = width; + + width = newwidth; + prevglyph = c; + + if (addcurcolor) + { + wline.colors.push_back({curcolor, (int) wline.cps.size()}); + addcurcolor = false; + } + + wline.cps.push_back(c); + + // Keep track of the last seen space, so we can "rewind" to it when + // wrapping. + if (c == ' ') + { + lastspaceindex = i; + widthoftrailingspace += charwidth; + } + else if (c != '\n') + widthoftrailingspace = 0.0f; + + i++; + } + + // Push the last line. + if (!wline.cps.empty()) + { + lines.push_back(wline); + + // Ignore the width of any trailing spaces, for individual lines. + if (linewidths) + linewidths->push_back(width - widthoftrailingspace); + } +} + +void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) +{ + ColoredCodepoints cps; + getCodepointsFromString(text, cps); + + std::vector codepointlines; + getWrap(cps, wraplimit, codepointlines, linewidths); + + std::string line; + + for (const ColoredCodepoints &codepoints : codepointlines) + { + line.clear(); + line.reserve(codepoints.cps.size()); + + for (uint32 codepoint : codepoints.cps) + { + char character[5] = {'\0'}; + char *end = utf8::unchecked::append(codepoint, character); + line.append(character, end - character); + } + + lines.push_back(line); + } +} + +void Font::setLineHeight(float height) +{ + lineHeight = height; +} + +float Font::getLineHeight() const +{ + return lineHeight; +} + +const Texture::Filter &Font::getFilter() const +{ + return filter; +} + +int Font::getAscent() const +{ + return floorf(rasterizers[0]->getAscent() / pixelDensity + 0.5f); +} + +int Font::getDescent() const +{ + return floorf(rasterizers[0]->getDescent() / pixelDensity + 0.5f); +} + +float Font::getBaseline() const +{ + // 1.25 is magic line height for true type fonts + if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) + return floorf(getHeight() / 1.25f + 0.5f); + else + return 0.0f; +} + +bool Font::hasGlyph(uint32 glyph) const +{ + for (const StrongRef &r : rasterizers) + { + if (r->hasGlyph(glyph)) + return true; + } + + return false; +} + +bool Font::hasGlyphs(const std::string &text) const +{ + if (text.size() == 0) + return false; + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (i != end) + { + uint32 codepoint = *i++; + + if (!hasGlyph(codepoint)) + return false; + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return true; +} + +void Font::setFallbacks(const std::vector &fallbacks) +{ + for (const Font *f : fallbacks) + { + if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType()) + throw love::Exception("Font fallbacks must be of the same font type."); + } + + rasterizers.resize(1); + + // NOTE: this won't invalidate already-rasterized glyphs. + for (const Font *f : fallbacks) + rasterizers.push_back(f->rasterizers[0]); +} + +float Font::getPixelDensity() const +{ + return pixelDensity; +} + +uint32 Font::getTextureCacheID() const +{ + return textureCacheID; +} + +bool Font::getConstant(const char *in, AlignMode &out) +{ + return alignModes.find(in, out); +} + +bool Font::getConstant(AlignMode in, const char *&out) +{ + return alignModes.find(in, out); +} + +StringMap::Entry Font::alignModeEntries[] = +{ + { "left", ALIGN_LEFT }, + { "right", ALIGN_RIGHT }, + { "center", ALIGN_CENTER }, + { "justify", ALIGN_JUSTIFY }, +}; + +StringMap Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries)); + +} // graphics +} // love diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h new file mode 100644 index 000000000..f6c4c1f17 --- /dev/null +++ b/src/modules/graphics/Font.h @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2006-2016 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +**/ + +#pragma once + +// STD +#include +#include +#include +#include + +// LOVE +#include "common/config.h" +#include "common/Object.h" +#include "common/Matrix.h" +#include "common/Vector.h" + +#include "font/Rasterizer.h" +#include "Texture.h" +#include "Volatile.h" +#include "vertex.h" + +namespace love +{ +namespace graphics +{ + +class Graphics; + +class Font : public Object, public Volatile +{ +public: + + static love::Type type; + + typedef std::vector Codepoints; + typedef vertex::XYf_STus_RGBAub GlyphVertex; + + enum AlignMode + { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT, + ALIGN_JUSTIFY, + ALIGN_MAX_ENUM + }; + + struct ColoredString + { + std::string str; + Colorf color; + }; + + struct IndexedColor + { + Colorf color; + int index; + }; + + struct ColoredCodepoints + { + std::vector cps; + std::vector colors; + }; + + struct TextInfo + { + int width; + int height; + }; + + // Used to determine when to change textures in the generated vertex array. + struct DrawCommand + { + ptrdiff_t texture; + int startvertex; + int vertexcount; + }; + + Font(love::font::Rasterizer *r, const Texture::Filter &filter); + + virtual ~Font(); + + std::vector generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector &vertices, + float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr); + + std::vector generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, + std::vector &vertices, TextInfo *info = nullptr); + + static void getCodepointsFromString(const std::string &str, Codepoints &codepoints); + static void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); + + /** + * Draws the specified text. + **/ + void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); + void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); + + /** + * Returns the height of the font. + **/ + float getHeight() const; + + /** + * Returns the width of the passed string. + * + * @param str A string of text. + **/ + int getWidth(const std::string &str); + + /** + * Returns the width of the passed character. + * + * @param character A character. + **/ + int getWidth(char character); + + /** + * Returns the maximal width of a wrapped string + * and optionally the number of lines + * + * @param text The input text + * @param wraplimit The number of pixels to wrap at + * @param max_width Optional output of the maximum width + * Returns a vector with the lines. + **/ + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + + /** + * Sets the line height (which should be a number to multiply the font size by, + * example: line height = 1.2 and size = 12 means that rendered line height = 12*1.2) + * @param height The new line height. + **/ + void setLineHeight(float height); + + /** + * Returns the line height. + **/ + float getLineHeight() const; + + virtual void setFilter(const Texture::Filter &f) = 0; + const Texture::Filter &getFilter() const; + + // Extra font metrics + int getAscent() const; + int getDescent() const; + float getBaseline() const; + + bool hasGlyph(uint32 glyph) const; + bool hasGlyphs(const std::string &text) const; + + void setFallbacks(const std::vector &fallbacks); + + float getPixelDensity() const; + + uint32 getTextureCacheID() const; + + static bool getConstant(const char *in, AlignMode &out); + static bool getConstant(AlignMode in, const char *&out); + + static int fontCount; + +protected: + + struct Glyph + { + ptrdiff_t texture; + int spacing; + GlyphVertex vertices[4]; + }; + + struct TextureSize + { + int width; + int height; + }; + + virtual void createTexture() = 0; + virtual void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) = 0; + + TextureSize getNextTextureSize() const; + love::font::GlyphData *getRasterizerGlyphData(uint32 glyph); + const Glyph &addGlyph(uint32 glyph); + const Glyph &findGlyph(uint32 glyph); + float getKerning(uint32 leftglyph, uint32 rightglyph); + void printv(Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices); + + std::vector> rasterizers; + + int height; + float lineHeight; + + int textureWidth; + int textureHeight; + + // maps glyphs to glyph texture information + std::unordered_map glyphs; + + // map of left/right glyph pairs to horizontal kerning. + std::unordered_map kerning; + + PixelFormat pixelFormat; + + Texture::Filter filter; + + float pixelDensity; + + int textureX, textureY; + int rowHeight; + + bool useSpacesAsTab; + + // ID which is incremented when the texture cache is invalidated. + uint32 textureCacheID; + + static const int TEXTURE_PADDING = 1; + + // This will be used if the Rasterizer doesn't have a tab character itself. + static const int SPACES_PER_TAB = 4; + + static StringMap::Entry alignModeEntries[]; + static StringMap alignModes; + +}; // Font + +} // graphics +} // love diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 3ad19e728..71a287339 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -408,6 +408,10 @@ public: **/ void polygon(DrawMode mode, const float *coords, size_t count); + /** + * Gets the system-dependent numeric limit for the specified parameter. + **/ + virtual double getSystemLimit(SystemLimit limittype) const = 0; const Matrix4 &getTransform() const; const Matrix4 &getProjection() const; diff --git a/src/modules/graphics/opengl/Font.cpp b/src/modules/graphics/opengl/Font.cpp index f98d5ea5b..b5ea8d624 100644 --- a/src/modules/graphics/opengl/Font.cpp +++ b/src/modules/graphics/opengl/Font.cpp @@ -17,21 +17,11 @@ * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ -#include "common/config.h" + +// LOVE #include "Font.h" -#include "font/GlyphData.h" - -#include "libraries/utf8/utf8.h" - -#include "common/math.h" -#include "common/Matrix.h" #include "graphics/Graphics.h" -#include -#include -#include // for max -#include - namespace love { namespace graphics @@ -39,84 +29,19 @@ namespace graphics namespace opengl { -static inline uint16 normToUint16(double n) -{ - return (uint16) (n * LOVE_UINT16_MAX); -} - -love::Type Font::type("Font", &Object::type); -int Font::fontCount = 0; - Font::Font(love::font::Rasterizer *r, const Texture::Filter &f) - : rasterizers({r}) - , height(r->getHeight()) - , lineHeight(1) - , textureWidth(128) - , textureHeight(128) - , filter(f) - , pixelDensity(r->getPixelDensity()) - , useSpacesAsTab(false) - , quadIndices(20) // We make this bigger at draw-time, if needed. - , textureCacheID(0) + : love::graphics::Font(r, f) , textureMemorySize(0) { - filter.mipmap = Texture::FILTER_NONE; - - // Try to find the best texture size match for the font size. default to the - // largest texture size if no rough match is found. - while (true) - { - if ((height * 0.8) * height * 30 <= textureWidth * textureHeight) - break; - - TextureSize nextsize = getNextTextureSize(); - - if (nextsize.width <= textureWidth && nextsize.height <= textureHeight) - break; - - textureWidth = nextsize.width; - textureHeight = nextsize.height; - } - - love::font::GlyphData *gd = r->getGlyphData(32); // Space character. - pixelFormat = gd->getFormat(); - gd->release(); - - if (!r->hasGlyph(9)) // No tab character in the Rasterizer. - useSpacesAsTab = true; - loadVolatile(); - - ++fontCount; } Font::~Font() { unloadVolatile(); - - --fontCount; } -Font::TextureSize Font::getNextTextureSize() const -{ - TextureSize size = {textureWidth, textureHeight}; - - int maxwidth = std::min(8192, gl.getMaxTextureSize()); - int maxheight = std::min(4096, gl.getMaxTextureSize()); - - if (size.width * 2 <= maxwidth || size.height * 2 <= maxheight) - { - // {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc. - if (size.width == size.height) - size.width *= 2; - else - size.height *= 2; - } - - return size; -} - -bool Font::createTexture() +void Font::createTexture() { auto gfx = Module::getInstance(Module::M_GRAPHICS); gfx->flushStreamDraws(); @@ -202,711 +127,18 @@ bool Font::createTexture() } else textures.push_back(t); - - return true; } -love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph) +void Font::uploadGlyphToTexture(font::GlyphData *gd, Glyph &glyph) { - // Use spaces for the tab 'glyph'. - if (glyph == 9 && useSpacesAsTab) - { - love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32); - PixelFormat fmt = spacegd->getFormat(); + bool isSRGB = isGammaCorrect(); + OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, isSRGB); - love::font::GlyphMetrics gm = {}; - gm.advance = spacegd->getAdvance() * SPACES_PER_TAB; - gm.bearingX = spacegd->getBearingX(); - gm.bearingY = spacegd->getBearingY(); + glyph.texture = textures.back(); - spacegd->release(); - - return new love::font::GlyphData(glyph, gm, fmt); - } - - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - return r->getGlyphData(glyph); - } - - return rasterizers[0]->getGlyphData(glyph); -} - -const Font::Glyph &Font::addGlyph(uint32 glyph) -{ - StrongRef gd(getRasterizerGlyphData(glyph), Acquire::NORETAIN); - - int w = gd->getWidth(); - int h = gd->getHeight(); - - if (w + TEXTURE_PADDING * 2 < textureWidth && h + TEXTURE_PADDING * 2 < textureHeight) - { - if (textureX + w + TEXTURE_PADDING > textureWidth) - { - // Out of space - new row! - textureX = TEXTURE_PADDING; - textureY += rowHeight; - rowHeight = TEXTURE_PADDING; - } - - if (textureY + h + TEXTURE_PADDING > textureHeight) - { - // Totally out of space - new texture! - createTexture(); - - // Makes sure the above code for checking if the glyph can fit at - // the current position in the texture is run again for this glyph. - return addGlyph(glyph); - } - } - - Glyph g; - - g.texture = 0; - g.spacing = floorf(gd->getAdvance() / pixelDensity + 0.5f); - - memset(g.vertices, 0, sizeof(GlyphVertex) * 4); - - // Don't waste space for empty glyphs. - if (w > 0 && h > 0) - { - bool isSRGB = isGammaCorrect(); - OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, isSRGB); - - g.texture = textures.back(); - - gl.bindTextureToUnit(g.texture, 0, false); - glTexSubImage2D(GL_TEXTURE_2D, 0, textureX, textureY, w, h, - fmt.externalformat, fmt.type, gd->getData()); - - double tX = (double) textureX, tY = (double) textureY; - double tWidth = (double) textureWidth, tHeight = (double) textureHeight; - - Color c(255, 255, 255, 255); - - // 0---2 - // | / | - // 1---3 - const GlyphVertex verts[4] = - { - {0.0f, 0.0f, normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c}, - {0.0f, h/pixelDensity, normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c}, - {w/pixelDensity, 0.0f, normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c}, - {w/pixelDensity, h/pixelDensity, normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c} - }; - - // Copy vertex data to the glyph and set proper bearing. - for (int i = 0; i < 4; i++) - { - g.vertices[i] = verts[i]; - g.vertices[i].x += gd->getBearingX() / pixelDensity; - g.vertices[i].y -= gd->getBearingY() / pixelDensity; - } - - textureX += w + TEXTURE_PADDING; - rowHeight = std::max(rowHeight, h + TEXTURE_PADDING); - } - - glyphs[glyph] = g; - return glyphs[glyph]; -} - -const Font::Glyph &Font::findGlyph(uint32 glyph) -{ - const auto it = glyphs.find(glyph); - - if (it != glyphs.end()) - return it->second; - - return addGlyph(glyph); -} - -float Font::getKerning(uint32 leftglyph, uint32 rightglyph) -{ - uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph; - - const auto it = kerning.find(packedglyphs); - if (it != kerning.end()) - return it->second; - - float k = rasterizers[0]->getKerning(leftglyph, rightglyph); - - for (const auto &r : rasterizers) - { - if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) - { - k = floorf(r->getKerning(leftglyph, rightglyph) / pixelDensity + 0.5f); - break; - } - } - - kerning[packedglyphs] = k; - return k; -} - -void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints) -{ - codepoints.reserve(text.size()); - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); - - while (i != end) - { - uint32 g = *i++; - codepoints.push_back(g); - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } -} - -void Font::getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) -{ - if (strs.empty()) - return; - - codepoints.cps.reserve(strs[0].str.size()); - - for (const ColoredString &cstr : strs) - { - // No need to add the color if the string is empty anyway, and the code - // further on assumes no two colors share the same starting position. - if (cstr.str.size() == 0) - continue; - - IndexedColor c = {cstr.color, (int) codepoints.cps.size()}; - codepoints.colors.push_back(c); - - getCodepointsFromString(cstr.str, codepoints.cps); - } - - if (codepoints.colors.size() == 1) - { - IndexedColor c = codepoints.colors[0]; - - if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f)) - codepoints.colors.pop_back(); - } -} - -float Font::getHeight() const -{ - return (float) floorf(height / pixelDensity + 0.5f); -} - -std::vector Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector offset, TextInfo *info) -{ - // Spacing counter and newline handling. - float dx = offset.x; - float dy = offset.y; - - float lineheight = getBaseline(); - int maxwidth = 0; - - // Keeps track of when we need to switch textures in our vertex array. - std::vector commands; - - // Pre-allocate space for the maximum possible number of vertices. - size_t vertstartsize = vertices.size(); - vertices.reserve(vertstartsize + codepoints.cps.size() * 4); - - uint32 prevglyph = 0; - - Colorf linearconstantcolor = gammaCorrectColor(constantcolor); - - Color curcolor = toColor(constantcolor); - int curcolori = -1; - int ncolors = (int) codepoints.colors.size(); - - for (int i = 0; i < (int) codepoints.cps.size(); i++) - { - uint32 g = codepoints.cps[i]; - - if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i) - { - Colorf color = gammaCorrectColor(codepoints.colors[++curcolori].color); - color *= linearconstantcolor; - unGammaCorrectColor(color); - - curcolor = toColor(color); - } - - if (g == '\n') - { - if (dx > maxwidth) - maxwidth = (int) dx; - - // Wrap newline, but do not print it. - dy += floorf(getHeight() * getLineHeight() + 0.5f); - dx = offset.x; - continue; - } - - // Ignore carriage returns - if (g == '\r') - continue; - - uint32 cacheid = textureCacheID; - - const Glyph &glyph = findGlyph(g); - - // If findGlyph invalidates the texture cache, re-start the loop. - if (cacheid != textureCacheID) - { - i = 0; - maxwidth = 0; - dx = offset.x; - dy = offset.y; - commands.clear(); - vertices.resize(vertstartsize); - prevglyph = 0; - curcolori = -1; - curcolor = toColor(constantcolor); - continue; - } - - // Add kerning to the current horizontal offset. - dx += getKerning(prevglyph, g); - - if (glyph.texture != 0) - { - // Copy the vertices and set their colors and relative positions. - for (int j = 0; j < 4; j++) - { - vertices.push_back(glyph.vertices[j]); - vertices.back().x += dx; - vertices.back().y += dy + lineheight; - vertices.back().color = curcolor; - } - - // Check if glyph texture has changed since the last iteration. - if (commands.empty() || commands.back().texture != glyph.texture) - { - // Add a new draw command if the texture has changed. - DrawCommand cmd; - cmd.startvertex = (int) vertices.size() - 4; - cmd.vertexcount = 0; - cmd.texture = glyph.texture; - commands.push_back(cmd); - } - - commands.back().vertexcount += 4; - } - - // Advance the x position for the next glyph. - dx += glyph.spacing; - - // Account for extra spacing given to space characters. - if (g == ' ' && extra_spacing != 0.0f) - dx = floorf(dx + extra_spacing); - - prevglyph = g; - } - - const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool - { - // Texture binds are expensive, so we should sort by that first. - if (a.texture != b.texture) - return a.texture < b.texture; - else - return a.startvertex < b.startvertex; - }; - - std::sort(commands.begin(), commands.end(), drawsort); - - if (dx > maxwidth) - maxwidth = (int) dx; - - if (info != nullptr) - { - info->width = maxwidth - offset.x; - info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y; - } - - return commands; -} - -std::vector Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, TextInfo *info) -{ - wrap = std::max(wrap, 0.0f); - - uint32 cacheid = textureCacheID; - - std::vector drawcommands; - vertices.reserve(text.cps.size() * 4); - - std::vector widths; - std::vector lines; - - getWrap(text, wrap, lines, &widths); - - float y = 0.0f; - float maxwidth = 0.0f; - - for (int i = 0; i < (int) lines.size(); i++) - { - const auto &line = lines[i]; - - float width = (float) widths[i]; - love::Vector offset(0.0f, floorf(y)); - float extraspacing = 0.0f; - - maxwidth = std::max(width, maxwidth); - - switch (align) - { - case ALIGN_RIGHT: - offset.x = floorf(wrap - width); - break; - case ALIGN_CENTER: - offset.x = floorf((wrap - width) / 2.0f); - break; - case ALIGN_JUSTIFY: - { - float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' '); - if (width < wrap && numspaces >= 1) - extraspacing = (wrap - width) / numspaces; - else - extraspacing = 0.0f; - break; - } - case ALIGN_LEFT: - default: - break; - } - - std::vector newcommands = generateVertices(line, constantcolor, vertices, extraspacing, offset); - - if (!newcommands.empty()) - { - auto firstcmd = newcommands.begin(); - - // If the first draw command in the new list has the same texture - // as the last one in the existing list we're building and its - // vertices are in-order, we can combine them (saving a draw call.) - if (!drawcommands.empty()) - { - auto prevcmd = drawcommands.back(); - if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex) - { - drawcommands.back().vertexcount += firstcmd->vertexcount; - ++firstcmd; - } - } - - // Append the new draw commands to the list we're building. - drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end()); - } - - y += getHeight() * getLineHeight(); - } - - if (info != nullptr) - { - info->width = (int) maxwidth; - info->height = (int) y; - } - - if (cacheid != textureCacheID) - { - vertices.clear(); - drawcommands = generateVerticesFormatted(text, constantcolor, wrap, align, vertices); - } - - return drawcommands; -} - -void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices) -{ - if (vertices.empty() || drawcommands.empty()) - return; - - Matrix4 m(gfx->getTransform(), t); - - for (const DrawCommand &cmd : drawcommands) - { - Graphics::StreamDrawRequest req; - req.formats[0] = vertex::CommonFormat::XYf_STus_RGBAub; - req.indexMode = vertex::TriangleIndexMode::QUADS; - req.vertexCount = cmd.vertexcount; - req.textureHandle = cmd.texture; - - Graphics::StreamVertexData data = gfx->requestStreamDraw(req); - GlyphVertex *vertexdata = (GlyphVertex *) data.stream[0]; - - memcpy(vertexdata, &vertices[cmd.startvertex], sizeof(GlyphVertex) * cmd.vertexcount); - m.transform(vertexdata, &vertices[cmd.startvertex], cmd.vertexcount); - } -} - -void Font::print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) -{ - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); - - std::vector vertices; - std::vector drawcommands = generateVertices(codepoints, constantcolor, vertices); - - printv(gfx, m, drawcommands, vertices); -} - -void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) -{ - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); - - std::vector vertices; - std::vector drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices); - - printv(gfx, m, drawcommands, vertices); -} - -int Font::getWidth(const std::string &str) -{ - if (str.size() == 0) return 0; - - std::istringstream iss(str); - std::string line; - int max_width = 0; - - while (getline(iss, line, '\n')) - { - int width = 0; - uint32 prevglyph = 0; - try - { - utf8::iterator i(line.begin(), line.begin(), line.end()); - utf8::iterator end(line.end(), line.begin(), line.end()); - - while (i != end) - { - uint32 c = *i++; - - // Ignore carriage returns - if (c == '\r') - continue; - - const Glyph &g = findGlyph(c); - width += g.spacing + getKerning(prevglyph, c); - - prevglyph = c; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - max_width = std::max(max_width, width); - } - - return max_width; -} - -int Font::getWidth(char character) -{ - const Glyph &g = findGlyph(character); - return g.spacing; -} - -void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *linewidths) -{ - // Per-line info. - float width = 0.0f; - float widthbeforelastspace = 0.0f; - float widthoftrailingspace = 0.0f; - uint32 prevglyph = 0; - - int lastspaceindex = -1; - - // Keeping the indexed colors "in sync" is a bit tricky, since we split - // things up and we might skip some glyphs but we don't want to skip any - // color which starts at those indices. - Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f); - bool addcurcolor = false; - int curcolori = -1; - int endcolori = (int) codepoints.colors.size() - 1; - - // A wrapped line of text. - ColoredCodepoints wline; - - int i = 0; - while (i < (int) codepoints.cps.size()) - { - uint32 c = codepoints.cps[i]; - - // Determine the current color before doing anything else, to make sure - // it's still applied to future glyphs even if this one is skipped. - if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i) - { - curcolor = codepoints.colors[curcolori + 1].color; - curcolori++; - addcurcolor = true; - } - - // Split text at newlines. - if (c == '\n') - { - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); - - // Make sure the new line keeps any color that was set previously. - addcurcolor = true; - - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - prevglyph = 0; // Reset kerning information. - lastspaceindex = -1; - wline.cps.clear(); - wline.colors.clear(); - i++; - - continue; - } - - // Ignore carriage returns - if (c == '\r') - { - i++; - continue; - } - - const Glyph &g = findGlyph(c); - float charwidth = g.spacing + getKerning(prevglyph, c); - float newwidth = width + charwidth; - - // Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're - // processing a newline character, though. - if (c != ' ' && newwidth > wraplimit) - { - // If this is the first character in the line and it exceeds the - // limit, skip it completely. - if (wline.cps.empty()) - i++; - else if (lastspaceindex != -1) - { - // 'Rewind' to the last seen space, if the line has one. - // FIXME: This could be more efficient... - while (!wline.cps.empty() && wline.cps.back() != ' ') - wline.cps.pop_back(); - - while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size()) - wline.colors.pop_back(); - - // Also 'rewind' to the color that the last character is using. - for (int colori = curcolori; colori >= 0; colori--) - { - if (codepoints.colors[colori].index <= lastspaceindex) - { - curcolor = codepoints.colors[colori].color; - curcolori = colori; - break; - } - } - - // Ignore the width of trailing spaces in wrapped lines. - width = widthbeforelastspace; - - i = lastspaceindex; - i++; // Start the next line after the space. - } - - lines.push_back(wline); - - if (linewidths) - linewidths->push_back(width); - - addcurcolor = true; - - prevglyph = 0; - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - wline.cps.clear(); - wline.colors.clear(); - lastspaceindex = -1; - - continue; - } - - if (prevglyph != ' ' && c == ' ') - widthbeforelastspace = width; - - width = newwidth; - prevglyph = c; - - if (addcurcolor) - { - wline.colors.push_back({curcolor, (int) wline.cps.size()}); - addcurcolor = false; - } - - wline.cps.push_back(c); - - // Keep track of the last seen space, so we can "rewind" to it when - // wrapping. - if (c == ' ') - { - lastspaceindex = i; - widthoftrailingspace += charwidth; - } - else if (c != '\n') - widthoftrailingspace = 0.0f; - - i++; - } - - // Push the last line. - if (!wline.cps.empty()) - { - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); - } -} - -void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) -{ - ColoredCodepoints cps; - getCodepointsFromString(text, cps); - - std::vector codepointlines; - getWrap(cps, wraplimit, codepointlines, linewidths); - - std::string line; - - for (const ColoredCodepoints &codepoints : codepointlines) - { - line.clear(); - line.reserve(codepoints.cps.size()); - - for (uint32 codepoint : codepoints.cps) - { - char character[5] = {'\0'}; - char *end = utf8::unchecked::append(codepoint, character); - line.append(character, end - character); - } - - lines.push_back(line); - } -} - -void Font::setLineHeight(float height) -{ - lineHeight = height; -} - -float Font::getLineHeight() const -{ - return lineHeight; + gl.bindTextureToUnit(glyph.texture, 0, false); + glTexSubImage2D(GL_TEXTURE_2D, 0, textureX, textureY, gd->getWidth(), gd->getHeight(), + fmt.externalformat, fmt.type, gd->getData()); } void Font::setFilter(const Texture::Filter &f) @@ -923,11 +155,6 @@ void Font::setFilter(const Texture::Filter &f) } } -const Texture::Filter &Font::getFilter() -{ - return filter; -} - bool Font::loadVolatile() { createTexture(); @@ -950,107 +177,6 @@ void Font::unloadVolatile() textureMemorySize = 0; } -int Font::getAscent() const -{ - return floorf(rasterizers[0]->getAscent() / pixelDensity + 0.5f); -} - -int Font::getDescent() const -{ - return floorf(rasterizers[0]->getDescent() / pixelDensity + 0.5f); -} - -float Font::getBaseline() const -{ - // 1.25 is magic line height for true type fonts - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) - return floorf(getHeight() / 1.25f + 0.5f); - else - return 0.0f; -} - -bool Font::hasGlyph(uint32 glyph) const -{ - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - return true; - } - - return false; -} - -bool Font::hasGlyphs(const std::string &text) const -{ - if (text.size() == 0) - return false; - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); - - while (i != end) - { - uint32 codepoint = *i++; - - if (!hasGlyph(codepoint)) - return false; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - return true; -} - -void Font::setFallbacks(const std::vector &fallbacks) -{ - for (const Font *f : fallbacks) - { - if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType()) - throw love::Exception("Font fallbacks must be of the same font type."); - } - - rasterizers.resize(1); - - // NOTE: this won't invalidate already-rasterized glyphs. - for (const Font *f : fallbacks) - rasterizers.push_back(f->rasterizers[0]); -} - -float Font::getPixelDensity() const -{ - return pixelDensity; -} - -uint32 Font::getTextureCacheID() const -{ - return textureCacheID; -} - -bool Font::getConstant(const char *in, AlignMode &out) -{ - return alignModes.find(in, out); -} - -bool Font::getConstant(AlignMode in, const char *&out) -{ - return alignModes.find(in, out); -} - -StringMap::Entry Font::alignModeEntries[] = -{ - { "left", ALIGN_LEFT }, - { "right", ALIGN_RIGHT }, - { "center", ALIGN_CENTER }, - { "justify", ALIGN_JUSTIFY }, -}; - -StringMap Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries)); - } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Font.h b/src/modules/graphics/opengl/Font.h index 713fa57a3..6fe6f695d 100644 --- a/src/modules/graphics/opengl/Font.h +++ b/src/modules/graphics/opengl/Font.h @@ -18,247 +18,44 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#ifndef LOVE_GRAPHICS_OPENGL_FONT_H -#define LOVE_GRAPHICS_OPENGL_FONT_H - -// STD -#include -#include -#include +#pragma once // LOVE -#include "common/config.h" -#include "common/Object.h" -#include "common/Matrix.h" -#include "common/Vector.h" - -#include "font/Rasterizer.h" -#include "graphics/Texture.h" -#include "graphics/Volatile.h" -#include "graphics/vertex.h" -#include "GLBuffer.h" - +#include "graphics/Font.h" #include "OpenGL.h" namespace love { namespace graphics { - -class Graphics; - namespace opengl { -class Font : public Object, public Volatile +class Font : public love::graphics::Font { public: - static love::Type type; - - typedef std::vector Codepoints; - typedef vertex::XYf_STus_RGBAub GlyphVertex; - - enum AlignMode - { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT, - ALIGN_JUSTIFY, - ALIGN_MAX_ENUM - }; - - struct ColoredString - { - std::string str; - Colorf color; - }; - - struct IndexedColor - { - Colorf color; - int index; - }; - - struct ColoredCodepoints - { - std::vector cps; - std::vector colors; - }; - - struct TextInfo - { - int width; - int height; - }; - - // Used to determine when to change textures in the generated vertex array. - struct DrawCommand - { - GLuint texture; - int startvertex; - int vertexcount; - }; - Font(love::font::Rasterizer *r, const Texture::Filter &filter); - virtual ~Font(); - std::vector generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector &vertices, - float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr); - - std::vector generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, - std::vector &vertices, TextInfo *info = nullptr); - - static void getCodepointsFromString(const std::string &str, Codepoints &codepoints); - static void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); - - /** - * Draws the specified text. - **/ - void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); - void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); - - /** - * Returns the height of the font. - **/ - float getHeight() const; - - /** - * Returns the width of the passed string. - * - * @param str A string of text. - **/ - int getWidth(const std::string &str); - - /** - * Returns the width of the passed character. - * - * @param character A character. - **/ - int getWidth(char character); - - /** - * Returns the maximal width of a wrapped string - * and optionally the number of lines - * - * @param text The input text - * @param wraplimit The number of pixels to wrap at - * @param max_width Optional output of the maximum width - * Returns a vector with the lines. - **/ - void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); - void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); - - /** - * Sets the line height (which should be a number to multiply the font size by, - * example: line height = 1.2 and size = 12 means that rendered line height = 12*1.2) - * @param height The new line height. - **/ - void setLineHeight(float height); - - /** - * Returns the line height. - **/ - float getLineHeight() const; - - void setFilter(const Texture::Filter &f); - const Texture::Filter &getFilter(); + void setFilter(const Texture::Filter &f) override; // Implements Volatile. - bool loadVolatile(); - void unloadVolatile(); - - // Extra font metrics - int getAscent() const; - int getDescent() const; - float getBaseline() const; - - bool hasGlyph(uint32 glyph) const; - bool hasGlyphs(const std::string &text) const; - - void setFallbacks(const std::vector &fallbacks); - - float getPixelDensity() const; - - uint32 getTextureCacheID() const; - - static bool getConstant(const char *in, AlignMode &out); - static bool getConstant(AlignMode in, const char *&out); - - static int fontCount; + bool loadVolatile() override; + void unloadVolatile() override; private: - struct Glyph - { - GLuint texture; - int spacing; - GlyphVertex vertices[4]; - }; - - struct TextureSize - { - int width; - int height; - }; - - TextureSize getNextTextureSize() const; - bool createTexture(); - love::font::GlyphData *getRasterizerGlyphData(uint32 glyph); - const Glyph &addGlyph(uint32 glyph); - const Glyph &findGlyph(uint32 glyph); - float getKerning(uint32 leftglyph, uint32 rightglyph); - void printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices); - - std::vector> rasterizers; - - int height; - float lineHeight; - - int textureWidth; - int textureHeight; + void createTexture() override; + void uploadGlyphToTexture(font::GlyphData *data, Glyph &glyph) override; // vector of packed textures std::vector textures; - // maps glyphs to glyph texture information - std::unordered_map glyphs; - - // map of left/right glyph pairs to horizontal kerning. - std::unordered_map kerning; - - PixelFormat pixelFormat; - - Texture::Filter filter; - - float pixelDensity; - - int textureX, textureY; - int rowHeight; - - bool useSpacesAsTab; - - // Index buffer used for drawing quads with GL_TRIANGLES. - QuadIndices quadIndices; - - // ID which is incremented when the texture cache is invalidated. - uint32 textureCacheID; - size_t textureMemorySize; - static const int TEXTURE_PADDING = 1; - - // This will be used if the Rasterizer doesn't have a tab character itself. - static const int SPACES_PER_TAB = 4; - - static StringMap::Entry alignModeEntries[]; - static StringMap alignModes; - }; // Font } // opengl } // graphics } // love - -#endif // LOVE_GRAPHICS_OPENGL_FONT_H diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 30ed49b5c..06a21e9a2 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -25,6 +25,7 @@ #include "Graphics.h" #include "font/Font.h" +#include "Font.h" #include "graphics/Polyline.h" #include "math/MathModule.h" #include "window/Window.h" @@ -1431,7 +1432,7 @@ Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh) return new Quad(v, sw, sh); } -Font *Graphics::newFont(love::font::Rasterizer *r, const Texture::Filter &filter) +graphics::Font *Graphics::newFont(love::font::Rasterizer *r, const Texture::Filter &filter) { return new Font(r, filter); } @@ -1500,7 +1501,7 @@ Mesh *Graphics::newMesh(const std::vector &vertexformat, con return new Mesh(vertexformat, data, datasize, drawmode, usage); } -Text *Graphics::newText(Font *font, const std::vector &text) +Text *Graphics::newText(graphics::Font *font, const std::vector &text) { return new Text(font, text); } @@ -1545,7 +1546,7 @@ Colorf Graphics::getBackgroundColor() const return states.back().backgroundColor; } -void Graphics::setFont(Font *font) +void Graphics::setFont(love::graphics::Font *font) { // We don't need to set a default font here if null is passed in, since we // only care about the default font in getFont and print. @@ -1553,7 +1554,7 @@ void Graphics::setFont(Font *font) state.font.set(font); } -Font *Graphics::getFont() +love::graphics::Font *Graphics::getFont() { checkSetDefaultFont(); return states.back().font.get(); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 69af03835..69ef4edb3 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -40,7 +40,7 @@ #include "math/Transform.h" -#include "Font.h" +#include "graphics/Font.h" #include "Image.h" #include "graphics/Quad.h" #include "graphics/Texture.h" @@ -178,7 +178,7 @@ public: /** * Creates a Font object. **/ - Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::getDefaultFilter()); + graphics::Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::getDefaultFilter()); SpriteBatch *newSpriteBatch(Texture *texture, int size, Mesh::Usage usage); @@ -194,7 +194,7 @@ public: Mesh *newMesh(const std::vector &vertexformat, int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage); Mesh *newMesh(const std::vector &vertexformat, const void *data, size_t datasize, Mesh::DrawMode drawmode, Mesh::Usage usage); - Text *newText(Font *font, const std::vector &text = {}); + Text *newText(graphics::Font *font, const std::vector &text = {}); Video *newVideo(love::video::VideoStream *stream, float pixeldensity); @@ -220,8 +220,8 @@ public: **/ Colorf getBackgroundColor() const; - void setFont(Font *font); - Font *getFont(); + void setFont(love::graphics::Font *font); + love::graphics::Font *getFont(); void setShader(Shader *shader); void setShader(); @@ -342,10 +342,7 @@ public: **/ Stats getStats() const; - /** - * Gets the system-dependent numeric limit for the specified parameter. - **/ - double getSystemLimit(SystemLimit limittype) const; + double getSystemLimit(SystemLimit limittype) const override; /** * Gets whether a graphics feature is supported on this system. @@ -391,7 +388,7 @@ private: CompareMode stencilCompare = COMPARE_ALWAYS; int stencilTestValue = 0; - StrongRef font; + StrongRef font; StrongRef shader; std::vector> canvases; @@ -427,7 +424,7 @@ private: void checkSetDefaultFont(); - StrongRef defaultFont; + StrongRef defaultFont; std::vector pendingScreenshotCallbacks; diff --git a/src/modules/graphics/opengl/Text.cpp b/src/modules/graphics/opengl/Text.cpp index d97aee49a..d04d4af22 100644 --- a/src/modules/graphics/opengl/Text.cpp +++ b/src/modules/graphics/opengl/Text.cpp @@ -33,7 +33,7 @@ namespace opengl love::Type Text::type("Text", &Drawable::type); -Text::Text(Font *font, const std::vector &text) +Text::Text(love::graphics::Font *font, const std::vector &text) : font(font) , vbo(nullptr) , quadIndices(20) @@ -262,13 +262,13 @@ void Text::draw(Graphics *gfx, const Matrix4 &m) size_t offset = (cmd.startvertex / 4) * 6 * elemsize; // TODO: Use glDrawElementsBaseVertex when supported? - gl.bindTextureToUnit(cmd.texture, 0, false); + gl.bindTextureToUnit((GLuint) cmd.texture, 0, false); gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getPointer(offset)); } } -void Text::setFont(Font *f) +void Text::setFont(love::graphics::Font *f) { font.set(f); @@ -278,7 +278,7 @@ void Text::setFont(Font *f) regenerateVertices(); } -Font *Text::getFont() const +love::graphics::Font *Text::getFont() const { return font.get(); } diff --git a/src/modules/graphics/opengl/Text.h b/src/modules/graphics/opengl/Text.h index 706a567df..aa4eac278 100644 --- a/src/modules/graphics/opengl/Text.h +++ b/src/modules/graphics/opengl/Text.h @@ -24,7 +24,7 @@ // LOVE #include "common/config.h" #include "graphics/Drawable.h" -#include "Font.h" +#include "graphics/Font.h" #include "GLBuffer.h" namespace love @@ -40,7 +40,7 @@ public: static love::Type type; - Text(Font *font, const std::vector &text = {}); + Text(love::graphics::Font *font, const std::vector &text = {}); virtual ~Text(); void set(const std::vector &text); @@ -54,8 +54,8 @@ public: // Implements Drawable. void draw(Graphics *gfx, const Matrix4 &m) override; - void setFont(Font *f); - Font *getFont() const; + void setFont(love::graphics::Font *f); + love::graphics::Font *getFont() const; /** * Gets the width of the currently set text. @@ -84,7 +84,7 @@ private: void regenerateVertices(); void addTextData(const TextData &s); - StrongRef font; + StrongRef font; GLBuffer *vbo; QuadIndices quadIndices; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index c9cbedfd6..93dba6917 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -572,7 +572,7 @@ int w_newFont(lua_State *L) { luax_checkgraphicscreated(L); - Font *font = nullptr; + graphics::Font *font = nullptr; // Convert to Rasterizer, if necessary. if (!luax_istype(L, 1, love::font::Rasterizer::type)) @@ -630,7 +630,7 @@ int w_newImageFont(lua_State *L) love::font::Rasterizer *rasterizer = luax_checktype(L, 1); // Create the font. - Font *font = instance()->newFont(rasterizer, filter); + love::graphics::Font *font = instance()->newFont(rasterizer, filter); // Push the type. luax_pushtype(L, font); @@ -1049,7 +1049,7 @@ int w_newText(lua_State *L) { luax_checkgraphicscreated(L); - Font *font = luax_checkfont(L, 1); + graphics::Font *font = luax_checkfont(L, 1); Text *t = nullptr; if (lua_isnoneornil(L, 2)) @@ -1160,21 +1160,21 @@ int w_getBackgroundColor(lua_State *L) int w_setNewFont(lua_State *L) { int ret = w_newFont(L); - Font *font = luax_checktype(L, -1); + love::graphics::Font *font = luax_checktype(L, -1); instance()->setFont(font); return ret; } int w_setFont(lua_State *L) { - Font *font = luax_checktype(L, 1); + love::graphics::Font *font = luax_checktype(L, 1); instance()->setFont(font); return 0; } int w_getFont(lua_State *L) { - Font *f = nullptr; + love::graphics::Font *f = nullptr; luax_catchexcept(L, [&](){ f = instance()->getFont(); }); luax_pushtype(L, f); diff --git a/src/modules/graphics/opengl/wrap_Graphics.h b/src/modules/graphics/opengl/wrap_Graphics.h index 14e76121c..9795a6718 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.h +++ b/src/modules/graphics/opengl/wrap_Graphics.h @@ -23,7 +23,7 @@ // LOVE #include "common/config.h" -#include "wrap_Font.h" +#include "graphics/wrap_Font.h" #include "wrap_Image.h" #include "graphics/wrap_Quad.h" #include "wrap_SpriteBatch.h" diff --git a/src/modules/graphics/opengl/wrap_Text.cpp b/src/modules/graphics/opengl/wrap_Text.cpp index c67dd3125..622b7850c 100644 --- a/src/modules/graphics/opengl/wrap_Text.cpp +++ b/src/modules/graphics/opengl/wrap_Text.cpp @@ -19,6 +19,7 @@ **/ #include "wrap_Text.h" +#include "graphics/wrap_Font.h" #include "math/wrap_Transform.h" namespace love @@ -33,47 +34,6 @@ Text *luax_checktext(lua_State *L, int idx) return luax_checktype(L, idx); } -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) -{ - Font::ColoredString coloredstr; - coloredstr.color = Colorf(1.0f, 1.0f, 1.0f, 1.0f); - - if (lua_istable(L, idx)) - { - int len = (int) luax_objlen(L, idx); - - for (int i = 1; i <= len; i++) - { - lua_rawgeti(L, idx, i); - - if (lua_istable(L, -1)) - { - for (int j = 1; j <= 4; j++) - lua_rawgeti(L, -j, j); - - coloredstr.color.r = (float) luaL_checknumber(L, -4); - coloredstr.color.g = (float) luaL_checknumber(L, -3); - coloredstr.color.b = (float) luaL_checknumber(L, -2); - coloredstr.color.a = (float) luaL_optnumber(L, -1, 1.0); - - lua_pop(L, 4); - } - else - { - coloredstr.str = luaL_checkstring(L, -1); - strings.push_back(coloredstr); - } - - lua_pop(L, 1); - } - } - else - { - coloredstr.str = luaL_checkstring(L, idx); - strings.push_back(coloredstr); - } -} - int w_Text_set(lua_State *L) { Text *t = luax_checktext(L, 1); diff --git a/src/modules/graphics/opengl/wrap_Text.h b/src/modules/graphics/opengl/wrap_Text.h index 3f136c3f2..3cfeac3cd 100644 --- a/src/modules/graphics/opengl/wrap_Text.h +++ b/src/modules/graphics/opengl/wrap_Text.h @@ -32,7 +32,6 @@ namespace opengl { Text *luax_checktext(lua_State *L, int idx); -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); extern "C" int luaopen_text(lua_State *L); } // opengl diff --git a/src/modules/graphics/opengl/wrap_Font.cpp b/src/modules/graphics/wrap_Font.cpp similarity index 84% rename from src/modules/graphics/opengl/wrap_Font.cpp rename to src/modules/graphics/wrap_Font.cpp index 231b6d6fb..8fc0adab0 100644 --- a/src/modules/graphics/opengl/wrap_Font.cpp +++ b/src/modules/graphics/wrap_Font.cpp @@ -21,7 +21,6 @@ // LOVE #include "common/config.h" #include "wrap_Font.h" -#include "wrap_Text.h" // C++ #include @@ -30,8 +29,47 @@ namespace love { namespace graphics { -namespace opengl + +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) { + Font::ColoredString coloredstr; + coloredstr.color = Colorf(1.0f, 1.0f, 1.0f, 1.0f); + + if (lua_istable(L, idx)) + { + int len = (int) luax_objlen(L, idx); + + for (int i = 1; i <= len; i++) + { + lua_rawgeti(L, idx, i); + + if (lua_istable(L, -1)) + { + for (int j = 1; j <= 4; j++) + lua_rawgeti(L, -j, j); + + coloredstr.color.r = (float) luaL_checknumber(L, -4); + coloredstr.color.g = (float) luaL_checknumber(L, -3); + coloredstr.color.b = (float) luaL_checknumber(L, -2); + coloredstr.color.a = (float) luaL_optnumber(L, -1, 1.0); + + lua_pop(L, 4); + } + else + { + coloredstr.str = luaL_checkstring(L, -1); + strings.push_back(coloredstr); + } + + lua_pop(L, 1); + } + } + else + { + coloredstr.str = luaL_checkstring(L, idx); + strings.push_back(coloredstr); + } +} Font *luax_checkfont(lua_State *L, int idx) { @@ -179,7 +217,7 @@ int w_Font_hasGlyphs(lua_State *L) int w_Font_setFallbacks(lua_State *L) { Font *t = luax_checkfont(L, 1); - std::vector fallbacks; + std::vector fallbacks; for (int i = 2; i <= lua_gettop(L); i++) fallbacks.push_back(luax_checkfont(L, i)); @@ -218,6 +256,5 @@ extern "C" int luaopen_font(lua_State *L) return luax_register_type(L, &Font::type, w_Font_functions, nullptr); } -} // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/wrap_Font.h b/src/modules/graphics/wrap_Font.h similarity index 87% rename from src/modules/graphics/opengl/wrap_Font.h rename to src/modules/graphics/wrap_Font.h index e6fe3c2ce..522a40b5d 100644 --- a/src/modules/graphics/opengl/wrap_Font.h +++ b/src/modules/graphics/wrap_Font.h @@ -18,8 +18,7 @@ * 3. This notice may not be removed or altered from any source distribution. **/ -#ifndef LOVE_GRAPHICS_OPENGL_WRAP_FONT_H -#define LOVE_GRAPHICS_OPENGL_WRAP_FONT_H +#pragma once // LOVE #include "common/runtime.h" @@ -29,14 +28,10 @@ namespace love { namespace graphics { -namespace opengl -{ Font *luax_checkfont(lua_State *L, int idx); +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); extern "C" int luaopen_font(lua_State *L); -} // opengl } // graphics } // love - -#endif // LOVE_GRAPHICS_OPENGL_WRAP_FONT_H