123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- /**
- * Copyright (c) 2006-2013 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 <math.h>
- #include <sstream>
- #include <algorithm> // for max
- namespace love
- {
- namespace graphics
- {
- namespace opengl
- {
- const int Font::TEXTURE_WIDTHS[] = {128, 256, 256, 512, 512, 1024, 1024};
- const int Font::TEXTURE_HEIGHTS[] = {128, 128, 256, 256, 512, 512, 1024};
- Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
- : rasterizer(r)
- , height(r->getHeight())
- , lineHeight(1)
- , mSpacing(1)
- , filter(filter)
- {
- this->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.
- textureSizeIndex = NUM_TEXTURE_SIZES - 1;
- for (int i = 0; i < NUM_TEXTURE_SIZES; i++)
- {
- // Make a rough estimate of the total used texture size, based on glyph
- // height. The estimated size is likely larger than the actual total
- // size, which is good because texture switching is expensive.
- if ((height * 0.8) * height * 95 <= TEXTURE_WIDTHS[i] * TEXTURE_HEIGHTS[i])
- {
- textureSizeIndex = i;
- break;
- }
- }
- textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
- textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
- love::font::GlyphData *gd = 0;
- try
- {
- gd = r->getGlyphData(32);
- type = (gd->getFormat() == love::font::GlyphData::FORMAT_LUMINANCE_ALPHA) ? FONT_TRUETYPE : FONT_IMAGE;
- loadVolatile();
- }
- catch (love::Exception &)
- {
- delete gd;
- throw;
- }
- delete gd;
- rasterizer->retain();
- }
- Font::~Font()
- {
- rasterizer->release();
- unloadVolatile();
- }
- bool Font::initializeTexture(GLenum format)
- {
- GLint internalformat = (format == GL_LUMINANCE_ALPHA) ? GL_LUMINANCE8_ALPHA8 : GL_RGBA8;
- // clear errors before initializing
- while (glGetError() != GL_NO_ERROR);
- glTexImage2D(GL_TEXTURE_2D,
- 0,
- internalformat,
- (GLsizei)textureWidth,
- (GLsizei)textureHeight,
- 0,
- format,
- GL_UNSIGNED_BYTE,
- NULL);
- return glGetError() == GL_NO_ERROR;
- }
- void Font::createTexture()
- {
- textureX = textureY = rowHeight = TEXTURE_PADDING;
- GLuint t;
- glGenTextures(1, &t);
- textures.push_back(t);
- gl.bindTexture(t);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- GLenum format = (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA);
- // Initialize the texture, attempting smaller sizes if initialization fails.
- bool initialized = false;
- while (textureSizeIndex >= 0)
- {
- textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
- textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
- initialized = initializeTexture(format);
- if (initialized || textureSizeIndex <= 0)
- break;
- --textureSizeIndex;
- }
- if (!initialized)
- {
- // Clean up before throwing.
- gl.deleteTexture(t);
- gl.bindTexture(0);
- textures.pop_back();
- throw love::Exception("Could not create font texture!");
- }
- // Fill the texture with transparent black.
- std::vector<GLubyte> emptyData(textureWidth * textureHeight * (type == FONT_TRUETYPE ? 2 : 4), 0);
- glTexSubImage2D(GL_TEXTURE_2D,
- 0,
- 0, 0,
- (GLsizei)textureWidth,
- (GLsizei)textureHeight,
- format,
- GL_UNSIGNED_BYTE,
- &emptyData[0]);
- setFilter(filter);
- }
- Font::Glyph *Font::addGlyph(uint32 glyph)
- {
- love::font::GlyphData *gd = rasterizer->getGlyphData(glyph);
- int w = gd->getWidth();
- int h = gd->getHeight();
- 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();
- }
- Glyph *g = new Glyph;
- g->texture = 0;
- g->spacing = gd->getAdvance();
- memset(g->vertices, 0, sizeof(GlyphVertex) * 4);
- // don't waste space for empty glyphs. also fixes a division by zero bug with ati drivers
- if (w > 0 && h > 0)
- {
- const GLuint t = textures.back();
- gl.bindTexture(t);
- glTexSubImage2D(GL_TEXTURE_2D,
- 0,
- textureX,
- textureY,
- w, h,
- (type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA),
- GL_UNSIGNED_BYTE,
- gd->getData());
- g->texture = t;
- const GlyphVertex verts[4] = {
- { 0.0f, 0.0f, float(textureX)/float(textureWidth), float(textureY)/float(textureHeight)},
- { 0.0f, float(h), float(textureX)/float(textureWidth), float(textureY+h)/float(textureHeight)},
- {float(w), float(h), float(textureX+w)/float(textureWidth), float(textureY+h)/float(textureHeight)},
- {float(w), 0.0f, float(textureX+w)/float(textureWidth), float(textureY)/float(textureHeight)},
- };
- // 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();
- g->vertices[i].y -= gd->getBearingY();
- }
- }
- if (w > 0)
- textureX += (w + TEXTURE_PADDING);
- if (h > 0)
- rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
- delete gd;
- glyphs[glyph] = g;
- return g;
- }
- Font::Glyph *Font::findGlyph(uint32 glyph)
- {
- auto it = glyphs.find(glyph);
- if (it != glyphs.end())
- return it->second;
- else
- return addGlyph(glyph);
- }
- float Font::getHeight() const
- {
- return static_cast<float>(height);
- }
- void Font::print(const std::string &text, float x, float y, float extra_spacing, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
- {
- // Spacing counter and newline handling.
- float dx = 0.0f;
- float dy = 0.0f;
- float lineheight = getBaseline();
- // Keeps track of when we need to switch textures in our vertex array.
- std::vector<GlyphArrayDrawInfo> glyphinfolist;
- // Pre-allocate space for the maximum possible number of vertices.
- std::vector<GlyphVertex> glyphverts;
- glyphverts.reserve(text.length() * 4);
- int vertexcount = 0;
- 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++;
- if (g == '\n')
- {
- // Wrap newline, but do not print it.
- dy += floorf(getHeight() * getLineHeight() + 0.5f);
- dx = 0.0f;
- continue;
- }
- Glyph *glyph = findGlyph(g);
- if (glyph->texture != 0)
- {
- // Copy the vertices and set their proper relative positions.
- for (int j = 0; j < 4; j++)
- {
- glyphverts.push_back(glyph->vertices[j]);
- glyphverts.back().x += dx;
- glyphverts.back().y += dy + lineheight;
- }
- // Check if glyph texture has changed since the last iteration.
- if (glyphinfolist.size() == 0 || glyphinfolist.back().texture != glyph->texture)
- {
- // keep track of each sub-section of the string whose glyphs use different textures than the previous section
- GlyphArrayDrawInfo gdrawinfo;
- gdrawinfo.startvertex = vertexcount;
- gdrawinfo.vertexcount = 0;
- gdrawinfo.texture = glyph->texture;
- glyphinfolist.push_back(gdrawinfo);
- }
- vertexcount += 4;
- glyphinfolist.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);
- }
- }
- catch (utf8::exception &e)
- {
- throw love::Exception("Decoding error: %s", e.what());
- }
- if (vertexcount <= 0 || glyphinfolist.size() == 0)
- return;
- // Sort glyph draw info list by texture first, and quad position in memory
- // second (using the struct's < operator).
- std::sort(glyphinfolist.begin(), glyphinfolist.end());
- glPushMatrix();
- Matrix t;
- t.setTransformation(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
- glMultMatrixf((const GLfloat *)t.getElements());
- glEnableClientState(GL_VERTEX_ARRAY);
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- glVertexPointer(2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[0].x);
- glTexCoordPointer(2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[0].s);
- gl.prepareDraw();
- // We need to draw a new vertex array for every section of the string which
- // uses a different texture than the previous section.
- std::vector<GlyphArrayDrawInfo>::const_iterator it;
- for (it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it)
- {
- gl.bindTexture(it->texture);
- glDrawArrays(GL_QUADS, it->startvertex, it->vertexcount);
- }
- glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- glDisableClientState(GL_VERTEX_ARRAY);
- glPopMatrix();
- }
- int Font::getWidth(const std::string &str)
- {
- if (str.size() == 0) return 0;
- std::istringstream iss(str);
- std::string line;
- Glyph *g;
- int max_width = 0;
- while (getline(iss, line, '\n'))
- {
- int width = 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++;
- g = findGlyph(c);
- width += static_cast<int>(g->spacing * mSpacing);
- }
- }
- catch(utf8::exception &e)
- {
- throw love::Exception("Decoding error: %s", e.what());
- }
- if (width > max_width)
- max_width = width;
- }
- return max_width;
- }
- int Font::getWidth(char character)
- {
- Glyph *g = findGlyph(character);
- return g->spacing;
- }
- std::vector<std::string> Font::getWrap(const std::string &text, float wrap, int *max_width, std::vector<bool> *wrappedlines)
- {
- using namespace std;
- const float width_space = static_cast<float>(getWidth(' '));
- vector<string> lines_to_draw;
- int maxw = 0;
- //split text at newlines
- istringstream iss(text);
- string line;
- ostringstream string_builder;
- while (getline(iss, line, '\n'))
- {
- // split line into words
- vector<string> words;
- istringstream word_iss(line);
- copy(istream_iterator<string>(word_iss), istream_iterator<string>(),
- back_inserter< vector<string> >(words));
- // put words back together until a wrap occurs
- float width = 0.0f;
- float oldwidth = 0.0f;
- string_builder.str("");
- vector<string>::const_iterator word_iter, wend = words.end();
- for (word_iter = words.begin(); word_iter != wend; ++word_iter)
- {
- const string &word = *word_iter;
- width += getWidth(word);
- // on wordwrap, push line to line buffer and clear string builder
- if (width > wrap && oldwidth > 0)
- {
- int realw = (int) width;
- // remove trailing space
- string tmp = string_builder.str();
- lines_to_draw.push_back(tmp.substr(0,tmp.size()-1));
- string_builder.str("");
- width = static_cast<float>(getWidth(word));
- realw -= (int) width;
- if (realw > maxw)
- maxw = realw;
- // Indicate that this line was automatically wrapped.
- if (wrappedlines)
- wrappedlines->push_back(true);
- }
- string_builder << word << " ";
- width += width_space;
- oldwidth = width;
- }
- // push last line
- if (width > maxw)
- maxw = (int) width;
- string tmp = string_builder.str();
- lines_to_draw.push_back(tmp.substr(0,tmp.size()-1));
- // Indicate that this line was not automatically wrapped.
- if (wrappedlines)
- wrappedlines->push_back(false);
- }
- if (max_width)
- *max_width = maxw;
- return lines_to_draw;
- }
- void Font::setLineHeight(float height)
- {
- this->lineHeight = height;
- }
- float Font::getLineHeight() const
- {
- return lineHeight;
- }
- void Font::setSpacing(float amount)
- {
- mSpacing = amount;
- }
- float Font::getSpacing() const
- {
- return mSpacing;
- }
- void Font::setFilter(const Texture::Filter &f)
- {
- filter = f;
- for (auto it = textures.begin(); it != textures.end(); ++it)
- {
- gl.bindTexture(*it);
- gl.setTextureFilter(filter);
- }
- }
- const Texture::Filter &Font::getFilter()
- {
- return filter;
- }
- bool Font::loadVolatile()
- {
- createTexture();
- return true;
- }
- void Font::unloadVolatile()
- {
- // nuke everything from orbit
- std::map<uint32, Glyph *>::iterator it = glyphs.begin();
- Glyph *g;
- while (it != glyphs.end())
- {
- g = it->second;
- delete g;
- glyphs.erase(it++);
- }
- std::vector<GLuint>::iterator iter = textures.begin();
- while (iter != textures.end())
- {
- gl.deleteTexture(*iter);
- iter++;
- }
- textures.clear();
- }
- int Font::getAscent() const
- {
- return rasterizer->getAscent();
- }
- int Font::getDescent() const
- {
- return rasterizer->getDescent();
- }
- float Font::getBaseline() const
- {
- // 1.25 is magic line height for true type fonts
- return (type == FONT_TRUETYPE) ? floorf(getHeight() / 1.25f + 0.5f) : 0.0f;
- }
- bool Font::hasGlyph(uint32 glyph) const
- {
- return rasterizer->hasGlyph(glyph);
- }
- bool Font::hasGlyphs(const std::string &text) const
- {
- return rasterizer->hasGlyphs(text);
- }
- } // opengl
- } // graphics
- } // love
|