Skip to content

Commit

Permalink
Implement basic kerning when rendering text.
Browse files Browse the repository at this point in the history
Only old-style `kern` tables are supported--modern GPOS-based kerning is not supported.
  • Loading branch information
kdickerson authored and Ruevski committed Jun 11, 2024
1 parent bdacb87 commit 6b79e69
Show file tree
Hide file tree
Showing 9 changed files with 384 additions and 13 deletions.
16 changes: 15 additions & 1 deletion src/describescreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ void TextWindow::ScreenEditTtfText(int link, uint32_t v) {
SS.TW.edit.request = hr;
}

void TextWindow::ScreenToggleTtfKerning(int link, uint32_t v) {
hRequest hr = { v };
Request *r = SK.GetRequest(hr);

SS.UndoRemember();
r->extraPoints = !r->extraPoints;

SS.MarkGroupDirty(r->group);
SS.ScheduleShowTW();
}

void TextWindow::ScreenSetTtfFont(int link, uint32_t v) {
int i = (int)v;
if(i < 0) return;
Expand Down Expand Up @@ -205,8 +216,11 @@ void TextWindow::DescribeSelection() {
Printf(false, "%FtTRUETYPE FONT TEXT%E");
Printf(true, " font = '%Fi%s%E'", e->font.c_str());
if(e->h.isFromRequest()) {
Printf(false, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E",
Printf(true, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E",
e->str.c_str(), &ScreenEditTtfText, e->h.request().v);
Printf(true, " %Fd%f%D%Ll%s apply kerning",
&ScreenToggleTtfKerning, e->h.request().v,
e->extraPoints ? CHECK_TRUE : CHECK_FALSE);
Printf(true, " select new font");
SS.fonts.LoadAll();
// Not using range-for here because we use i inside the output.
Expand Down
3 changes: 2 additions & 1 deletion src/drawentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
Vector v = topLeft.Minus(botLeft);
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());

SS.fonts.PlotString(font, str, sbl, botLeft, u, v);
// `extraPoints` is storing kerning boolean
SS.fonts.PlotString(font, str, sbl, extraPoints, botLeft, u, v);
break;
}

Expand Down
3 changes: 2 additions & 1 deletion src/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
// Request-specific generation.
switch(type) {
case Type::TTF_TEXT: {
double actualAspectRatio = SS.fonts.AspectRatio(font, str);
// `extraPoints` is storing kerning boolean
double actualAspectRatio = SS.fonts.AspectRatio(font, str, extraPoints);
if(EXACT(actualAspectRatio != 0.0)) {
// We could load the font, so use the actual value.
aspectRatio = actualAspectRatio;
Expand Down
29 changes: 23 additions & 6 deletions src/ttf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ TtfFont *TtfFontList::LoadFont(const std::string &font)
}

void TtfFontList::PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v)
SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v)
{
TtfFont *tf = LoadFont(font);
if(!str.empty() && tf != NULL) {
tf->PlotString(str, sbl, origin, u, v);
tf->PlotString(str, sbl, kerning, origin, u, v);
} else {
// No text or no font; so draw a big X for an error marker.
SBezier sb;
Expand All @@ -123,11 +123,11 @@ void TtfFontList::PlotString(const std::string &font, const std::string &str,
}
}

double TtfFontList::AspectRatio(const std::string &font, const std::string &str)
double TtfFontList::AspectRatio(const std::string &font, const std::string &str, bool kerning)
{
TtfFont *tf = LoadFont(font);
if(tf != NULL) {
return tf->AspectRatio(str);
return tf->AspectRatio(str, kerning);
}

return 0.0;
Expand Down Expand Up @@ -331,7 +331,7 @@ static int CubicTo(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p,
}

void TtfFont::PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v)
SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v)
{
ssassert(fontFace != NULL, "Expected font face to be loaded");

Expand All @@ -344,6 +344,7 @@ void TtfFont::PlotString(const std::string &str,
outlineFuncs.delta = 0;

FT_Pos dx = 0;
uint32_t prevGid = 0;
for(char32_t cid : ReadUTF8(str)) {
uint32_t gid = FT_Get_Char_Index(fontFace, cid);
if (gid == 0) {
Expand Down Expand Up @@ -382,6 +383,13 @@ void TtfFont::PlotString(const std::string &str,
*/
FT_BBox cbox;
FT_Outline_Get_CBox(&fontFace->glyph->outline, &cbox);

// Apply Kerning, if any:
FT_Vector kernVector;
if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) {
dx += kernVector.x;
}

FT_Pos bx = dx - cbox.xMin;
// Yes, this is what FreeType calls left-side bearing.
// Then interchangeably uses that with "left-side bearing". Sigh.
Expand All @@ -402,14 +410,16 @@ void TtfFont::PlotString(const std::string &str,
// And we're done, so advance our position by the requested advance
// width, plus the user-requested extra advance.
dx += fontFace->glyph->advance.x;
prevGid = gid;
}
}

double TtfFont::AspectRatio(const std::string &str) {
double TtfFont::AspectRatio(const std::string &str, bool kerning) {
ssassert(fontFace != NULL, "Expected font face to be loaded");

// We always request a unit size character, so the aspect ratio is the same as advance length.
double dx = 0;
uint32_t prevGid = 0;
for(char32_t chr : ReadUTF8(str)) {
uint32_t gid = FT_Get_Char_Index(fontFace, chr);
if (gid == 0) {
Expand All @@ -424,7 +434,14 @@ double TtfFont::AspectRatio(const std::string &str) {
break;
}

// Apply Kerning, if any:
FT_Vector kernVector;
if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) {
dx += (double)kernVector.x / capHeight;
}

dx += (double)fontFace->glyph->advance.x / capHeight;
prevGid = gid;
}

return dx;
Expand Down
8 changes: 4 additions & 4 deletions src/ttf.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class TtfFont {
bool LoadFromResource(FT_LibraryRec_ *fontLibrary, bool keepOpen = false);

void PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &str);
SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &str, bool kerning);

bool ExtractTTFData(bool keepOpen);
};
Expand All @@ -43,8 +43,8 @@ class TtfFontList {
TtfFont *LoadFont(const std::string &font);

void PlotString(const std::string &font, const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &font, const std::string &str);
SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &font, const std::string &str, bool kerning);
};

#endif
1 change: 1 addition & 0 deletions src/ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ class TextWindow {
// All of these are callbacks from the GUI code; first from when
// we're describing an entity
static void ScreenEditTtfText(int link, uint32_t v);
static void ScreenToggleTtfKerning(int link, uint32_t v);
static void ScreenSetTtfFont(int link, uint32_t v);
static void ScreenUnselectAll(int link, uint32_t v);

Expand Down
Binary file added test/request/ttf_text/kerning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6b79e69

Please sign in to comment.