2
0
Эх сурвалжийг харах

Add a 'subpixel glyph positions' option to UI (#1953)

When this option is enabled, text will be formatted with subpixel
(fractional) positions on the x-axis. Positions on the y-axis are
still pixel-aligned.

Note that this option has no effect if the hinting level is set
to FONT_HINT_LEVEL_NORMAL, as each glyph is rounded to an integral
pixel size by the hinter. It only makes a different if the hinting
mode is LIGHT (vertical hinting only) or NONE.

With subpixel positioning, the output will look blurrier due to
texture filtering. TODO: Add horizontal oversampling to improve
sharpness. That needs extra memory so it should be configurable.
Iain Merrick 8 жил өмнө
parent
commit
e603eed814

+ 15 - 5
Source/Samples/47_Typography/Typography.cpp

@@ -81,10 +81,6 @@ void Typography::Start()
     CreateCheckbox("White background", URHO3D_HANDLER(Typography, HandleWhiteBackground))
     CreateCheckbox("White background", URHO3D_HANDLER(Typography, HandleWhiteBackground))
         ->SetChecked(false);
         ->SetChecked(false);
 
 
-    // Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
-    CreateCheckbox("UI::SetForceAutoHint", URHO3D_HANDLER(Typography, HandleForceAutoHint))
-        ->SetChecked(ui->GetForceAutoHint());
-
     // Add a checkbox to toggle SRGB output conversion (if available).
     // Add a checkbox to toggle SRGB output conversion (if available).
     // This will give more correct text output for FreeType fonts, as the FreeType rasterizer
     // This will give more correct text output for FreeType fonts, as the FreeType rasterizer
     // outputs linear coverage values rather than SRGB values. However, this feature isn't
     // outputs linear coverage values rather than SRGB values. However, this feature isn't
@@ -92,6 +88,14 @@ void Typography::Start()
     CreateCheckbox("Graphics::SetSRGB", URHO3D_HANDLER(Typography, HandleSRGB))
     CreateCheckbox("Graphics::SetSRGB", URHO3D_HANDLER(Typography, HandleSRGB))
         ->SetChecked(GetSubsystem<Graphics>()->GetSRGB());
         ->SetChecked(GetSubsystem<Graphics>()->GetSRGB());
 
 
+    // Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
+    CreateCheckbox("UI::SetForceAutoHint", URHO3D_HANDLER(Typography, HandleForceAutoHint))
+        ->SetChecked(ui->GetForceAutoHint());
+
+    // Add a checkbox for the global SubpixelGlyphPositions setting. This affects character spacing.
+    CreateCheckbox("UI::SetSubpixelGlyphPositions", URHO3D_HANDLER(Typography, HandleSubpixelGlyphPositions))
+        ->SetChecked(ui->GetSubpixelGlyphPositions());
+
     // Add a drop-down menu to control the font hinting level.
     // Add a drop-down menu to control the font hinting level.
     const char* items[] = {
     const char* items[] = {
         "FONT_HINT_LEVEL_NONE",
         "FONT_HINT_LEVEL_NONE",
@@ -225,10 +229,16 @@ void Typography::HandleSRGB(StringHash eventType, VariantMap& eventData)
     }
     }
 }
 }
 
 
+void Typography::HandleSubpixelGlyphPositions(StringHash eventType, VariantMap& eventData)
+{
+    CheckBox* box = static_cast<CheckBox*>(eventData[Toggled::P_ELEMENT].GetPtr());
+    bool checked = box->IsChecked();
+    GetSubsystem<UI>()->SetSubpixelGlyphPositions(checked);
+}
+
 void Typography::HandleFontHintLevel(StringHash eventType, VariantMap& eventData)
 void Typography::HandleFontHintLevel(StringHash eventType, VariantMap& eventData)
 {
 {
     DropDownList* list = static_cast<DropDownList*>(eventData[Toggled::P_ELEMENT].GetPtr());
     DropDownList* list = static_cast<DropDownList*>(eventData[Toggled::P_ELEMENT].GetPtr());
     unsigned i = list->GetSelection();
     unsigned i = list->GetSelection();
-
     GetSubsystem<UI>()->SetFontHintLevel((FontHintLevel)i);
     GetSubsystem<UI>()->SetFontHintLevel((FontHintLevel)i);
 }
 }

+ 1 - 0
Source/Samples/47_Typography/Typography.h

@@ -58,4 +58,5 @@ private:
     void HandleSRGB(StringHash eventType, VariantMap& eventData);
     void HandleSRGB(StringHash eventType, VariantMap& eventData);
     void HandleForceAutoHint(StringHash eventType, VariantMap& eventData);
     void HandleForceAutoHint(StringHash eventType, VariantMap& eventData);
     void HandleFontHintLevel(StringHash eventType, VariantMap& eventData);
     void HandleFontHintLevel(StringHash eventType, VariantMap& eventData);
+    void HandleSubpixelGlyphPositions(StringHash eventType, VariantMap& eventData);
 };
 };

+ 11 - 9
Source/Urho3D/AngelScript/UIAPI.cpp

@@ -76,7 +76,7 @@ static void RegisterFont(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Font", "bool SaveXML(File@+, int, bool usedGlyphs = false, const String&in indentation = \"\t\")", asFUNCTION(FontSaveXMLFile), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Font", "bool SaveXML(File@+, int, bool usedGlyphs = false, const String&in indentation = \"\t\")", asFUNCTION(FontSaveXMLFile), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Font", "bool SaveXML(VectorBuffer&, int, bool usedGlyphs = false, const String&in indentation = \"\t\")", asFUNCTION(FontSaveXMLVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Font", "bool SaveXML(VectorBuffer&, int, bool usedGlyphs = false, const String&in indentation = \"\t\")", asFUNCTION(FontSaveXMLVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Font", "bool SaveXML(const String&in, int, bool usedGlyphs = false, const String&in indentation = \"\t\")", asFUNCTION(FontSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Font", "bool SaveXML(const String&in, int, bool usedGlyphs = false, const String&in indentation = \"\t\")", asFUNCTION(FontSaveXML), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Font", "IntVector2 GetTotalGlyphOffset(int) const", asMETHOD(Font, GetTotalGlyphOffset), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Font", "IntVector2 GetTotalGlyphOffset(float) const", asMETHOD(Font, GetTotalGlyphOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod("Font", "void set_absoluteGlyphOffset(const IntVector2&)", asMETHOD(Font, SetAbsoluteGlyphOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod("Font", "void set_absoluteGlyphOffset(const IntVector2&)", asMETHOD(Font, SetAbsoluteGlyphOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod("Font", "const IntVector2& get_absoluteGlyphOffset() const", asMETHOD(Font, GetAbsoluteGlyphOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod("Font", "const IntVector2& get_absoluteGlyphOffset() const", asMETHOD(Font, GetAbsoluteGlyphOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod("Font", "void set_scaledGlyphOffset(const Vector2&)", asMETHOD(Font, SetScaledGlyphOffset), asCALL_THISCALL);
     engine->RegisterObjectMethod("Font", "void set_scaledGlyphOffset(const Vector2&)", asMETHOD(Font, SetScaledGlyphOffset), asCALL_THISCALL);
@@ -411,10 +411,10 @@ static void RegisterText(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Text", "const Color& get_effectColor() const", asMETHOD(Text, GetEffectColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "const Color& get_effectColor() const", asMETHOD(Text, GetEffectColor), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint get_numRows() const", asMETHOD(Text, GetNumRows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint get_numRows() const", asMETHOD(Text, GetNumRows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint get_numChars() const", asMETHOD(Text, GetNumChars), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text", "uint get_numChars() const", asMETHOD(Text, GetNumChars), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text", "int get_rowWidths(uint) const", asMETHOD(Text, GetRowWidth), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text", "IntVector2 get_charPositions(uint)", asMETHOD(Text, GetCharPosition), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text", "IntVector2 get_charSizes(uint)", asMETHOD(Text, GetCharSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text", "int get_rowHeight() const", asMETHOD(Text, GetRowHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "float get_rowWidths(uint) const", asMETHOD(Text, GetRowWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "Vector2 get_charPositions(uint)", asMETHOD(Text, GetCharPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "Vector2 get_charSizes(uint)", asMETHOD(Text, GetCharSize), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text", "float get_rowHeight() const", asMETHOD(Text, GetRowHeight), asCALL_THISCALL);
 }
 }
 
 
 static void RegisterText3D(asIScriptEngine* engine)
 static void RegisterText3D(asIScriptEngine* engine)
@@ -466,10 +466,10 @@ static void RegisterText3D(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Text3D", "FaceCameraMode get_faceCameraMode() const", asMETHOD(Text3D, GetFaceCameraMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "FaceCameraMode get_faceCameraMode() const", asMETHOD(Text3D, GetFaceCameraMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "uint get_numRows() const", asMETHOD(Text3D, GetNumRows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "uint get_numRows() const", asMETHOD(Text3D, GetNumRows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "uint get_numChars() const", asMETHOD(Text3D, GetNumChars), asCALL_THISCALL);
     engine->RegisterObjectMethod("Text3D", "uint get_numChars() const", asMETHOD(Text3D, GetNumChars), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text3D", "int get_rowWidths(uint) const", asMETHOD(Text3D, GetRowWidth), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text3D", "IntVector2 get_charPositions(uint)", asMETHOD(Text3D, GetCharPosition), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text3D", "IntVector2 get_charSizes(uint)", asMETHOD(Text3D, GetCharSize), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Text3D", "int get_rowHeight() const", asMETHOD(Text3D, GetRowHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "float get_rowWidths(uint) const", asMETHOD(Text3D, GetRowWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "Vector2 get_charPositions(uint)", asMETHOD(Text3D, GetCharPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "Vector2 get_charSizes(uint)", asMETHOD(Text3D, GetCharSize), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Text3D", "float get_rowHeight() const", asMETHOD(Text3D, GetRowHeight), asCALL_THISCALL);
 }
 }
 
 
 static void RegisterLineEdit(asIScriptEngine* engine)
 static void RegisterLineEdit(asIScriptEngine* engine)
@@ -789,6 +789,8 @@ static void RegisterUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "bool get_forceAutoHint() const", asMETHOD(UI, GetForceAutoHint), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_forceAutoHint() const", asMETHOD(UI, GetForceAutoHint), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_fontHintLevel(FontHintLevel)", asMETHOD(UI, SetFontHintLevel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_fontHintLevel(FontHintLevel)", asMETHOD(UI, SetFontHintLevel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "FontHintLevel get_fontHintLevel() const", asMETHOD(UI, GetFontHintLevel), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "FontHintLevel get_fontHintLevel() const", asMETHOD(UI, GetFontHintLevel), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void set_subpixelGlyphPositions(bool)", asMETHOD(UI, SetSubpixelGlyphPositions), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "bool get_subpixelGlyphPositions() const", asMETHOD(UI, GetSubpixelGlyphPositions), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_scale(float value)", asMETHOD(UI, SetScale), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_scale(float value)", asMETHOD(UI, SetScale), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "float get_scale() const", asMETHOD(UI, GetScale), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "float get_scale() const", asMETHOD(UI, GetScale), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_customSize(const IntVector2&in)", asMETHODPR(UI, SetCustomSize, (const IntVector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_customSize(const IntVector2&in)", asMETHODPR(UI, SetCustomSize, (const IntVector2&), void), asCALL_THISCALL);

+ 1 - 1
Source/Urho3D/LuaScript/pkgs/UI/Font.pkg

@@ -15,7 +15,7 @@ class Font : public Resource
     
     
     const IntVector2& GetAbsoluteGlyphOffset() const;
     const IntVector2& GetAbsoluteGlyphOffset() const;
     const Vector2& GetScaledGlyphOffset() const;
     const Vector2& GetScaledGlyphOffset() const;
-    IntVector2 GetTotalGlyphOffset(int pointSize) const;
+    IntVector2 GetTotalGlyphOffset(float pointSize) const;
     FontType GetFontType() const;
     FontType GetFontType() const;
     bool IsSDFFont() const;
     bool IsSDFFont() const;
     
     

+ 5 - 5
Source/Urho3D/LuaScript/pkgs/UI/Text.pkg

@@ -49,12 +49,12 @@ class Text : public UIElement
     int GetEffectStrokeThickness() const;
     int GetEffectStrokeThickness() const;
     bool GetEffectRoundStroke() const;
     bool GetEffectRoundStroke() const;
     const Color& GetEffectColor() const;
     const Color& GetEffectColor() const;
-    int GetRowHeight() const;
+    float GetRowHeight() const;
     unsigned GetNumRows() const;
     unsigned GetNumRows() const;
     unsigned GetNumChars() const;
     unsigned GetNumChars() const;
-    int GetRowWidth(unsigned index) const;
-    IntVector2 GetCharPosition(unsigned index);
-    IntVector2 GetCharSize(unsigned index);
+    float GetRowWidth(unsigned index) const;
+    Vector2 GetCharPosition(unsigned index);
+    Vector2 GetCharSize(unsigned index);
 
 
     void SetEffectDepthBias(float bias);
     void SetEffectDepthBias(float bias);
     float GetEffectDepthBias() const;
     float GetEffectDepthBias() const;
@@ -75,7 +75,7 @@ class Text : public UIElement
     tolua_property__get_set int effectStrokeThickness;
     tolua_property__get_set int effectStrokeThickness;
     tolua_property__get_set bool effectRoundStroke;
     tolua_property__get_set bool effectRoundStroke;
     tolua_property__get_set Color& effectColor;
     tolua_property__get_set Color& effectColor;
-    tolua_readonly tolua_property__get_set int rowHeight;
+    tolua_readonly tolua_property__get_set float rowHeight;
     tolua_readonly tolua_property__get_set unsigned numRows;
     tolua_readonly tolua_property__get_set unsigned numRows;
     tolua_readonly tolua_property__get_set unsigned numChars;
     tolua_readonly tolua_property__get_set unsigned numChars;
 };
 };

+ 5 - 5
Source/Urho3D/LuaScript/pkgs/UI/Text3D.pkg

@@ -61,12 +61,12 @@ class Text3D : public Drawable
     float GetEffectDepthBias() const;
     float GetEffectDepthBias() const;
     int GetWidth() const;
     int GetWidth() const;
     int GetHeight() const;
     int GetHeight() const;
-    int GetRowHeight() const;
+    float GetRowHeight() const;
     unsigned GetNumRows() const;
     unsigned GetNumRows() const;
     unsigned GetNumChars() const;
     unsigned GetNumChars() const;
-    int GetRowWidth(unsigned index) const;
-    IntVector2 GetCharPosition(unsigned index);
-    IntVector2 GetCharSize(unsigned index);
+    float GetRowWidth(unsigned index) const;
+    Vector2 GetCharPosition(unsigned index);
+    Vector2 GetCharSize(unsigned index);
     const Color& GetColor(Corner corner) const;
     const Color& GetColor(Corner corner) const;
     float GetOpacity() const;
     float GetOpacity() const;
     bool IsFixedScreenSize() const;
     bool IsFixedScreenSize() const;
@@ -90,7 +90,7 @@ class Text3D : public Drawable
     tolua_property__get_set int width;
     tolua_property__get_set int width;
     tolua_property__get_set Color& color; // Write only property.
     tolua_property__get_set Color& color; // Write only property.
     tolua_readonly tolua_property__get_set int height;
     tolua_readonly tolua_property__get_set int height;
-    tolua_readonly tolua_property__get_set int rowHeight;
+    tolua_readonly tolua_property__get_set float rowHeight;
     tolua_readonly tolua_property__get_set unsigned numRows;
     tolua_readonly tolua_property__get_set unsigned numRows;
     tolua_readonly tolua_property__get_set unsigned numChars;
     tolua_readonly tolua_property__get_set unsigned numChars;
     tolua_property__get_set float opacity;
     tolua_property__get_set float opacity;

+ 4 - 0
Source/Urho3D/LuaScript/pkgs/UI/UI.pkg

@@ -31,6 +31,7 @@ class UI : public Object
     void SetUseMutableGlyphs(bool enable);
     void SetUseMutableGlyphs(bool enable);
     void SetForceAutoHint(bool enable);
     void SetForceAutoHint(bool enable);
     void SetFontHintLevel(FontHintLevel level);
     void SetFontHintLevel(FontHintLevel level);
+    void SetSubpixelGlyphPositions(bool enable);
     void SetScale(float scale);
     void SetScale(float scale);
     void SetWidth(float width);
     void SetWidth(float width);
     void SetHeight(float height);
     void SetHeight(float height);
@@ -59,6 +60,7 @@ class UI : public Object
     bool GetUseMutableGlyphs() const;
     bool GetUseMutableGlyphs() const;
     bool GetForceAutoHint() const;
     bool GetForceAutoHint() const;
     FontHintLevel GetFontHintLevel() const;
     FontHintLevel GetFontHintLevel() const;
+    bool GetSubpixelGlyphPositions() const;
     bool HasModalElement() const;
     bool HasModalElement() const;
     bool IsDragging() const;
     bool IsDragging() const;
     float GetScale() const;
     float GetScale() const;
@@ -82,6 +84,8 @@ class UI : public Object
     tolua_property__get_set bool useScreenKeyboard;
     tolua_property__get_set bool useScreenKeyboard;
     tolua_property__get_set bool useMutableGlyphs;
     tolua_property__get_set bool useMutableGlyphs;
     tolua_property__get_set bool forceAutoHint;
     tolua_property__get_set bool forceAutoHint;
+    tolua_property__get_set FontHintLevel fontHintLevel;
+    tolua_property__get_set bool subpixelGlyphPositions;
     tolua_readonly tolua_property__has_set bool modalElement;
     tolua_readonly tolua_property__has_set bool modalElement;
     tolua_property__get_set float scale;
     tolua_property__get_set float scale;
     tolua_property__get_set IntVector2& customSize;
     tolua_property__get_set IntVector2& customSize;

+ 3 - 3
Source/Urho3D/UI/Font.cpp

@@ -178,10 +178,10 @@ FontFace* Font::GetFace(float pointSize)
     }
     }
 }
 }
 
 
-IntVector2 Font::GetTotalGlyphOffset(int pointSize) const
+IntVector2 Font::GetTotalGlyphOffset(float pointSize) const
 {
 {
-    Vector2 multipliedOffset = (float)pointSize * scaledOffset_;
-    return absoluteOffset_ + IntVector2((int)multipliedOffset.x_, (int)multipliedOffset.y_);
+    Vector2 multipliedOffset = pointSize * scaledOffset_;
+    return absoluteOffset_ + IntVector2((int)(multipliedOffset.x_ + 0.5f), (int)(multipliedOffset.y_ + 0.5f));
 }
 }
 
 
 void Font::ReleaseFaces()
 void Font::ReleaseFaces()

+ 1 - 1
Source/Urho3D/UI/Font.h

@@ -80,7 +80,7 @@ public:
     const Vector2& GetScaledGlyphOffset() const { return scaledOffset_; }
     const Vector2& GetScaledGlyphOffset() const { return scaledOffset_; }
 
 
     /// Return the total effective offset for a point size.
     /// Return the total effective offset for a point size.
-    IntVector2 GetTotalGlyphOffset(int pointSize) const;
+    IntVector2 GetTotalGlyphOffset(float pointSize) const;
 
 
     /// Release font faces and recreate them next time when requested. Called when font textures lost or global font properties change.
     /// Release font faces and recreate them next time when requested. Called when font textures lost or global font properties change.
     void ReleaseFaces();
     void ReleaseFaces();

+ 2 - 2
Source/Urho3D/UI/FontFace.cpp

@@ -70,7 +70,7 @@ const FontGlyph* FontFace::GetGlyph(unsigned c)
         return 0;
         return 0;
 }
 }
 
 
-short FontFace::GetKerning(unsigned c, unsigned d) const
+float FontFace::GetKerning(unsigned c, unsigned d) const
 {
 {
     if (kerningMapping_.Empty())
     if (kerningMapping_.Empty())
         return 0;
         return 0;
@@ -83,7 +83,7 @@ short FontFace::GetKerning(unsigned c, unsigned d) const
 
 
     unsigned value = (c << 16) + d;
     unsigned value = (c << 16) + d;
 
 
-    HashMap<unsigned, short>::ConstIterator i = kerningMapping_.Find(value);
+    HashMap<unsigned, float>::ConstIterator i = kerningMapping_.Find(value);
     if (i != kerningMapping_.End())
     if (i != kerningMapping_.End())
         return i->second_;
         return i->second_;
 
 

+ 5 - 5
Source/Urho3D/UI/FontFace.h

@@ -52,7 +52,7 @@ struct URHO3D_API FontGlyph
     /// Glyph Y offset from origin.
     /// Glyph Y offset from origin.
     short offsetY_;
     short offsetY_;
     /// Horizontal advance.
     /// Horizontal advance.
-    short advanceX_;
+    float advanceX_;
     /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.
     /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.
     unsigned page_;
     unsigned page_;
     /// Used flag.
     /// Used flag.
@@ -79,7 +79,7 @@ public:
     virtual bool HasMutableGlyphs() const { return false; }
     virtual bool HasMutableGlyphs() const { return false; }
 
 
     /// Return the kerning for a character and the next character.
     /// Return the kerning for a character and the next character.
-    short GetKerning(unsigned c, unsigned d) const;
+    float GetKerning(unsigned c, unsigned d) const;
     /// Return true when one of the texture has a data loss.
     /// Return true when one of the texture has a data loss.
     bool IsDataLost() const;
     bool IsDataLost() const;
 
 
@@ -87,7 +87,7 @@ public:
     float GetPointSize() const { return pointSize_; }
     float GetPointSize() const { return pointSize_; }
 
 
     /// Return row height.
     /// Return row height.
-    int GetRowHeight() const { return rowHeight_; }
+    float GetRowHeight() const { return rowHeight_; }
 
 
     /// Return textures.
     /// Return textures.
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
@@ -104,13 +104,13 @@ protected:
     /// Glyph mapping.
     /// Glyph mapping.
     HashMap<unsigned, FontGlyph> glyphMapping_;
     HashMap<unsigned, FontGlyph> glyphMapping_;
     /// Kerning mapping.
     /// Kerning mapping.
-    HashMap<unsigned, short> kerningMapping_;
+    HashMap<unsigned, float> kerningMapping_;
     /// Glyph texture pages.
     /// Glyph texture pages.
     Vector<SharedPtr<Texture2D> > textures_;
     Vector<SharedPtr<Texture2D> > textures_;
     /// Point size.
     /// Point size.
     float pointSize_;
     float pointSize_;
     /// Row height.
     /// Row height.
-    int rowHeight_;
+    float rowHeight_;
 };
 };
 
 
 }
 }

+ 2 - 2
Source/Urho3D/UI/FontFaceBitmap.cpp

@@ -251,7 +251,7 @@ bool FontFaceBitmap::Load(FontFace* fontFace, bool usedGlyphs)
     for (unsigned i = 0; i < newImages.Size(); ++i)
     for (unsigned i = 0; i < newImages.Size(); ++i)
         textures_[i] = LoadFaceTexture(newImages[i]);
         textures_[i] = LoadFaceTexture(newImages[i]);
 
 
-    for (HashMap<unsigned, short>::ConstIterator i = fontFace->kerningMapping_.Begin(); i != fontFace->kerningMapping_.End(); ++i)
+    for (HashMap<unsigned, float>::ConstIterator i = fontFace->kerningMapping_.Begin(); i != fontFace->kerningMapping_.End(); ++i)
     {
     {
         unsigned first = (i->first_) >> 16;
         unsigned first = (i->first_) >> 16;
         unsigned second = (i->first_) & 0xffff;
         unsigned second = (i->first_) & 0xffff;
@@ -329,7 +329,7 @@ bool FontFaceBitmap::Save(Serializer& dest, int pointSize, const String& indenta
     if (!kerningMapping_.Empty())
     if (!kerningMapping_.Empty())
     {
     {
         XMLElement kerningsElem = rootElem.CreateChild("kernings");
         XMLElement kerningsElem = rootElem.CreateChild("kernings");
-        for (HashMap<unsigned, short>::ConstIterator i = kerningMapping_.Begin(); i != kerningMapping_.End(); ++i)
+        for (HashMap<unsigned, float>::ConstIterator i = kerningMapping_.Begin(); i != kerningMapping_.End(); ++i)
         {
         {
             XMLElement kerningElem = kerningsElem.CreateChild("kerning");
             XMLElement kerningElem = kerningsElem.CreateChild("kerning");
             kerningElem.SetInt("first", i->first_ >> 16);
             kerningElem.SetInt("first", i->first_ >> 16);

+ 25 - 11
Source/Urho3D/UI/FontFaceFreeType.cpp

@@ -41,9 +41,9 @@
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
-inline int RoundToPixels(FT_Pos value)
+inline float FixedToFloat(FT_Pos value)
 {
 {
-    return (int)(value >> 6) + (((value & 0x3f) >= 0x20) ? 1 : 0);
+    return value / 64.0f;
 }
 }
 
 
 /// FreeType library subsystem.
 /// FreeType library subsystem.
@@ -172,19 +172,20 @@ bool FontFaceFreeType::Load(const unsigned char* fontData, unsigned fontDataSize
         loadMode_ |= FT_LOAD_TARGET_LIGHT;
         loadMode_ |= FT_LOAD_TARGET_LIGHT;
     }
     }
 
 
-    ascender_ = RoundToPixels(face->size->metrics.ascender);
-    rowHeight_ = RoundToPixels(face->size->metrics.height);
+    ascender_ = FixedToFloat(face->size->metrics.ascender);
+    rowHeight_ = FixedToFloat(face->size->metrics.height);
     pointSize_ = pointSize;
     pointSize_ = pointSize;
 
 
     // Check if the font's OS/2 info gives different (larger) values for ascender & descender
     // Check if the font's OS/2 info gives different (larger) values for ascender & descender
     TT_OS2* os2Info = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
     TT_OS2* os2Info = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
     if (os2Info)
     if (os2Info)
     {
     {
-        int descender = RoundToPixels(face->size->metrics.descender);
-        ascender_ = Max(ascender_, os2Info->usWinAscent * face->size->metrics.y_ppem / face->units_per_EM);
-        ascender_ = Max(ascender_, os2Info->sTypoAscender * face->size->metrics.y_ppem / face->units_per_EM);
-        descender = Max(descender, os2Info->usWinDescent * face->size->metrics.y_ppem / face->units_per_EM);
-        descender = Max(descender, os2Info->sTypoDescender * face->size->metrics.y_ppem / face->units_per_EM);
+        float descender = FixedToFloat(face->size->metrics.descender);
+        float unitsPerEm = face->units_per_EM;
+        ascender_ = Max(ascender_, os2Info->usWinAscent * face->size->metrics.y_ppem / unitsPerEm);
+        ascender_ = Max(ascender_, os2Info->sTypoAscender * face->size->metrics.y_ppem / unitsPerEm);
+        descender = Max(descender, os2Info->usWinDescent * face->size->metrics.y_ppem / unitsPerEm);
+        descender = Max(descender, os2Info->sTypoDescender * face->size->metrics.y_ppem / unitsPerEm);
         rowHeight_ = Max(rowHeight_, ascender_ + descender);
         rowHeight_ = Max(rowHeight_, ascender_ + descender);
     }
     }
 
 
@@ -265,7 +266,7 @@ bool FontFaceFreeType::Load(const unsigned char* fontData, unsigned fontDataSize
                     {
                     {
                         unsigned leftIndex = deserializer.ReadUShort();
                         unsigned leftIndex = deserializer.ReadUShort();
                         unsigned rightIndex = deserializer.ReadUShort();
                         unsigned rightIndex = deserializer.ReadUShort();
-                        short amount = RoundToPixels(deserializer.ReadShort());
+                        short amount = FixedToFloat(deserializer.ReadShort());
 
 
                         unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex] : 0;
                         unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex] : 0;
                         unsigned rightCharCode = rightIndex < numGlyphs ? charCodes[rightIndex] : 0;
                         unsigned rightCharCode = rightIndex < numGlyphs ? charCodes[rightIndex] : 0;
@@ -367,7 +368,20 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
         fontGlyph.height_ = slot->bitmap.rows;
         fontGlyph.height_ = slot->bitmap.rows;
         fontGlyph.offsetX_ = slot->bitmap_left;
         fontGlyph.offsetX_ = slot->bitmap_left;
         fontGlyph.offsetY_ = ascender_ - slot->bitmap_top;
         fontGlyph.offsetY_ = ascender_ - slot->bitmap_top;
-        fontGlyph.advanceX_ = (short)RoundToPixels(slot->metrics.horiAdvance);
+
+        UI* ui = font_->GetSubsystem<UI>();
+        FontHintLevel level = ui->GetFontHintLevel();
+        bool subpixel = ui->GetSubpixelGlyphPositions();
+        if (level <= FONT_HINT_LEVEL_LIGHT && subpixel && slot->linearHoriAdvance)
+        {
+            // linearHoriAdvance is stored in 16.16 fixed point, not the usual 26.6
+            fontGlyph.advanceX_ = slot->linearHoriAdvance / 65536.0;
+        }
+        else
+        {
+            // Round to nearest pixel (only necessary when hinting is disabled)
+            fontGlyph.advanceX_ = floor(FixedToFloat(slot->metrics.horiAdvance) + 0.5f);
+        }
     }
     }
 
 
     int x = 0, y = 0;
     int x = 0, y = 0;

+ 1 - 1
Source/Urho3D/UI/FontFaceFreeType.h

@@ -60,7 +60,7 @@ private:
     /// Load mode.
     /// Load mode.
     int loadMode_;
     int loadMode_;
     /// Ascender.
     /// Ascender.
-    int ascender_;
+    float ascender_;
     /// Has mutable glyph.
     /// Has mutable glyph.
     bool hasMutableGlyph_;
     bool hasMutableGlyph_;
     /// Glyph area allocator.
     /// Glyph area allocator.

+ 20 - 20
Source/Urho3D/UI/Text.cpp

@@ -159,12 +159,12 @@ void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData,
         UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
         UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
         batch.SetColor(selectionColor_);
         batch.SetColor(selectionColor_);
 
 
-        IntVector2 currentStart = charLocations_[selectionStart_].position_;
-        IntVector2 currentEnd = currentStart;
+        Vector2 currentStart = charLocations_[selectionStart_].position_;
+        Vector2 currentEnd = currentStart;
         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
         {
         {
             // Check if row changes, and start a new quad in that case
             // Check if row changes, and start a new quad in that case
-            if (charLocations_[i].size_ != IntVector2::ZERO)
+            if (charLocations_[i].size_ != Vector2::ZERO)
             {
             {
                 if (charLocations_[i].position_.y_ != currentStart.y_)
                 if (charLocations_[i].position_.y_ != currentStart.y_)
                 {
                 {
@@ -223,7 +223,7 @@ void Text::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData,
                 {
                 {
                     float x = Cos(angle * i) * floatThickness;
                     float x = Cos(angle * i) * floatThickness;
                     float y = Sin(angle * i) * floatThickness;
                     float y = Sin(angle * i) * floatThickness;
-                    ConstructBatch(pageBatch, pageGlyphLocation, (int)x, (int)y, &effectColor_, effectDepthBias_);
+                    ConstructBatch(pageBatch, pageGlyphLocation, x, y, &effectColor_, effectDepthBias_);
                 }
                 }
             }
             }
             else
             else
@@ -438,29 +438,29 @@ void Text::SetEffectDepthBias(float bias)
     effectDepthBias_ = bias;
     effectDepthBias_ = bias;
 }
 }
 
 
-int Text::GetRowWidth(unsigned index) const
+float Text::GetRowWidth(unsigned index) const
 {
 {
     return index < rowWidths_.Size() ? rowWidths_[index] : 0;
     return index < rowWidths_.Size() ? rowWidths_[index] : 0;
 }
 }
 
 
-IntVector2 Text::GetCharPosition(unsigned index)
+Vector2 Text::GetCharPosition(unsigned index)
 {
 {
     if (charLocationsDirty_)
     if (charLocationsDirty_)
         UpdateCharLocations();
         UpdateCharLocations();
     if (charLocations_.Empty())
     if (charLocations_.Empty())
-        return IntVector2::ZERO;
+        return Vector2::ZERO;
     // For convenience, return the position of the text ending if index exceeded
     // For convenience, return the position of the text ending if index exceeded
     if (index > charLocations_.Size() - 1)
     if (index > charLocations_.Size() - 1)
         index = charLocations_.Size() - 1;
         index = charLocations_.Size() - 1;
     return charLocations_[index].position_;
     return charLocations_[index].position_;
 }
 }
 
 
-IntVector2 Text::GetCharSize(unsigned index)
+Vector2 Text::GetCharSize(unsigned index)
 {
 {
     if (charLocationsDirty_)
     if (charLocationsDirty_)
         UpdateCharLocations();
         UpdateCharLocations();
     if (charLocations_.Size() < 2)
     if (charLocations_.Size() < 2)
-        return IntVector2::ZERO;
+        return Vector2::ZERO;
     // For convenience, return the size of the last char if index exceeded (last size entry is zero)
     // For convenience, return the size of the last char if index exceeded (last size entry is zero)
     if (index > charLocations_.Size() - 2)
     if (index > charLocations_.Size() - 2)
         index = charLocations_.Size() - 2;
         index = charLocations_.Size() - 2;
@@ -527,7 +527,7 @@ void Text::UpdateText(bool onResize)
         int width = 0;
         int width = 0;
         int height = 0;
         int height = 0;
         int rowWidth = 0;
         int rowWidth = 0;
-        int rowHeight = (int)(rowSpacing_ * rowHeight_);
+        int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
 
 
         // First see if the text must be split up
         // First see if the text must be split up
         if (!wordWrap_)
         if (!wordWrap_)
@@ -706,7 +706,7 @@ void Text::UpdateCharLocations()
         return;
         return;
     fontFace_ = face;
     fontFace_ = face;
 
 
-    int rowHeight = (int)(rowSpacing_ * rowHeight_);
+    int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
 
 
     // Store position & size of each character, and locations per texture page
     // Store position & size of each character, and locations per texture page
     unsigned numChars = unicodeText_.Size();
     unsigned numChars = unicodeText_.Size();
@@ -719,19 +719,19 @@ void Text::UpdateCharLocations()
 
 
     unsigned rowIndex = 0;
     unsigned rowIndex = 0;
     unsigned lastFilled = 0;
     unsigned lastFilled = 0;
-    int x = GetRowStartPosition(rowIndex) + offset.x_;
-    int y = offset.y_;
+    float x = floor(GetRowStartPosition(rowIndex) + offset.x_ + 0.5f);
+    float y = floor(offset.y_ + 0.5f);
 
 
     for (unsigned i = 0; i < printText_.Size(); ++i)
     for (unsigned i = 0; i < printText_.Size(); ++i)
     {
     {
         CharLocation loc;
         CharLocation loc;
-        loc.position_ = IntVector2(x, y);
+        loc.position_ = Vector2(x, y);
 
 
         unsigned c = printText_[i];
         unsigned c = printText_[i];
         if (c != '\n')
         if (c != '\n')
         {
         {
             const FontGlyph* glyph = face->GetGlyph(c);
             const FontGlyph* glyph = face->GetGlyph(c);
-            loc.size_ = IntVector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
+            loc.size_ = Vector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
             if (glyph)
             if (glyph)
             {
             {
                 // Store glyph's location for rendering. Verify that glyph page is valid
                 // Store glyph's location for rendering. Verify that glyph page is valid
@@ -744,7 +744,7 @@ void Text::UpdateCharLocations()
         }
         }
         else
         else
         {
         {
-            loc.size_ = IntVector2::ZERO;
+            loc.size_ = Vector2::ZERO;
             x = GetRowStartPosition(++rowIndex);
             x = GetRowStartPosition(++rowIndex);
             y += rowHeight;
             y += rowHeight;
         }
         }
@@ -758,8 +758,8 @@ void Text::UpdateCharLocations()
         lastFilled = printToText_[i] + 1;
         lastFilled = printToText_[i] + 1;
     }
     }
     // Store the ending position
     // Store the ending position
-    charLocations_[numChars].position_ = IntVector2(x, y);
-    charLocations_[numChars].size_ = IntVector2::ZERO;
+    charLocations_[numChars].position_ = Vector2(x, y);
+    charLocations_[numChars].size_ = Vector2::ZERO;
 
 
     charLocationsDirty_ = false;
     charLocationsDirty_ = false;
 }
 }
@@ -784,7 +784,7 @@ void Text::ValidateSelection()
 
 
 int Text::GetRowStartPosition(unsigned rowIndex) const
 int Text::GetRowStartPosition(unsigned rowIndex) const
 {
 {
-    int rowWidth = 0;
+    float rowWidth = 0;
 
 
     if (rowIndex < rowWidths_.Size())
     if (rowIndex < rowWidths_.Size())
         rowWidth = rowWidths_[rowIndex];
         rowWidth = rowWidths_[rowIndex];
@@ -806,7 +806,7 @@ int Text::GetRowStartPosition(unsigned rowIndex) const
     return ret;
     return ret;
 }
 }
 
 
-void Text::ConstructBatch(UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, int dx, int dy, Color* color,
+void Text::ConstructBatch(UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, float dx, float dy, Color* color,
     float depthBias)
     float depthBias)
 {
 {
     unsigned startDataSize = pageBatch.vertexData_->Size();
     unsigned startDataSize = pageBatch.vertexData_->Size();

+ 12 - 12
Source/Urho3D/UI/Text.h

@@ -45,16 +45,16 @@ enum TextEffect
 struct CharLocation
 struct CharLocation
 {
 {
     /// Position.
     /// Position.
-    IntVector2 position_;
+    Vector2 position_;
     /// Size.
     /// Size.
-    IntVector2 size_;
+    Vector2 size_;
 };
 };
 
 
 /// Glyph and its location within the text. Used when preparing text rendering.
 /// Glyph and its location within the text. Used when preparing text rendering.
 struct GlyphLocation
 struct GlyphLocation
 {
 {
     /// Construct.
     /// Construct.
-    GlyphLocation(int x, int y, const FontGlyph* glyph) :
+    GlyphLocation(float x, float y, const FontGlyph* glyph) :
         x_(x),
         x_(x),
         y_(y),
         y_(y),
         glyph_(glyph)
         glyph_(glyph)
@@ -62,9 +62,9 @@ struct GlyphLocation
     }
     }
 
 
     /// X coordinate.
     /// X coordinate.
-    int x_;
+    float x_;
     /// Y coordinate.
     /// Y coordinate.
-    int y_;
+    float y_;
     /// Glyph.
     /// Glyph.
     const FontGlyph* glyph_;
     const FontGlyph* glyph_;
 };
 };
@@ -177,7 +177,7 @@ public:
     const Color& GetEffectColor() const { return effectColor_; }
     const Color& GetEffectColor() const { return effectColor_; }
 
 
     /// Return row height.
     /// Return row height.
-    int GetRowHeight() const { return rowHeight_; }
+    float GetRowHeight() const { return rowHeight_; }
 
 
     /// Return number of rows.
     /// Return number of rows.
     unsigned GetNumRows() const { return rowWidths_.Size(); }
     unsigned GetNumRows() const { return rowWidths_.Size(); }
@@ -186,11 +186,11 @@ public:
     unsigned GetNumChars() const { return unicodeText_.Size(); }
     unsigned GetNumChars() const { return unicodeText_.Size(); }
 
 
     /// Return width of row by index.
     /// Return width of row by index.
-    int GetRowWidth(unsigned index) const;
+    float GetRowWidth(unsigned index) const;
     /// Return position of character by index relative to the text element origin.
     /// Return position of character by index relative to the text element origin.
-    IntVector2 GetCharPosition(unsigned index);
+    Vector2 GetCharPosition(unsigned index);
     /// Return size of character by index.
     /// Return size of character by index.
-    IntVector2 GetCharSize(unsigned index);
+    Vector2 GetCharSize(unsigned index);
 
 
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     void SetEffectDepthBias(float bias);
     void SetEffectDepthBias(float bias);
@@ -220,7 +220,7 @@ protected:
     int GetRowStartPosition(unsigned rowIndex) const;
     int GetRowStartPosition(unsigned rowIndex) const;
     /// Contruct batch.
     /// Contruct batch.
     void ConstructBatch
     void ConstructBatch
-        (UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, int dx = 0, int dy = 0, Color* color = 0,
+        (UIBatch& pageBatch, const PODVector<GlyphLocation>& pageGlyphLocation, float dx = 0, float dy = 0, Color* color = 0,
             float depthBias = 0.0f);
             float depthBias = 0.0f);
 
 
     /// Font.
     /// Font.
@@ -260,7 +260,7 @@ protected:
     /// Text effect Z bias.
     /// Text effect Z bias.
     float effectDepthBias_;
     float effectDepthBias_;
     /// Row height.
     /// Row height.
-    int rowHeight_;
+    float rowHeight_;
     /// Text as Unicode characters.
     /// Text as Unicode characters.
     PODVector<unsigned> unicodeText_;
     PODVector<unsigned> unicodeText_;
     /// Text modified into printed form.
     /// Text modified into printed form.
@@ -268,7 +268,7 @@ protected:
     /// Mapping of printed form back to original char indices.
     /// Mapping of printed form back to original char indices.
     PODVector<unsigned> printToText_;
     PODVector<unsigned> printToText_;
     /// Row widths.
     /// Row widths.
-    PODVector<int> rowWidths_;
+    PODVector<float> rowWidths_;
     /// Glyph locations per each texture in the font.
     /// Glyph locations per each texture in the font.
     Vector<PODVector<GlyphLocation> > pageGlyphLocations_;
     Vector<PODVector<GlyphLocation> > pageGlyphLocations_;
     /// Cached locations of each character in the text.
     /// Cached locations of each character in the text.

+ 2 - 2
Source/Urho3D/UI/Text3D.cpp

@@ -487,12 +487,12 @@ int Text3D::GetRowWidth(unsigned index) const
     return text_.GetRowWidth(index);
     return text_.GetRowWidth(index);
 }
 }
 
 
-IntVector2 Text3D::GetCharPosition(unsigned index)
+Vector2 Text3D::GetCharPosition(unsigned index)
 {
 {
     return text_.GetCharPosition(index);
     return text_.GetCharPosition(index);
 }
 }
 
 
-IntVector2 Text3D::GetCharSize(unsigned index)
+Vector2 Text3D::GetCharSize(unsigned index)
 {
 {
     return text_.GetCharSize(index);
     return text_.GetCharSize(index);
 }
 }

+ 2 - 2
Source/Urho3D/UI/Text3D.h

@@ -144,9 +144,9 @@ public:
     /// Return width of row by index.
     /// Return width of row by index.
     int GetRowWidth(unsigned index) const;
     int GetRowWidth(unsigned index) const;
     /// Return position of character by index relative to the text element origin.
     /// Return position of character by index relative to the text element origin.
-    IntVector2 GetCharPosition(unsigned index);
+    Vector2 GetCharPosition(unsigned index);
     /// Return size of character by index.
     /// Return size of character by index.
-    IntVector2 GetCharSize(unsigned index);
+    Vector2 GetCharSize(unsigned index);
     /// Return corner color.
     /// Return corner color.
     const Color& GetColor(Corner corner) const;
     const Color& GetColor(Corner corner) const;
     /// Return opacity.
     /// Return opacity.

+ 10 - 0
Source/Urho3D/UI/UI.cpp

@@ -107,6 +107,7 @@ UI::UI(Context* context) :
     useMutableGlyphs_(false),
     useMutableGlyphs_(false),
     forceAutoHint_(false),
     forceAutoHint_(false),
     fontHintLevel_(FONT_HINT_LEVEL_NORMAL),
     fontHintLevel_(FONT_HINT_LEVEL_NORMAL),
+    subpixelGlyphPositions_(false),
     uiRendered_(false),
     uiRendered_(false),
     nonModalBatchSize_(0),
     nonModalBatchSize_(0),
     dragElementsCount_(0),
     dragElementsCount_(0),
@@ -615,6 +616,15 @@ void UI::SetFontHintLevel(FontHintLevel level)
     }
     }
 }
 }
 
 
+void UI::SetSubpixelGlyphPositions(bool enable)
+{
+    if (enable != subpixelGlyphPositions_)
+    {
+        subpixelGlyphPositions_ = enable;
+        ReleaseFontFaces();
+    }
+}
+
 void UI::SetScale(float scale)
 void UI::SetScale(float scale)
 {
 {
     uiScale_ = Max(scale, M_EPSILON);
     uiScale_ = Max(scale, M_EPSILON);

+ 7 - 0
Source/Urho3D/UI/UI.h

@@ -110,6 +110,8 @@ public:
     void SetForceAutoHint(bool enable);
     void SetForceAutoHint(bool enable);
     /// Set the hinting level used by FreeType fonts.
     /// Set the hinting level used by FreeType fonts.
     void SetFontHintLevel(FontHintLevel level);
     void SetFontHintLevel(FontHintLevel level);
+    /// Set whether text glyphs can have fractional positions. Default is false (pixel-aligned).
+    void SetSubpixelGlyphPositions(bool enable);
     /// Set %UI scale. 1.0 is default (pixel perfect). Resize the root element to match.
     /// Set %UI scale. 1.0 is default (pixel perfect). Resize the root element to match.
     void SetScale(float scale);
     void SetScale(float scale);
     /// Scale %UI to the specified width in pixels.
     /// Scale %UI to the specified width in pixels.
@@ -186,6 +188,9 @@ public:
     /// Return the current FreeType font hinting level.
     /// Return the current FreeType font hinting level.
     FontHintLevel GetFontHintLevel() const { return fontHintLevel_; }
     FontHintLevel GetFontHintLevel() const { return fontHintLevel_; }
 
 
+    // Return whether text glyphs can have fractional positions.
+    bool GetSubpixelGlyphPositions() const { return subpixelGlyphPositions_; }
+
     /// Return true when UI has modal element(s).
     /// Return true when UI has modal element(s).
     bool HasModalElement() const;
     bool HasModalElement() const;
 
 
@@ -351,6 +356,8 @@ private:
     bool forceAutoHint_;
     bool forceAutoHint_;
     /// FreeType hinting level (default is FONT_HINT_LEVEL_NORMAL).
     /// FreeType hinting level (default is FONT_HINT_LEVEL_NORMAL).
     FontHintLevel fontHintLevel_;
     FontHintLevel fontHintLevel_;
+    /// Flag for subpixel text glyph positions.
+    bool subpixelGlyphPositions_;
     /// Flag for UI already being rendered this frame.
     /// Flag for UI already being rendered this frame.
     bool uiRendered_;
     bool uiRendered_;
     /// Non-modal batch size (used internally for rendering).
     /// Non-modal batch size (used internally for rendering).

+ 8 - 8
Source/Urho3D/UI/UIBatch.cpp

@@ -82,7 +82,7 @@ void UIBatch::SetDefaultColor()
     }
     }
 }
 }
 
 
-void UIBatch::AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth, int texHeight)
+void UIBatch::AddQuad(float x, float y, float width, float height, int texOffsetX, int texOffsetY, int texWidth, int texHeight)
 {
 {
     unsigned topLeftColor, topRightColor, bottomLeftColor, bottomRightColor;
     unsigned topLeftColor, topRightColor, bottomLeftColor, bottomRightColor;
 
 
@@ -107,10 +107,10 @@ void UIBatch::AddQuad(int x, int y, int width, int height, int texOffsetX, int t
 
 
     const IntVector2& screenPos = element_->GetScreenPosition();
     const IntVector2& screenPos = element_->GetScreenPosition();
 
 
-    float left = (float)(x + screenPos.x_) - posAdjust.x_;
-    float right = left + (float)width;
-    float top = (float)(y + screenPos.y_) - posAdjust.x_;
-    float bottom = top + (float)height;
+    float left = x + screenPos.x_ - posAdjust.x_;
+    float right = left + width;
+    float top = y + screenPos.y_ - posAdjust.x_;
+    float bottom = top + height;
 
 
     float leftUV = texOffsetX * invTextureSize_.x_;
     float leftUV = texOffsetX * invTextureSize_.x_;
     float topUV = texOffsetY * invTextureSize_.y_;
     float topUV = texOffsetY * invTextureSize_.y_;
@@ -422,14 +422,14 @@ bool UIBatch::Merge(const UIBatch& batch)
     return true;
     return true;
 }
 }
 
 
-unsigned UIBatch::GetInterpolatedColor(int x, int y)
+unsigned UIBatch::GetInterpolatedColor(float x, float y)
 {
 {
     const IntVector2& size = element_->GetSize();
     const IntVector2& size = element_->GetSize();
 
 
     if (size.x_ && size.y_)
     if (size.x_ && size.y_)
     {
     {
-        float cLerpX = Clamp((float)x / (float)size.x_, 0.0f, 1.0f);
-        float cLerpY = Clamp((float)y / (float)size.y_, 0.0f, 1.0f);
+        float cLerpX = Clamp(x / (float)size.x_, 0.0f, 1.0f);
+        float cLerpY = Clamp(y / (float)size.y_, 0.0f, 1.0f);
 
 
         Color topColor = element_->GetColor(C_TOPLEFT).Lerp(element_->GetColor(C_TOPRIGHT), cLerpX);
         Color topColor = element_->GetColor(C_TOPLEFT).Lerp(element_->GetColor(C_TOPRIGHT), cLerpX);
         Color bottomColor = element_->GetColor(C_BOTTOMLEFT).Lerp(element_->GetColor(C_BOTTOMRIGHT), cLerpX);
         Color bottomColor = element_->GetColor(C_BOTTOMLEFT).Lerp(element_->GetColor(C_BOTTOMRIGHT), cLerpX);

+ 2 - 2
Source/Urho3D/UI/UIBatch.h

@@ -51,7 +51,7 @@ public:
     /// Restore UI element's default color.
     /// Restore UI element's default color.
     void SetDefaultColor();
     void SetDefaultColor();
     /// Add a quad.
     /// Add a quad.
-    void AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0, int texHeight = 0);
+    void AddQuad(float x, float y, float width, float height, int texOffsetX, int texOffsetY, int texWidth = 0, int texHeight = 0);
     /// Add a quad using a transform matrix.
     /// Add a quad using a transform matrix.
     void AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0,
     void AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0,
         int texHeight = 0);
         int texHeight = 0);
@@ -67,7 +67,7 @@ public:
     /// Merge with another batch.
     /// Merge with another batch.
     bool Merge(const UIBatch& batch);
     bool Merge(const UIBatch& batch);
     /// Return an interpolated color for the UI element.
     /// Return an interpolated color for the UI element.
-    unsigned GetInterpolatedColor(int x, int y);
+    unsigned GetInterpolatedColor(float x, float y);
 
 
     /// Add or merge a batch.
     /// Add or merge a batch.
     static void AddOrMerge(const UIBatch& batch, PODVector<UIBatch>& batches);
     static void AddOrMerge(const UIBatch& batch, PODVector<UIBatch>& batches);

+ 15 - 4
bin/Data/LuaScripts/47_Typography.lua

@@ -36,10 +36,6 @@ function Start()
     CreateCheckbox("White background", "HandleWhiteBackground")
     CreateCheckbox("White background", "HandleWhiteBackground")
         :SetChecked(false)
         :SetChecked(false)
 
 
-    -- Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
-    CreateCheckbox("UI::SetForceAutoHint", "HandleForceAutoHint")
-        :SetChecked(ui:GetForceAutoHint())
-
     -- Add a checkbox to toggle SRGB output conversion (if available).
     -- Add a checkbox to toggle SRGB output conversion (if available).
     -- This will give more correct text output for FreeType fonts, as the FreeType rasterizer
     -- This will give more correct text output for FreeType fonts, as the FreeType rasterizer
     -- outputs linear coverage values rather than SRGB values. However, this feature isn't
     -- outputs linear coverage values rather than SRGB values. However, this feature isn't
@@ -47,6 +43,14 @@ function Start()
     CreateCheckbox("Graphics::SetSRGB", "HandleSRGB")
     CreateCheckbox("Graphics::SetSRGB", "HandleSRGB")
         :SetChecked(graphics:GetSRGB())
         :SetChecked(graphics:GetSRGB())
 
 
+    -- Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
+    CreateCheckbox("UI::SetForceAutoHint", "HandleForceAutoHint")
+        :SetChecked(ui:GetForceAutoHint())
+
+    -- Add a checkbox for the global SubpixelGlyphPositions setting. This affects character spacing.
+    CreateCheckbox("UI::SetSubpixelGlyphPositions", "HandleSubpixelGlyphPositions")
+        :SetChecked(ui:GetSubpixelGlyphPositions())
+
     -- Add a drop-down menu to control the font hinting level.
     -- Add a drop-down menu to control the font hinting level.
     local items = {
     local items = {
         "FONT_HINT_LEVEL_NONE",
         "FONT_HINT_LEVEL_NONE",
@@ -161,6 +165,13 @@ function HandleSRGB(eventType, eventData)
     end
     end
 end
 end
 
 
+function HandleSubpixelGlyphPositions(eventType, eventData)
+    local box = eventData["Element"]:GetPtr("CheckBox")
+    local checked = box:IsChecked()
+
+    ui:SetSubpixelGlyphPositions(checked)
+end
+
 function HandleFontHintLevel(eventType, eventData)
 function HandleFontHintLevel(eventType, eventData)
     local list = eventData["Element"]:GetPtr("DropDownList")
     local list = eventData["Element"]:GetPtr("DropDownList")
     local i = list:GetSelection()
     local i = list:GetSelection()

+ 17 - 4
bin/Data/Scripts/47_Typography.as

@@ -37,10 +37,6 @@ void Start()
     CreateCheckbox("White background", "HandleWhiteBackground")
     CreateCheckbox("White background", "HandleWhiteBackground")
         .checked = false;
         .checked = false;
 
 
-    // Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
-    CreateCheckbox("UI::SetForceAutoHint", "HandleForceAutoHint")
-        .checked = ui.forceAutoHint;
-
     // Add a checkbox to toggle SRGB output conversion (if available).
     // Add a checkbox to toggle SRGB output conversion (if available).
     // This will give more correct text output for FreeType fonts, as the FreeType rasterizer
     // This will give more correct text output for FreeType fonts, as the FreeType rasterizer
     // outputs linear coverage values rather than SRGB values. However, this feature isn't
     // outputs linear coverage values rather than SRGB values. However, this feature isn't
@@ -48,6 +44,14 @@ void Start()
     CreateCheckbox("Graphics::SetSRGB", "HandleSRGB")
     CreateCheckbox("Graphics::SetSRGB", "HandleSRGB")
         .checked = graphics.sRGB;
         .checked = graphics.sRGB;
 
 
+    // Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
+    CreateCheckbox("UI::SetForceAutoHint", "HandleForceAutoHint")
+        .checked = ui.forceAutoHint;
+
+    // Add a checkbox for the global SubpixelGlyphPositions setting. This affects character spacing.
+    CreateCheckbox("UI::SetSubpixelGlyphPositions", "HandleSubpixelGlyphPositions")
+        .checked = ui.subpixelGlyphPositions;
+
     // Add a drop-down menu to control the font hinting level.
     // Add a drop-down menu to control the font hinting level.
     Array<String> items = {
     Array<String> items = {
         "FONT_HINT_LEVEL_NONE",
         "FONT_HINT_LEVEL_NONE",
@@ -174,6 +178,15 @@ void HandleSRGB(StringHash eventType, VariantMap& eventData)
     }
     }
 }
 }
 
 
+void HandleSubpixelGlyphPositions(StringHash eventType, VariantMap& eventData)
+{
+    CheckBox@ box = eventData["Element"].GetPtr();
+    bool checked = box.checked;
+
+    ui.subpixelGlyphPositions = checked;
+}
+
+
 void HandleFontHintLevel(StringHash eventType, VariantMap& eventData)
 void HandleFontHintLevel(StringHash eventType, VariantMap& eventData)
 {
 {
     DropDownList@ list = eventData["Element"].GetPtr();
     DropDownList@ list = eventData["Element"].GetPtr();