|
@@ -32,6 +32,8 @@
|
|
|
#include "../UI/FontFaceFreeType.h"
|
|
#include "../UI/FontFaceFreeType.h"
|
|
|
#include "../UI/UI.h"
|
|
#include "../UI/UI.h"
|
|
|
|
|
|
|
|
|
|
+#include <assert.h>
|
|
|
|
|
+
|
|
|
#include <ft2build.h>
|
|
#include <ft2build.h>
|
|
|
#include FT_FREETYPE_H
|
|
#include FT_FREETYPE_H
|
|
|
#include FT_TRUETYPE_TABLES_H
|
|
#include FT_TRUETYPE_TABLES_H
|
|
@@ -104,7 +106,12 @@ bool FontFaceFreeType::Load(const unsigned char* fontData, unsigned fontDataSize
|
|
|
freeType_ = freeType;
|
|
freeType_ = freeType;
|
|
|
|
|
|
|
|
UI* ui = font_->GetSubsystem<UI>();
|
|
UI* ui = font_->GetSubsystem<UI>();
|
|
|
- int maxTextureSize = ui->GetMaxFontTextureSize();
|
|
|
|
|
|
|
+ const int maxTextureSize = ui->GetMaxFontTextureSize();
|
|
|
|
|
+ const FontHintLevel hintLevel = ui->GetFontHintLevel();
|
|
|
|
|
+ const float subpixelThreshold = ui->GetFontSubpixelThreshold();
|
|
|
|
|
+
|
|
|
|
|
+ subpixel_ = (hintLevel <= FONT_HINT_LEVEL_LIGHT) && (pointSize <= subpixelThreshold);
|
|
|
|
|
+ oversampling_ = subpixel_ ? ui->GetFontOversampling() : 1;
|
|
|
|
|
|
|
|
FT_Face face;
|
|
FT_Face face;
|
|
|
FT_Error error;
|
|
FT_Error error;
|
|
@@ -128,7 +135,7 @@ bool FontFaceFreeType::Load(const unsigned char* fontData, unsigned fontDataSize
|
|
|
URHO3D_LOGERROR("Could not create font face");
|
|
URHO3D_LOGERROR("Could not create font face");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- error = FT_Set_Char_Size(face, 0, pointSize * 64, FONT_DPI, FONT_DPI);
|
|
|
|
|
|
|
+ error = FT_Set_Char_Size(face, 0, pointSize * 64, oversampling_ * FONT_DPI, FONT_DPI);
|
|
|
if (error)
|
|
if (error)
|
|
|
{
|
|
{
|
|
|
FT_Done_Face(face);
|
|
FT_Done_Face(face);
|
|
@@ -340,6 +347,65 @@ bool FontFaceFreeType::SetupNextTexture(int textureWidth, int textureHeight)
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void FontFaceFreeType::BoxFilter(unsigned char* dest, size_t destSize, const unsigned char* src, size_t srcSize)
|
|
|
|
|
+{
|
|
|
|
|
+ const int filterSize = oversampling_;
|
|
|
|
|
+
|
|
|
|
|
+ assert(filterSize > 0);
|
|
|
|
|
+ assert(destSize == srcSize + filterSize - 1);
|
|
|
|
|
+
|
|
|
|
|
+ if (filterSize == 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ memcpy(dest, src, srcSize);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // "accumulator" holds the total value of filterSize samples. We add one sample
|
|
|
|
|
+ // and remove one sample per step (with special cases for left and right edges).
|
|
|
|
|
+ int accumulator = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // The divide might make these inner loops slow. If so, some possible optimizations:
|
|
|
|
|
+ // a) Turn it into a fixed-point multiply-and-shift rather than an integer divide;
|
|
|
|
|
+ // b) Make this function a template, with the filter size a compile-time constant.
|
|
|
|
|
+
|
|
|
|
|
+ int i = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (srcSize < filterSize)
|
|
|
|
|
+ {
|
|
|
|
|
+ for (; i < srcSize; ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ accumulator += src[i];
|
|
|
|
|
+ dest[i] = accumulator / filterSize;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (; i < filterSize; ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ dest[i] = accumulator / filterSize;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ for ( ; i < filterSize; ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ accumulator += src[i];
|
|
|
|
|
+ dest[i] = accumulator / filterSize;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (; i < srcSize; ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ accumulator += src[i];
|
|
|
|
|
+ accumulator -= src[i - filterSize];
|
|
|
|
|
+ dest[i] = accumulator / filterSize;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (; i < srcSize + filterSize - 1; ++i)
|
|
|
|
|
+ {
|
|
|
|
|
+ accumulator -= src[i - filterSize];
|
|
|
|
|
+ dest[i] = accumulator / filterSize;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
{
|
|
{
|
|
|
if (!face_)
|
|
if (!face_)
|
|
@@ -354,6 +420,8 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
{
|
|
{
|
|
|
const char* family = face->family_name ? face->family_name : "NULL";
|
|
const char* family = face->family_name ? face->family_name : "NULL";
|
|
|
URHO3D_LOGERRORF("FT_Load_Char failed (family: %s, char code: %u)", family, charCode);
|
|
URHO3D_LOGERRORF("FT_Load_Char failed (family: %s, char code: %u)", family, charCode);
|
|
|
|
|
+ fontGlyph.texWidth_ = 0;
|
|
|
|
|
+ fontGlyph.texHeight_ = 0;
|
|
|
fontGlyph.width_ = 0;
|
|
fontGlyph.width_ = 0;
|
|
|
fontGlyph.height_ = 0;
|
|
fontGlyph.height_ = 0;
|
|
|
fontGlyph.offsetX_ = 0;
|
|
fontGlyph.offsetX_ = 0;
|
|
@@ -364,15 +432,14 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
// Note: position within texture will be filled later
|
|
// Note: position within texture will be filled later
|
|
|
- fontGlyph.width_ = slot->bitmap.width;
|
|
|
|
|
|
|
+ fontGlyph.texWidth_ = slot->bitmap.width + oversampling_ - 1;
|
|
|
|
|
+ fontGlyph.texHeight_ = slot->bitmap.rows;
|
|
|
|
|
+ fontGlyph.width_ = slot->bitmap.width + oversampling_ - 1;
|
|
|
fontGlyph.height_ = slot->bitmap.rows;
|
|
fontGlyph.height_ = slot->bitmap.rows;
|
|
|
- fontGlyph.offsetX_ = slot->bitmap_left;
|
|
|
|
|
- fontGlyph.offsetY_ = ascender_ - slot->bitmap_top;
|
|
|
|
|
|
|
+ fontGlyph.offsetX_ = slot->bitmap_left - (oversampling_ - 1) / 2.0f;
|
|
|
|
|
+ fontGlyph.offsetY_ = floorf(ascender_ + 0.5f) - slot->bitmap_top;
|
|
|
|
|
|
|
|
- UI* ui = font_->GetSubsystem<UI>();
|
|
|
|
|
- FontHintLevel level = ui->GetFontHintLevel();
|
|
|
|
|
- bool subpixel = ui->GetSubpixelGlyphPositions();
|
|
|
|
|
- if (level <= FONT_HINT_LEVEL_LIGHT && subpixel && slot->linearHoriAdvance)
|
|
|
|
|
|
|
+ if (subpixel_ && slot->linearHoriAdvance)
|
|
|
{
|
|
{
|
|
|
// linearHoriAdvance is stored in 16.16 fixed point, not the usual 26.6
|
|
// linearHoriAdvance is stored in 16.16 fixed point, not the usual 26.6
|
|
|
fontGlyph.advanceX_ = slot->linearHoriAdvance / 65536.0;
|
|
fontGlyph.advanceX_ = slot->linearHoriAdvance / 65536.0;
|
|
@@ -380,14 +447,18 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
// Round to nearest pixel (only necessary when hinting is disabled)
|
|
// Round to nearest pixel (only necessary when hinting is disabled)
|
|
|
- fontGlyph.advanceX_ = floor(FixedToFloat(slot->metrics.horiAdvance) + 0.5f);
|
|
|
|
|
|
|
+ fontGlyph.advanceX_ = floorf(FixedToFloat(slot->metrics.horiAdvance) + 0.5f);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ fontGlyph.width_ /= oversampling_;
|
|
|
|
|
+ fontGlyph.offsetX_ /= oversampling_;
|
|
|
|
|
+ fontGlyph.advanceX_ /= oversampling_;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int x = 0, y = 0;
|
|
int x = 0, y = 0;
|
|
|
- if (fontGlyph.width_ > 0 && fontGlyph.height_ > 0)
|
|
|
|
|
|
|
+ if (fontGlyph.texWidth_ > 0 && fontGlyph.texHeight_ > 0)
|
|
|
{
|
|
{
|
|
|
- if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
|
|
|
|
|
|
|
+ if (!allocator_.Allocate(fontGlyph.texWidth_ + 1, fontGlyph.texHeight_ + 1, x, y))
|
|
|
{
|
|
{
|
|
|
if (image)
|
|
if (image)
|
|
|
{
|
|
{
|
|
@@ -403,7 +474,7 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
|
|
|
|
|
|
|
+ if (!allocator_.Allocate(fontGlyph.texWidth_ + 1, fontGlyph.texHeight_ + 1, x, y))
|
|
|
{
|
|
{
|
|
|
URHO3D_LOGWARNINGF("FontFaceFreeType::LoadCharGlyph: failed to position char code %u in blank page", charCode);
|
|
URHO3D_LOGWARNINGF("FontFaceFreeType::LoadCharGlyph: failed to position char code %u in blank page", charCode);
|
|
|
return false;
|
|
return false;
|
|
@@ -424,8 +495,8 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
fontGlyph.page_ = textures_.Size() - 1;
|
|
fontGlyph.page_ = textures_.Size() - 1;
|
|
|
- dest = new unsigned char[fontGlyph.width_ * fontGlyph.height_];
|
|
|
|
|
- pitch = (unsigned)fontGlyph.width_;
|
|
|
|
|
|
|
+ dest = new unsigned char[fontGlyph.texWidth_ * fontGlyph.texHeight_];
|
|
|
|
|
+ pitch = (unsigned)fontGlyph.texWidth_;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
|
|
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
|
|
@@ -433,8 +504,9 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
for (unsigned y = 0; y < (unsigned)slot->bitmap.rows; ++y)
|
|
for (unsigned y = 0; y < (unsigned)slot->bitmap.rows; ++y)
|
|
|
{
|
|
{
|
|
|
unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
|
|
unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
|
|
|
- unsigned char* rowDest = dest + y * pitch;
|
|
|
|
|
|
|
+ unsigned char* rowDest = dest + (oversampling_ - 1)/2 + y * pitch;
|
|
|
|
|
|
|
|
|
|
+ // Don't do any oversampling, just unpack the bits directly.
|
|
|
for (unsigned x = 0; x < (unsigned)slot->bitmap.width; ++x)
|
|
for (unsigned x = 0; x < (unsigned)slot->bitmap.width; ++x)
|
|
|
rowDest[x] = (unsigned char)((src[x >> 3] & (0x80 >> (x & 7))) ? 255 : 0);
|
|
rowDest[x] = (unsigned char)((src[x >> 3] & (0x80 >> (x & 7))) ? 255 : 0);
|
|
|
}
|
|
}
|
|
@@ -445,15 +517,13 @@ bool FontFaceFreeType::LoadCharGlyph(unsigned charCode, Image* image)
|
|
|
{
|
|
{
|
|
|
unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
|
|
unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
|
|
|
unsigned char* rowDest = dest + y * pitch;
|
|
unsigned char* rowDest = dest + y * pitch;
|
|
|
-
|
|
|
|
|
- for (unsigned x = 0; x < (unsigned)slot->bitmap.width; ++x)
|
|
|
|
|
- rowDest[x] = src[x];
|
|
|
|
|
|
|
+ BoxFilter(rowDest, fontGlyph.texWidth_, src, slot->bitmap.width);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!image)
|
|
if (!image)
|
|
|
{
|
|
{
|
|
|
- textures_.Back()->SetData(0, fontGlyph.x_, fontGlyph.y_, fontGlyph.width_, fontGlyph.height_, dest);
|
|
|
|
|
|
|
+ textures_.Back()->SetData(0, fontGlyph.x_, fontGlyph.y_, fontGlyph.texWidth_, fontGlyph.texHeight_, dest);
|
|
|
delete[] dest;
|
|
delete[] dest;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|