Browse Source

Move most graphics Font code out of the opengl subfolder.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
c7419e0e37

+ 4 - 2
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

+ 20 - 10
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 = "<group>"; };
 		FA0B7BA61A95902C000E1D17 /* wrap_Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Canvas.cpp; sourceTree = "<group>"; };
 		FA0B7BA71A95902C000E1D17 /* wrap_Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Canvas.h; sourceTree = "<group>"; };
-		FA0B7BA81A95902C000E1D17 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = "<group>"; };
-		FA0B7BA91A95902C000E1D17 /* wrap_Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Font.h; sourceTree = "<group>"; };
 		FA0B7BAA1A95902C000E1D17 /* wrap_Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Graphics.cpp; sourceTree = "<group>"; };
 		FA0B7BAB1A95902C000E1D17 /* wrap_Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Graphics.h; sourceTree = "<group>"; };
 		FA0B7BAC1A95902C000E1D17 /* wrap_Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Image.cpp; sourceTree = "<group>"; };
@@ -1608,6 +1609,10 @@
 		FA19C4C21B4B0BD50059B0B3 /* wrap_Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Video.cpp; sourceTree = "<group>"; };
 		FA19C4C31B4B0BD50059B0B3 /* wrap_Video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Video.h; sourceTree = "<group>"; };
 		FA19C4C41B4B0BD50059B0B3 /* wrap_Video.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Video.lua; sourceTree = "<group>"; };
+		FA1BA09B1E16CFCE00AA2803 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = "<group>"; };
+		FA1BA09C1E16CFCE00AA2803 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = "<group>"; };
+		FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = "<group>"; };
+		FA1BA0A11E16D97500AA2803 /* wrap_Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Font.h; sourceTree = "<group>"; };
 		FA1DC2611C5D9555008F99A0 /* HashFunction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HashFunction.cpp; sourceTree = "<group>"; };
 		FA1DC2621C5D9555008F99A0 /* HashFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HashFunction.h; sourceTree = "<group>"; };
 		FA1E887C1DF363CD00E808AA /* Filter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Filter.cpp; sourceTree = "<group>"; };
@@ -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 */,

+ 919 - 0
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 <math.h>
+#include <sstream>
+#include <algorithm> // for max
+#include <limits>
+
+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<Graphics>(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<love::font::Rasterizer> &r : rasterizers)
+	{
+		if (r->hasGlyph(glyph))
+			return r->getGlyphData(glyph);
+	}
+
+	return rasterizers[0]->getGlyphData(glyph);
+}
+
+const Font::Glyph &Font::addGlyph(uint32 glyph)
+{
+	StrongRef<love::font::GlyphData> 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<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
+		utf8::iterator<std::string::const_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<ColoredString> &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::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &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<DrawCommand> 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::DrawCommand> Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
+{
+	wrap = std::max(wrap, 0.0f);
+
+	uint32 cacheid = textureCacheID;
+
+	std::vector<DrawCommand> drawcommands;
+	vertices.reserve(text.cps.size() * 4);
+
+	std::vector<int> widths;
+	std::vector<ColoredCodepoints> 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<DrawCommand> 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<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &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<ColoredString> &text, const Matrix4 &m, const Colorf &constantcolor)
+{
+	ColoredCodepoints codepoints;
+	getCodepointsFromString(text, codepoints);
+
+	std::vector<GlyphVertex> vertices;
+	std::vector<DrawCommand> drawcommands = generateVertices(codepoints, constantcolor, vertices);
+
+	printv(gfx, m, drawcommands, vertices);
+}
+
+void Font::printf(graphics::Graphics *gfx, const std::vector<ColoredString> &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor)
+{
+	ColoredCodepoints codepoints;
+	getCodepointsFromString(text, codepoints);
+
+	std::vector<GlyphVertex> vertices;
+	std::vector<DrawCommand> 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<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
+			utf8::iterator<std::string::const_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<ColoredCodepoints> &lines, std::vector<int> *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<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
+{
+	ColoredCodepoints cps;
+	getCodepointsFromString(text, cps);
+
+	std::vector<ColoredCodepoints> 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<love::font::Rasterizer> &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<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
+		utf8::iterator<std::string::const_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<Font *> &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<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
+{
+	{ "left", ALIGN_LEFT },
+	{ "right", ALIGN_RIGHT },
+	{ "center", ALIGN_CENTER },
+	{ "justify", ALIGN_JUSTIFY },
+};
+
+StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));
+
+} // graphics
+} // love

+ 245 - 0
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 <unordered_map>
+#include <string>
+#include <vector>
+#include <stddef.h>
+
+// 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<uint32> 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<uint32> cps;
+		std::vector<IndexedColor> 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<DrawCommand> generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector<GlyphVertex> &vertices,
+	                                          float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr);
+
+	std::vector<DrawCommand> generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align,
+	                                                   std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
+
+	static void getCodepointsFromString(const std::string &str, Codepoints &codepoints);
+	static void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
+
+	/**
+	 * Draws the specified text.
+	 **/
+	void print(graphics::Graphics *gfx, const std::vector<ColoredString> &text, const Matrix4 &m, const Colorf &constantColor);
+	void printf(graphics::Graphics *gfx, const std::vector<ColoredString> &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<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *line_widths = nullptr);
+	void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *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<Font *> &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<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices);
+
+	std::vector<StrongRef<love::font::Rasterizer>> rasterizers;
+
+	int height;
+	float lineHeight;
+
+	int textureWidth;
+	int textureHeight;
+
+	// maps glyphs to glyph texture information
+	std::unordered_map<uint32, Glyph> glyphs;
+
+	// map of left/right glyph pairs to horizontal kerning.
+	std::unordered_map<uint64, float> 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<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
+	static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
+	
+}; // Font
+
+} // graphics
+} // love

+ 4 - 0
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;

+ 11 - 885
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"
-#include "Font.h"
-#include "font/GlyphData.h"
-
-#include "libraries/utf8/utf8.h"
 
-#include "common/math.h"
-#include "common/Matrix.h"
+// LOVE
+#include "Font.h"
 #include "graphics/Graphics.h"
 
-#include <math.h>
-#include <sstream>
-#include <algorithm> // for max
-#include <limits>
-
 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<graphics::Graphics>(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)
-{
-	// 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<love::font::Rasterizer> &r : rasterizers)
-	{
-		if (r->hasGlyph(glyph))
-			return r->getGlyphData(glyph);
-	}
-
-	return rasterizers[0]->getGlyphData(glyph);
-}
-
-const Font::Glyph &Font::addGlyph(uint32 glyph)
-{
-	StrongRef<love::font::GlyphData> 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)
+void Font::uploadGlyphToTexture(font::GlyphData *gd, Glyph &glyph)
 {
-	const auto it = glyphs.find(glyph);
+	bool isSRGB = isGammaCorrect();
+	OpenGL::TextureFormat fmt = gl.convertPixelFormat(pixelFormat, false, isSRGB);
 
-	if (it != glyphs.end())
-		return it->second;
+	glyph.texture = textures.back();
 
-	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<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
-		utf8::iterator<std::string::const_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<ColoredString> &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::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &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<DrawCommand> 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::DrawCommand> Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
-{
-	wrap = std::max(wrap, 0.0f);
-
-	uint32 cacheid = textureCacheID;
-
-	std::vector<DrawCommand> drawcommands;
-	vertices.reserve(text.cps.size() * 4);
-
-	std::vector<int> widths;
-	std::vector<ColoredCodepoints> 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<DrawCommand> 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<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &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<ColoredString> &text, const Matrix4 &m, const Colorf &constantcolor)
-{
-	ColoredCodepoints codepoints;
-	getCodepointsFromString(text, codepoints);
-
-	std::vector<GlyphVertex> vertices;
-	std::vector<DrawCommand> drawcommands = generateVertices(codepoints, constantcolor, vertices);
-
-	printv(gfx, m, drawcommands, vertices);
-}
-
-void Font::printf(graphics::Graphics *gfx, const std::vector<ColoredString> &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor)
-{
-	ColoredCodepoints codepoints;
-	getCodepointsFromString(text, codepoints);
-
-	std::vector<GlyphVertex> vertices;
-	std::vector<DrawCommand> 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<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
-			utf8::iterator<std::string::const_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<ColoredCodepoints> &lines, std::vector<int> *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<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
-{
-	ColoredCodepoints cps;
-	getCodepointsFromString(text, cps);
-
-	std::vector<ColoredCodepoints> 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<love::font::Rasterizer> &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<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
-		utf8::iterator<std::string::const_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<Font *> &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<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
-{
-	{ "left", ALIGN_LEFT },
-	{ "right", ALIGN_RIGHT },
-	{ "center", ALIGN_CENTER },
-	{ "justify", ALIGN_JUSTIFY },
-};
-
-StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));
-
 } // opengl
 } // graphics
 } // love

+ 8 - 211
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 <unordered_map>
-#include <string>
-#include <vector>
+#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<uint32> 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<uint32> cps;
-		std::vector<IndexedColor> 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<DrawCommand> generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector<GlyphVertex> &vertices,
-	                                          float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr);
-
-	std::vector<DrawCommand> generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align,
-	                                                  std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
-
-	static void getCodepointsFromString(const std::string &str, Codepoints &codepoints);
-	static void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
-
-	/**
-	 * Draws the specified text.
-	 **/
-	void print(graphics::Graphics *gfx, const std::vector<ColoredString> &text, const Matrix4 &m, const Colorf &constantColor);
-	void printf(graphics::Graphics *gfx, const std::vector<ColoredString> &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<ColoredString> &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *line_widths = nullptr);
-	void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *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<Font *> &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<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices);
-
-	std::vector<StrongRef<love::font::Rasterizer>> 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<GLuint> textures;
 
-	// maps glyphs to glyph texture information
-	std::unordered_map<uint32, Glyph> glyphs;
-
-	// map of left/right glyph pairs to horizontal kerning.
-	std::unordered_map<uint64, float> 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<AlignMode, ALIGN_MAX_ENUM>::Entry alignModeEntries[];
-	static StringMap<AlignMode, ALIGN_MAX_ENUM> alignModes;
-
 }; // Font
 
 } // opengl
 } // graphics
 } // love
-
-#endif // LOVE_GRAPHICS_OPENGL_FONT_H

+ 5 - 4
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<Mesh::AttribFormat> &vertexformat, con
 	return new Mesh(vertexformat, data, datasize, drawmode, usage);
 }
 
-Text *Graphics::newText(Font *font, const std::vector<Font::ColoredString> &text)
+Text *Graphics::newText(graphics::Font *font, const std::vector<Font::ColoredString> &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();

+ 8 - 11
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<Mesh::AttribFormat> &vertexformat, int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage);
 	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, Mesh::DrawMode drawmode, Mesh::Usage usage);
 
-	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
+	Text *newText(graphics::Font *font, const std::vector<Font::ColoredString> &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> font;
+		StrongRef<love::graphics::Font> font;
 		StrongRef<Shader> shader;
 
 		std::vector<StrongRef<Canvas>> canvases;
@@ -427,7 +424,7 @@ private:
 
 	void checkSetDefaultFont();
 
-	StrongRef<Font> defaultFont;
+	StrongRef<love::graphics::Font> defaultFont;
 
 	std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
 

+ 4 - 4
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<Font::ColoredString> &text)
+Text::Text(love::graphics::Font *font, const std::vector<Font::ColoredString> &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();
 }

+ 5 - 5
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<Font::ColoredString> &text = {});
+	Text(love::graphics::Font *font, const std::vector<Font::ColoredString> &text = {});
 	virtual ~Text();
 
 	void set(const std::vector<Font::ColoredString> &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> font;
+	StrongRef<love::graphics::Font> font;
 	GLBuffer *vbo;
 	QuadIndices quadIndices;
 

+ 6 - 6
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<love::font::Rasterizer>(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<Font>(L, -1);
+	love::graphics::Font *font = luax_checktype<love::graphics::Font>(L, -1);
 	instance()->setFont(font);
 	return ret;
 }
 
 int w_setFont(lua_State *L)
 {
-	Font *font = luax_checktype<Font>(L, 1);
+	love::graphics::Font *font = luax_checktype<love::graphics::Font>(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);

+ 1 - 1
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"

+ 1 - 41
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<Text>(L, idx);
 }
 
-void luax_checkcoloredstring(lua_State *L, int idx, std::vector<Font::ColoredString> &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);

+ 0 - 1
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<Font::ColoredString> &strings);
 extern "C" int luaopen_text(lua_State *L);
 
 } // opengl

+ 41 - 4
src/modules/graphics/opengl/wrap_Font.cpp → 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 <algorithm>
@@ -30,8 +29,47 @@ namespace love
 {
 namespace graphics
 {
-namespace opengl
+
+void luax_checkcoloredstring(lua_State *L, int idx, std::vector<Font::ColoredString> &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<Font *> fallbacks;
+	std::vector<graphics::Font *> 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

+ 2 - 7
src/modules/graphics/opengl/wrap_Font.h → 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<Font::ColoredString> &strings);
 extern "C" int luaopen_font(lua_State *L);
 
-} // opengl
 } // graphics
 } // love
-
-#endif // LOVE_GRAPHICS_OPENGL_WRAP_FONT_H