Explorar o código

Added support for encoding multiple font sizes into a single GPB with gameplay-encoder.
Font class and related text rendering was updated to support multi-font GPBs.

sgrenier %!s(int64=12) %!d(string=hai) anos
pai
achega
ca478902ff

+ 114 - 90
gameplay/src/Bundle.cpp

@@ -28,7 +28,7 @@
 #define BUNDLE_MAX_STRING_LENGTH        5000
 
 #define BUNDLE_VERSION_MAJOR_FONT_FORMAT  1
-#define BUNDLE_VERSION_MINOR_FONT_FORMAT  3
+#define BUNDLE_VERSION_MINOR_FONT_FORMAT  4
 
 namespace gameplay
 {
@@ -1647,123 +1647,147 @@ Font* Bundle::loadFont(const char* id)
         return NULL;
     }
 
-    // Read font style and size.
-    unsigned int style, size;
+    // Read font style
+    unsigned int style;
     if (_stream->read(&style, 4, 1) != 1)
     {
         GP_ERROR("Failed to read style for font '%s'.", id);
         return NULL;
     }
-    if (_stream->read(&size, 4, 1) != 1)
+
+    // In bundle version 1.4 we introduced storing multiple font sizes per font
+    unsigned int fontSizeCount = 1;
+    if (getVersionMajor() >= 1 && getVersionMinor() >= 4)
     {
-        GP_ERROR("Failed to read size for font '%s'.", id);
-        return NULL;
+        if (_stream->read(&fontSizeCount, 4, 1) != 1)
+        {
+            GP_ERROR("Failed to read font size count for font '%s'.", id);
+            return NULL;
+        }
     }
 
-    // Read character set.
-    std::string charset = readString(_stream);
+    Font* masterFont = NULL;
 
-    // Read font glyphs.
-    unsigned int glyphCount;
-    if (_stream->read(&glyphCount, 4, 1) != 1)
-    {
-        GP_ERROR("Failed to read glyph count for font '%s'.", id);
-        return NULL;
-    }
-    if (glyphCount == 0)
+    for (unsigned int i = 0; i < fontSizeCount; ++i)
     {
-        GP_ERROR("Invalid glyph count (must be greater than 0) for font '%s'.", id);
-        return NULL;
-    }
+        // Read font size
+        unsigned int size;
+        if (_stream->read(&size, 4, 1) != 1)
+        {
+            GP_ERROR("Failed to read size for font '%s'.", id);
+            return NULL;
+        }
 
-    Font::Glyph* glyphs = new Font::Glyph[glyphCount];
-    if (_stream->read(glyphs, sizeof(Font::Glyph), glyphCount) != glyphCount)
-    {
-        GP_ERROR("Failed to read glyphs for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        return NULL;
-    }
+        // Read character set.
+        std::string charset = readString(_stream);
 
-    // Read texture attributes.
-    unsigned int width, height, textureByteCount;
-    if (_stream->read(&width, 4, 1) != 1)
-    {
-        GP_ERROR("Failed to read texture width for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        return NULL;
-    }
-    if (_stream->read(&height, 4, 1) != 1)
-    {
-        GP_ERROR("Failed to read texture height for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        return NULL;
-    }
-    if (_stream->read(&textureByteCount, 4, 1) != 1)
-    {
-        GP_ERROR("Failed to read texture byte count for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        return NULL;
-    }
-    if (textureByteCount != (width * height))
-    {
-        GP_ERROR("Invalid texture byte count for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        return NULL;
-    }
+        // Read font glyphs.
+        unsigned int glyphCount;
+        if (_stream->read(&glyphCount, 4, 1) != 1)
+        {
+            GP_ERROR("Failed to read glyph count for font '%s'.", id);
+            return NULL;
+        }
+        if (glyphCount == 0)
+        {
+            GP_ERROR("Invalid glyph count (must be greater than 0) for font '%s'.", id);
+            return NULL;
+        }
 
-    // Read texture data.
-    unsigned char* textureData = new unsigned char[textureByteCount];
-    if (_stream->read(textureData, 1, textureByteCount) != textureByteCount)
-    {
-        GP_ERROR("Failed to read texture data for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        SAFE_DELETE_ARRAY(textureData);
-        return NULL;
-    }
+        Font::Glyph* glyphs = new Font::Glyph[glyphCount];
+        if (_stream->read(glyphs, sizeof(Font::Glyph), glyphCount) != glyphCount)
+        {
+            GP_ERROR("Failed to read glyphs for font '%s'.", id);
+            SAFE_DELETE_ARRAY(glyphs);
+            return NULL;
+        }
 
-    unsigned int format = Font::BITMAP;
-    // After bundle version we add enum FontFormat to bundle format
-    if (getVersionMajor() >= BUNDLE_VERSION_MAJOR_FONT_FORMAT &&
-        getVersionMinor() >= BUNDLE_VERSION_MINOR_FONT_FORMAT)
-    {
-        if (_stream->read(&format, 4, 1) != 1)
+        // Read texture attributes.
+        unsigned int width, height, textureByteCount;
+        if (_stream->read(&width, 4, 1) != 1)
+        {
+            GP_ERROR("Failed to read texture width for font '%s'.", id);
+            SAFE_DELETE_ARRAY(glyphs);
+            return NULL;
+        }
+        if (_stream->read(&height, 4, 1) != 1)
+        {
+            GP_ERROR("Failed to read texture height for font '%s'.", id);
+            SAFE_DELETE_ARRAY(glyphs);
+            return NULL;
+        }
+        if (_stream->read(&textureByteCount, 4, 1) != 1)
+        {
+            GP_ERROR("Failed to read texture byte count for font '%s'.", id);
+            SAFE_DELETE_ARRAY(glyphs);
+            return NULL;
+        }
+        if (textureByteCount != (width * height))
         {
-            GP_ERROR("Failed to font format'%u'.", format);
+            GP_ERROR("Invalid texture byte count for font '%s'.", id);
+            SAFE_DELETE_ARRAY(glyphs);
+            return NULL;
+        }
+
+        // Read texture data.
+        unsigned char* textureData = new unsigned char[textureByteCount];
+        if (_stream->read(textureData, 1, textureByteCount) != textureByteCount)
+        {
+            GP_ERROR("Failed to read texture data for font '%s'.", id);
             SAFE_DELETE_ARRAY(glyphs);
             SAFE_DELETE_ARRAY(textureData);
             return NULL;
         }
-    }
 
-    // Create the texture for the font.
-    Texture* texture = Texture::create(Texture::ALPHA, width, height, textureData, true);
+        unsigned int format = Font::BITMAP;
 
-    // Free the texture data (no longer needed).
-    SAFE_DELETE_ARRAY(textureData);
+        // In bundle version 1.3 we added a format field
+        if (getVersionMajor() >= 1 && getVersionMinor() >= 3)
+        {
+            if (_stream->read(&format, 4, 1) != 1)
+            {
+                GP_ERROR("Failed to font format'%u'.", format);
+                SAFE_DELETE_ARRAY(glyphs);
+                SAFE_DELETE_ARRAY(textureData);
+                return NULL;
+            }
+        }
 
-    if (texture == NULL)
-    {
-        GP_ERROR("Failed to create texture for font '%s'.", id);
-        SAFE_DELETE_ARRAY(glyphs);
-        return NULL;
-    }
+        // Create the texture for the font.
+        Texture* texture = Texture::create(Texture::ALPHA, width, height, textureData, true);
+
+        // Free the texture data (no longer needed).
+        SAFE_DELETE_ARRAY(textureData);
 
-    // Create the font.
-    Font* font = Font::create(family.c_str(), Font::PLAIN, size, glyphs, glyphCount, texture, (Font::Format)format);
+        if (texture == NULL)
+        {
+            GP_ERROR("Failed to create texture for font '%s'.", id);
+            SAFE_DELETE_ARRAY(glyphs);
+            return NULL;
+        }
 
-    // Free the glyph array.
-    SAFE_DELETE_ARRAY(glyphs);
+        // Create the font for this size
+        Font* font = Font::create(family.c_str(), Font::PLAIN, size, glyphs, glyphCount, texture, (Font::Format)format);
 
-    // Release the texture since the Font now owns it.
-    SAFE_RELEASE(texture);
+        // Free the glyph array.
+        SAFE_DELETE_ARRAY(glyphs);
 
-    if (font)
-    {
-        font->_path = _path;
-        font->_id = id;
+        // Release the texture since the Font now owns it.
+        SAFE_RELEASE(texture);
+
+        if (font)
+        {
+            font->_path = _path;
+            font->_id = id;
+
+            if (masterFont)
+                masterFont->_sizes.push_back(font);
+            else
+                masterFont = font;
+        }
     }
 
-    return font;
+    return masterFont;
 }
 
 void Bundle::setTransform(const float* values, Transform* transform)

+ 178 - 20
gameplay/src/Font.cpp

@@ -32,6 +32,12 @@ Font::~Font()
     SAFE_DELETE(_batch);
     SAFE_DELETE_ARRAY(_glyphs);
     SAFE_RELEASE(_texture);
+
+    // Free child fonts
+    for (size_t i = 0, count = _sizes.size(); i < count; ++i)
+    {
+        SAFE_RELEASE(_sizes[i]);
+    }
 }
 
 Font* Font::create(const char* path, const char* id)
@@ -151,9 +157,17 @@ Font* Font::create(const char* family, Style style, unsigned int size, Glyph* gl
     return font;
 }
 
-unsigned int Font::getSize()
+unsigned int Font::getSize(unsigned int index) const
+{
+    GP_ASSERT(index <= _sizes.size());
+
+    // index zero == this font
+    return index == 0 ? _size : _sizes[index - 1]->_size;
+}
+
+unsigned int Font::getSizeCount() const
 {
-    return _size;
+    return _sizes.size() + 1; // +1 for "this" font
 }
 
 Font::Format Font::getFormat()
@@ -170,7 +184,13 @@ bool Font::isCharacterSupported(int character) const
 
 void Font::start()
 {
-    GP_ASSERT(_batch);
+    // no-op : fonts now are lazily started on the first draw call
+}
+
+void Font::lazyStart()
+{
+    if (_batch->isStarted())
+        return; // already started
 
     // Update the projection matrix for our batch to match the current viewport
     const Rectangle& vp = Game::getInstance()->getViewport();
@@ -185,16 +205,42 @@ void Font::start()
     _batch->start();
 }
 
+void Font::finish()
+{
+    // Finish any font batches that have been started
+    if (_batch->isStarted())
+        _batch->finish();
+
+    for (size_t i = 0, count = _sizes.size(); i < count; ++i)
+    {
+        SpriteBatch* batch = _sizes[i]->_batch;
+        if (batch->isStarted())
+            batch->finish();
+    }
+}
+
 Font::Text* Font::createText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify,
     bool wrap, bool rightToLeft, const Rectangle* clip)
 {
     GP_ASSERT(text);
     GP_ASSERT(_glyphs);
     GP_ASSERT(_batch);
+    GP_ASSERT(_size);
 
     if (size == 0)
+    {
         size = _size;
-    GP_ASSERT(_size);
+    }
+    else
+    {
+        // Delegate to closest sized font
+        Font* f = findClosestSize(size);
+        if (f != this)
+        {
+            return f->createText(text, area, color, size, justify, wrap, rightToLeft, clip);
+        }
+    }
+
     float scale = (float)size / _size;
     int spacing = (int)(size * _spacing);
     int yPos = area.y;
@@ -205,6 +251,9 @@ Font::Text* Font::createText(const char* text, const Rectangle& area, const Vect
     getMeasurementInfo(text, area, size, justify, wrap, rightToLeft, &xPositions, &yPos, &lineLengths);
 
     Text* batch = new Text(text);
+    batch->_font = this;
+    batch->_font->addRef();
+
     GP_ASSERT(batch->_vertices);
     GP_ASSERT(batch->_indices);
 
@@ -416,20 +465,69 @@ Font::Text* Font::createText(const char* text, const Rectangle& area, const Vect
     return batch;
 }
 
+Font* Font::findClosestSize(int size)
+{
+    if (size == _size)
+        return this;
+
+    int diff = abs(size - (int)_size);
+    Font* closest = this;
+    for (size_t i = 0, count = _sizes.size(); i < count; ++i)
+    {
+        Font* f = _sizes[i];
+        int d = abs(size - (int)f->_size);
+        if (d < diff)
+        {
+            diff = d;
+            closest = f;
+        }
+    }
+
+    return closest;
+}
+
 void Font::drawText(Text* text)
 {
+    GP_ASSERT(text);
+    GP_ASSERT(text->_font);
+
+    if (text->_font != this)
+    {
+        // Make sure we draw using the font/batch the text was created with
+        text->_font->drawText(text);
+        return;
+    }
+
     GP_ASSERT(_batch);
     GP_ASSERT(text->_vertices);
     GP_ASSERT(text->_indices);
+
+    lazyStart();
     _batch->draw(text->_vertices, text->_vertexCount, text->_indices, text->_indexCount);
 }
 
 void Font::drawText(const char* text, int x, int y, const Vector4& color, unsigned int size, bool rightToLeft)
 {
-    if (size == 0)
-        size = _size;
     GP_ASSERT(_size);
     GP_ASSERT(text);
+
+    if (size == 0)
+    {
+        size = _size;
+    }
+    else
+    {
+        // Delegate to closest sized font
+        Font* f = findClosestSize(size);
+        if (f != this)
+        {
+            f->drawText(text, x, y, color, size, rightToLeft);
+            return;
+        }
+    }
+
+    lazyStart();
+
     float scale = (float)size / _size;
     int spacing = (int)(size * _spacing);
     const char* cursor = NULL;
@@ -530,7 +628,7 @@ void Font::drawText(const char* text, int x, int y, const Vector4& color, unsign
                     if (getFormat() == DISTANCE_FIELD )
                     {
                         if (_cutoffParam == NULL)
-                            _cutoffParam = getSpriteBatch()->getMaterial()->getParameter("u_cutoff");    
+                            _cutoffParam = _batch->getMaterial()->getParameter("u_cutoff");    
                         // TODO: Fix me so that smaller font are much smoother
                         _cutoffParam->setVector2(Vector2(1.0, 1.0));
                     }
@@ -561,10 +659,25 @@ void Font::drawText(const char* text, int x, int y, float red, float green, floa
 void Font::drawText(const char* text, const Rectangle& area, const Vector4& color, unsigned int size, Justify justify, bool wrap, bool rightToLeft, const Rectangle* clip)
 {
     GP_ASSERT(text);
+    GP_ASSERT(_size);
 
     if (size == 0)
+    {
         size = _size;
-    GP_ASSERT(_size);
+    }
+    else
+    {
+        // Delegate to closest sized font
+        Font* f = findClosestSize(size);
+        if (f != this)
+        {
+            f->drawText(text, area, color, size, justify, wrap, rightToLeft, clip);
+            return;
+        }
+    }
+
+    lazyStart();
+
     float scale = (float)size / _size;
     int spacing = (int)(size * _spacing);
     int yPos = area.y;
@@ -679,7 +792,7 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
                         if (getFormat() == DISTANCE_FIELD)
                         {
                             if (_cutoffParam == NULL)
-                                _cutoffParam = getSpriteBatch()->getMaterial()->getParameter("u_cutoff");
+                                _cutoffParam = _batch->getMaterial()->getParameter("u_cutoff");
                             // TODO: Fix me so that smaller font are much smoother
                             _cutoffParam->setVector2(Vector2(1.0, 1.0));
                         }
@@ -761,12 +874,6 @@ void Font::drawText(const char* text, const Rectangle& area, const Vector4& colo
     }
 }
 
-void Font::finish()
-{
-    GP_ASSERT(_batch);
-    _batch->finish();
-}
-
 void Font::measureText(const char* text, unsigned int size, unsigned int* width, unsigned int* height)
 {
     GP_ASSERT(_size);
@@ -774,6 +881,21 @@ void Font::measureText(const char* text, unsigned int size, unsigned int* width,
     GP_ASSERT(width);
     GP_ASSERT(height);
 
+    if (size == 0)
+    {
+        size = _size;
+    }
+    else
+    {
+        // Delegate to closest sized font
+        Font* f = findClosestSize(size);
+        if (f != this)
+        {
+            f->measureText(text, size, width, height);
+            return;
+        }
+    }
+
     const size_t length = strlen(text);
     if (length == 0)
     {
@@ -814,6 +936,21 @@ void Font::measureText(const char* text, const Rectangle& clip, unsigned int siz
     GP_ASSERT(text);
     GP_ASSERT(out);
 
+    if (size == 0)
+    {
+        size = _size;
+    }
+    else
+    {
+        // Delegate to closest sized font
+        Font* f = findClosestSize(size);
+        if (f != this)
+        {
+            f->measureText(text, clip, size, out, justify, wrap, ignoreClip);
+            return;
+        }
+    }
+
     if (strlen(text) == 0)
     {
         out->set(0, 0, 0, 0);
@@ -1146,6 +1283,9 @@ void Font::getMeasurementInfo(const char* text, const Rectangle& area, unsigned
     GP_ASSERT(text);
     GP_ASSERT(yPosition);
 
+    if (size == 0)
+        size = _size;
+
     float scale = (float)size / _size;
 
     Justify vAlign = static_cast<Justify>(justify & 0xF0);
@@ -1352,11 +1492,23 @@ int Font::getIndexOrLocation(const char* text, const Rectangle& area, unsigned i
     GP_ASSERT(text);
     GP_ASSERT(outLocation);
 
+    if (size == 0)
+    {
+        size = _size;
+    }
+    else
+    {
+        // Delegate to closest sized font
+        Font* f = findClosestSize(size);
+        if (f != this)
+        {
+            return f->getIndexOrLocation(text, area, size, inLocation, outLocation, destIndex, justify, wrap, rightToLeft);
+        }
+    }
+
     unsigned int charIndex = 0;
 
     // Essentially need to measure text until we reach inLocation.
-    if (size == 0)
-        size = _size;
     float scale = (float)size / _size;
     int spacing = (int)(size * _spacing);
     int yPos = area.y;
@@ -1585,6 +1737,7 @@ unsigned int Font::getTokenWidth(const char* token, unsigned int length, unsigne
 
     if (size == 0)
         size = _size;
+
     int spacing = (int)(size * _spacing);
 
     // Calculate width of word or line.
@@ -1743,9 +1896,13 @@ void Font::addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Jus
     }
 }
 
-SpriteBatch* Font::getSpriteBatch() const
+SpriteBatch* Font::getSpriteBatch(unsigned int size) const
 {
-    return _batch;
+    if (size == 0)
+        return _batch;
+
+    // Find the closest sized child font
+    return const_cast<Font*>(this)->findClosestSize(size)->_batch;
 }
 
 Font::Justify Font::getJustify(const char* justify)
@@ -1824,7 +1981,7 @@ Font::Justify Font::getJustify(const char* justify)
     return Font::ALIGN_TOP_LEFT;
 }
 
-Font::Text::Text(const char* text) : _text(text ? text : ""), _vertexCount(0), _vertices(NULL), _indexCount(0), _indices(NULL)
+Font::Text::Text(const char* text) : _text(text ? text : ""), _vertexCount(0), _vertices(NULL), _indexCount(0), _indices(NULL), _font(NULL)
 {
     const size_t length = strlen(text);
     _vertices = new SpriteBatch::SpriteVertex[length * 4];
@@ -1835,6 +1992,7 @@ Font::Text::~Text()
 {
     SAFE_DELETE_ARRAY(_vertices);
     SAFE_DELETE_ARRAY(_indices);
+    SAFE_RELEASE(_font);
 }
 
 const char* Font::Text::getText()

+ 27 - 6
gameplay/src/Font.h

@@ -84,10 +84,12 @@ public:
         const char* getText();
 
     private:
+
         /**
          * Hidden copy constructor.
          */
-        Text(const Text&); 
+        Text(const Text&);
+
         /**
          * Hidden copy assignment operator.
          */
@@ -99,6 +101,7 @@ public:
         unsigned int _indexCount;
         unsigned short* _indices;
         Vector4 _color;
+        Font* _font;
     };
 
     /**
@@ -120,9 +123,20 @@ public:
     static Font* create(const char* path, const char* id = NULL);
 
     /**
-     * Gets the font size (max height of glyphs) in pixels.
+     * Gets the font size (max height of glyphs) in pixels, at the specified index.
+     *
+     * The Font class can store multiple sizes of glyphs for a font. The number of font
+     * sizes stored can be retrieved via getSizeCount.
+     *
+     * @param index Index of the size to returned (default is 0).
+     * @see getSizeCount
+     */
+    unsigned int getSize(unsigned int index = 0) const;
+
+    /**
+     * Returns the number of font sizes supported by this Font.
      */
-    unsigned int getSize();
+    unsigned int getSizeCount() const;
 
     /**
      * Gets the font format. BITMAP or DISTANCEMAP.
@@ -277,11 +291,13 @@ public:
                             Justify justify = ALIGN_TOP_LEFT, bool wrap = true, bool rightToLeft = false);
 
     /**
-     * Gets the sprite batch for this Font.
+     * Gets the sprite batch used to draw this Font.
      * 
-     * @return The sprite batch for this Font.
+     * @param size The font size to be drawn.
+     *
+     * @return The SpriteBatch that most closely matches the requested font size.
      */
-    SpriteBatch* getSpriteBatch() const;
+    SpriteBatch* getSpriteBatch(unsigned int size) const;
 
     /**
      * Gets the Justify value from the given string.
@@ -372,12 +388,17 @@ private:
     void addLineInfo(const Rectangle& area, int lineWidth, int lineLength, Justify hAlign,
                      std::vector<int>* xPositions, std::vector<unsigned int>* lineLengths, bool rightToLeft);
 
+    Font* findClosestSize(int size);
+
+    void lazyStart();
+
     Format _format;
     std::string _path;
     std::string _id;
     std::string _family;
     Style _style;
     unsigned int _size;
+    std::vector<Font*> _sizes; // stores additional font sizes of the same family
     float _spacing;
     Glyph* _glyphs;
     unsigned int _glyphCount;

+ 3 - 2
gameplay/src/Label.cpp

@@ -100,10 +100,11 @@ unsigned int Label::drawText(Form* form, const Rectangle& clip)
     if (_text.size() > 0 && _font)
     {
         Control::State state = getState();
+        unsigned int fontSize = getFontSize(state);
 
-        SpriteBatch* batch = _font->getSpriteBatch();
+        SpriteBatch* batch = _font->getSpriteBatch(fontSize);
         startBatch(form, batch);
-        _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(state), getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
+        _font->drawText(_text.c_str(), _textBounds, _textColor, fontSize, getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
         finishBatch(form, batch);
 
         return 1;

+ 3 - 2
gameplay/src/Slider.cpp

@@ -457,10 +457,11 @@ unsigned int Slider::drawText(Form* form, const Rectangle& clip)
     if (_valueTextVisible && _font)
     {
         Control::State state = getState();
+        unsigned int fontSize = getFontSize(state);
 
-        SpriteBatch* batch = _font->getSpriteBatch();
+        SpriteBatch* batch = _font->getSpriteBatch(fontSize);
         startBatch(form, batch);
-        _font->drawText(_valueText.c_str(), _textBounds, _textColor, getFontSize(state), _valueTextAlignment, true, getTextRightToLeft(state), &_viewportClipBounds);
+        _font->drawText(_valueText.c_str(), _textBounds, _textColor, fontSize, _valueTextAlignment, true, getTextRightToLeft(state), &_viewportClipBounds);
         finishBatch(form, batch);
 
         ++drawCalls;

+ 3 - 2
gameplay/src/TextBox.cpp

@@ -368,10 +368,11 @@ unsigned int TextBox::drawText(Form* form, const Rectangle& clip)
     {
         Control::State state = getState();
         const std::string displayedText = getDisplayedText();
+        unsigned int fontSize = getFontSize(state);
 
-        SpriteBatch* batch = _font->getSpriteBatch();
+        SpriteBatch* batch = _font->getSpriteBatch(fontSize);
         startBatch(form, batch);
-        _font->drawText(displayedText.c_str(), _textBounds, _textColor, getFontSize(state), getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
+        _font->drawText(displayedText.c_str(), _textBounds, _textColor, fontSize, getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
         finishBatch(form, batch);
 
         return 1;

+ 65 - 4
gameplay/src/lua/lua_Font.cpp

@@ -27,6 +27,7 @@ void luaRegister_Font()
         {"getLocationAtIndex", lua_Font_getLocationAtIndex},
         {"getRefCount", lua_Font_getRefCount},
         {"getSize", lua_Font_getSize},
+        {"getSizeCount", lua_Font_getSizeCount},
         {"getSpriteBatch", lua_Font_getSpriteBatch},
         {"isCharacterSupported", lua_Font_isCharacterSupported},
         {"measureText", lua_Font_measureText},
@@ -1709,9 +1710,30 @@ int lua_Font_getSize(lua_State* state)
             lua_error(state);
             break;
         }
+        case 2:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 2);
+
+                Font* instance = getInstance(state);
+                unsigned int result = instance->getSize(param1);
+
+                // Push the return value onto the stack.
+                lua_pushunsigned(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Font_getSize - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_pushstring(state, "Invalid number of parameters (expected 1 or 2).");
             lua_error(state);
             break;
         }
@@ -1719,7 +1741,7 @@ int lua_Font_getSize(lua_State* state)
     return 0;
 }
 
-int lua_Font_getSpriteBatch(lua_State* state)
+int lua_Font_getSizeCount(lua_State* state)
 {
     // Get the number of parameters.
     int paramCount = lua_gettop(state);
@@ -1732,7 +1754,46 @@ int lua_Font_getSpriteBatch(lua_State* state)
             if ((lua_type(state, 1) == LUA_TUSERDATA))
             {
                 Font* instance = getInstance(state);
-                void* returnPtr = (void*)instance->getSpriteBatch();
+                unsigned int result = instance->getSizeCount();
+
+                // Push the return value onto the stack.
+                lua_pushunsigned(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Font_getSizeCount - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_Font_getSpriteBatch(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 2:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 2);
+
+                Font* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getSpriteBatch(param1);
                 if (returnPtr)
                 {
                     gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
@@ -1755,7 +1816,7 @@ int lua_Font_getSpriteBatch(lua_State* state)
         }
         default:
         {
-            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
             lua_error(state);
             break;
         }

+ 1 - 0
gameplay/src/lua/lua_Font.h

@@ -16,6 +16,7 @@ int lua_Font_getIndexAtLocation(lua_State* state);
 int lua_Font_getLocationAtIndex(lua_State* state);
 int lua_Font_getRefCount(lua_State* state);
 int lua_Font_getSize(lua_State* state);
+int lua_Font_getSizeCount(lua_State* state);
 int lua_Font_getSpriteBatch(lua_State* state);
 int lua_Font_isCharacterSupported(lua_State* state);
 int lua_Font_measureText(lua_State* state);

BIN=BIN
samples/browser/res/common/arial.gpb


+ 1 - 1
samples/browser/res/common/default.theme

@@ -365,7 +365,7 @@ theme mainMenu
         stateNormal
         {
             textColor = #ffffffff
-            font = res/common/title.gpb
+            font = res/common/arial.gpb
             fontSize = 24
             textAlignment = ALIGN_BOTTOM_HCENTER
         }

+ 50 - 50
samples/browser/res/common/terrain/shapes.material

@@ -1,50 +1,50 @@
-material textured
-{
-    u_worldViewProjectionMatrix = WORLD_VIEW_PROJECTION_MATRIX
-    u_inverseTransposeWorldViewMatrix = INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX
-    
-    u_ambientColor = SCENE_AMBIENT_COLOR
-    u_directionalLightDirection[0] = LIGHT0_DIRECTION
-    u_directionalLightColor[0] = LIGHT0_COLOR
-
-    sampler u_diffuseTexture
-    {
-        mipmap = true
-        wrapS = CLAMP
-        wrapT = CLAMP
-        minFilter = LINEAR_MIPMAP_LINEAR
-        magFilter = LINEAR
-    }
-
-    renderState
-    {
-        cullFace = true
-        depthTest = true
-    }
-
-    technique
-    {
-        pass
-        {
-            vertexShader = res/shaders/textured.vert
-            fragmentShader = res/shaders/textured.frag
-            defines = DIRECTIONAL_LIGHT_COUNT 1
-        }
-    }
-}
-
-material sphere : textured
-{
-    sampler u_diffuseTexture
-    {
-        path = res/png/dirt.png
-    }
-}
-
-material box : textured
-{
-    sampler u_diffuseTexture
-    {
-        path = res/png/crate.png
-    }
-}
+material textured
+{
+    u_worldViewProjectionMatrix = WORLD_VIEW_PROJECTION_MATRIX
+    u_inverseTransposeWorldViewMatrix = INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX
+
+    u_ambientColor = SCENE_AMBIENT_COLOR
+    u_directionalLightDirection[0] = LIGHT_DIRECTION_0
+    u_directionalLightColor[0] = LIGHT_COLOR_0
+
+    sampler u_diffuseTexture
+    {
+        mipmap = true
+        wrapS = CLAMP
+        wrapT = CLAMP
+        minFilter = LINEAR_MIPMAP_LINEAR
+        magFilter = LINEAR
+    }
+
+    renderState
+    {
+        cullFace = true
+        depthTest = true
+    }
+
+    technique
+    {
+        pass
+        {
+            vertexShader = res/shaders/textured.vert
+            fragmentShader = res/shaders/textured.frag
+            defines = DIRECTIONAL_LIGHT_COUNT 1
+        }
+    }
+}
+
+material sphere : textured
+{
+    sampler u_diffuseTexture
+    {
+        path = res/png/dirt.png
+    }
+}
+
+material box : textured
+{
+    sampler u_diffuseTexture
+    {
+        path = res/png/crate.png
+    }
+}

+ 1 - 1
samples/browser/res/common/terrain/terrain.form

@@ -146,7 +146,7 @@ form terrainForm
             consumeInputEvents = false
             autoWidth = true
             autoHeight = true
-            font = res/common/title.gpb
+            font = res/common/arial.gpb
             fontSize = 24
             textAlignment = ALIGN_VCENTER_HCENTER
         }

BIN=BIN
samples/browser/res/common/title.gpb


+ 18 - 21
samples/browser/src/SamplesGame.cpp

@@ -27,7 +27,6 @@ void SamplesGame::initialize()
     getScriptController()->loadScript("res/common/camera.lua");
 
     // Create the selection form
-    Font* titleFont = Font::create("res/common/title.gpb");
     _sampleSelectForm = Form::create("sampleSelect", NULL, Layout::LAYOUT_VERTICAL);
     _sampleSelectForm->setWidth(200);
     _sampleSelectForm->setAutoHeight(Control::AUTO_SIZE_STRETCH);
@@ -38,7 +37,6 @@ void SamplesGame::initialize()
 		Label* categoryLabel = Label::create((*_categories)[i].c_str());
         categoryLabel->setAutoWidth(Control::AUTO_SIZE_FIT);
         categoryLabel->setAutoHeight(Control::AUTO_SIZE_FIT);
-        categoryLabel->setFont(titleFont);
         categoryLabel->setFontSize(22);
         categoryLabel->setText((*_categories)[i].c_str());
         _sampleSelectForm->addControl(categoryLabel);
@@ -59,7 +57,6 @@ void SamplesGame::initialize()
         }
     }
     _sampleSelectForm->setFocus();
-    SAFE_RELEASE(titleFont);
 
     // Disable virtual gamepads.
     unsigned int gamepadCount = getGamepadCount();
@@ -197,24 +194,24 @@ void SamplesGame::gestureTapEvent(int x, int y)
         _activeSample->gestureTapEvent(x, y);
 }
 
-void SamplesGame::gestureLongTapEvent(int x, int y, float duration)
-{
-	if (_activeSample)
-		_activeSample->gestureLongTapEvent(x, y, duration);
-}
-
-void SamplesGame::gestureDragEvent(int x, int y)
-{
-	if (_activeSample)
-		_activeSample->gestureDragEvent(x, y);
-}
-
-void SamplesGame::gestureDropEvent(int x, int y)
-{
-	if (_activeSample)
-		_activeSample->gestureDropEvent(x, y);
-}
-
+void SamplesGame::gestureLongTapEvent(int x, int y, float duration)
+{
+	if (_activeSample)
+		_activeSample->gestureLongTapEvent(x, y, duration);
+}
+
+void SamplesGame::gestureDragEvent(int x, int y)
+{
+	if (_activeSample)
+		_activeSample->gestureDragEvent(x, y);
+}
+
+void SamplesGame::gestureDropEvent(int x, int y)
+{
+	if (_activeSample)
+		_activeSample->gestureDropEvent(x, y);
+}
+
 void SamplesGame::controlEvent(Control* control, EventType evt)
 {
     const size_t size = _samples->size();

+ 1 - 1
samples/browser/src/TextSample.cpp

@@ -209,7 +209,7 @@ void TextSample::controlEvent(Control* control, EventType evt)
     }
     else if (strcmp(id, "smallerButton") == 0)
     {
-        if (_size > 12)
+        if (_size > 8)
         {
             _size -= 2;
             Label* sizeLabel = static_cast<Label*>(_form->getControl("sizeLabel"));

+ 1 - 2
tools/encoder/gameplay-encoder.vcxproj.user

@@ -1,8 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <LocalDebuggerCommandArguments>
-    </LocalDebuggerCommandArguments>
+    <LocalDebuggerCommandArguments>-p -s 8,10,12 C:\Users\sgrenier\Documents\Sourcecode\GamePlay\bin\windows\arial.ttf</LocalDebuggerCommandArguments>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
     <LocalDebuggerEnvironment>
     </LocalDebuggerEnvironment>

+ 1 - 0
tools/encoder/src/Base.h

@@ -13,6 +13,7 @@
 #include <cstring>
 #include <iostream>
 #include <fstream>
+#include <sstream>
 #include <string>
 #include <vector>
 #include <list>

+ 44 - 14
tools/encoder/src/EncoderArguments.cpp

@@ -23,7 +23,6 @@ extern int __logVerbosity = 1;
 EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _normalMap(false),
     _parseError(false),
-    _fontSize(0),
     _fontPreview(false),
     _fontFormat(Font::BITMAP),
     _textOutput(false),
@@ -288,7 +287,7 @@ void EncoderArguments::printUsage() const
         "  terrain generation tools.\n" \
     "\n" \
     "TTF file options:\n" \
-    "  -s <size>\tSize of the bitmap font. (in pixels).\n" \
+    "  -s <sizes>\tComma-separated list of font sizes (in pixels).\n" \
     "  -p\t\tOutput font preview.\n" \
     "  -f Format of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
     "\n" \
@@ -331,9 +330,9 @@ const char* EncoderArguments::getNodeId() const
     return _nodeId.c_str();
 }
 
-unsigned int EncoderArguments::getFontSize() const
+std::vector<unsigned int> EncoderArguments::getFontSizes() const
 {
-    return _fontSize;
+    return _fontSizes;
 }
 
 EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
@@ -578,30 +577,61 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
         }
         else
         {
-            // Font Size
+            // Font Sizes
             // old format was -s##
+            const char* sizes = NULL;
             if (str.length() > 2)
             {
                 char n = str[2];
                 if (n > '0' && n <= '9')
                 {
-                    const char* number = str.c_str() + 2;
-                    _fontSize = atoi(number);
-                    break;
+                    sizes = str.c_str() + 2;
                 }
             }
-
-            (*index)++;
-            if (*index < options.size())
+            else
             {
-                _fontSize = atoi(options[*index].c_str());
+                (*index)++;
+                if (*index < options.size())
+                {
+                    sizes = options[*index].c_str();
+                }
             }
-            else
+
+            if (sizes == NULL)
             {
-                LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
+                LOG(1, "Error: invalid format for argument: -s");
                 _parseError = true;
                 return;
             }
+
+            // Parse comma-separated list of font sizes
+            char* ptr = const_cast<char*>(sizes);
+            std::string sizeStr;
+            while (ptr)
+            {
+                char* end = strchr(ptr, ',');
+                if (end)
+                {
+                    sizeStr = std::string(ptr, end - ptr);
+                    ptr = end + 1;
+                }
+                else
+                {
+                    sizeStr = ptr;
+                    ptr = NULL;
+                }
+                if (sizeStr.length() > 0)
+                {
+                    int size = atoi(sizeStr.c_str());
+                    if (size <= 0)
+                    {
+                        LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
+                        _parseError = true;
+                        return;
+                    }
+                    _fontSizes.push_back((unsigned int)size);
+                }
+            }
         }
         break;
     case 't':

+ 2 - 4
tools/encoder/src/EncoderArguments.h

@@ -152,7 +152,7 @@ public:
      */
     void printUsage() const;
 
-    unsigned int getFontSize() const;
+    std::vector<unsigned int> getFontSizes() const;
 
     bool fontPreviewEnabled() const;
 
@@ -166,8 +166,6 @@ public:
 
     const char* getNodeId() const;
 
-
-
     static std::string getRealPath(const std::string& filepath);
 
 private:
@@ -205,7 +203,7 @@ private:
     int _heightmapResolution[2];
 
     bool _parseError;
-    unsigned int _fontSize;
+    std::vector<unsigned int> _fontSizes;
     bool _fontPreview;
     Font::FontFormat _fontFormat;
     bool _textOutput;

+ 1 - 1
tools/encoder/src/GPBFile.h

@@ -21,7 +21,7 @@ namespace gameplay
  * Increment the version number when making a change that break binary compatibility.
  * [0] is major, [1] is minor.
  */
-const unsigned char GPB_VERSION[2] = {1, 3};
+const unsigned char GPB_VERSION[2] = {1, 4};
 
 /**
  * The GamePlay Binary file class handles writing the GamePlay Binary file.

+ 277 - 197
tools/encoder/src/TTFFontEncoder.cpp

@@ -107,10 +107,8 @@ unsigned char* createDistanceFields(unsigned char* img, unsigned int width, unsi
     return out;
 }
 
-int writeFont(const char* inFilePath, const char* outFilePath, unsigned int fontSize, const char* id, bool fontpreview = false, Font::FontFormat fontFormat = Font::BITMAP)
+int writeFont(const char* inFilePath, const char* outFilePath, std::vector<unsigned int>& fontSizes, const char* id, bool fontpreview = false, Font::FontFormat fontFormat = Font::BITMAP)
 {
-    TTFGlyph glyphArray[END_INDEX - START_INDEX];
-
     // Initialize freetype library.
     FT_Library library;
     FT_Error error = FT_Init_FreeType(&library);
@@ -129,96 +127,219 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
         return -1;
     }
 
-    int actualfontHeight = 0;
+    // Stores a single genreated font size to be written into the GPB
+    struct FontData
+    {
+        // Array of glyphs for a font
+        TTFGlyph glyphArray[END_INDEX - START_INDEX];
 
-    // Stores final height of a row required to render all glyphs
-    int rowSize = 0;
+        // Stores final height of a row required to render all glyphs
+        int fontSize;
 
-    FT_GlyphSlot slot = NULL;
-    //FT_Int32 loadFlags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT;
-    FT_Int32 loadFlags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT;// | FT_LOAD_MONOCHROME;
-    //FT_Int32 loadFlags = FT_LOAD_NO_SCALE;
+        // Actual size of the underlying glyphs (may be different from fontSize)
+        int glyphSize;
 
-    // We want to generate fonts that fit exactly the requested pixels size.
-    // Since free type (due to modern fonts) does not directly correlate requested
-    // size to glyph size, we'll brute-force attempt to set the largest font size
-    // possible that will fit within the requested pixel size.
-    for (unsigned int requestedSize = fontSize; requestedSize > 0; --requestedSize)
-    {
-        // Set the pixel size.
-        //error = FT_Set_Char_Size( face, 0, requestedSize );
-        error = FT_Set_Char_Size( face, 0, requestedSize * 64, 0, 0 );
-        if (error)
+        // Font texture
+        unsigned char* imageBuffer;
+        unsigned int imageWidth;
+        unsigned int imageHeight;
+
+        FontData() : fontSize(0), glyphSize(0), imageBuffer(NULL), imageWidth(0), imageHeight(0)
         {
-            LOG(1, "FT_Set_Pixel_Sizes error: %d \n", error);
-            return -1;
         }
 
-        // Save glyph information (slot contains the actual glyph bitmap).
-        slot = face->glyph;
+        ~FontData()
+        {
+            if (imageBuffer)
+                free(imageBuffer);
+        }
+    };
+    std::vector<FontData*> fonts;
 
-        rowSize = 0;
-        actualfontHeight = 0;
+    for (size_t fontIndex = 0, count = fontSizes.size(); fontIndex < count; ++fontIndex)
+    {
+        unsigned int fontSize = fontSizes[fontIndex];
 
-        // Find the width of the image.
-        for (unsigned char ascii = START_INDEX; ascii < END_INDEX; ++ascii)
+        FontData* font = new FontData();
+        font->fontSize = fontSize;
+
+        TTFGlyph* glyphArray = font->glyphArray;
+
+        int rowSize = 0;
+        int glyphSize = 0;
+        int actualfontHeight = 0;
+
+        FT_GlyphSlot slot = NULL;
+        FT_Int32 loadFlags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT;
+
+        // We want to generate fonts that fit exactly the requested pixels size.
+        // Since free type (due to modern fonts) does not directly correlate requested
+        // size to glyph size, we'll brute-force attempt to set the largest font size
+        // possible that will fit within the requested pixel size.
+        for (unsigned int requestedSize = fontSize; requestedSize > 0; --requestedSize)
         {
-            // Load glyph image into the slot (erase previous one)
-            error = FT_Load_Char(face, ascii, loadFlags);
+            // Set the pixel size.
+            error = FT_Set_Char_Size( face, 0, requestedSize * 64, 0, 0 );
             if (error)
             {
-                LOG(1, "FT_Load_Char error : %d \n", error);
+                LOG(1, "FT_Set_Pixel_Sizes error: %d \n", error);
+                return -1;
             }
 
-            int bitmapRows = slot->bitmap.rows;
-            actualfontHeight = (actualfontHeight < bitmapRows) ? bitmapRows : actualfontHeight;
+            // Save glyph information (slot contains the actual glyph bitmap).
+            slot = face->glyph;
 
-            if (slot->bitmap.rows > slot->bitmap_top)
+            rowSize = 0;
+            glyphSize = 0;
+            actualfontHeight = 0;
+
+            // Find the width of the image.
+            for (unsigned char ascii = START_INDEX; ascii < END_INDEX; ++ascii)
+            {
+                // Load glyph image into the slot (erase previous one)
+                error = FT_Load_Char(face, ascii, loadFlags);
+                if (error)
+                {
+                    LOG(1, "FT_Load_Char error : %d \n", error);
+                }
+
+                int bitmapRows = slot->bitmap.rows;
+                actualfontHeight = (actualfontHeight < bitmapRows) ? bitmapRows : actualfontHeight;
+
+                if (slot->bitmap.rows > slot->bitmap_top)
+                {
+                    bitmapRows += (slot->bitmap.rows - slot->bitmap_top);
+                }
+                rowSize = (rowSize < bitmapRows) ? bitmapRows : rowSize;
+            }
+
+            // Have we found a pixel size that fits?
+            if (rowSize <= (int)fontSize)
             {
-                bitmapRows += (slot->bitmap.rows - slot->bitmap_top);
+                glyphSize = rowSize;
+                rowSize = fontSize;
+                break;
             }
-            rowSize = (rowSize < bitmapRows) ? bitmapRows : rowSize;
         }
 
-        // Have we found a pixel size that fits?
-        if (rowSize <= (int)fontSize)
+        if (slot == NULL || glyphSize == 0)
         {
-            rowSize = fontSize;
-            break;
+            LOG(1, "Cannot generate a font of the requested size: %d\n", fontSize);
+            return -1;
         }
-    }
 
-    if (slot == NULL)
-    {
-        LOG(1, "Cannot generate a font of the requested size.");
-        return -1;
-    }
+        // If there's an existing generated font in the list of this size, ignore and delete it
+        int duplicateSize = 0;
+        for (size_t i = 0; i < fonts.size(); ++i)
+        {
+            if (fonts[i]->glyphSize == glyphSize)
+            {
+                duplicateSize = fonts[i]->fontSize;
+                break;
+            }
+        }
+        if (duplicateSize != 0)
+        {
+            LOG(1, "Warning: Requested font size (%d) produces same size glyphs as font size (%d). Skipping size %d.\n", fontSize, duplicateSize, fontSize);
+            SAFE_DELETE(font);
+            continue;
+        }
 
-    // Include padding in the rowSize.
-    rowSize += GLYPH_PADDING;
+        if (rowSize != (int)fontSize)
+        {
+            LOG(1, "Warning: Could not genreate font of requested size (%d). Generating size %d instead.\n", fontSize, rowSize);
+        }
 
-    // Initialize with padding.
-    int penX = 0;
-    int penY = 0;
-    int row = 0;
+        // Include padding in the rowSize.
+        rowSize += GLYPH_PADDING;
 
-    double powerOf2 = 2;
-    unsigned int imageWidth = 0;
-    unsigned int imageHeight = 0;
-    bool textureSizeFound = false;
+        // Initialize with padding.
+        int penX = 0;
+        int penY = 0;
+        int row = 0;
 
-    int advance;
-    int i;
+        double powerOf2 = 2;
+        unsigned int imageWidth = 0;
+        unsigned int imageHeight = 0;
+        bool textureSizeFound = false;
 
-    while (textureSizeFound == false)
-    {
-        imageWidth =  (unsigned int)pow(2.0, powerOf2);
-        imageHeight = (unsigned int)pow(2.0, powerOf2);
+        int advance;
+        int i;
+
+        while (textureSizeFound == false)
+        {
+            imageWidth =  (unsigned int)pow(2.0, powerOf2);
+            imageHeight = (unsigned int)pow(2.0, powerOf2);
+            penX = 0;
+            penY = 0;
+            row = 0;
+
+            // Find out the squared texture size that would fit all the require font glyphs.
+            i = 0;
+            for (unsigned char ascii = START_INDEX; ascii < END_INDEX; ++ascii)
+            {
+                // Load glyph image into the slot (erase the previous one).
+                error = FT_Load_Char(face, ascii, loadFlags);
+                if (error)
+                {
+                    LOG(1, "FT_Load_Char error : %d \n", error);
+                }
+                // Glyph image.
+                int glyphWidth = slot->bitmap.pitch;
+                int glyphHeight = slot->bitmap.rows;
+
+                advance = glyphWidth + GLYPH_PADDING; 
+
+                // If we reach the end of the image wrap aroud to the next row.
+                if ((penX + advance) > (int)imageWidth)
+                {
+                    penX = 0;
+                    row += 1;
+                    penY = row * rowSize;
+                    if (penY + rowSize > (int)imageHeight)
+                    {
+                        powerOf2++;
+                        break;
+                    }
+                }
+
+                // penY should include the glyph offsets.
+                penY += (actualfontHeight - glyphHeight) + (glyphHeight - slot->bitmap_top);
+
+                // Set the pen position for the next glyph
+                penX += advance; // Move X to next glyph position
+                // Move Y back to the top of the row.
+                penY = row * rowSize;
+
+                if (ascii == (END_INDEX - 1))
+                {
+                    textureSizeFound = true;
+                }
+                i++;
+            }
+        }
+
+        // Try further to find a tighter texture size.
+        powerOf2 = 1;
+        for (;;)
+        {
+            if ((penY + rowSize) >= pow(2.0, powerOf2))
+            {
+                powerOf2++;
+            }
+            else
+            {
+                imageHeight = (int)pow(2.0, powerOf2);
+                break;
+            }
+        }
+
+        // Allocate temporary image buffer to draw the glyphs into.
+        unsigned char* imageBuffer = (unsigned char*)malloc(imageWidth * imageHeight);
+        memset(imageBuffer, 0, imageWidth * imageHeight);
         penX = 0;
         penY = 0;
         row = 0;
-
-        // Find out the squared texture size that would fit all the require font glyphs.
         i = 0;
         for (unsigned char ascii = START_INDEX; ascii < END_INDEX; ++ascii)
         {
@@ -228,11 +349,13 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
             {
                 LOG(1, "FT_Load_Char error : %d \n", error);
             }
+
             // Glyph image.
+            unsigned char* glyphBuffer =  slot->bitmap.buffer;
             int glyphWidth = slot->bitmap.pitch;
             int glyphHeight = slot->bitmap.rows;
 
-            advance = glyphWidth + GLYPH_PADDING; 
+            advance = glyphWidth + GLYPH_PADDING;
 
             // If we reach the end of the image wrap aroud to the next row.
             if ((penX + advance) > (int)imageWidth)
@@ -242,100 +365,40 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
                 penY = row * rowSize;
                 if (penY + rowSize > (int)imageHeight)
                 {
-                    powerOf2++;
-                    break;
+                    free(imageBuffer);
+                    LOG(1, "Image size exceeded!");
+                    return -1;
                 }
             }
 
             // penY should include the glyph offsets.
             penY += (actualfontHeight - glyphHeight) + (glyphHeight - slot->bitmap_top);
 
-            // Set the pen position for the next glyph
-            penX += advance; // Move X to next glyph position
+            // Draw the glyph to the bitmap with a one pixel padding.
+            drawBitmap(imageBuffer, penX, penY, imageWidth, glyphBuffer, glyphWidth, glyphHeight);
+
             // Move Y back to the top of the row.
             penY = row * rowSize;
 
-            if (ascii == (END_INDEX - 1))
-            {
-                textureSizeFound = true;
-            }
-            i++;
-        }
-    }
-
-    // Try further to find a tighter texture size.
-    powerOf2 = 1;
-    for (;;)
-    {
-        if ((penY + rowSize) >= pow(2.0, powerOf2))
-        {
-            powerOf2++;
-        }
-        else
-        {
-            imageHeight = (int)pow(2.0, powerOf2);
-            break;
-        }
-    }
-    
-    // Allocate temporary image buffer to draw the glyphs into.
-    unsigned char* imageBuffer = (unsigned char*)malloc(imageWidth * imageHeight);
-    memset(imageBuffer, 0, imageWidth * imageHeight);
-    penX = 0;
-    penY = 0;
-    row = 0;
-    i = 0;
-    for (unsigned char ascii = START_INDEX; ascii < END_INDEX; ++ascii)
-    {
-        // Load glyph image into the slot (erase the previous one).
-        error = FT_Load_Char(face, ascii, loadFlags);
-        if (error)
-        {
-            LOG(1, "FT_Load_Char error : %d \n", error);
-        }
-
-        // Glyph image.
-        unsigned char* glyphBuffer =  slot->bitmap.buffer;
-        int glyphWidth = slot->bitmap.pitch;
-        int glyphHeight = slot->bitmap.rows;
+            glyphArray[i].index = ascii;
+            glyphArray[i].width = advance - GLYPH_PADDING;
 
-        advance = glyphWidth + GLYPH_PADDING;
+            // Generate UV coords.
+            glyphArray[i].uvCoords[0] = (float)penX / (float)imageWidth;
+            glyphArray[i].uvCoords[1] = (float)penY / (float)imageHeight;
+            glyphArray[i].uvCoords[2] = (float)(penX + advance - GLYPH_PADDING) / (float)imageWidth;
+            glyphArray[i].uvCoords[3] = (float)(penY + rowSize - GLYPH_PADDING) / (float)imageHeight;
 
-        // If we reach the end of the image wrap aroud to the next row.
-        if ((penX + advance) > (int)imageWidth)
-        {
-            penX = 0;
-            row += 1;
-            penY = row * rowSize;
-            if (penY + rowSize > (int)imageHeight)
-            {
-                free(imageBuffer);
-                LOG(1, "Image size exceeded!");
-                return -1;
-            }
+            // Set the pen position for the next glyph
+            penX += advance;
+            i++;
         }
-        
-        // penY should include the glyph offsets.
-        penY += (actualfontHeight - glyphHeight) + (glyphHeight - slot->bitmap_top);
-
-        // Draw the glyph to the bitmap with a one pixel padding.
-        drawBitmap(imageBuffer, penX, penY, imageWidth, glyphBuffer, glyphWidth, glyphHeight);
-        
-        // Move Y back to the top of the row.
-        penY = row * rowSize;
-
-        glyphArray[i].index = ascii;
-        glyphArray[i].width = advance - GLYPH_PADDING;
-
-        // Generate UV coords.
-        glyphArray[i].uvCoords[0] = (float)penX / (float)imageWidth;
-        glyphArray[i].uvCoords[1] = (float)penY / (float)imageHeight;
-        glyphArray[i].uvCoords[2] = (float)(penX + advance - GLYPH_PADDING) / (float)imageWidth;
-        glyphArray[i].uvCoords[3] = (float)(penY + rowSize - GLYPH_PADDING) / (float)imageHeight;
-
-        // Set the pen position for the next glyph
-        penX += advance;
-        i++;
+
+        font->glyphSize = glyphSize;
+        font->imageBuffer = imageBuffer;
+        font->imageWidth = imageWidth;
+        font->imageHeight = imageHeight;
+        fonts.push_back(font);
     }
 
     // File header and version.
@@ -357,69 +420,86 @@ int writeFont(const char* inFilePath, const char* outFilePath, unsigned int font
     // TODO: Switch based on TTF style name and write appropriate font style unsigned int for now just hardcoding to 0 = PLAIN.
     writeUint(gpbFp, 0);
 
-    // Font size (pixels).
-    writeUint(gpbFp, rowSize - GLYPH_PADDING);
-
-    // Character set. TODO: Empty for now
-    writeString(gpbFp, "");
-
-    // Glyphs.
-    unsigned int glyphSetSize = END_INDEX - START_INDEX;
-    writeUint(gpbFp, glyphSetSize);
-    fwrite(&glyphArray, sizeof(TTFGlyph), glyphSetSize, gpbFp);
-
-    // Image dimensions
-    unsigned int imageSize = imageWidth * imageHeight;
-    writeUint(gpbFp, imageWidth);
-    writeUint(gpbFp, imageHeight);
-    writeUint(gpbFp, imageSize);
-    
-    unsigned char* distanceFieldBuffer = NULL;
-    if (fontFormat == Font::DISTANCE_FIELD)
-    {
-        // Flip height and width since the distance field map generator is column-wise.
-        distanceFieldBuffer = createDistanceFields(imageBuffer, imageHeight, imageWidth);
-        fwrite(distanceFieldBuffer, sizeof(unsigned char), imageSize, gpbFp);
-        writeUint(gpbFp, Font::DISTANCE_FIELD);
-    }
-    else
+    // Number of included font sizes (GPB version 1.3+)
+    writeUint(gpbFp, (unsigned int)fonts.size());
+
+    for (size_t i = 0, count = fonts.size(); i < count; ++i)
     {
-        fwrite(imageBuffer, sizeof(unsigned char), imageSize, gpbFp);
-        writeUint(gpbFp, Font::BITMAP);
-    }
+        FontData* font = fonts[i];
 
-    // Close file.
-    fclose(gpbFp);
+        // Font size (pixels).
+        writeUint(gpbFp, font->fontSize);
 
-    LOG(1, "%s.gpb created successfully. \n", getBaseName(outFilePath).c_str());
+        // Character set. TODO: Empty for now
+        writeString(gpbFp, "");
+
+        // Glyphs.
+        unsigned int glyphSetSize = END_INDEX - START_INDEX;
+        writeUint(gpbFp, glyphSetSize);
+        fwrite(&font->glyphArray, sizeof(TTFGlyph), glyphSetSize, gpbFp);
+
+        // Image dimensions
+        unsigned int imageSize = font->imageWidth * font->imageHeight;
+        writeUint(gpbFp, font->imageWidth);
+        writeUint(gpbFp, font->imageHeight);
+        writeUint(gpbFp, imageSize);
+
+        FILE* previewFp = NULL;
+        std::string pgmFilePath;
+        if (fontpreview)
+        {
+            // Save out a pgm monochome image file for preview
+            std::ostringstream pgmFilePathStream;
+            pgmFilePathStream << getFilenameNoExt(outFilePath) << "-" << font->fontSize << ".pgm";
+            pgmFilePath = pgmFilePathStream.str();
+            previewFp = fopen(pgmFilePath.c_str(), "wb");
+            fprintf(previewFp, "P5 %u %u 255\n", font->imageWidth, font->imageHeight);
+        }
 
-    // Save out a pgm monochome image file for preview
-    if (fontpreview)
-    {
-        // Write out font map to an image.
-        std::string pgmFilePath = getFilenameNoExt(outFilePath);
-        pgmFilePath.append(".pgm");
-        FILE* previewFp = fopen(pgmFilePath.c_str(), "wb");
-        fprintf(previewFp, "P5 %u %u 255\n", imageWidth, imageHeight);
-        
         if (fontFormat == Font::DISTANCE_FIELD)
         {
-            // Write out the preview buffer
-            fwrite((const char*)distanceFieldBuffer, sizeof(unsigned char), imageSize, previewFp);
+            // Flip height and width since the distance field map generator is column-wise.
+            unsigned char* distanceFieldBuffer = createDistanceFields(font->imageBuffer, font->imageHeight, font->imageWidth);
+
+            fwrite(distanceFieldBuffer, sizeof(unsigned char), imageSize, gpbFp);
+            writeUint(gpbFp, Font::DISTANCE_FIELD);
+
+            if (previewFp)
+            {
+                fwrite((const char*)distanceFieldBuffer, sizeof(unsigned char), imageSize, previewFp);
+                fclose(previewFp);
+            }
+
+            free(distanceFieldBuffer);
         }
         else
         {
-            fwrite((const char*)imageBuffer, sizeof(unsigned char), imageWidth * imageHeight, previewFp);
+            fwrite(font->imageBuffer, sizeof(unsigned char), imageSize, gpbFp);
+            writeUint(gpbFp, Font::BITMAP);
+
+            if (previewFp)
+            {
+                fwrite((const char*)font->imageBuffer, sizeof(unsigned char), font->imageWidth * font->imageHeight, previewFp);
+                fclose(previewFp);
+            }
         }
-        fclose(previewFp);
 
-        LOG(1, "%s.pgm preview image created successfully. \n", getBaseName(pgmFilePath).c_str());
+        if (previewFp)
+        {
+            fclose(previewFp);
+            LOG(1, "%s.pgm preview image created successfully. \n", getBaseName(pgmFilePath).c_str());
+        }
     }
 
-    // Cleanup resources.
-    free(imageBuffer);
-    if (fontFormat == Font::DISTANCE_FIELD)
-        free(distanceFieldBuffer);
+    // Close file.
+    fclose(gpbFp);
+
+    LOG(1, "%s.gpb created successfully. \n", getBaseName(outFilePath).c_str());
+
+    for (size_t i = 0, count = fonts.size(); i < count; ++i)
+    {
+        delete fonts[i];
+    }
 
     FT_Done_Face(face);
     FT_Done_FreeType(library);

+ 2 - 2
tools/encoder/src/TTFFontEncoder.h

@@ -23,12 +23,12 @@ public:
  * 
  * @param inFilePath Input file path to the tiff file.
  * @param outFilePath Output file path to write the gpb to.
- * @param fontSize Size of the font.
+ * @param fontSizes List of sizes to generate for the font.
  * @param id ID string of the font in the ref table.
  * @param fontpreview True if the pgm font preview file should be written. (For debugging)
  * 
  * @return 0 if successful, -1 if error.
  */
-int writeFont(const char* inFilePath, const char* outFilePath, unsigned int fontSize, const char* id, bool fontpreview, Font::FontFormat fontFormat);
+int writeFont(const char* inFilePath, const char* outFilePath, std::vector<unsigned int>& fontSize, const char* id, bool fontpreview, Font::FontFormat fontFormat);
 
 }

+ 47 - 17
tools/encoder/src/main.cpp

@@ -15,23 +15,53 @@ using namespace gameplay;
  * 
  * @return A valid font size.
  */
-static unsigned int promptUserFontSize()
+static std::vector<unsigned int> promptUserFontSize()
 {
-    static const int lowerBound = 12;
+    static const int lowerBound = 8;
     static const int upperBound = 96;
-    unsigned int fontSize = 0;
-    char buffer[80];
+    std::vector<unsigned int> fontSizes;
+    bool parseError = false;
+    char buffer[4096];
     do
     {
-        printf("Enter font size (pixels) (between %d and %d):\n", lowerBound, upperBound);
-        std::cin.getline(buffer, 80);
-        int i = atoi(buffer);
-        if (i >= lowerBound && i <= upperBound)
+        parseError = false;
+        fontSizes.clear();
+
+        // Prompt for font sizes
+        printf("Enter a comma-separated list of font sizes (pixels) to generate:\n");
+        std::cin.getline(buffer, 4096);
+
+        // Parse comma-separated list of fonts sizes and validate
+        char* ptr = const_cast<char*>(buffer);
+        std::string sizeStr;
+        while (ptr)
         {
-            fontSize = (unsigned int)i;
+            char* end = strchr(ptr, ',');
+            if (end)
+            {
+                sizeStr = std::string(ptr, end - ptr);
+                ptr = end + 1;
+            }
+            else
+            {
+                sizeStr = ptr;
+                ptr = NULL;
+            }
+            if (sizeStr.length() > 0)
+            {
+                int size = atoi(sizeStr.c_str());
+                if (size < lowerBound || size > upperBound)
+                {
+                    printf("Invalid font size: %d. Must be between %d-%d.\n", size, lowerBound, upperBound);
+                    parseError = true;
+                    continue;
+                }
+                fontSizes.push_back((unsigned int)size);
+            }
         }
-    } while (fontSize == 0);
-    return fontSize;
+    } while (parseError);
+
+    return fontSizes;
 }
 
 
@@ -83,26 +113,26 @@ int main(int argc, const char** argv)
         }
     case EncoderArguments::FILEFORMAT_TTF:
         {
-            unsigned int fontSize = arguments.getFontSize();
+            std::vector<unsigned int> fontSizes = arguments.getFontSizes();
             
             Font::FontFormat fontFormat = arguments.getFontFormat();
             if (fontFormat == Font::BITMAP)
             {
-                if (fontSize == 0)
+                if (fontSizes.size() == 0)
                 {
-                    fontSize = promptUserFontSize();
+                    fontSizes = promptUserFontSize();
                 }
             }
             else
             {
                 // Distance fields use size
-                if (fontSize == 0)
+                if (fontSizes.size() == 0)
                 {
-                    fontSize = FONT_SIZE_DISTANCEFIELD;
+                    fontSizes.push_back(FONT_SIZE_DISTANCEFIELD);
                 }
             }
             std::string id = getBaseName(arguments.getFilePath());
-            writeFont(arguments.getFilePath().c_str(), arguments.getOutputFilePath().c_str(), fontSize, id.c_str(), arguments.fontPreviewEnabled(), fontFormat);
+            writeFont(arguments.getFilePath().c_str(), arguments.getOutputFilePath().c_str(), fontSizes, id.c_str(), arguments.fontPreviewEnabled(), fontFormat);
             break;
         }
     case EncoderArguments::FILEFORMAT_GPB: