Skip to content

Commit

Permalink
Merge pull request #841 from googlefonts/upgrade-old-kern-writer
Browse files Browse the repository at this point in the history
Add variable kerning to old kern writer
  • Loading branch information
madig committed May 22, 2024
2 parents 3d09b72 + 0d15b13 commit 8c895d0
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 9 deletions.
17 changes: 12 additions & 5 deletions Lib/ufo2ft/featureWriters/kernFeatureWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,13 @@ def getKerningPairs(
side2Classes: Mapping[str, tuple[str, ...]],
) -> list[KerningPair]:
if self.context.isVariable:
return self.getVariableKerningPairs(side1Classes, side2Classes)
return self.getVariableKerningPairs(
self.context.font,
side1Classes,
side2Classes,
self.context.glyphSet,
self.options,
)

glyphSet = self.context.glyphSet
font = self.context.font
Expand Down Expand Up @@ -432,14 +438,15 @@ def getKerningPairs(

return result

@staticmethod
def getVariableKerningPairs(
self,
designspace: DesignSpaceDocument,
side1Classes: Mapping[str, tuple[str, ...]],
side2Classes: Mapping[str, tuple[str, ...]],
glyphSet: Mapping[str, str],
options: SimpleNamespace,
) -> list[KerningPair]:
designspace: DesignSpaceDocument = self.context.font
glyphSet = self.context.glyphSet
quantization = self.options.quantization
quantization = options.quantization

# Gather utility variables for faster kerning lookups.
# TODO: Do we construct these in code elsewhere?
Expand Down
52 changes: 48 additions & 4 deletions Lib/ufo2ft/featureWriters/kernFeatureWriter2.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
"""Old implementation of KernFeatureWriter as of ufo2ft v2.30.0 for backward compat."""

from __future__ import annotations

from types import SimpleNamespace
from typing import Mapping

from fontTools import unicodedata
from fontTools.designspaceLib import DesignSpaceDocument

from ufo2ft.constants import INDIC_SCRIPTS, USE_SCRIPTS
from ufo2ft.featureWriters import BaseFeatureWriter, ast
from ufo2ft.featureWriters.kernFeatureWriter import (
KernFeatureWriter as NewKernFeatureWriter,
)
from ufo2ft.util import classifyGlyphs, quantize, unicodeScriptDirection

SIDE1_PREFIX = "public.kern1."
Expand Down Expand Up @@ -113,7 +120,9 @@ class KernFeatureWriter(BaseFeatureWriter):
def setContext(self, font, feaFile, compiler=None):
ctx = super().setContext(font, feaFile, compiler=compiler)
ctx.gdefClasses = self.getGDEFGlyphClasses()
ctx.kerning = self.getKerningData(font, feaFile, self.getOrderedGlyphSet())
ctx.kerning = self.getKerningData(
font, self.options, feaFile, self.getOrderedGlyphSet()
)

feaScripts = ast.getScriptLanguageSystems(feaFile)
ctx.scriptGroups = self._groupScriptsByTagAndDirection(feaScripts)
Expand Down Expand Up @@ -168,9 +177,9 @@ def _write(self):
return True

@classmethod
def getKerningData(cls, font, feaFile=None, glyphSet=None):
def getKerningData(cls, font, options, feaFile=None, glyphSet=None):
side1Classes, side2Classes = cls.getKerningClasses(font, feaFile, glyphSet)
pairs = cls.getKerningPairs(font, side1Classes, side2Classes, glyphSet)
pairs = cls.getKerningPairs(font, side1Classes, side2Classes, glyphSet, options)
return SimpleNamespace(
side1Classes=side1Classes, side2Classes=side2Classes, pairs=pairs
)
Expand All @@ -183,6 +192,13 @@ def getKerningGroups(font, glyphSet=None):
allGlyphs = set(font.keys())
side1Groups = {}
side2Groups = {}

if isinstance(font, DesignSpaceDocument):
default_font = font.findDefault()
assert default_font is not None
font = default_font.font
assert font is not None

for name, members in font.groups.items():
# prune non-existent or skipped glyphs
members = [g for g in members if g in allGlyphs]
Expand All @@ -208,7 +224,35 @@ def getKerningClasses(cls, font, feaFile=None, glyphSet=None):
return side1Classes, side2Classes

@staticmethod
def getKerningPairs(font, side1Classes, side2Classes, glyphSet=None):
def getKerningPairs(font, side1Classes, side2Classes, glyphSet=None, options=None):
if isinstance(font, DesignSpaceDocument):
# Reuse the newer kern writers variable kerning extractor. Repack
# some arguments and the return type for this.
side1ClassesRaw: Mapping[str, tuple[str, ...]] = {
group_name: tuple(
glyph
for glyphs in glyph_defs.glyphSet()
for glyph in glyphs.glyphSet()
)
for group_name, glyph_defs in side1Classes.items()
}
side2ClassesRaw: Mapping[str, tuple[str, ...]] = {
group_name: tuple(
glyph
for glyphs in glyph_defs.glyphSet()
for glyph in glyphs.glyphSet()
)
for group_name, glyph_defs in side2Classes.items()
}
pairs = NewKernFeatureWriter.getVariableKerningPairs(
font,
side1ClassesRaw,
side2ClassesRaw,
glyphSet or {},
options or SimpleNamespace(**KernFeatureWriter.options),
)
return [KerningPair(pair.side1, pair.side2, pair.value) for pair in pairs]

if glyphSet:
allGlyphs = set(glyphSet.keys())
else:
Expand Down
73 changes: 73 additions & 0 deletions tests/featureWriters/variableFeatureWriter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,76 @@ def test_variable_features(FontClass):
} curs;
""" # noqa: B950
)


def test_variable_features_old_kern_writer(FontClass):
tmp = io.StringIO()
designspace = designspaceLib.DesignSpaceDocument.fromfile(
"tests/data/TestVarfea.designspace"
)
designspace.loadSourceFonts(FontClass)

default_source = designspace.findDefault()
assert default_source is not None
default_ufo = default_source.font
assert default_ufo is not None
default_ufo.lib["com.github.googlei18n.ufo2ft.featureWriters"] = [
{
"module": "ufo2ft.featureWriters.kernFeatureWriter2",
"class": "KernFeatureWriter",
},
{
"module": "ufo2ft.featureWriters.markFeatureWriter",
"class": "MarkFeatureWriter",
},
{
"module": "ufo2ft.featureWriters.gdefFeatureWriter",
"class": "GdefFeatureWriter",
},
{
"module": "ufo2ft.featureWriters.cursFeatureWriter",
"class": "CursFeatureWriter",
},
]

_ = compileVariableTTF(designspace, debugFeatureFile=tmp)

assert dedent("\n" + tmp.getvalue()) == dedent(
"""
markClass dotabove-ar <anchor (wght=100:100 wght=1000:125) (wght=100:320 wght=1000:416)> @MC_top;
markClass gravecmb <anchor 250 400> @MC_top;
lookup kern_rtl {
lookupflag IgnoreMarks;
pos alef-ar.fina alef-ar.fina <(wght=100:15 wght=1000:35) 0 (wght=100:15 wght=1000:35) 0>;
} kern_rtl;
feature kern {
lookup kern_rtl;
} kern;
feature mark {
lookup mark2base {
pos base alef-ar.fina
<anchor (wght=100:211 wght=1000:214) (wght=100:730 wght=1000:797)> mark @MC_top;
pos base a
<anchor 250 400> mark @MC_top;
} mark2base;
} mark;
table GDEF {
LigatureCaretByPos peh-ar.init 100;
} GDEF;
feature curs {
lookup curs_rtl {
lookupflag RightToLeft IgnoreMarks;
pos cursive alef-ar.fina <anchor (wght=100:299 wght=1000:330) (wght=100:97 wght=1000:115)> <anchor NULL>;
pos cursive peh-ar.init <anchor NULL> <anchor (wght=100:161 wght=1000:73) (wght=100:54 wght=1000:89)>;
pos cursive peh-ar.init.BRACKET.varAlt01 <anchor NULL> <anchor (wght=100:89 wght=1000:73) (wght=100:53 wght=1000:85)>;
} curs_rtl;
} curs;
""" # noqa: B950
)

0 comments on commit 8c895d0

Please sign in to comment.