Added per-character color support to love.graphics.print/printf and Text objects. Resolves issue #865.

The aforementioned functions now optionally accept an array instead of a plain string. The array is in the format of: {{r, g, b [, a]}, "first colored text, ", {r, g, b [, a]}, "second colored text", ...}, where the color values specify the color to use in the next section of the text.
This commit is contained in:
Alex Szpakowski
2015-11-01 23:17:46 -04:00
parent 799e303673
commit 389b91cf6f
9 changed files with 399 additions and 195 deletions
+237 -127
View File
@@ -38,6 +38,11 @@ namespace graphics
namespace opengl
{
static inline uint16 normToUint16(double n)
{
return (uint16) (n * LOVE_UINT16_MAX);
}
int Font::fontCount = 0;
Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
@@ -272,18 +277,20 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
g.texture = t;
float tX = (float) textureX, tY = (float) textureY;
float tWidth = (float) textureWidth, tHeight = (float) textureHeight;
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, tX/tWidth, tY/tHeight},
{ 0.0f, float(h), tX/tWidth, (tY+h)/tHeight},
{float(w), 0.0f, (tX+w)/tWidth, tY/tHeight},
{float(w), float(h), (tX+w)/tWidth, (tY+h)/tHeight}
{float(0), float(0), normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
{float(0), float(h), normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
{float(w), float(0), normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
{float(w), float(h), normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
};
// Copy vertex data to the glyph and set proper bearing.
@@ -341,7 +348,7 @@ float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
return k;
}
void Font::codepointsFromString(const std::string &text, Codepoints &codepoints)
void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints)
{
codepoints.reserve(text.size());
@@ -362,12 +369,36 @@ void Font::codepointsFromString(const std::string &text, Codepoints &codepoints)
}
}
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)
{
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 == Color(255, 255, 255, 255))
codepoints.colors.pop_back();
}
}
float Font::getHeight() const
{
return (float) height;
}
std::vector<Font::DrawCommand> Font::generateVertices(const Codepoints &codepoints, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
Font::DrawCommands Font::generateVertices(const ColoredCodepoints &codepoints, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
{
// Spacing counter and newline handling.
float dx = offset.x;
@@ -377,17 +408,29 @@ std::vector<Font::DrawCommand> Font::generateVertices(const Codepoints &codepoin
int maxwidth = 0;
// Keeps track of when we need to switch textures in our vertex array.
std::vector<DrawCommand> drawcommands;
DrawCommands drawcmds;
// Pre-allocate space for the maximum possible number of vertices.
size_t vertstartsize = vertices.size();
vertices.reserve(vertstartsize + codepoints.size());
vertices.reserve(vertstartsize + codepoints.cps.size() * 4);
uint32 prevglyph = 0;
for (int i = 0; i < (int) codepoints.size(); i++)
Color curcolor(255, 255, 255, 255);
int curcolori = -1;
int ncolors = (int) codepoints.colors.size();
if (ncolors == 0 || (ncolors == 1 && codepoints.colors[0].color == curcolor))
drawcmds.usecolors = false;
else
drawcmds.usecolors = true;
for (int i = 0; i < (int) codepoints.cps.size(); i++)
{
uint32 g = codepoints[i];
uint32 g = codepoints.cps[i];
if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i)
curcolor = codepoints.colors[++curcolori].color;
if (g == '\n')
{
@@ -411,9 +454,11 @@ std::vector<Font::DrawCommand> Font::generateVertices(const Codepoints &codepoin
maxwidth = 0;
dx = offset.x;
dy = offset.y;
drawcommands.clear();
drawcmds.commands.clear();
vertices.resize(vertstartsize);
prevglyph = 0;
curcolori = -1;
curcolor = Color(255, 255, 255, 255);
continue;
}
@@ -422,26 +467,27 @@ std::vector<Font::DrawCommand> Font::generateVertices(const Codepoints &codepoin
if (glyph.texture != 0)
{
// Copy the vertices and set their proper relative positions.
// 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 (drawcommands.empty() || drawcommands.back().texture != glyph.texture)
if (drawcmds.commands.empty() || drawcmds.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;
drawcommands.push_back(cmd);
drawcmds.commands.push_back(cmd);
}
drawcommands.back().vertexcount += 4;
drawcmds.commands.back().vertexcount += 4;
}
// Advance the x position for the next glyph.
@@ -464,7 +510,7 @@ std::vector<Font::DrawCommand> Font::generateVertices(const Codepoints &codepoin
return a.startvertex < b.startvertex;
};
std::sort(drawcommands.begin(), drawcommands.end(), drawsort);
std::sort(drawcmds.commands.begin(), drawcmds.commands.end(), drawsort);
if (dx > maxwidth)
maxwidth = (int) dx;
@@ -475,28 +521,28 @@ std::vector<Font::DrawCommand> Font::generateVertices(const Codepoints &codepoin
info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y;
}
return drawcommands;
return drawcmds;
}
std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
Font::DrawCommands Font::generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
{
Codepoints codepoints;
codepointsFromString(text, codepoints);
ColoredCodepoints codepoints;
getCodepointsFromString(text, codepoints.cps);
return generateVertices(codepoints, vertices, extra_spacing, offset, info);
}
std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
Font::DrawCommands Font::generateVerticesFormatted(const ColoredCodepoints &text, 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.length() * 4);
DrawCommands drawcmds;
vertices.reserve(text.cps.size() * 4);
std::vector<int> widths;
std::vector<Codepoints> lines;
std::vector<ColoredCodepoints> lines;
getWrap(text, wrap, lines, &widths);
@@ -523,7 +569,7 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string
break;
case ALIGN_JUSTIFY:
{
float numspaces = (float) std::count(line.begin(), line.end(), ' ');
float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' ');
if (width < wrap && numspaces >= 1)
extraspacing = (wrap - width) / numspaces;
else
@@ -535,27 +581,29 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string
break;
}
std::vector<DrawCommand> commands = generateVertices(line, vertices, extraspacing, offset);
DrawCommands cmds = generateVertices(line, vertices, extraspacing, offset);
if (!commands.empty())
if (!cmds.commands.empty())
{
auto firstcmd = commands.begin();
auto firstcmd = cmds.commands.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())
if (!drawcmds.commands.empty())
{
auto prevcmd = drawcommands.back();
auto prevcmd = drawcmds.commands.back();
if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
{
drawcommands.back().vertexcount += firstcmd->vertexcount;
drawcmds.commands.back().vertexcount += firstcmd->vertexcount;
++firstcmd;
}
}
// Append the new draw commands to the list we're building.
drawcommands.insert(drawcommands.end(), firstcmd, commands.end());
drawcmds.commands.insert(drawcmds.commands.end(), firstcmd, cmds.commands.end());
drawcmds.usecolors = drawcmds.usecolors || cmds.usecolors;
}
y += getHeight() * getLineHeight();
@@ -570,19 +618,19 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string
if (cacheid != textureCacheID)
{
vertices.clear();
drawcommands = generateVerticesFormatted(text, wrap, align, vertices);
drawcmds = generateVerticesFormatted(text, wrap, align, vertices);
}
return drawcommands;
return drawcmds;
}
void Font::drawVertices(const std::vector<DrawCommand> &drawcommands)
void Font::drawVertices(const DrawCommands &drawcommands)
{
// Vertex attribute pointers need to be set before calling this function.
// This assumes that the attribute pointers are constant for all vertices.
int totalverts = 0;
for (const DrawCommand &cmd : drawcommands)
for (const DrawCommand &cmd : drawcommands.commands)
totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
if ((size_t) totalverts / 4 > quadIndices.getSize())
@@ -597,7 +645,7 @@ void Font::drawVertices(const std::vector<DrawCommand> &drawcommands)
// We need a separate draw call for every section of the text which uses a
// different texture than the previous section.
for (const DrawCommand &cmd : drawcommands)
for (const DrawCommand &cmd : drawcommands.commands)
{
GLsizei count = (cmd.vertexcount / 4) * 6;
size_t offset = (cmd.startvertex / 4) * 6 * elemsize;
@@ -608,9 +656,9 @@ void Font::drawVertices(const std::vector<DrawCommand> &drawcommands)
}
}
void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices)
void Font::printv(const Matrix4 &t, const DrawCommands &drawcommands, const std::vector<GlyphVertex> &vertices)
{
if (vertices.empty() || drawcommands.empty())
if (vertices.empty() || drawcommands.commands.empty())
return;
OpenGL::TempDebugGroup debuggroup("Font print");
@@ -618,28 +666,42 @@ void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands
OpenGL::TempTransform transform(gl);
transform.get() *= t;
gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].x);
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].s);
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(GlyphVertex), &vertices[0].s);
uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
if (drawcommands.usecolors)
{
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlyphVertex), &vertices[0].color.r);
enabledattribs |= ATTRIBFLAG_COLOR;
}
gl.useVertexAttribArrays(enabledattribs);
drawVertices(drawcommands);
}
void Font::print(const std::string &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
void Font::print(const std::vector<ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
ColoredCodepoints codepoints;
getCodepointsFromString(text, codepoints);
std::vector<GlyphVertex> vertices;
std::vector<DrawCommand> drawcommands = generateVertices(text, vertices);
DrawCommands drawcommands = generateVertices(codepoints, vertices);
Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
printv(t, drawcommands, vertices);
}
void Font::printf(const std::string &text, float x, float y, float wrap, AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
void Font::printf(const std::vector<ColoredString> &text, float x, float y, float wrap, AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
ColoredCodepoints codepoints;
getCodepointsFromString(text, codepoints);
std::vector<GlyphVertex> vertices;
std::vector<DrawCommand> drawcommands = generateVerticesFormatted(text, wrap, align, vertices);
DrawCommands drawcommands = generateVerticesFormatted(codepoints, wrap, align, vertices);
Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -690,104 +752,149 @@ int Font::getWidth(char character)
return g.spacing;
}
void Font::getWrap(const std::string &text, float wraplimit, std::vector<Codepoints> &lines, std::vector<int> *linewidths)
void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *linewidths)
{
// Split text at newlines.
std::string line;
std::istringstream iss(text);
// 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.
Color curcolor(255, 255, 255, 255);
bool addcurcolor = false;
int curcolori = -1;
int endcolori = (int) codepoints.colors.size() - 1;
// A wrapped line of text.
Codepoints wline;
ColoredCodepoints wline;
while (std::getline(iss, line, '\n'))
int i = 0;
while (i < (int) codepoints.cps.size())
{
float width = 0.0f;
float widthbeforelastspace = 0.0f;
float widthoftrailingspace = 0.0f;
uint32 prevglyph = 0;
uint32 c = codepoints.cps[i];
wline.clear();
wline.reserve(line.length());
try
// 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)
{
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());
curcolor = codepoints.colors[curcolori + 1].color;
curcolori++;
addcurcolor = true;
}
utf8::iterator<std::string::const_iterator> lastspaceit = end;
// Split text at newlines.
if (c == '\n')
{
lines.push_back(wline);
while (i != end)
// 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;
}
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)
{
uint32 c = *i;
// '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();
const Glyph &g = findGlyph(c);
float charwidth = g.spacing + getKerning(prevglyph, c);
float newwidth = width + charwidth;
while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size())
wline.colors.pop_back();
// 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)
// Also 'rewind' to the color that the last character is using.
for (int colori = curcolori; colori >= 0; colori--)
{
// If this is the first character in the line and it exceeds
// the limit, skip it completely.
if (wline.empty())
++i;
else if (lastspaceit != end)
if (codepoints.colors[colori].index <= lastspaceindex)
{
// 'Rewind' to the last seen space, if the line has one.
// FIXME: This should preferably use vector::erase.
while (!wline.empty() && wline.back() != ' ')
wline.pop_back();
// Ignore the width of trailing spaces in wrapped lines.
width = widthbeforelastspace;
i = lastspaceit;
++i; // Start the next line after the space.
curcolor = codepoints.colors[colori].color;
curcolori = colori;
break;
}
lines.push_back(wline);
if (linewidths)
linewidths->push_back(width);
prevglyph = 0; // Reset kerning information.
width = widthbeforelastspace = widthoftrailingspace = 0.0f;
wline.clear();
lastspaceit = end;
continue;
}
if (prevglyph != ' ' && c == ' ')
widthbeforelastspace = width;
// Ignore the width of trailing spaces in wrapped lines.
width = widthbeforelastspace;
width = newwidth;
prevglyph = c;
wline.push_back(c);
// Keep track of the last seen space, so we can "rewind" to it
// when wrapping.
if (c == ' ')
{
lastspaceit = i;
widthoftrailingspace += charwidth;
}
else if (c != '\n')
widthoftrailingspace = 0.0f;
++i;
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;
}
catch (utf8::exception &e)
if (prevglyph != ' ' && c == ' ')
widthbeforelastspace = width;
width = newwidth;
prevglyph = c;
if (addcurcolor)
{
throw love::Exception("UTF-8 decoding error: %s", e.what());
wline.colors.push_back({curcolor, (int) wline.cps.size()});
addcurcolor = false;
}
// Remove trailing newlines.
if (!wline.empty() && wline.back() == '\n')
wline.pop_back();
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.
@@ -798,17 +905,20 @@ void Font::getWrap(const std::string &text, float wraplimit, std::vector<Codepoi
void Font::getWrap(const std::string &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
{
std::vector<Codepoints> codepointlines;
getWrap(text, wraplimit, codepointlines, linewidths);
ColoredCodepoints codepoints;
getCodepointsFromString(text, codepoints.cps);
std::vector<ColoredCodepoints> codepointlines;
getWrap(codepoints, wraplimit, codepointlines, linewidths);
std::string line;
for (const Codepoints &codepoints : codepointlines)
for (const ColoredCodepoints &codepoints : codepointlines)
{
line.clear();
line.reserve(codepoints.size());
line.reserve(codepoints.cps.size());
for (uint32 codepoint : codepoints)
for (uint32 codepoint : codepoints.cps)
{
char character[5] = {'\0'};
char *end = utf8::unchecked::append(codepoint, character);
+38 -11
View File
@@ -61,10 +61,29 @@ public:
ALIGN_MAX_ENUM
};
struct ColoredString
{
std::string str;
Color color;
};
struct IndexedColor
{
Color color;
int index;
};
struct ColoredCodepoints
{
std::vector<uint32> cps;
std::vector<IndexedColor> colors;
};
struct GlyphVertex
{
float x, y;
float s, t;
float x, y;
uint16 s, t;
Color color;
};
struct TextInfo
@@ -81,16 +100,25 @@ public:
int vertexcount;
};
struct DrawCommands
{
std::vector<DrawCommand> commands;
bool usecolors;
};
Font(love::font::Rasterizer *r, const Texture::Filter &filter = Texture::getDefaultFilter());
virtual ~Font();
std::vector<DrawCommand> generateVertices(const Codepoints &str, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr);
std::vector<DrawCommand> generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = Vector(), TextInfo *info = nullptr);
DrawCommands generateVertices(const ColoredCodepoints &codepoints, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr);
DrawCommands generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = Vector(), TextInfo *info = nullptr);
std::vector<DrawCommand> generateVerticesFormatted(const std::string &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
DrawCommands generateVerticesFormatted(const ColoredCodepoints &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
void drawVertices(const std::vector<DrawCommand> &drawcommands);
void drawVertices(const DrawCommands &drawcommands);
static void getCodepointsFromString(const std::string &str, Codepoints &codepoints);
static void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
/**
* Prints the text at the designated position with rotation and scaling.
@@ -106,9 +134,9 @@ public:
* @param kx Shear along the x axis.
* @param ky Shear along the y axis.
**/
void print(const std::string &text, float x, float y, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
void print(const std::vector<ColoredString> &text, float x, float y, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
void printf(const std::string &text, float x, float y, float wrap, AlignMode align, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
void printf(const std::vector<ColoredString> &text, float x, float y, float wrap, AlignMode align, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
/**
* Returns the height of the font.
@@ -139,7 +167,7 @@ public:
* Returns a vector with the lines.
**/
void getWrap(const std::string &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *line_widths = nullptr);
void getWrap(const std::string &text, float wraplimit, std::vector<Codepoints> &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,
@@ -205,8 +233,7 @@ private:
const Glyph &addGlyph(uint32 glyph);
const Glyph &findGlyph(uint32 glyph);
float getKerning(uint32 leftglyph, uint32 rightglyph);
void codepointsFromString(const std::string &str, Codepoints &codepoints);
void printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices);
void printv(const Matrix4 &t, const DrawCommands &drawcommands, const std::vector<GlyphVertex> &vertices);
std::vector<StrongRef<love::font::Rasterizer>> rasterizers;
+3 -3
View File
@@ -814,7 +814,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::string &text)
Text *Graphics::newText(Font *font, const std::vector<Font::ColoredString> &text)
{
return new Text(font, text);
}
@@ -1105,7 +1105,7 @@ bool Graphics::isWireframe() const
return states.back().wireframe;
}
void Graphics::print(const std::string &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
void Graphics::print(const std::vector<Font::ColoredString> &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
checkSetDefaultFont();
@@ -1115,7 +1115,7 @@ void Graphics::print(const std::string &str, float x, float y , float angle, flo
state.font->print(str, x, y, angle, sx, sy, ox, oy, kx, ky);
}
void Graphics::printf(const std::string &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
void Graphics::printf(const std::vector<Font::ColoredString> &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
checkSetDefaultFont();
+3 -3
View File
@@ -183,7 +183,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::string &text = "");
Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
bool isGammaCorrect() const;
@@ -328,7 +328,7 @@ public:
* @param kx Shear along the x-axis.
* @param ky Shear along the y-axis.
**/
void print(const std::string &str, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
void print(const std::vector<Font::ColoredString> &str, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
/**
* Draw formatted text on screen at the specified coordinates.
@@ -346,7 +346,7 @@ public:
* @param kx Shear along the x-axis.
* @param ky Shear along the y-axis.
**/
void printf(const std::string &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
void printf(const std::vector<Font::ColoredString> &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
/**
* Draws a point at (x,y).
+47 -36
View File
@@ -30,7 +30,7 @@ namespace graphics
namespace opengl
{
Text::Text(Font *font, const std::string &text)
Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
: font(font)
, vbo(nullptr)
, text_info()
@@ -111,13 +111,13 @@ void Text::regenerateVertices()
void Text::addTextData(const TextData &t)
{
std::vector<Font::GlyphVertex> vertices;
std::vector<Font::DrawCommand> new_commands;
Font::DrawCommands new_commands;
// We only have formatted text if the align mode is valid.
if (t.align == Font::ALIGN_MAX_ENUM)
new_commands = font->generateVertices(t.text, vertices, 0.0f, Vector(0.0f, 0.0f), &text_info);
new_commands = font->generateVertices(t.codepoints, vertices, 0.0f, Vector(0.0f, 0.0f), &text_info);
else
new_commands = font->generateVerticesFormatted(t.text, t.wrap, t.align, vertices, &text_info);
new_commands = font->generateVerticesFormatted(t.codepoints, t.wrap, t.align, vertices, &text_info);
if (t.use_matrix)
t.matrix.transform(&vertices[0], &vertices[0], (int) vertices.size());
@@ -127,34 +127,37 @@ void Text::addTextData(const TextData &t)
if (!t.append_vertices)
{
voffset = 0;
draw_commands.clear();
draw_commands.commands.clear();
draw_commands.usecolors = false;
}
uploadVertices(vertices, voffset);
if (!new_commands.empty())
if (!new_commands.commands.empty())
{
// The start vertex should be adjusted to account for the vertex offset.
for (Font::DrawCommand &cmd : new_commands)
for (Font::DrawCommand &cmd : new_commands.commands)
cmd.startvertex += (int) voffset;
auto firstcmd = new_commands.begin();
auto firstcmd = new_commands.commands.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 (!draw_commands.empty())
if (!draw_commands.commands.empty())
{
auto prevcmd = draw_commands.back();
auto prevcmd = draw_commands.commands.back();
if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
{
draw_commands.back().vertexcount += firstcmd->vertexcount;
draw_commands.commands.back().vertexcount += firstcmd->vertexcount;
++firstcmd;
}
}
// Append the new draw commands to the list we're building.
draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
draw_commands.commands.insert(draw_commands.commands.end(), firstcmd, new_commands.commands.end());
draw_commands.usecolors = draw_commands.usecolors || new_commands.usecolors;
}
vert_offset = voffset + vertices.size();
@@ -165,20 +168,20 @@ void Text::addTextData(const TextData &t)
regenerateVertices();
}
void Text::set(const std::string &text)
void Text::set(const std::vector<Font::ColoredString> &text)
{
if (text.empty())
return set();
addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, false, false, Matrix3()});
return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
}
void Text::set(const std::string &text, float wrap, Font::AlignMode align)
void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
{
if (text.empty())
if (text.empty() || (text.size() == 1 && text[0].str.empty()))
return set();
addTextData({text, wrap, align, false, false, Matrix3()});
Font::ColoredCodepoints codepoints;
Font::getCodepointsFromString(text, codepoints);
addTextData({codepoints, wrap, align, false, false, Matrix3()});
}
void Text::set()
@@ -186,30 +189,29 @@ void Text::set()
clear();
}
void Text::add(const std::string &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
void Text::add(const std::vector<Font::ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
if (text.empty())
return;
Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, true, true, m});
return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, x, y, angle, sx, sy, ox, oy, kx, ky);
}
void Text::addf(const std::string &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
void Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
if (text.empty())
if (text.empty() || (text.size() == 1 && text[0].str.empty()))
return;
Font::ColoredCodepoints codepoints;
Font::getCodepointsFromString(text, codepoints);
Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
addTextData({text, wrap, align, true, true, m});
addTextData({codepoints, wrap, align, true, true, m});
}
void Text::clear()
{
text_data.clear();
draw_commands.clear();
draw_commands.commands.clear();
draw_commands.usecolors = false;
texture_cache_id = font->getTextureCacheID();
text_info = {};
vert_offset = 0;
@@ -217,7 +219,7 @@ void Text::clear()
void Text::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
if (vbo == nullptr || draw_commands.empty())
if (vbo == nullptr || draw_commands.commands.empty())
return;
OpenGL::TempDebugGroup debuggroup("Text object draw");
@@ -226,8 +228,9 @@ void Text::draw(float x, float y, float angle, float sx, float sy, float ox, flo
if (font->getTextureCacheID() != texture_cache_id)
regenerateVertices();
const size_t pos_offset = offsetof(Font::GlyphVertex, x);
const size_t tex_offset = offsetof(Font::GlyphVertex, s);
const size_t pos_offset = offsetof(Font::GlyphVertex, x);
const size_t tex_offset = offsetof(Font::GlyphVertex, s);
const size_t color_offset = offsetof(Font::GlyphVertex, color.r);
const size_t stride = sizeof(Font::GlyphVertex);
OpenGL::TempTransform transform(gl);
@@ -239,10 +242,18 @@ void Text::draw(float x, float y, float angle, float sx, float sy, float ox, flo
// Font::drawVertices expects AttribPointer calls to be done already.
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(pos_offset));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(tex_offset));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, vbo->getPointer(tex_offset));
if (draw_commands.usecolors)
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, vbo->getPointer(color_offset));
}
gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
if (draw_commands.usecolors)
enabledattribs |= ATTRIBFLAG_COLOR;
gl.useVertexAttribArrays(enabledattribs);
font->drawVertices(draw_commands);
}
+7 -7
View File
@@ -38,15 +38,15 @@ class Text : public Drawable
{
public:
Text(Font *font, const std::string &text = "");
Text(Font *font, const std::vector<Font::ColoredString> &text = {});
virtual ~Text();
void set(const std::string &text);
void set(const std::string &text, float wrap, Font::AlignMode align);
void set(const std::vector<Font::ColoredString> &text);
void set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align);
void set();
void add(const std::string &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
void addf(const std::string &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
void add(const std::vector<Font::ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
void addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
void clear();
// Implements Drawable.
@@ -69,7 +69,7 @@ private:
struct TextData
{
std::string text;
Font::ColoredCodepoints codepoints;
float wrap;
Font::AlignMode align;
bool use_matrix;
@@ -84,7 +84,7 @@ private:
StrongRef<Font> font;
GLBuffer *vbo;
std::vector<Font::DrawCommand> draw_commands;
Font::DrawCommands draw_commands;
std::vector<TextData> text_data;
Font::TextInfo text_info;
@@ -851,7 +851,9 @@ int w_newText(lua_State *L)
luax_catchexcept(L, [&](){ t = instance()->newText(font); });
else
{
std::string text = luax_checkstring(L, 2);
std::vector<Font::ColoredString> text;
luax_checkcoloredstring(L, 2, text);
luax_catchexcept(L, [&](){ t = instance()->newText(font, text); });
}
@@ -1466,7 +1468,9 @@ int w_draw(lua_State *L)
int w_print(lua_State *L)
{
std::string str = luax_checkstring(L, 1);
std::vector<Font::ColoredString> str;
luax_checkcoloredstring(L, 1, str);
float x = (float)luaL_optnumber(L, 2, 0.0);
float y = (float)luaL_optnumber(L, 3, 0.0);
float angle = (float)luaL_optnumber(L, 4, 0.0f);
@@ -1485,7 +1489,9 @@ int w_print(lua_State *L)
int w_printf(lua_State *L)
{
std::string str = luax_checkstring(L, 1);
std::vector<Font::ColoredString> str;
luax_checkcoloredstring(L, 1, str);
float x = (float)luaL_checknumber(L, 2);
float y = (float)luaL_checknumber(L, 3);
float wrap = (float)luaL_checknumber(L, 4);
+54 -5
View File
@@ -32,6 +32,47 @@ Text *luax_checktext(lua_State *L, int idx)
return luax_checktype<Text>(L, idx, GRAPHICS_TEXT_ID);
}
void luax_checkcoloredstring(lua_State *L, int idx, std::vector<Font::ColoredString> &strings)
{
Font::ColoredString coloredstr;
coloredstr.color = Color(255, 255, 255, 255);
if (lua_istable(L, idx))
{
int len = 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 = (unsigned char) luaL_checknumber(L, -4);
coloredstr.color.g = (unsigned char) luaL_checknumber(L, -3);
coloredstr.color.b = (unsigned char) luaL_checknumber(L, -2);
coloredstr.color.a = (unsigned char) luaL_optnumber(L, -1, 255);
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);
@@ -44,7 +85,8 @@ int w_Text_set(lua_State *L)
else if (lua_isnoneornil(L, 3))
{
// Single argument: unformatted text.
std::string newtext = luax_checkstring(L, 2);
std::vector<Font::ColoredString> newtext;
luax_checkcoloredstring(L, 2, newtext);
luax_catchexcept(L, [&](){ t->set(newtext); });
}
else
@@ -57,7 +99,8 @@ int w_Text_set(lua_State *L)
if (!Font::getConstant(alignstr, align))
return luaL_error(L, "Invalid align mode: %s", alignstr);
std::string newtext = luax_checkstring(L, 2);
std::vector<Font::ColoredString> newtext;
luax_checkcoloredstring(L, 2, newtext);
luax_catchexcept(L, [&](){ t->set(newtext, wraplimit, align); });
}
@@ -76,7 +119,8 @@ int w_Text_setf(lua_State *L)
if (!Font::getConstant(alignstr, align))
return luaL_error(L, "Invalid align mode: %s", alignstr);
std::string newtext = luax_checkstring(L, 2);
std::vector<Font::ColoredString> newtext;
luax_checkcoloredstring(L, 2, newtext);
luax_catchexcept(L, [&](){ t->set(newtext, wraplimit, align); });
@@ -86,7 +130,9 @@ int w_Text_setf(lua_State *L)
int w_Text_add(lua_State *L)
{
Text *t = luax_checktext(L, 1);
std::string text = luax_checkstring(L, 2);
std::vector<Font::ColoredString> text;
luax_checkcoloredstring(L, 2, text);
float x = (float) luaL_optnumber(L, 3, 0.0);
float y = (float) luaL_optnumber(L, 4, 0.0);
@@ -105,7 +151,10 @@ int w_Text_add(lua_State *L)
int w_Text_addf(lua_State *L)
{
Text *t = luax_checktext(L, 1);
std::string text = luax_checkstring(L, 2);
std::vector<Font::ColoredString> text;
luax_checkcoloredstring(L, 2, text);
float wrap = (float) luaL_checknumber(L, 3);
Font::AlignMode align = Font::ALIGN_MAX_ENUM;
+1
View File
@@ -32,6 +32,7 @@ namespace opengl
{
Text *luax_checktext(lua_State *L, int idx);
void luax_checkcoloredstring(lua_State *L, int idx, std::vector<Font::ColoredString> &strings);
int w_Text_set(lua_State *L);
int w_Text_setf(lua_State *L);
int w_Text_add(lua_State *L);