Skip to content

Commit

Permalink
Pass "extra" substitutions from designspace rules to classifyGlyphs (#…
Browse files Browse the repository at this point in the history
…731)

* Pick up "extra" substitutions from designspace rules and route them to classifyGlyphs
  • Loading branch information
simoncozens authored Mar 20, 2023
1 parent 0f123a7 commit fca66fe
Show file tree
Hide file tree
Showing 24 changed files with 721 additions and 14 deletions.
14 changes: 14 additions & 0 deletions Lib/ufo2ft/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
from collections import defaultdict
from enum import IntEnum

from fontTools import varLib
Expand Down Expand Up @@ -121,6 +122,7 @@ def call_postprocessor(otf, ufo, glyphSet, *, postProcessorClass, **kwargs):
cffVersion=1,
subroutinizer=None,
_tables=None,
extraSubstitutions=None,
),
}

Expand Down Expand Up @@ -271,6 +273,7 @@ def compileTTF(ufo, **kwargs):
layerNames=None,
colrLayerReuse=False,
colrAutoClipBoxes=False,
extraSubstitutions=None,
),
}

Expand Down Expand Up @@ -386,6 +389,11 @@ def compileInterpolatableTTFsFromDS(designSpaceDoc, **kwargs):
if kwargs["notdefGlyph"] is None:
kwargs["notdefGlyph"] = _getDefaultNotdefGlyph(designSpaceDoc)

kwargs["extraSubstitutions"] = defaultdict(set)
for rule in designSpaceDoc.rules:
for left, right in rule.subs:
kwargs["extraSubstitutions"][left].add(right)

ttfs = compileInterpolatableTTFs(ufos, **kwargs)

if kwargs["inplace"]:
Expand All @@ -407,6 +415,7 @@ def compileInterpolatableTTFsFromDS(designSpaceDoc, **kwargs):
optimizeCFF=CFFOptimization.NONE,
colrLayerReuse=False,
colrAutoClipBoxes=False,
extraSubstitutions=None,
),
}

Expand Down Expand Up @@ -451,6 +460,11 @@ def compileInterpolatableOTFsFromDS(designSpaceDoc, **kwargs):
if kwargs["notdefGlyph"] is None:
kwargs["notdefGlyph"] = _getDefaultNotdefGlyph(designSpaceDoc)

kwargs["extraSubstitutions"] = defaultdict(set)
for rule in designSpaceDoc.rules:
for left, right in rule.subs:
kwargs["extraSubstitutions"][left].add(right)

otfs = []
for source in designSpaceDoc.sources:
otfs.append(
Expand Down
14 changes: 11 additions & 3 deletions Lib/ufo2ft/featureCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class BaseFeatureCompiler:
layout tables from these.
"""

def __init__(self, ufo, ttFont=None, glyphSet=None, **kwargs):
def __init__(self, ufo, ttFont=None, glyphSet=None, extraSubstitutions=None):
"""
Args:
ufo: an object representing a UFO (defcon.Font or equivalent)
Expand All @@ -84,6 +84,10 @@ def __init__(self, ufo, ttFont=None, glyphSet=None, **kwargs):
the same glyph order as the ufo object.
glyphSet: a (optional) dict containing pre-processed copies of
the UFO glyphs.
extraSubstitutions: an optional dictionary mapping glyph names
to a set of other glyphs which should be considered reachable
from them (for example when using designspace rules to effect
substitutions).
"""
self.ufo = ufo

Expand All @@ -103,6 +107,8 @@ def __init__(self, ufo, ttFont=None, glyphSet=None, **kwargs):
glyphSet = ufo
self.glyphSet = OrderedDict((gn, glyphSet[gn]) for gn in glyphOrder)

self.extraSubstitutions = extraSubstitutions

def setupFeatures(self):
"""Make the features source.
Expand Down Expand Up @@ -174,6 +180,7 @@ def __init__(
glyphSet=None,
featureWriters=None,
feaIncludeDir=None,
extraSubstitutions=None,
**kwargs,
):
"""
Expand All @@ -199,8 +206,9 @@ def __init__(
the feature file. If None, the include directory is set to the
parent directory of the UFO, provided the UFO has a path.
"""
BaseFeatureCompiler.__init__(self, ufo, ttFont, glyphSet)

BaseFeatureCompiler.__init__(
self, ufo, ttFont, glyphSet, extraSubstitutions=extraSubstitutions
)
self.feaIncludeDir = feaIncludeDir

self.initFeatureWriters(featureWriters)
Expand Down
5 changes: 5 additions & 0 deletions Lib/ufo2ft/featureWriters/baseFeatureWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ def compileGSUB(self):
compiler._gsub = gsub
return gsub

def extraSubstitutions(self):
compiler = self.context.compiler
if compiler is not None:
return compiler.extraSubstitutions

def getOpenTypeCategories(self):
"""Return 'public.openTypeCategories' values as a tuple of sets of
unassigned, bases, ligatures, marks, components."""
Expand Down
3 changes: 2 additions & 1 deletion Lib/ufo2ft/featureWriters/cursFeatureWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def _makeCursiveFeature(self):
cmap = self.makeUnicodeToGlyphNameMapping()
if any(unicodeScriptDirection(uv) == "LTR" for uv in cmap):
gsub = self.compileGSUB()
dirGlyphs = classifyGlyphs(unicodeScriptDirection, cmap, gsub)
extras = self.extraSubstitutions()
dirGlyphs = classifyGlyphs(unicodeScriptDirection, cmap, gsub, extras)
shouldSplit = "LTR" in dirGlyphs
else:
shouldSplit = False
Expand Down
5 changes: 3 additions & 2 deletions Lib/ufo2ft/featureWriters/kernFeatureWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,10 @@ def setContext(self, font, feaFile, compiler=None):
# `glyphUnicodeMapping: dict[str, int] | None` to `BaseFeatureCompiler`?
cmap = self.makeUnicodeToGlyphNameMapping()
gsub = self.compileGSUB()
extras = self.extraSubstitutions()
ctx.knownScripts = self.guessFontScripts()
scriptGlyphs = classifyGlyphs(self.knownScriptsPerCodepoint, cmap, gsub)
bidiGlyphs = classifyGlyphs(unicodeBidiType, cmap, gsub)
scriptGlyphs = classifyGlyphs(self.knownScriptsPerCodepoint, cmap, gsub, extras)
bidiGlyphs = classifyGlyphs(unicodeBidiType, cmap, gsub, extras)
ctx.bidiGlyphs = bidiGlyphs

glyphScripts = {}
Expand Down
5 changes: 3 additions & 2 deletions Lib/ufo2ft/featureWriters/kernFeatureWriter2.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,12 @@ def _makeKerningLookups(self):
# type. We then mark each kerning pair with these properties when
# any of the glyphs involved in a pair intersects these groups.
gsub = self.compileGSUB()
dirGlyphs = classifyGlyphs(unicodeScriptDirection, cmap, gsub)
extras = self.extraSubstitutions()
dirGlyphs = classifyGlyphs(unicodeScriptDirection, cmap, gsub, extras)
directions = self._intersectPairs("directions", dirGlyphs)
shouldSplit = "RTL" in directions
if shouldSplit:
bidiGlyphs = classifyGlyphs(unicodeBidiType, cmap, gsub)
bidiGlyphs = classifyGlyphs(unicodeBidiType, cmap, gsub, extras)
self._intersectPairs("bidiTypes", bidiGlyphs)
else:
shouldSplit = False
Expand Down
5 changes: 3 additions & 2 deletions Lib/ufo2ft/featureWriters/markFeatureWriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,8 @@ def unicodeIsNotAbvm(uv):
# substitutions and get the set of all the relevant glyphs,
# including alternate glyphs.
gsub = self.compileGSUB()
glyphGroups = classifyGlyphs(unicodeIsAbvm, cmap, gsub)
extras = self.extraSubstitutions()
glyphGroups = classifyGlyphs(unicodeIsAbvm, cmap, gsub, extras)
# the 'glyphGroups' dict is keyed by the return value of the
# classifying include, so here 'True' means all the
# Indic/USE/Khmer glyphs
Expand All @@ -885,7 +886,7 @@ def unicodeIsNotAbvm(uv):
# If a character can be used in Indic/USE/Khmer scripts as well
# as other scripts, we want to return it in both 'abvmGlyphs'
# (done above) and 'notAbvmGlyphs' (done below) sets.
glyphGroups = classifyGlyphs(unicodeIsNotAbvm, cmap, gsub)
glyphGroups = classifyGlyphs(unicodeIsNotAbvm, cmap, gsub, extras)
notAbvmGlyphs = glyphGroups.get(True, set())
# Since cmap might not cover all glyphs, we union with the
# glyph set.
Expand Down
12 changes: 11 additions & 1 deletion Lib/ufo2ft/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,17 @@ def closeGlyphsOverGSUB(gsub, glyphs):
gsub.closure_glyphs(subsetter)


def classifyGlyphs(unicodeFunc, cmap, gsub=None):
def classifyGlyphs(unicodeFunc, cmap, gsub=None, extra_substitutions=None):
"""'unicodeFunc' is a callable that takes a Unicode codepoint and
returns a string, or collection of strings, denoting some Unicode
property associated with the given character (or None if a character
is considered 'neutral'). 'cmap' is a dictionary mapping Unicode
codepoints to glyph names. 'gsub' is an (optional) fonttools GSUB
table object, used to find all the glyphs that are "reachable" via
substitutions from the initial sets of glyphs defined in the cmap.
'extra_substitutions' is an optional dictionary mapping glyph names
to a set of other glyphs which should be considered reachable from them
(for example when using designspace rules to effect substitutions).
Returns a dictionary of glyph sets associated with the given Unicode
properties.
Expand All @@ -317,6 +320,13 @@ def classifyGlyphs(unicodeFunc, cmap, gsub=None):
closeGlyphsOverGSUB(gsub, s)
glyphs.update(s - neutralGlyphs)

if extra_substitutions:
for glyphs in glyphSets.values():
to_append = set()
for glyph in glyphs:
to_append |= extra_substitutions.get(glyph, set())
glyphs.update(to_append)

return glyphSets


Expand Down
6 changes: 6 additions & 0 deletions tests/data/Alternates-Regular.ufo/features.fea
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Prefix: Languagesystems
languagesystem DFLT dflt;

languagesystem ory2 dflt;
languagesystem latn dflt; # This is needed in our full builds because we use feature variations

14 changes: 14 additions & 0 deletions tests/data/Alternates-Regular.ufo/fontinfo.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ascender</key>
<integer>1069</integer>
<key>capHeight</key>
<integer>714</integer>
<key>descender</key>
<integer>-293</integer>
<key>familyName</key>
<string>Alternates</string>
</dict>
</plist>
20 changes: 20 additions & 0 deletions tests/data/Alternates-Regular.ufo/glyphs/contents.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ka-oriya</key>
<string>ka-oriya.glif</string>
<key>ka-oriya.BRACKET.varAlt01</key>
<string>ka-oriya.B_R_A_C_K_E_T_.varA_lt01.glif</string>
<key>ka-oriya.below</key>
<string>ka-oriya.below.glif</string>
<key>lVocalicMatra-oriya</key>
<string>lV_ocalicM_atra-oriya.glif</string>
<key>lVocalicMatra-oriya.BRACKET.varAlt01</key>
<string>lV_ocalicM_atra-oriya.B_R_A_C_K_E_T_.varA_lt01.glif</string>
<key>uuMatra-oriya</key>
<string>uuM_atra-oriya.glif</string>
<key>uuMatra-oriya.BRACKET.varAlt01</key>
<string>uuM_atra-oriya.B_R_A_C_K_E_T_.varA_lt01.glif</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version='1.0' encoding='UTF-8'?>
<glyph name="ka-oriya.BRACKET.varAlt01" format="2">
<advance width="752.38889"/>
<note>
kaorya
</note>
<anchor x="496" y="0" name="bottom"/>
<anchor x="574" y="0" name="bottom_ra"/>
<anchor x="574" y="10" name="bottom_ta"/>
<anchor x="367" y="658" name="top"/>
<anchor x="624" y="658" name="topright"/>
<outline>
<contour>
<point x="152" y="201" type="curve"/>
<point x="152" y="201" type="line"/>
<point x="178" y="258" type="line"/>
<point x="132" y="300"/>
<point x="108" y="350"/>
<point x="108" y="403" type="curve"/>
<point x="108" y="520"/>
<point x="221" y="593"/>
<point x="367" y="593" type="curve"/>
<point x="504" y="593"/>
<point x="640" y="527"/>
<point x="640" y="395" type="curve"/>
<point x="640" y="351"/>
<point x="625" y="312"/>
<point x="600" y="281" type="curve"/>
<point x="671" y="250" type="line"/>
<point x="702" y="290"/>
<point x="720" y="339"/>
<point x="720" y="395" type="curve"/>
<point x="720" y="575"/>
<point x="545" y="666"/>
<point x="367" y="666" type="curve"/>
<point x="181" y="666"/>
<point x="32" y="566"/>
<point x="32" y="411" type="curve"/>
<point x="32" y="332"/>
<point x="70" y="262"/>
</contour>
<contour>
<point x="244" y="74" type="curve"/>
<point x="244" y="74" type="line"/>
<point x="323" y="74"/>
<point x="373" y="132"/>
<point x="373" y="215" type="curve"/>
<point x="373" y="297"/>
<point x="324" y="355"/>
<point x="245" y="355" type="curve"/>
<point x="165" y="355"/>
<point x="114" y="297"/>
<point x="114" y="213" type="curve"/>
<point x="114" y="131"/>
<point x="164" y="74"/>
</contour>
<contour>
<point x="245" y="135" type="curve"/>
<point x="245" y="135" type="line"/>
<point x="204" y="135"/>
<point x="178" y="170"/>
<point x="178" y="214" type="curve"/>
<point x="178" y="258"/>
<point x="204" y="293"/>
<point x="245" y="293" type="curve"/>
<point x="285" y="293"/>
<point x="312" y="258"/>
<point x="312" y="215" type="curve"/>
<point x="312" y="170"/>
<point x="286" y="135"/>
</contour>
<contour>
<point x="312" y="148" type="line"/>
<point x="304" y="0" type="line"/>
<point x="382" y="0" type="line"/>
<point x="382" y="216" type="line" smooth="yes"/>
<point x="382" y="265"/>
<point x="403" y="294"/>
<point x="440" y="294" type="curve"/>
<point x="475" y="294"/>
<point x="496" y="265"/>
<point x="496" y="216" type="curve"/>
<point x="496" y="0" type="line"/>
<point x="574" y="0" type="line"/>
<point x="574" y="222" type="line" smooth="yes"/>
<point x="574" y="305"/>
<point x="535" y="355"/>
<point x="452" y="355" type="curve"/>
<point x="377" y="355"/>
<point x="335" y="305"/>
<point x="335" y="222" type="curve"/>
</contour>
</outline>
<lib>
<dict>
<key>com.schriftgestaltung.Glyphs._originalLayerName</key>
<string></string>
</dict>
</lib>
</glyph>
Loading

0 comments on commit fca66fe

Please sign in to comment.