Browse Source

New font engine is implemented and appears to work perfectly. Bugs may be discovered and the internals can certainly be optimized and improved (at some point, perhaps, a less naive packing algorithm would be nice) but for now, it's good to go.

--HG--
branch : newfont
Bill Meltsner 14 years ago
parent
commit
cbff71d089

+ 137 - 32
src/modules/graphics/opengl/Font.cpp

@@ -20,10 +20,13 @@
 
 #include "Font.h"
 #include <font/GlyphData.h>
+#include "Quad.h"
 
 #include <common/math.h>
 #include <math.h>
 
+#include <algorithm> // for max
+
 namespace love
 {
 namespace graphics
@@ -32,15 +35,101 @@ namespace opengl
 {
 
 	Font::Font(love::font::Rasterizer * r, const Image::Filter& filter)
-	: rasterizer(r), height(r->getHeight()), lineHeight(1), mSpacing(1)
+	: rasterizer(r), height(r->getHeight()), lineHeight(1), mSpacing(1), filter(filter)
 	{
-		love::font::GlyphData * gd = r->getGlyphData(0);
+		love::font::GlyphData * gd = r->getGlyphData(32);
 		type = (gd->getFormat() == love::font::GlyphData::FORMAT_LUMINANCE_ALPHA ? FONT_TRUETYPE : FONT_IMAGE);
 		delete gd;
+		createTexture();
 	}
 
 	Font::~Font()
 	{
+		unloadVolatile();
+	}
+	
+	void Font::createTexture()
+	{
+		texture_x = texture_y = rowHeight = 0;
+		GLuint t;
+		glGenTextures(1, &t);
+		textures.push_back(t);
+		glBindTexture(GL_TEXTURE_2D, t);
+		
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
+						(filter.mag == Image::FILTER_LINEAR) ? GL_LINEAR : GL_NEAREST);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+						(filter.min == Image::FILTER_LINEAR) ? GL_LINEAR : GL_NEAREST);
+		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		GLint format = (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA);
+		glTexImage2D(GL_TEXTURE_2D, 
+					 0, 
+					 GL_RGBA, 
+					 (GLsizei)TEXTURE_WIDTH, 
+					 (GLsizei)TEXTURE_HEIGHT, 
+					 0, 
+					 format, 
+					 GL_UNSIGNED_BYTE, 
+					 NULL);
+	}
+	
+	Font::Glyph * Font::addGlyph(int glyph)
+	{
+		Glyph * g = new Glyph;
+		g->list = glGenLists(1);
+		if (g->list == 0) { // opengl failed to generate the list
+			delete g;
+			return NULL;
+		}
+		love::font::GlyphData *gd = rasterizer->getGlyphData(glyph);
+		g->spacing = gd->getAdvance();
+		int w = gd->getWidth();
+		int h = gd->getHeight();
+		if (texture_x + w > TEXTURE_WIDTH) { // out of space - new row!
+			texture_x = 0;
+			texture_y += rowHeight;
+			rowHeight = 0;
+		}
+		if (texture_y + h > TEXTURE_HEIGHT) { // totally out of space - new texture!
+			createTexture();
+		}
+		GLuint t = textures.back();
+		glBindTexture(GL_TEXTURE_2D, t);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, texture_x, texture_y, w, h, (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA), GL_UNSIGNED_BYTE, gd->getData());
+		
+		Quad::Viewport v;
+		v.x = texture_x;
+		v.y = texture_y;
+		v.w = w;
+		v.h = h;
+		Quad * q = new Quad(v, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+		const vertex * verts = q->getVertices();
+		
+		glEnableClientState(GL_VERTEX_ARRAY);
+		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+		glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&verts[0].x);
+		glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid *)&verts[0].s);
+		
+		glNewList(g->list, GL_COMPILE);
+		glBindTexture(GL_TEXTURE_2D, t);
+		glPushMatrix();
+		glTranslatef(static_cast<float>(gd->getBearingX()), static_cast<float>(-gd->getBearingY()), 0.0f);
+		glDrawArrays(GL_QUADS, 0, 4);
+		glPopMatrix();
+		glEndList();
+		
+		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+		glDisableClientState(GL_VERTEX_ARRAY);
+		
+		delete q;
+		delete gd;
+		
+		texture_x += w;
+		rowHeight = std::max(rowHeight, h);
+		
+		glyphs[glyph] = g;
+		return g;
 	}
 
 	float Font::getHeight() const
@@ -48,9 +137,9 @@ namespace opengl
 		return static_cast<float>(height);
 	}
 
-	void Font::print(std::string text, float x, float y, float angle, float sx, float sy) const
+	void Font::print(std::string text, float x, float y, float angle, float sx, float sy)
 	{
-		/*float dx = 0.0f; // spacing counter for newline handling
+		float dx = 0.0f; // spacing counter for newline handling
 		glPushMatrix();
 
 		glTranslatef(ceil(x), ceil(y), 0.0f);
@@ -63,57 +152,66 @@ namespace opengl
 				dx = 0.0f;
 				continue;
 			}
-			if (!glyphs[g]) g = 32; // space
+			Glyph * glyph = glyphs[g];
+			if (!glyph) glyph = addGlyph(g);
 			glPushMatrix();
 			// 1.25 is magic line height for true type fonts
 			if (type == FONT_TRUETYPE) glTranslatef(0, floor(getHeight() / 1.25f + 0.5f), 0);
-			glyphs[g]->draw(0, 0, 0, 1, 1, 0, 0);
+			glCallList(glyph->list);
 			glPopMatrix();
-			glTranslatef(static_cast<GLfloat>(spacing[g]), 0, 0);
-			dx += spacing[g];
+			glTranslatef(static_cast<GLfloat>(glyph->spacing), 0, 0);
+			dx += glyph->spacing;
 		}
-		glPopMatrix();*/
+		glPopMatrix();
 	}
 
-	void Font::print(char character, float x, float y) const
+	void Font::print(char character, float x, float y)
 	{
-		/*if (!glyphs[(int)character]) character = ' ';
+		Glyph * glyph = glyphs[character];
+		if (!glyph) glyph = addGlyph(character);
 		glPushMatrix();
 		glTranslatef(x, floor(y+getHeight() + 0.5f), 0.0f);
-		glCallList(list+character);
-		glPopMatrix();*/
+		glCallList(glyph->list);
+		glPopMatrix();
 	}
 
-	int Font::getWidth(const std::string & line) const
+	int Font::getWidth(const std::string & line)
 	{
 		if(line.size() == 0) return 0;
 		int temp = 0;
+		
+		Glyph * g;
 
 		for(unsigned int i = 0; i < line.size(); i++)
 		{
-			temp += static_cast<int>((spacing[(int)line[i]] * mSpacing));
+			g = glyphs[line[i]];
+			if (!g) g = addGlyph(line[i]);
+			temp += static_cast<int>(g->spacing * mSpacing);
 		}
 
 		return temp;
 	}
 
-	int Font::getWidth(const char * line) const
+	int Font::getWidth(const char * line)
 	{
 		return this->getWidth(std::string(line));
 	}
 
-	int Font::getWidth(const char character) const
+	int Font::getWidth(const char character)
 	{
-		return spacing[(int)character];
+		Glyph * g = glyphs[character];
+		if (!g) g = addGlyph(character);
+		return g->spacing;
 	}
 
-	int Font::getWrap(const std::string & line, float wrap, int * lines) const
+	int Font::getWrap(const std::string & line, float wrap, int * lines)
 	{
 		if(line.size() == 0) return 0;
 		int maxw = 0;
 		int linen = 1;
 		int temp = 0;
 		std::string text;
+		Glyph * g;
 
 		for(unsigned int i = 0; i < line.size(); i++)
 		{
@@ -127,7 +225,9 @@ namespace opengl
 				temp = getWidth(text);
 				linen++;
 			}
-			temp += static_cast<int>((spacing[(int)line[i]] * mSpacing));
+			g = glyphs[line[i]];
+			if (!g) g = addGlyph(line[i]);
+			temp += static_cast<int>(g->spacing * mSpacing);
 			text += line[i];
 		}
 
@@ -137,7 +237,7 @@ namespace opengl
 		return maxw;
 	}
 
-	int Font::getWrap(const char * line, float wrap, int * lines) const
+	int Font::getWrap(const char * line, float wrap, int * lines)
 	{
 		return getWrap(std::string(line), wrap, lines);
 	}
@@ -164,21 +264,26 @@ namespace opengl
 
 	bool Font::loadVolatile()
 	{
-		/*// reload all glyphs
-		for(unsigned int i = 0; i < MAX_CHARS; i++)
-		{
-			glyphs[i]->load();
-			glNewList(list + i, GL_COMPILE);
-			glyphs[i]->draw(0, 0, 0, 1, 1, 0, 0);
-			glEndList();
-		}
-		return true;*/
+		createTexture();
 	}
 
 	void Font::unloadVolatile()
 	{
-		/*// delete the glyphs
-		glDeleteLists(list, MAX_CHARS);*/
+		// nuke everything from orbit
+		std::map<int, Glyph *>::iterator it = glyphs.begin();
+		Glyph * g;
+		while (it != glyphs.end()) {
+			g = it->second;
+			glDeleteLists(g->list, 1);
+			delete g;
+			glyphs.erase(it++);
+		}
+		std::vector<GLuint>::iterator iter = textures.begin();
+		while (iter != textures.end()) {
+			glDeleteTextures(1, (GLuint*)&*iter);
+			iter++;
+		}
+		textures.clear();
 	}
 
 } // opengl

+ 27 - 8
src/modules/graphics/opengl/Font.h

@@ -22,7 +22,9 @@
 #define LOVE_GRAPHICS_OPENGL_FONT_H
 
 // STD
+#include <map>
 #include <string>
+#include <vector>
 
 // LOVE
 #include <common/Object.h>
@@ -48,13 +50,30 @@ namespace opengl
 			FONT_UNKNOWN
 		};
 		
+		struct Glyph
+		{
+			GLuint list;
+			int spacing;
+		};
+		
 		love::font::Rasterizer * rasterizer;
 
 		int height;
 		float lineHeight;
 		float mSpacing; // modifies the spacing by multiplying it with this value
-		GLuint list; // the list of glyphs, for quicker drawing
+		std::vector<GLuint> textures; // vector of packed textures
+		std::map<int, Glyph *> glyphs; // maps glyphs to display lists
 		FontType type;
+		Image::Filter filter;
+		
+		static const int TEXTURE_WIDTH = 512;
+		static const int TEXTURE_HEIGHT = 512;
+		
+		int texture_x, texture_y;
+		int rowHeight;
+		
+		void createTexture();
+		Glyph * addGlyph(int glyph);
 
 	public:
 
@@ -75,7 +94,7 @@ namespace opengl
 		* @param y The y-coordinate.
 		* @param angle The amount of rotation.
 		**/
-		void print(std::string text, float x, float y, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f) const;
+		void print(std::string text, float x, float y, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f);
 
 		/**
 		* Prints the character at the designated position.
@@ -84,7 +103,7 @@ namespace opengl
 		* @param x The x-coordinate.
 		* @param y The y-coordinate.
 		**/
-		void print(char character, float x, float y) const;
+		void print(char character, float x, float y);
 
 		/**
 		* Returns the height of the font.
@@ -96,15 +115,15 @@ namespace opengl
 		*
 		* @param line A line of text.
 		**/
-		int getWidth(const std::string & line) const;
-		int getWidth(const char * line) const;
+		int getWidth(const std::string & line);
+		int getWidth(const char * line);
 
 		/**
 		* Returns the width of the passed character.
 		*
 		* @param character A character.
 		**/
-		int getWidth(const char character) const;
+		int getWidth(const char character);
 
 		/**
 		 * Returns the maximal width of a wrapped string
@@ -114,8 +133,8 @@ namespace opengl
 		 * @param wrap The number of pixels to wrap at
 		 * @param lines Optional output of the number of lines needed
 		 **/
-		int getWrap(const std::string & line, float wrap, int *lines = 0) const;
-		int getWrap(const char * line, float wrap, int *lines = 0) const;
+		int getWrap(const std::string & line, float wrap, int *lines = 0);
+		int getWrap(const char * line, float wrap, int *lines = 0);
 
 		/**
 		* Sets the line height (which should be a number to multiply the font size by,

+ 3 - 0
src/modules/graphics/opengl/Graphics.cpp

@@ -281,6 +281,9 @@ namespace opengl
 		// Reset modelview matrix
 		glMatrixMode(GL_MODELVIEW);
 		glLoadIdentity();
+		
+		// Set pixel row alignment
+		glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
 
 		// Set the new display mode as the current display mode.
 		currentMode.width = width;

+ 0 - 8
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -255,10 +255,6 @@ namespace opengl
 			luax_convobj(L, idxs, 2, "font", "newRasterizer");
 		}
 
-		// Convert to FontData, if necessary.
-		if(luax_istype(L, 1, FONT_RASTERIZER_T))
-			luax_convobj(L, 1, "font", "newFontData");
-
 		love::font::Rasterizer * rasterizer = luax_checktype<love::font::Rasterizer>(L, 1, "Rasterizer", FONT_RASTERIZER_T);
 
 		// Create the font.
@@ -450,10 +446,6 @@ namespace opengl
 				luax_convobj(L, idxs, 2, "font", "newRasterizer");
 			}
 
-			// Convert to FontData, if necessary.
-			if(luax_istype(L, 1, FONT_RASTERIZER_T))
-				luax_convobj(L, 1, "font", "newFontData");
-
 			love::font::Rasterizer * rasterizer = luax_checktype<love::font::Rasterizer>(L, 1, "Rasterizer", FONT_RASTERIZER_T);
 
 			// Create the font.