Merge pull request #3429 from fonttools/fealib-liga-order
[feaLib] keep declaration order of ligatures within ligature set
This commit is contained in:
commit
a1cb14d3ec
@ -1338,7 +1338,7 @@ class Builder(object):
|
||||
# substitutions to be specified on target sequences that contain
|
||||
# glyph classes, the implementation software will enumerate
|
||||
# all specific glyph sequences if glyph classes are detected"
|
||||
for g in sorted(itertools.product(*glyphs)):
|
||||
for g in itertools.product(*glyphs):
|
||||
lookup.ligatures[g] = replacement
|
||||
|
||||
# GSUB 5/6
|
||||
|
@ -1567,19 +1567,6 @@ def buildAlternateSubstSubtable(mapping):
|
||||
return self
|
||||
|
||||
|
||||
def _getLigatureKey(components):
|
||||
# Computes a key for ordering ligatures in a GSUB Type-4 lookup.
|
||||
|
||||
# When building the OpenType lookup, we need to make sure that
|
||||
# the longest sequence of components is listed first, so we
|
||||
# use the negative length as the primary key for sorting.
|
||||
# To make buildLigatureSubstSubtable() deterministic, we use the
|
||||
# component sequence as the secondary key.
|
||||
|
||||
# For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l).
|
||||
return (-len(components), components)
|
||||
|
||||
|
||||
def buildLigatureSubstSubtable(mapping):
|
||||
"""Builds a ligature substitution (GSUB4) subtable.
|
||||
|
||||
@ -1613,7 +1600,7 @@ def buildLigatureSubstSubtable(mapping):
|
||||
# with fontTools >= 3.1:
|
||||
# self.ligatures = dict(mapping)
|
||||
self.ligatures = {}
|
||||
for components in sorted(mapping.keys(), key=_getLigatureKey):
|
||||
for components in sorted(mapping.keys(), key=self._getLigatureSortKey):
|
||||
ligature = ot.Ligature()
|
||||
ligature.Component = components[1:]
|
||||
ligature.CompCount = len(ligature.Component) + 1
|
||||
|
@ -1123,6 +1123,35 @@ class LigatureSubst(FormatSwitchingBaseTable):
|
||||
self.ligatures = ligatures
|
||||
del self.Format # Don't need this anymore
|
||||
|
||||
@staticmethod
|
||||
def _getLigatureSortKey(components):
|
||||
# Computes a key for ordering ligatures in a GSUB Type-4 lookup.
|
||||
|
||||
# When building the OpenType lookup, we need to make sure that
|
||||
# the longest sequence of components is listed first, so we
|
||||
# use the negative length as the key for sorting.
|
||||
# Note, we no longer need to worry about deterministic order because the
|
||||
# ligature mapping `dict` remembers the insertion order, and this in
|
||||
# turn depends on the order in which the ligatures are written in the FEA.
|
||||
# Since python sort algorithm is stable, the ligatures of equal length
|
||||
# will keep the relative order in which they appear in the feature file.
|
||||
# For example, given the following ligatures (all starting with 'f' and
|
||||
# thus belonging to the same LigatureSet):
|
||||
#
|
||||
# feature liga {
|
||||
# sub f i by f_i;
|
||||
# sub f f f by f_f_f;
|
||||
# sub f f by f_f;
|
||||
# sub f f i by f_f_i;
|
||||
# } liga;
|
||||
#
|
||||
# this should sort to: f_f_f, f_f_i, f_i, f_f
|
||||
# This is also what fea-rs does, see:
|
||||
# https://github.com/adobe-type-tools/afdko/issues/1727
|
||||
# https://github.com/fonttools/fonttools/issues/3428
|
||||
# https://github.com/googlefonts/fontc/pull/680
|
||||
return -len(components)
|
||||
|
||||
def preWrite(self, font):
|
||||
self.Format = 1
|
||||
ligatures = getattr(self, "ligatures", None)
|
||||
@ -1135,13 +1164,11 @@ class LigatureSubst(FormatSwitchingBaseTable):
|
||||
|
||||
# ligatures is map from components-sequence to lig-glyph
|
||||
newLigatures = dict()
|
||||
for comps, lig in sorted(
|
||||
ligatures.items(), key=lambda item: (-len(item[0]), item[0])
|
||||
):
|
||||
for comps in sorted(ligatures.keys(), key=self._getLigatureSortKey):
|
||||
ligature = Ligature()
|
||||
ligature.Component = comps[1:]
|
||||
ligature.CompCount = len(comps)
|
||||
ligature.LigGlyph = lig
|
||||
ligature.LigGlyph = ligatures[comps]
|
||||
newLigatures.setdefault(comps[0], []).append(ligature)
|
||||
ligatures = newLigatures
|
||||
|
||||
|
@ -147,8 +147,8 @@
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0">
|
||||
<LigatureSet glyph="c">
|
||||
<Ligature components="s" glyph="c_s"/>
|
||||
<Ligature components="t" glyph="c_t"/>
|
||||
<Ligature components="s" glyph="c_s"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
|
@ -11,6 +11,16 @@ feature F1 {
|
||||
# if glyph classes are detected in <glyph sequence>. Thus, the above
|
||||
# example produces an identical representation in the font as if all
|
||||
# the sequences were manually enumerated by the font editor:
|
||||
#
|
||||
# NOTE(anthrotype): The previous sentence is no longer entirely true, since we
|
||||
# now preserve the order in which the ligatures (with same length and first glyph)
|
||||
# were specified in the feature file and do not sort them alphabetically
|
||||
# by the ligature component names. Therefore, the way this particular example from
|
||||
# the FEA spec is written will produce two slightly different representations
|
||||
# in the font in which the ligatures are enumerated differently, however the two
|
||||
# lookups are functionally equivalent.
|
||||
# See: https://github.com/fonttools/fonttools/issues/3428
|
||||
# https://github.com/adobe-type-tools/afdko/issues/1727
|
||||
feature F2 {
|
||||
sub one slash two by onehalf;
|
||||
sub one.oldstyle slash two by onehalf;
|
||||
|
@ -43,16 +43,16 @@
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0">
|
||||
<LigatureSet glyph="one">
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="slash,two" glyph="onehalf"/>
|
||||
<Ligature components="slash,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
</LigatureSet>
|
||||
<LigatureSet glyph="one.oldstyle">
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="slash,two" glyph="onehalf"/>
|
||||
<Ligature components="slash,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
@ -62,16 +62,16 @@
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0">
|
||||
<LigatureSet glyph="one">
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="slash,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="slash,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
</LigatureSet>
|
||||
<LigatureSet glyph="one.oldstyle">
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="slash,two" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two" glyph="onehalf"/>
|
||||
<Ligature components="slash,two.oldstyle" glyph="onehalf"/>
|
||||
<Ligature components="fraction,two.oldstyle" glyph="onehalf"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
|
@ -16,20 +16,20 @@
|
||||
<Ligature components="Jsmall" glyph="IJsmall"/>
|
||||
</LigatureSet>
|
||||
<LigatureSet glyph="f">
|
||||
<Ligature components="f,b" glyph="ffb"/>
|
||||
<Ligature components="f,h" glyph="ffh"/>
|
||||
<Ligature components="f,i" glyph="ffi"/>
|
||||
<Ligature components="f,k" glyph="ffk"/>
|
||||
<Ligature components="f,l" glyph="ffl"/>
|
||||
<Ligature components="f,t" glyph="fft"/>
|
||||
<Ligature components="b" glyph="fb"/>
|
||||
<Ligature components="f" glyph="ff"/>
|
||||
<Ligature components="h" glyph="fh"/>
|
||||
<Ligature components="f,b" glyph="ffb"/>
|
||||
<Ligature components="f,h" glyph="ffh"/>
|
||||
<Ligature components="f,k" glyph="ffk"/>
|
||||
<Ligature components="i" glyph="fi"/>
|
||||
<Ligature components="j" glyph="fj"/>
|
||||
<Ligature components="k" glyph="fk"/>
|
||||
<Ligature components="l" glyph="fl"/>
|
||||
<Ligature components="f" glyph="ff"/>
|
||||
<Ligature components="t" glyph="ft"/>
|
||||
<Ligature components="b" glyph="fb"/>
|
||||
<Ligature components="h" glyph="fh"/>
|
||||
<Ligature components="k" glyph="fk"/>
|
||||
<Ligature components="j" glyph="fj"/>
|
||||
</LigatureSet>
|
||||
<LigatureSet glyph="i">
|
||||
<Ligature components="j" glyph="ij"/>
|
||||
|
@ -1051,11 +1051,11 @@ class BuilderTest(object):
|
||||
func = lambda writer, font: value.toXML(writer, font, valueName="Val")
|
||||
assert getXML(func) == ['<Val XPlacement="7" YPlacement="23"/>']
|
||||
|
||||
def test_getLigatureKey(self):
|
||||
def test_getLigatureSortKey(self):
|
||||
components = lambda s: [tuple(word) for word in s.split()]
|
||||
c = components("fi fl ff ffi fff")
|
||||
c.sort(key=builder._getLigatureKey)
|
||||
assert c == components("fff ffi ff fi fl")
|
||||
c.sort(key=otTables.LigatureSubst._getLigatureSortKey)
|
||||
assert c == components("ffi fff fi fl ff")
|
||||
|
||||
def test_getSinglePosValueKey(self):
|
||||
device = builder.buildDevice({10: 1, 11: 3})
|
||||
|
Loading…
x
Reference in New Issue
Block a user