Implement basic kerning when rendering text.

Only old-style `kern` tables are supported--modern GPOS-based kerning is not supported.
pull/1224/merge
Kyle Dickerson 2024-05-29 22:01:29 -07:00 committed by ruevs
parent cc64fed2d0
commit 40a1fdc6af
9 changed files with 384 additions and 13 deletions

View File

@ -19,6 +19,17 @@ void TextWindow::ScreenEditTtfText(int link, uint32_t v) {
SS.TW.edit.request = hr; 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) { void TextWindow::ScreenSetTtfFont(int link, uint32_t v) {
int i = (int)v; int i = (int)v;
if(i < 0) return; if(i < 0) return;
@ -205,8 +216,11 @@ void TextWindow::DescribeSelection() {
Printf(false, "%FtTRUETYPE FONT TEXT%E"); Printf(false, "%FtTRUETYPE FONT TEXT%E");
Printf(true, " font = '%Fi%s%E'", e->font.c_str()); Printf(true, " font = '%Fi%s%E'", e->font.c_str());
if(e->h.isFromRequest()) { 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); 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"); Printf(true, " select new font");
SS.fonts.LoadAll(); SS.fonts.LoadAll();
// Not using range-for here because we use i inside the output. // Not using range-for here because we use i inside the output.

View File

@ -475,7 +475,8 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
Vector v = topLeft.Minus(botLeft); Vector v = topLeft.Minus(botLeft);
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude()); 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; break;
} }

View File

@ -90,7 +90,8 @@ void Request::Generate(IdList<Entity,hEntity> *entity,
// Request-specific generation. // Request-specific generation.
switch(type) { switch(type) {
case Type::TTF_TEXT: { 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)) { if(EXACT(actualAspectRatio != 0.0)) {
// We could load the font, so use the actual value. // We could load the font, so use the actual value.
aspectRatio = actualAspectRatio; aspectRatio = actualAspectRatio;

View File

@ -108,11 +108,11 @@ TtfFont *TtfFontList::LoadFont(const std::string &font)
} }
void TtfFontList::PlotString(const std::string &font, const std::string &str, 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); TtfFont *tf = LoadFont(font);
if(!str.empty() && tf != NULL) { if(!str.empty() && tf != NULL) {
tf->PlotString(str, sbl, origin, u, v); tf->PlotString(str, sbl, kerning, origin, u, v);
} else { } else {
// No text or no font; so draw a big X for an error marker. // No text or no font; so draw a big X for an error marker.
SBezier sb; SBezier sb;
@ -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); TtfFont *tf = LoadFont(font);
if(tf != NULL) { if(tf != NULL) {
return tf->AspectRatio(str); return tf->AspectRatio(str, kerning);
} }
return 0.0; return 0.0;
@ -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, 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"); ssassert(fontFace != NULL, "Expected font face to be loaded");
@ -344,6 +344,7 @@ void TtfFont::PlotString(const std::string &str,
outlineFuncs.delta = 0; outlineFuncs.delta = 0;
FT_Pos dx = 0; FT_Pos dx = 0;
uint32_t prevGid = 0;
for(char32_t cid : ReadUTF8(str)) { for(char32_t cid : ReadUTF8(str)) {
uint32_t gid = FT_Get_Char_Index(fontFace, cid); uint32_t gid = FT_Get_Char_Index(fontFace, cid);
if (gid == 0) { if (gid == 0) {
@ -382,6 +383,13 @@ void TtfFont::PlotString(const std::string &str,
*/ */
FT_BBox cbox; FT_BBox cbox;
FT_Outline_Get_CBox(&fontFace->glyph->outline, &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; FT_Pos bx = dx - cbox.xMin;
// Yes, this is what FreeType calls left-side bearing. // Yes, this is what FreeType calls left-side bearing.
// Then interchangeably uses that with "left-side bearing". Sigh. // Then interchangeably uses that with "left-side bearing". Sigh.
@ -402,14 +410,16 @@ void TtfFont::PlotString(const std::string &str,
// And we're done, so advance our position by the requested advance // And we're done, so advance our position by the requested advance
// width, plus the user-requested extra advance. // width, plus the user-requested extra advance.
dx += fontFace->glyph->advance.x; 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"); 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. // We always request a unit size character, so the aspect ratio is the same as advance length.
double dx = 0; double dx = 0;
uint32_t prevGid = 0;
for(char32_t chr : ReadUTF8(str)) { for(char32_t chr : ReadUTF8(str)) {
uint32_t gid = FT_Get_Char_Index(fontFace, chr); uint32_t gid = FT_Get_Char_Index(fontFace, chr);
if (gid == 0) { if (gid == 0) {
@ -424,7 +434,14 @@ double TtfFont::AspectRatio(const std::string &str) {
break; 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; dx += (double)fontFace->glyph->advance.x / capHeight;
prevGid = gid;
} }
return dx; return dx;

View File

@ -24,8 +24,8 @@ public:
bool LoadFromResource(FT_LibraryRec_ *fontLibrary, bool keepOpen = false); bool LoadFromResource(FT_LibraryRec_ *fontLibrary, bool keepOpen = false);
void PlotString(const std::string &str, void PlotString(const std::string &str,
SBezierList *sbl, Vector origin, Vector u, Vector v); SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v);
double AspectRatio(const std::string &str); double AspectRatio(const std::string &str, bool kerning);
bool ExtractTTFData(bool keepOpen); bool ExtractTTFData(bool keepOpen);
}; };
@ -43,8 +43,8 @@ public:
TtfFont *LoadFont(const std::string &font); TtfFont *LoadFont(const std::string &font);
void PlotString(const std::string &font, const std::string &str, void 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);
double AspectRatio(const std::string &font, const std::string &str); double AspectRatio(const std::string &font, const std::string &str, bool kerning);
}; };
#endif #endif

View File

@ -402,6 +402,7 @@ public:
// All of these are callbacks from the GUI code; first from when // All of these are callbacks from the GUI code; first from when
// we're describing an entity // we're describing an entity
static void ScreenEditTtfText(int link, uint32_t v); 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 ScreenSetTtfFont(int link, uint32_t v);
static void ScreenUnselectAll(int link, uint32_t v); static void ScreenUnselectAll(int link, uint32_t v);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,331 @@
±²³SolveSpaceREVa
Group.h.v=00000001
Group.type=5000
Group.name=#references
Group.color=ff000000
Group.skipFirst=0
Group.predef.swapUV=0
Group.predef.negateU=0
Group.predef.negateV=0
Group.visible=1
Group.suppress=0
Group.relaxConstraints=0
Group.allowRedundant=0
Group.allDimsReference=0
Group.scale=1.00000000000000000000
Group.remap={
}
AddGroup
Group.h.v=00000002
Group.type=5001
Group.order=1
Group.name=sketch-in-plane
Group.activeWorkplane.v=80020000
Group.color=ff000000
Group.subtype=6000
Group.skipFirst=0
Group.predef.q.w=1.00000000000000000000
Group.predef.origin.v=00010001
Group.predef.swapUV=0
Group.predef.negateU=0
Group.predef.negateV=0
Group.visible=1
Group.suppress=0
Group.relaxConstraints=0
Group.allowRedundant=0
Group.allDimsReference=0
Group.scale=1.00000000000000000000
Group.remap={
}
AddGroup
Param.h.v.=00010010
AddParam
Param.h.v.=00010011
AddParam
Param.h.v.=00010012
AddParam
Param.h.v.=00010020
Param.val=1.00000000000000000000
AddParam
Param.h.v.=00010021
AddParam
Param.h.v.=00010022
AddParam
Param.h.v.=00010023
AddParam
Param.h.v.=00020010
AddParam
Param.h.v.=00020011
AddParam
Param.h.v.=00020012
AddParam
Param.h.v.=00020020
Param.val=0.50000000000000000000
AddParam
Param.h.v.=00020021
Param.val=0.50000000000000000000
AddParam
Param.h.v.=00020022
Param.val=0.50000000000000000000
AddParam
Param.h.v.=00020023
Param.val=0.50000000000000000000
AddParam
Param.h.v.=00030010
AddParam
Param.h.v.=00030011
AddParam
Param.h.v.=00030012
AddParam
Param.h.v.=00030020
Param.val=0.50000000000000000000
AddParam
Param.h.v.=00030021
Param.val=-0.50000000000000000000
AddParam
Param.h.v.=00030022
Param.val=-0.50000000000000000000
AddParam
Param.h.v.=00030023
Param.val=-0.50000000000000000000
AddParam
Param.h.v.=00040010
Param.val=-5.00000000000000000000
AddParam
Param.h.v.=00040011
Param.val=5.00000000000000000000
AddParam
Param.h.v.=00040013
Param.val=-5.00000000000000000000
AddParam
Param.h.v.=00040014
Param.val=-5.00000000000000000000
AddParam
Param.h.v.=00040016
Param.val=23.08769405528209262002
AddParam
Param.h.v.=00040017
Param.val=-5.00000000000000000000
AddParam
Param.h.v.=00040019
Param.val=23.08769405528209262002
AddParam
Param.h.v.=0004001a
Param.val=5.00000000000000000000
AddParam
Request.h.v=00000001
Request.type=100
Request.group.v=00000001
Request.construction=0
AddRequest
Request.h.v=00000002
Request.type=100
Request.group.v=00000001
Request.construction=0
AddRequest
Request.h.v=00000003
Request.type=100
Request.group.v=00000001
Request.construction=0
AddRequest
Request.h.v=00000004
Request.type=600
Request.extraPoints=1
Request.workplane.v=80020000
Request.group.v=00000002
Request.construction=0
Request.str=Text
Request.font=Gentium-R.ttf
Request.aspectRatio=2.80876940552820908437
AddRequest
Entity.h.v=00010000
Entity.type=10000
Entity.construction=0
Entity.point[0].v=00010001
Entity.normal.v=00010020
Entity.actVisible=1
AddEntity
Entity.h.v=00010001
Entity.type=2000
Entity.construction=1
Entity.actVisible=1
AddEntity
Entity.h.v=00010020
Entity.type=3000
Entity.construction=0
Entity.point[0].v=00010001
Entity.actNormal.w=1.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00020000
Entity.type=10000
Entity.construction=0
Entity.point[0].v=00020001
Entity.normal.v=00020020
Entity.actVisible=1
AddEntity
Entity.h.v=00020001
Entity.type=2000
Entity.construction=1
Entity.actVisible=1
AddEntity
Entity.h.v=00020020
Entity.type=3000
Entity.construction=0
Entity.point[0].v=00020001
Entity.actNormal.w=0.50000000000000000000
Entity.actNormal.vx=0.50000000000000000000
Entity.actNormal.vy=0.50000000000000000000
Entity.actNormal.vz=0.50000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00030000
Entity.type=10000
Entity.construction=0
Entity.point[0].v=00030001
Entity.normal.v=00030020
Entity.actVisible=1
AddEntity
Entity.h.v=00030001
Entity.type=2000
Entity.construction=1
Entity.actVisible=1
AddEntity
Entity.h.v=00030020
Entity.type=3000
Entity.construction=0
Entity.point[0].v=00030001
Entity.actNormal.w=0.50000000000000000000
Entity.actNormal.vx=-0.50000000000000000000
Entity.actNormal.vy=-0.50000000000000000000
Entity.actNormal.vz=-0.50000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040000
Entity.type=15000
Entity.construction=0
Entity.str=Text
Entity.font=Gentium-R.ttf
Entity.point[0].v=00040001
Entity.point[1].v=00040002
Entity.point[2].v=00040003
Entity.point[3].v=00040004
Entity.extraPoints=1
Entity.normal.v=00040020
Entity.workplane.v=80020000
Entity.actVisible=1
AddEntity
Entity.h.v=00040001
Entity.type=2001
Entity.construction=0
Entity.workplane.v=80020000
Entity.actPoint.x=-5.00000000000000000000
Entity.actPoint.y=5.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040002
Entity.type=2001
Entity.construction=0
Entity.workplane.v=80020000
Entity.actPoint.x=-5.00000000000000000000
Entity.actPoint.y=-5.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040003
Entity.type=2001
Entity.construction=0
Entity.workplane.v=80020000
Entity.actPoint.x=23.08769405528209262002
Entity.actPoint.y=-5.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040004
Entity.type=2001
Entity.construction=0
Entity.workplane.v=80020000
Entity.actPoint.x=23.08769405528209262002
Entity.actPoint.y=5.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=00040020
Entity.type=3001
Entity.construction=0
Entity.point[0].v=00040001
Entity.workplane.v=80020000
Entity.actNormal.w=1.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=80020000
Entity.type=10000
Entity.construction=0
Entity.point[0].v=80020002
Entity.normal.v=80020001
Entity.actVisible=1
AddEntity
Entity.h.v=80020001
Entity.type=3010
Entity.construction=0
Entity.point[0].v=80020002
Entity.actNormal.w=1.00000000000000000000
Entity.actVisible=1
AddEntity
Entity.h.v=80020002
Entity.type=2012
Entity.construction=1
Entity.actVisible=1
AddEntity

View File

@ -6,6 +6,12 @@ TEST_CASE(normal_roundtrip) {
CHECK_SAVE("normal.slvs"); CHECK_SAVE("normal.slvs");
} }
TEST_CASE(kerning_roundtrip) {
CHECK_LOAD("kerning.slvs");
CHECK_RENDER("kerning.png");
CHECK_SAVE("kerning.slvs");
}
TEST_CASE(normal_migrate_from_v20) { TEST_CASE(normal_migrate_from_v20) {
CHECK_LOAD("normal_v20.slvs"); CHECK_LOAD("normal_v20.slvs");
CHECK_SAVE("normal.slvs"); CHECK_SAVE("normal.slvs");