فهرست منبع

Fixing existing Font::drawText() method.
Adding Font::drawText() that clips within a viewport while applying alignment and wrapping.
Adding two versions of Font::measureText() -- a simple one that gives width and height, and a complex one that applies clipping / alignment / wrapping within a viewport.

Adam Blake 14 سال پیش
والد
کامیت
c2ae3cc0a0
2فایلهای تغییر یافته به همراه858 افزوده شده و 18 حذف شده
  1. 793 18
      gameplay/src/Font.cpp
  2. 65 0
      gameplay/src/Font.h

+ 793 - 18
gameplay/src/Font.cpp

@@ -178,26 +178,26 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color)
     for (int i = 0; i < length; ++i)
     for (int i = 0; i < length; ++i)
     {
     {
         char c = text[i];
         char c = text[i];
-        unsigned int index = c - 32; // HACK for ASCII
-        if (index >= 0 && index < _glyphCount)
-        {
-            Glyph& g = _glyphs[index];
 
 
-            // Draw this character.
-            switch (c)
+        // Draw this character.
+        switch (c)
+        {
+        case ' ':
+            xPos += _size>>1;
+            break;
+        case '\r':
+        case '\n':
+            yPos += _size;
+            xPos = x;
+            break;
+        case '\t':
+            xPos += (_size>>1)+4;
+            break;
+        default:
+            unsigned int index = c - 32; // HACK for ASCII
+            if (index >= 0 && index < _glyphCount)
             {
             {
-            case ' ':
-                xPos += _size>>1;
-                break;
-            case '\r':
-            case '\n':
-                yPos += _size;
-                xPos = x;
-                break;
-            case '\t':
-                xPos += (_size>>1)+4;
-                break;
-            default:
+                Glyph& g = _glyphs[index];
                 _batch->draw(xPos, yPos, g.width, _size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
                 _batch->draw(xPos, yPos, g.width, _size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
                 xPos += g.width + (_size>>3);
                 xPos += g.width + (_size>>3);
                 break;
                 break;
@@ -206,9 +206,784 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color)
     }
     }
 }
 }
 
 
+void Font::drawText(const char* text, const Rectangle& area, const Vector4& color, Justify justify, bool wrap)
+{
+    const int length = strlen(text);
+    int yPos = area.y;
+    int fontSize = (int)_size;
+
+    Justify vAlign = static_cast<Justify>(justify & 0xF0);
+    if (vAlign == 0)
+    {
+        vAlign = ALIGN_TOP;
+    }
+
+    Justify hAlign = static_cast<Justify>(justify & 0x0F);
+    if (hAlign == 0)
+    {
+        hAlign = ALIGN_LEFT;
+    }
+
+    // For alignments other than top-left, need to calculate the y position to begin drawing from
+    // and the starting x position of each line.
+    std::vector<int> xPositions;
+    if (vAlign != ALIGN_TOP || hAlign != ALIGN_LEFT)
+    {
+        int lineWidth = 0;
+        int delimWidth = 0;
+        char* token;
+
+        token = const_cast<char*>(text);
+        if (wrap)
+        {
+            // Go a word at a time.
+            bool reachedEOF = false;
+            while (token[0] != 0)
+            {
+                unsigned int tokenWidth = 0;
+
+                // Handle delimiters until next token.
+                char delimiter = token[0];
+                while (delimiter == ' ' ||
+                       delimiter == '\t' ||
+                       delimiter == '\r' ||
+                       delimiter == '\n' ||
+                       //delimiter == '-' ||
+                       delimiter == 0)
+                {
+                    switch (delimiter)
+                    {
+                        case ' ':
+                            delimWidth += fontSize>>1;
+                            break;
+                        case '\r':
+                        case '\n':
+                            yPos += fontSize;
+
+                            if (lineWidth > 0)
+                            {
+                                int hWhitespace = area.width - lineWidth;
+                                if (hAlign == ALIGN_HCENTER)
+                                {
+                                    xPositions.push_back(area.x + hWhitespace / 2);
+                                }
+                                else if (hAlign == ALIGN_RIGHT)
+                                {
+                                    xPositions.push_back(area.x + hWhitespace);
+                                }
+                            }
+
+                            lineWidth = 0;
+                            delimWidth = 0;
+                            break;
+                        case '\t':
+                            delimWidth += (fontSize>>1)+4;
+                            break;
+                        case 0:
+                            reachedEOF = true;
+                            break;
+                            /*
+                        case '-':
+                            unsigned int glyphIndex = delimiter - 32;
+                            Glyph& g = _glyphs[glyphIndex];
+                            tokenWidth += g.width + (_size>>3);
+                            //lineWidth += g.width + (_size>>3);
+                            //delimWidth += g.width + (_size>>3);
+                            break;*/
+                    }
+
+                    if (reachedEOF)
+                    {
+                        break;
+                    }
+
+                    token++;
+                    delimiter = token[0];
+                }
+
+                if (reachedEOF || token == NULL)
+                {
+                    break;
+                }
+
+                unsigned int tokenLength = strcspn(token, " \r\n\t");
+                tokenWidth += getTokenWidth(token, tokenLength);
+
+                // Wrap if necessary.
+                if (lineWidth + tokenWidth + delimWidth > area.width)
+                {
+                    yPos += fontSize;
+
+                    // Push position of current line.
+                    int hWhitespace = area.width - lineWidth;
+
+                    if (hAlign == ALIGN_HCENTER)
+                    {
+                        xPositions.push_back(area.x + hWhitespace / 2);
+                    }
+                    else if (hAlign == ALIGN_RIGHT)
+                    {
+                        xPositions.push_back(area.x + hWhitespace);
+                    }
+
+                    // Move token to the next line.
+                    lineWidth = 0;
+                    delimWidth = 0;
+                }
+                else
+                {
+                    lineWidth += delimWidth;
+                    delimWidth = 0;
+                }
+
+                lineWidth += tokenWidth;
+                token += tokenLength;
+            }
+
+            // Final calculation of vertical position.
+            int textHeight = yPos - area.y;
+            int vWhiteSpace = area.height - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                yPos = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                yPos = area.y + vWhiteSpace;
+            }
+
+            // Calculation of final horizontal position.
+            int hWhitespace = area.width - lineWidth;
+            if (hAlign == ALIGN_HCENTER)
+            {
+                xPositions.push_back(area.x + hWhitespace / 2);
+            }
+            else if (hAlign == ALIGN_RIGHT)
+            {
+                xPositions.push_back(area.x + hWhitespace);
+            }
+        }
+        else
+        {
+            // Go a line at a time.
+            while (token[0] != 0)
+            {
+                char delimiter = token[0];
+                while (delimiter == '\n')
+                {
+                    yPos += fontSize;
+                    ++token;
+                    delimiter = token[0];
+                }
+
+                unsigned int tokenLength = strcspn(token, "\n");
+                if (tokenLength == 0)
+                {
+                    tokenLength = strlen(token);
+                }
+
+                int lineWidth = getTokenWidth(token, tokenLength);
+                int whitespace = area.width - lineWidth;
+                if (hAlign == ALIGN_HCENTER)
+                {
+                    xPositions.push_back(area.x + whitespace / 2);
+                }
+                else if (hAlign == ALIGN_RIGHT)
+                {
+                    xPositions.push_back(area.x + whitespace);
+                }
+
+                token += tokenLength;
+            }
+
+            int textHeight = yPos - area.y;
+            int vWhiteSpace = area.height - textHeight;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                yPos = area.y + vWhiteSpace / 2;
+            }
+            else if (vAlign == ALIGN_BOTTOM)
+            {
+                yPos = area.y + vWhiteSpace;
+            }
+        }
+
+        if (vAlign == ALIGN_TOP)
+        {
+            yPos = area.y;
+        }
+    }
+
+    // Now we have the info we need in order to render.
+    int xPos = area.x;
+    std::vector<int>::const_iterator xPositionsIt = xPositions.begin();
+    if (xPositionsIt != xPositions.end())
+    {
+        xPos = *xPositionsIt++;
+    }
+
+    char* token = const_cast<char*>(text);
+    while (token[0] != 0)
+    {
+        // Handle delimiters until next token.
+        char delimiter = token[0];
+        bool nextLine = true;
+        bool reachedEOF = false;
+        while (delimiter == ' ' ||
+               delimiter == '\t' ||
+               delimiter == '\r' ||
+               delimiter == '\n' ||
+               //delimiter == '-' ||
+               delimiter == 0)
+        {
+            switch (delimiter)
+            {
+                case ' ':
+                    xPos += fontSize>>1;
+                    break;
+                case '\r':
+                case '\n':
+                    yPos += fontSize;
+
+                    // Only use next xPos for first newline character (in case of multiple consecutive newlines).
+                    if (nextLine)
+                    {
+                        if (xPositionsIt != xPositions.end())
+                        {
+                            xPos = *xPositionsIt++;
+                        }
+                        else
+                        {
+                            xPos = area.x;
+                        }
+                        nextLine = false;
+                    }
+                    break;
+                case '\t':
+                    xPos += (fontSize>>1)+4;
+                    break;
+                case 0:
+                    reachedEOF = true;
+                    break;
+                /*case '-':
+                    // Dash is special -- a delimiter that isn't whitespace.
+                    unsigned int glyphIndex = delimiter - 32;
+                    Glyph& g = _glyphs[glyphIndex];
+                    _batch->draw(xPos, yPos, g.width, _size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
+                    xPos += g.width + (_size>>3);
+
+                    // Get next xPos again (in case of a substring such as "\n-\n").
+                    nextLine = true;
+                    break;*/
+            }
+
+            if (reachedEOF)
+            {
+                break;
+            }
+
+            token++;
+            delimiter = token[0];
+        }
+
+        if (reachedEOF || token == NULL)
+        {
+            break;
+        }
+
+        unsigned int tokenLength = strcspn(token, " \r\n\t");
+        delimiter = token[tokenLength];
+        bool truncated = false;
+        unsigned int tokenWidth = getTokenWidth(token, tokenLength);
+
+        // Wrap if necessary.
+        if (wrap &&
+            xPos + tokenWidth > area.x + area.width)
+        {
+            yPos += fontSize;
+
+            if (xPositionsIt != xPositions.end())
+            {
+                xPos = *xPositionsIt++;
+            }
+            else
+            {
+                xPos = area.x;
+            }
+        }
+
+        bool draw = true;
+        if (yPos < area.y)
+        {
+            // Skip drawing until linebreak or wrap.
+            draw = false;
+        }
+        else if (yPos > area.y + area.height)
+        {
+            // Truncate below area's vertical limit.
+            break;
+        }
+
+        for (unsigned int i = 0; i < tokenLength; ++i)
+        {
+            char c = token[i];
+            unsigned int glyphIndex = c - 32; // HACK for ASCII
+        
+            if (glyphIndex >= 0 && glyphIndex < _glyphCount)
+            {
+                Glyph& g = _glyphs[glyphIndex];
+
+                if (xPos + (int)g.width >= area.x + area.width)
+                {
+                    // Truncate this line and go on to the next one.
+                    truncated = true;
+                    break;
+                }
+                else if (xPos >= area.x)
+                {
+                    // Draw this character.
+                    if (draw)
+                    {
+                        _batch->draw(xPos, yPos, g.width, _size, g.uvs[0], g.uvs[1], g.uvs[2], g.uvs[3], color);
+                    }
+                }
+                xPos += g.width + (fontSize>>3);
+            }
+        }
+
+        if (!truncated)
+        {
+            // Get next token.
+            token += tokenLength;
+        }
+        else
+        {
+            // Skip the rest of this line.
+            unsigned int tokenLength = strcspn(token, "\n");
+
+            if (tokenLength > 0)
+            {
+                // Move x position to that of the next line.
+                /*
+                if (xPositionsIt != xPositions.end())
+                {
+                    xPos = *xPositionsIt++;
+                }
+                else
+                {
+                    xPos = area.x;
+                }*/
+                
+                // Get first token of next line.
+                token += tokenLength;
+            }
+        }
+    }
+}
+
 void Font::end()
 void Font::end()
 {
 {
     _batch->end();
     _batch->end();
 }
 }
 
 
+void Font::measureText(const char* text, unsigned int* width, unsigned int* height)
+{
+    const int length = strlen(text);
+    int index = 0;
+    char* token;
+    char* textCopy = new char[length+1];
+    strncpy(textCopy, text, length);
+    textCopy[length] = 0;
+
+    // Measure each line.
+    // if (lineWidth > maxWidth) maxWidth = lineWidth;
+    // height += lineHeight
+
+    *width = 0;
+    *height = 0;
+
+    token = strtok(textCopy, "\n");
+    while (token != NULL)
+    {
+        while (text[index] == '\n')
+        {
+            *height += _size;
+            ++index;
+        }
+
+        int tokenLength = strlen(token);
+        unsigned int tokenWidth = getTokenWidth(token, tokenLength);
+        if (tokenWidth > *width)
+        {
+            *width = tokenWidth;
+        }
+
+        *height += _size;
+        token = strtok(NULL, "\n");
+        index += tokenLength + 1;
+    }
+}
+
+void Font::measureText(const char* text, const Rectangle& viewport, Justify justify, bool wrap, bool clipped, Rectangle* out)
+{
+    Justify vAlign = static_cast<Justify>(justify & 0xF0);
+    Justify hAlign = static_cast<Justify>(justify & 0x0F);
+
+    int fontSize = (int)_size;
+
+    const int length = strlen(text);
+    int index = 0;
+    char* token = const_cast<char*>(text);
+
+    unsigned int lineWidth = 0;
+    unsigned int delimWidth = 0;
+    int emptyLinesCount = 0;
+    std::vector<bool> emptyLines;
+    std::vector<Vector2> lines;
+
+    int x = INT_MAX;
+    int y = viewport.y;
+    unsigned int width = 0;
+    int yPos = viewport.y;
+
+    if (wrap)
+    {
+        bool reachedEOF = false;
+        while (token[0] != 0)
+        {
+            // Handle delimiters until next token.
+            char delimiter = token[0];
+            while (delimiter == ' ' ||
+                    delimiter == '\t' ||
+                    delimiter == '\r' ||
+                    delimiter == '\n' ||
+                    delimiter == '-' ||
+                    delimiter == 0)
+            {
+                switch (delimiter)
+                {
+                    case ' ':
+                        delimWidth += fontSize>>1;
+                        break;
+                    case '\r':
+                    case '\n':
+                        yPos += fontSize;
+
+                        if (lineWidth > 0)
+                        {
+                            emptyLines.push_back(false);
+
+                            int hWhitespace = viewport.width - lineWidth;
+                            int xPos = viewport.x;
+                            if (hAlign == ALIGN_HCENTER)
+                            {
+                                xPos = viewport.x + hWhitespace / 2;
+                            }
+                            else if (hAlign == ALIGN_RIGHT)
+                            {
+                                xPos = viewport.x + hWhitespace;
+                            }
+
+                            lines.push_back(Vector2(xPos, lineWidth));
+                            if (xPos < x)
+                            {
+                                x = xPos;
+                            }
+                            if (lineWidth > width)
+                            {
+                                width = lineWidth;
+                            }
+                        }
+                        else
+                        {
+                            emptyLines.push_back(true);
+                            lines.push_back(Vector2(FLT_MAX, 0));
+                        }
+
+                        lineWidth = 0;
+                        delimWidth = 0;
+                        break;
+                    case '\t':
+                        delimWidth += (fontSize>>1)+4;
+                        break;
+                    case 0:
+                        reachedEOF = true;
+                        break;
+                    case '-':
+                        unsigned int glyphIndex = delimiter - 32;
+                        Glyph& g = _glyphs[glyphIndex];
+                        lineWidth += g.width + (fontSize>>3);
+                        break;
+                }
+
+                if (reachedEOF)
+                {
+                    break;
+                }
+
+                token++;
+                delimiter = token[0];
+            }
+
+            if (reachedEOF || token == NULL)
+            {
+                break;
+            }
+
+            unsigned int tokenLength = strcspn(token, " -\r\n\t");
+            unsigned int tokenWidth = getTokenWidth(token, tokenLength);
+
+            // Wrap if necessary.
+            if (lineWidth + tokenWidth + delimWidth > viewport.width)
+            {
+                emptyLines.push_back(false);
+
+                yPos += fontSize;
+
+                int hWhitespace = viewport.width - lineWidth;
+                int xPos = viewport.x;
+                if (hAlign == ALIGN_HCENTER)
+                {
+                    xPos = viewport.x + hWhitespace / 2;
+                }
+                else if (hAlign == ALIGN_RIGHT)
+                {
+                    xPos = viewport.x + hWhitespace;
+                }
+
+                lines.push_back(Vector2(xPos, lineWidth));
+                if (xPos < x)
+                {
+                    x = xPos;
+                }
+                if (lineWidth > width)
+                {
+                    width = lineWidth;
+                }
+
+                lineWidth = 0;
+            }
+            else
+            {
+                lineWidth += delimWidth;
+            }
+
+            delimWidth = 0;
+            lineWidth += tokenWidth;
+            token += tokenLength;
+        }
+    }
+    else
+    {
+        unsigned int lineWidth = 0;
+        while (token[0] != 0)
+        {
+            bool nextLine = true;
+            while (token[0] == '\n')
+            {
+                if (nextLine)
+                {
+                    yPos += fontSize * (emptyLinesCount+1);
+                    nextLine = false;
+                    emptyLinesCount = 0;
+                    emptyLines.push_back(false);
+                }
+                else
+                {
+                    ++emptyLinesCount;
+                    emptyLines.push_back(true);
+                    lines.push_back(Vector2(FLT_MAX, 0));
+                }
+                
+                token++;
+            }
+
+            unsigned int tokenLength = strcspn(token, "\n");
+            lineWidth = getTokenWidth(token, tokenLength);
+            
+            int xPos = viewport.x;
+            int hWhitespace = viewport.width - lineWidth;
+            if (hAlign == ALIGN_HCENTER)
+            {
+                xPos = viewport.x + hWhitespace / 2;
+            }
+            else if (hAlign == ALIGN_RIGHT)
+            {
+                xPos = viewport.x + hWhitespace;
+            }
+
+            lines.push_back(Vector2(xPos, lineWidth));
+            if (xPos < x)
+            {
+                x = xPos;
+            }
+            if (lineWidth > width)
+            {
+                width = lineWidth;
+            }
+
+            token += tokenLength;
+        }
+
+        yPos += fontSize;
+    }
+
+    int hWhitespace = viewport.width - lineWidth;
+    int xPos = viewport.x;
+    if (hAlign == ALIGN_HCENTER)
+    {
+        xPos = viewport.x + hWhitespace / 2;
+    }
+    else if (hAlign == ALIGN_RIGHT)
+    {
+        xPos = viewport.x + hWhitespace;
+    }
+
+    if (wrap)
+    {
+        lines.push_back(Vector2(xPos, lineWidth));
+    }
+
+    if (xPos < x)
+    {
+        x = xPos;
+    }
+    if (lineWidth > width)
+    {
+        width = lineWidth;
+    }
+
+    int textHeight = yPos - viewport.y;
+    int vWhitespace = viewport.height - textHeight;
+    if (vAlign == ALIGN_VCENTER)
+    {
+        y = viewport.y + vWhitespace / 2;
+    }
+    else if (vAlign == ALIGN_BOTTOM)
+    {
+        y = viewport.y + vWhitespace;
+    }
+    else
+    {
+        y = viewport.y;
+    }
+
+    if (clipped)
+    {
+        if (y >= viewport.y)
+        {
+            out->y = y;
+
+            int croppedLinesCount = (textHeight - viewport.height) / fontSize + 1;
+            if (croppedLinesCount > 0)
+            {
+                unsigned int emptyIndex = emptyLines.size() - croppedLinesCount;
+                while (emptyIndex < emptyLines.size() && emptyLines[emptyIndex] == true)
+                {
+                    y += fontSize;
+                    textHeight -= fontSize;
+                    emptyIndex++;
+                }
+
+                textHeight -= fontSize * croppedLinesCount;
+
+                for (unsigned int i = croppedLinesCount; i < lines.size(); ++i)
+                {
+                    if (lines[i].x < x)
+                    {
+                        x = lines[i].x;
+                        width = lines[i].y;
+                    }
+                }
+            }
+
+            out->height = textHeight;
+        }
+        else
+        {
+            int croppedLinesCount = (viewport.y - y) / fontSize + 1;
+            unsigned int emptyIndex = croppedLinesCount;
+            while (emptyIndex < emptyLines.size() && emptyLines[emptyIndex] == true)
+            {
+                y += fontSize;
+                textHeight -= fontSize;
+                emptyIndex++;
+            }
+
+            int croppedV = 0;
+            if (vAlign == ALIGN_VCENTER)
+            {
+                croppedV = (textHeight - viewport.height + vWhitespace/2 + 0.01) / fontSize + 1;
+                if (croppedV > 0)
+                {
+                    emptyIndex = emptyLines.size() - croppedV;
+                    while (emptyIndex < emptyLines.size() && emptyLines[emptyIndex] == true)
+                    {
+                        textHeight -= fontSize;
+                        emptyIndex++;
+                    }
+
+                    textHeight -= fontSize * croppedV;
+                }
+            }
+
+            x = INT_MAX;
+            width = 0;
+            for (unsigned int i = croppedLinesCount; i < lines.size() - croppedV; ++i)
+            {
+                if (lines[i].x < x)
+                {
+                    x = lines[i].x;
+                }
+                if (lines[i].y > width)
+                {
+                    width = lines[i].y;
+                }
+            }
+
+            out->y = y + fontSize * croppedLinesCount;
+            out->height = textHeight - fontSize * croppedLinesCount;
+        }
+
+        out->x = (x >= viewport.x)? x : viewport.x;
+        out->width = (width <= viewport.width)? width : viewport.width;
+    }
+    else
+    {
+        out->x = x;
+        out->y = y;
+        out->width = width;
+        out->height = textHeight;
+    }
+}
+
+unsigned int Font::getTokenWidth(const char* token, unsigned int length)
+{
+    // Calculate width of word or line.
+    unsigned int tokenWidth = 0;
+    for (unsigned int i = 0; i < length; ++i)
+    {
+        char c = token[i];
+        switch (c)
+        {
+        case ' ':
+            tokenWidth += _size>>1;
+            break;
+        case '\t':
+            tokenWidth += (_size>>1)+4;
+            break;
+        default:
+            unsigned int glyphIndex = c - 32;
+            if (glyphIndex >= 0 && glyphIndex < _glyphCount)
+            {
+                Glyph& g = _glyphs[glyphIndex];
+                tokenWidth += g.width + (_size>>3);
+            }
+            break;
+        }
+    }
+
+    return tokenWidth;
+}
+
 }
 }

+ 65 - 0
gameplay/src/Font.h

@@ -30,6 +30,33 @@ public:
         BOLD_ITALIC = 4
         BOLD_ITALIC = 4
     };
     };
 
 
+    /**
+     * Defines the set of allowable alignments when drawing text.
+     */
+    enum Justify
+    {
+        // Specify horizontal alignment, use default vertical alignment (ALIGN_TOP).
+        ALIGN_LEFT = 0x01,
+        ALIGN_HCENTER = 0x02,
+        ALIGN_RIGHT = 0x04,
+    
+        // Specify vertical alignment, use default horizontal alignment (ALIGN_LEFT).
+        ALIGN_TOP = 0x10,
+        ALIGN_VCENTER = 0x20,
+        ALIGN_BOTTOM = 0x40,
+
+        // Specify both vertical and horizontal alignment.
+        ALIGN_TOP_LEFT = ALIGN_TOP | ALIGN_LEFT,
+        ALIGN_VCENTER_LEFT = ALIGN_VCENTER | ALIGN_LEFT,
+        ALIGN_BOTTOM_LEFT = ALIGN_BOTTOM | ALIGN_LEFT,
+        ALIGN_TOP_HCENTER = ALIGN_TOP | ALIGN_HCENTER,
+        ALIGN_VCENTER_HCENTER = ALIGN_VCENTER | ALIGN_HCENTER,
+        ALIGN_BOTTOM_HCENTER = ALIGN_BOTTOM | ALIGN_HCENTER,
+        ALIGN_TOP_RIGHT = ALIGN_TOP | ALIGN_RIGHT,
+        ALIGN_VCENTER_RIGHT = ALIGN_VCENTER | ALIGN_RIGHT,
+        ALIGN_BOTTOM_RIGHT = ALIGN_BOTTOM | ALIGN_RIGHT
+    };
+
     /**
     /**
      * Defines a font glyph within the texture map for a font.
      * Defines a font glyph within the texture map for a font.
      */
      */
@@ -101,11 +128,46 @@ public:
      */
      */
     void drawText(const char* text, int x, int y, const Vector4& color);
     void drawText(const char* text, int x, int y, const Vector4& color);
 
 
+    /**
+     * Draws the specified text within a rectangular area, with a specified alignment.
+     * Clips text outside the viewport.  Optionally wraps text to fit within the width of the viewport.
+     *
+     * @param text The text to draw.
+     * @param viewport The viewport area to draw within.  Text starts from the top-left of this rectangle.
+     * @param color The color of text.
+     * @param justify Justification of text within the viewport.
+     * @param wrap Wraps text to fit within the width of the viewport if true.
+     */
+    void drawText(const char* text, const Rectangle& viewport, const Vector4& color, Justify justify = ALIGN_TOP_LEFT, bool wrap = true);
+
     /**
     /**
      * Ends text drawing for this font.
      * Ends text drawing for this font.
      */
      */
     void end();
     void end();
 
 
+    /**
+     * Measures a string's width and height without alignment, wrapping or clipping.
+     *
+     * @param text The text to measure.
+     * @param width Destination for the text's width.
+     * @param height Destination for the text's height.
+     */
+    void measureText(const char* text, unsigned int* width, unsigned int* height);
+
+    /**
+     * Measures a string's bounding box after alignment, wrapping and clipping within a viewport.
+     *
+     * @param text The text to measure.
+     * @param viewport The viewport area to align, wrap and clip text within while measuring.
+     * @param justify Justification of text within the viewport.
+     * @param wrap Whether to measure text with wrapping applied.
+     * @param clipped Whether to clip 'out' to the viewport.  Set true for the bounds of what would actually be drawn
+     *                within the given viewport; false for bounds that are guaranteed to fit the entire string of text.
+     * @param out Destination rectangle to store the bounds in.
+     */
+    void measureText(const char* text, const Rectangle& viewport, Justify justify, bool wrap, bool clipped, Rectangle* out);
+
+
 private:
 private:
 
 
     /**
     /**
@@ -123,6 +185,9 @@ private:
      */
      */
     ~Font();
     ~Font();
 
 
+    // Utilities
+    unsigned int getTokenWidth(const char* token, unsigned int length);
+
     std::string _path;
     std::string _path;
     std::string _id;
     std::string _id;
     std::string _family;
     std::string _family;