I want to draw perfect border around my text. Problem is that text is rendered a bit too high compared to border. I am using orthographics camera with X facing right and Y facing down. glm code: glm::ortho(0.0f, w, h, 0.0f);
To unpack?
a font I use freetype.
enum
{
DPI = 72, //72 or 96
HIGHRES = 64
};
FT_Library library = nullptr;
FT_Face face = nullptr;
float ascender;
float descender;
float size = 50.0f;
void InitFreeType()
{
FT_Matrix matrix = {
static_cast<int>((1.0 / (float)HIGHRES) * 0x10000L),
static_cast<int>((0.0) * 0x10000L),
static_cast<int>((0.0) * 0x10000L),
static_cast<int>((1.0) * 0x10000L) };
FT_Init_FreeType(&library);
FT_New_Face(library, "arial.ttf", 0, &face);
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
FT_Set_Char_Size(face, static_cast<ulong>(size * (float)HIGHRES), 0, DPI * HIGHRES, DPI);
FT_Set_Transform(face, &matrix, NULL);
metrics = face->size->metrics;
ascender = (metrics.ascender >> 6) * 0.01f;
descender = (metrics.descender >> 6) * 0.01f;
height = (metrics.height >> 6) * 0.01f;
linegap = height - ascender + descender;
FT_Done_Face(face);
FT_Done_FreeType(library);
}
glyphs are then packed into a texture atlas
struct Kerning
{
uint32 codepoint = -1;
float kerning = 0.0f;
};
struct Glyph
{
// rect is a struct with floats x, y, w and h
Rect uv = 0.0f;
std::vector<Kerning> kerning;
Vector2<int> bearing = 0;
Vector2<float> advance = 0.0f;
Vector2<int> size = 0;
uint32 codepoint = -1;
};
std::vector<Glyph> glyphs;
void LoadGlyph(uint32 codepoint)
{
FT_Load_Char(face, codepoint, FT_LOAD_RENDER);
FT_GlyphSlot slot = face->glyph;
FT_Bitmap bitmap = slot->bitmap;
int glyphTop = slot->bitmap_top;
int glyphLeft = slot->bitmap_left;
int rightPadding = 1;
int bottomPadding = 1;
// atlas.BYTES_PER_PIXEL = 1 since, right now it uses only R channel, may use more later, though.
int srcWidth = bitmap.width / atlas.BYTES_PER_PIXEL;
int srcHeight = bitmap.rows;
int tgtWidth = srcWidth + rightPadding;
int tgtHeight = srcHeight + bottomPadding;
auto buffer = std::make_unique<uchar[]>(tgtWidth * tgtHeight * atlas.BYTES_PER_PIXEL);
uchar* destPointer = buffer.get() + tgtWidth * atlas.BYTES_PER_PIXEL;
uchar* srcPointer = bitmap.buffer;
for(int i = 0; i < srcHeight; ++i) {
memcpy(destPointer, srcPointer, bitmap.width);
destPointer += tgtWidth * atlas.BYTES_PER_PIXEL;
srcPointer += bitmap.pitch;
}
// gives top-left corner position of the glyph inside the atlas
auto origin = atlas.PackTexture(buffer.get(), { tgtWidth, tgtHeight });
Glyph current;
current.codepoint = codepoint;
current.size.x = tgtWidth - rightPadding;
current.size.y = tgtHeight;
current.bearing.x = glyphLeft;
current.bearing.y = glyphTop;
current.uv.x = (float)origin.x / (float)atlas.atlasSize.w;
current.uv.y = (float)origin.y / (float)atlas.atlasSize.h;
current.uv.w = (float)current.size.w / (float)atlas.atlasSize.w;
current.uv.h = (float)current.size.h / (float)atlas.atlasSize.h;
current.advance.x = slot->advance.x / (float)HIGHRES;
current.advance.y = slot->advance.y / (float)HIGHRES;
glyphs.push_back(current);
GenerateKerning();
FT_Done_Face(face);
FT_Done_FreeType(library);
}
void GenerateKerning()
{
int glyphIndex = 0;
int prevIndex = 0;
Glyph* prevG = nullptr;
FT_Vector kerning;
/* For each glyph couple combination, check if kerning is necessary */
/* Starts at index 1 since 0 is for the special backgroudn glyph */
for(auto& g : glyphs) {
glyphIndex = FT_Get_Char_Index(face, g.codepoint);
g.kerning.clear();
for(int j = 1; j < glyphs.size(); ++j) {
prevG = &glyphs[j];
prevIndex = FT_Get_Char_Index(face, prevG->codepoint);
FT_Get_Kerning(face, prevIndex, glyphIndex, FT_KERNING_UNFITTED, &kerning);
if(kerning.x) {
Kerning k = { prevG->codepoint, kerning.x / static_cast<float>(HIGHRES * HIGHRES) };
g.kerning.push_back(k);
}
}
}
}
float getKerning(Glyph* glyph, uint32 codepoint)
{
for(const auto& kerning : glyph->kerning) {
if(kerning.codepoint == codepoint) {
return kerning.kerning;
}
} return 0.0f;
}
Glyph* FindGlyph(uint32 codepoint)
{
for(auto& glyph : glyphs) {
if(glyph.codepoint == codepoint) {
return &glyph;
}
} return nullptr;
}
Glyph* getGlyph(uint32 codepoint)
{
if(Glyph* glyph = FindGlyph(codepoint)) {
return glyph;
}
if(LoadGlyph(codepoint)) {
return FindGlyph(codepoint);
}
return nullptr;
}
and now it times to rendering part
struct Font
{
Font()
{
InitFreeType();
//create a texture, add the texture atlas data to it... code that does not matter in this case
}
void DrawString(Renderer2D& renderer, const std::string& string, float x, float y, uint32 color)
{
float advancedX = x;
for(int i = 0; i < string.size(); /* nothing */) {
const char* c = &string[i];
//AdvanceIndexAndGetCodepoint() there is no need to show this method, does not change any rendering stuff
uint32 codepoint = AdvanceIndexAndGetCodepoint(i, c);
auto glyph = getGlyph(codepoint);
advancedX += getKerning(glyph, codepoint);
float finalX = advancedX + static_cast<float>(glyph->bearing.x);
float finalY = y + (textureFont.ascender + textureFont.descender - static_cast<float>(glyph->bearing.y));
float w = static_cast<float>(glyph->size.w);
float h = static_cast<float>(glyph->size.h);
auto uv = glyph->uv;
renderer.FillRect(finalX, finalY, w, h, texture, uv);
advancedX += glyph->advance.x;
}
}
Texture* texture;
}
There are few functions that are not shown because they do not change any rendering stuff, such as AdvanceIndexAndGetCodepoint
. There is a problem with getStringWidth
/Height()
or getGlyphWidth
/Height()
, or even my freetype initialization is wrong.
It is pointless pointless to show you guys whole Renderer2D
class, it works fine.
float getGlyphWidth(uint32 codepoint)
{
float result = 0;
auto glyph = textureFont.getGlyph(codepoint);
float kerning = textureFont.getKerning(glyph, codepoint);
result += kerning;
result += glyph->advance.x;
return result;
}
float Font::getStringWidth(const std::string& string)
{
float result = 0.0f;
for(uint i = 0; i < string.size(); /* nothing */) {
const char* c = &string[i];
uint32 codepoint = AdvanceIndexAndGetCodepoint(i, c);
result += getGlyphWidth(codepoint);
}
return result;
}
float getGlyphHeight(uint32 codepoint)
{
float min = 0.0f;
float max = 0.0f;
auto glyph = textureFont.getGlyph(codepoint);
float height = static_cast<float>(glyph->size.h);
float offset = static_cast<float>(glyph->bearing.y) - height;
if(offset < min)
min = offset;
if(height > max)
max = height;
return abs(min) + abs(max);
}
//returns the highest glyph, so all glyphs can fit to the border
float getStringHeight(const std::string& string)
{
float result = 0.0f;
for(uint i = 0; i < string.size(); /* nothing */) {
const char* c = &string[i];
uint32 codepoint = AdvanceIndexAndGetCodepoint(i, c);
result = Max(result, getGlyphHeight(codepoint));
}
return result;
}
and with this code the border can be rendered
void main()
{
// create window, context
Font font;
Renderer2D renderer;
bool quit = false;
while(!quit) {
float textX = 100.0f;
float textY = 100.0f;
float textW = getStringWidth("Jorg");
float textH = getStringHeight("Jorg");
//ABGR
renderer.FillRect(textX, textY, textW, textH, 0xff000000);
renderer.DrawString("Jorg", textX, textY, 0xffffffff, font);
}
}
as you can see J
is above black rectangle and width is also not exactly correct.
I obtained this effect by doing random numer calculations
for string position I subtracted 7 from X and added 6 to Y
and from border size I subtracted 26 from width and 8 from height.
Of course it cannot be done this way, because for different font sizes those numers will change.
How can I calculate width and height of any string properly?
User contributions licensed under CC BY-SA 3.0