Merge pull request #1582 from fonttools/max_ctx_recalc

(Re)calculate OS/2.usMaxContext value
This commit is contained in:
Miguel Sousa 2019-04-26 06:52:32 -07:00 committed by GitHub
commit 6edf6257e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 801 additions and 4 deletions

View File

@ -7,6 +7,7 @@ from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.parser import Parser
from fontTools.feaLib.ast import FeatureFile
from fontTools.otlLib import builder as otl
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.ttLib import newTable, getTableModule
from fontTools.ttLib.tables import otBase, otTables
from collections import defaultdict, OrderedDict
@ -137,6 +138,9 @@ class Builder(object):
fontTable.table = table
elif tag in self.font:
del self.font[tag]
if (any(tag in self.font for tag in ("GPOS", "GSUB")) and
"OS/2" in self.font):
self.font["OS/2"].usMaxContext = maxCtxFont(self.font)
if "GDEF" in tables:
gdef = self.buildGDEF()
if gdef:

View File

@ -21,6 +21,7 @@ that works:
fb.setupHorizontalHeader()
fb.setupNameTable(...)
fb.setupOS2()
fb.addOpenTypeFeatures(...)
fb.setupPost()
fb.save(...)
@ -299,7 +300,7 @@ _OS2Defaults = dict(
sCapHeight = 0,
usDefaultChar = 0, # .notdef
usBreakChar = 32, # space
usMaxContext = 2, # just kerning
usMaxContext = 0,
usLowerOpticalPointSize = 0,
usUpperOpticalPointSize = 0,
)

View File

@ -0,0 +1,101 @@
from __future__ import print_function, division, absolute_import, unicode_literals
__all__ = ['maxCtxFont']
def maxCtxFont(font):
"""Calculate the usMaxContext value for an entire font."""
maxCtx = 0
for tag in ('GSUB', 'GPOS'):
if tag not in font:
continue
table = font[tag].table
if not table.LookupList:
continue
for lookup in table.LookupList.Lookup:
for st in lookup.SubTable:
maxCtx = maxCtxSubtable(maxCtx, tag, lookup.LookupType, st)
return maxCtx
def maxCtxSubtable(maxCtx, tag, lookupType, st):
"""Calculate usMaxContext based on a single lookup table (and an existing
max value).
"""
# single positioning, single / multiple substitution
if (tag == 'GPOS' and lookupType == 1) or (
tag == 'GSUB' and lookupType in (1, 2, 3)):
maxCtx = max(maxCtx, 1)
# pair positioning
elif tag == 'GPOS' and lookupType == 2:
maxCtx = max(maxCtx, 2)
# ligatures
elif tag == 'GSUB' and lookupType == 4:
for ligatures in st.ligatures.values():
for ligature in ligatures:
maxCtx = max(maxCtx, ligature.CompCount)
# context
elif (tag == 'GPOS' and lookupType == 7) or (
tag == 'GSUB' and lookupType == 5):
maxCtx = maxCtxContextualSubtable(
maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub')
# chained context
elif (tag == 'GPOS' and lookupType == 8) or (
tag == 'GSUB' and lookupType == 6):
maxCtx = maxCtxContextualSubtable(
maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub', 'Chain')
# extensions
elif (tag == 'GPOS' and lookupType == 9) or (
tag == 'GSUB' and lookupType == 7):
maxCtx = maxCtxSubtable(
maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)
# reverse-chained context
elif tag == 'GSUB' and lookupType == 8:
maxCtx = maxCtxContextualRule(maxCtx, st, 'Reverse')
return maxCtx
def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=''):
"""Calculate usMaxContext based on a contextual feature subtable."""
if st.Format == 1:
for ruleset in getattr(st, '%s%sRuleSet' % (chain, ruleType)):
if ruleset is None:
continue
for rule in getattr(ruleset, '%s%sRule' % (chain, ruleType)):
if rule is None:
continue
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
elif st.Format == 2:
for ruleset in getattr(st, '%s%sClassSet' % (chain, ruleType)):
if ruleset is None:
continue
for rule in getattr(ruleset, '%s%sClassRule' % (chain, ruleType)):
if rule is None:
continue
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
elif st.Format == 3:
maxCtx = maxCtxContextualRule(maxCtx, st, chain)
return maxCtx
def maxCtxContextualRule(maxCtx, st, chain):
"""Calculate usMaxContext based on a contextual feature rule."""
if not chain:
return max(maxCtx, st.GlyphCount)
elif chain == 'Reverse':
return max(maxCtx, st.GlyphCount + st.LookAheadGlyphCount)
return max(maxCtx, st.InputGlyphCount + st.LookAheadGlyphCount)

View File

@ -7,6 +7,7 @@ from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
from fontTools import ttLib
from fontTools.ttLib.tables import otTables
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.pens.basePen import NullPen
from fontTools.misc.loggingTools import Timer
from fontTools.subset.cff import *
@ -322,6 +323,10 @@ Other font-specific options:
Update the 'OS/2 xAvgCharWidth' field after subsetting.
--no-recalc-average-width
Don't change the 'OS/2 xAvgCharWidth' field. [default]
--recalc-max-context
Update the 'OS/2 usMaxContext' field after subsetting.
--no-recalc-max-context
Don't change the 'OS/2 usMaxContext' field. [default]
--font-number=<number>
Select font number for TrueType Collection (.ttc/.otc), starting from 0.
@ -2305,6 +2310,7 @@ class Options(object):
self.recalc_timestamp = False # Recalculate font modified timestamp
self.prune_unicode_ranges = True # Clear unused 'ulUnicodeRange' bits
self.recalc_average_width = False # update 'xAvgCharWidth'
self.recalc_max_context = False # update 'usMaxContext'
self.canonical_order = None # Order tables as recommended
self.flavor = None # May be 'woff' or 'woff2'
self.with_zopfli = False # use zopfli instead of zlib for WOFF 1.0
@ -2614,6 +2620,11 @@ class Subsetter(object):
if avg_width != font[tag].xAvgCharWidth:
font[tag].xAvgCharWidth = avg_width
log.info("%s xAvgCharWidth updated: %d", tag, avg_width)
if self.options.recalc_max_context:
max_context = maxCtxFont(font)
if max_context != font[tag].usMaxContext:
font[tag].usMaxContext = max_context
log.info("%s usMaxContext updated: %d", tag, max_context)
clazz = ttLib.getTableClass(tag)
if hasattr(clazz, 'prune_post_subset'):
with timer("prune '%s'" % tag):

View File

@ -233,6 +233,57 @@
</GlobalSubrs>
</CFF>
<GPOS>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="kern"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
<Coverage Format="1">
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
<!-- PairSetCount=1 -->
<PairSet index="0">
<!-- PairValueCount=1 -->
<PairValueRecord index="0">
<SecondGlyph value="a"/>
<Value1 XAdvance="-50"/>
</PairValueRecord>
</PairSet>
</PairPos>
</Lookup>
</LookupList>
</GPOS>
<hmtx>
<mtx name=".notdef" width="600" lsb="100"/>
<mtx name=".null" width="600" lsb="100"/>

View File

@ -119,7 +119,7 @@
<sCapHeight value="0"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="2"/>
<usMaxContext value="1"/>
</OS_2>
<hmtx>
@ -257,6 +257,45 @@
</extraNames>
</post>
<GSUB>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="salt"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="A" out="a"/>
</SingleSubst>
</Lookup>
</LookupList>
</GSUB>
<DSIG>
<!-- note that the Digital Signature will be invalid after recompilation! -->
<tableHeader flag="0x1" numSigs="1" version="1"/>

View File

@ -105,7 +105,7 @@
<sCapHeight value="0"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="2"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>

View File

@ -119,7 +119,7 @@
<sCapHeight value="0"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="2"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>

View File

@ -117,6 +117,7 @@ def test_build_ttf(tmpdir):
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2()
fb.addOpenTypeFeatures("feature salt { sub A by a; } salt;")
fb.setupPost()
fb.setupDummyDSIG()
@ -145,6 +146,7 @@ def test_build_otf(tmpdir):
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2()
fb.addOpenTypeFeatures("feature kern { pos A a -50; } kern;")
fb.setupPost()
fb.setupDummyDSIG()

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="A"/>
</GlyphOrder>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="2"/>
<maxPoints value="0"/>
<maxContours value="0"/>
<maxCompositePoints value="0"/>
<maxCompositeContours value="0"/>
<maxZones value="2"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
<maxComponentDepth value="0"/>
</maxp>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="0"/>
<underlineThickness value="0"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
</extraNames>
</post>
<GPOS>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="latn"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="9"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ExtensionPos index="0" Format="1">
<ExtensionLookupType value="1"/>
<SinglePos Format="1">
<Coverage Format="1">
<Glyph value="A"/>
</Coverage>
<ValueFormat value="4"/>
<Value XAdvance="20"/>
</SinglePos>
</ExtensionPos>
</Lookup>
</LookupList>
</GPOS>
</ttFont>

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="g20"/>
<GlyphID id="2" name="g21"/>
<GlyphID id="3" name="g22"/>
<GlyphID id="4" name="g60"/>
<GlyphID id="5" name="g61"/>
<GlyphID id="6" name="g62"/>
<GlyphID id="7" name="g63"/>
</GlyphOrder>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="8"/>
<maxPoints value="0"/>
<maxContours value="0"/>
<maxCompositePoints value="0"/>
<maxCompositeContours value="0"/>
<maxZones value="2"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
<maxComponentDepth value="0"/>
</maxp>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="0"/>
<underlineThickness value="0"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
</extraNames>
</post>
<GSUB>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="latn"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="4"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=5 -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
</SingleSubst>
</Lookup>
<Lookup index="1">
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<LigatureSubst index="0" Format="1">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
</LigatureSubst>
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
<LookupFlag value="8"/>
<!-- SubTableCount=1 -->
<LigatureSubst index="0" Format="1">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
</LigatureSubst>
</Lookup>
<Lookup index="3">
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MultipleSubst index="0" Format="1">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="1">
<Coverage Format="1">
<Glyph value="g20"/>
</Coverage>
<!-- SubRuleSetCount=1 -->
<SubRuleSet index="0">
<!-- SubRuleCount=1 -->
<SubRule index="0">
<!-- GlyphCount=2 -->
<!-- SubstCount=0 -->
<Input index="0" value="g20"/>
</SubRule>
</SubRuleSet>
</ContextSubst>
</Lookup>
</LookupList>
</GSUB>
</ttFont>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="g20"/>
<GlyphID id="2" name="g21"/>
<GlyphID id="3" name="g22"/>
<GlyphID id="4" name="g60"/>
<GlyphID id="5" name="g61"/>
<GlyphID id="6" name="g62"/>
<GlyphID id="7" name="g63"/>
</GlyphOrder>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="8"/>
<maxPoints value="0"/>
<maxContours value="0"/>
<maxCompositePoints value="0"/>
<maxCompositeContours value="0"/>
<maxZones value="2"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
<maxComponentDepth value="0"/>
</maxp>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="0"/>
<underlineThickness value="0"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
</extraNames>
</post>
<GSUB>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="latn"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="4"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=5 -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0" Format="1">
<Substitution in="g20" out="g60"/>
<Substitution in="g21" out="g61"/>
</SingleSubst>
</Lookup>
<Lookup index="1">
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<LigatureSubst index="0" Format="1">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
</LigatureSubst>
</Lookup>
<Lookup index="2">
<LookupType value="4"/>
<LookupFlag value="8"/>
<!-- SubTableCount=1 -->
<LigatureSubst index="0" Format="1">
<LigatureSet glyph="g21">
<Ligature components="g22" glyph="g61"/>
</LigatureSet>
</LigatureSubst>
</Lookup>
<Lookup index="3">
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MultipleSubst index="0" Format="1">
<Substitution in="g21" out="g61,g62,g63"/>
</MultipleSubst>
</Lookup>
<Lookup index="4">
<LookupType value="5"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextSubst index="0" Format="2">
<Coverage Format="1">
<Glyph value="g20"/>
</Coverage>
<ClassDef Format="2">
<ClassDef glyph="g20" class="1"/>
</ClassDef>
<!-- SubClassSetCount=2 -->
<SubClassSet index="0" empty="1"/>
<SubClassSet index="1">
<!-- SubClassRuleCount=1 -->
<SubClassRule index="0">
<!-- GlyphCount=2 -->
<!-- SubstCount=0 -->
<Class index="0" value="1"/>
</SubClassRule>
</SubClassSet>
</ContextSubst>
</Lookup>
</LookupList>
</GSUB>
</ttFont>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="g18"/>
<GlyphID id="2" name="g19"/>
<GlyphID id="3" name="g23"/>
<GlyphID id="4" name="g24"/>
</GlyphOrder>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="5"/>
<maxPoints value="0"/>
<maxContours value="0"/>
<maxCompositePoints value="0"/>
<maxCompositeContours value="0"/>
<maxZones value="2"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
<maxComponentDepth value="0"/>
</maxp>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="0"/>
<underlineThickness value="0"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
</extraNames>
</post>
<GSUB>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="latn"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="test"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
<Lookup index="0">
<LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ExtensionSubst index="0" Format="1">
<ExtensionLookupType value="1"/>
<SingleSubst Format="1">
<Substitution in="g18" out="g23"/>
<Substitution in="g19" out="g24"/>
</SingleSubst>
</ExtensionSubst>
</Lookup>
</LookupList>
</GSUB>
</ttFont>

View File

@ -0,0 +1,76 @@
from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
import os
import pytest
from fontTools.ttLib import TTFont
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
def test_max_ctx_calc_no_features():
font = TTFont()
assert maxCtxFont(font) == 0
font.setGlyphOrder(['.notdef'])
addOpenTypeFeaturesFromString(font, '')
assert maxCtxFont(font) == 0
def test_max_ctx_calc_features():
glyphs = '.notdef space A B C a b c'.split()
features = """
lookup GSUB_EXT useExtension {
sub a by b;
} GSUB_EXT;
lookup GPOS_EXT useExtension {
pos a b -10;
} GPOS_EXT;
feature sub1 {
sub A by a;
sub A B by b;
sub A B C by c;
sub [A B] C by c;
sub [A B] C [A B] by c;
sub A by A B;
sub A' C by A B;
sub a' by b;
sub a' b by c;
sub a from [A B C];
rsub a by b;
rsub a' by b;
rsub a b' by c;
rsub a b' c by A;
rsub [a b] c' by A;
rsub [a b] c' [a b] by B;
lookup GSUB_EXT;
} sub1;
feature pos1 {
pos A 20;
pos A B -50;
pos A B' 10 C;
lookup GPOS_EXT;
} pos1;
"""
font = TTFont()
font.setGlyphOrder(glyphs)
addOpenTypeFeaturesFromString(font, features)
assert maxCtxFont(font) == 3
@pytest.mark.parametrize('file_name, max_context', [
('gsub_51', 2),
('gsub_52', 2),
('gsub_71', 1),
('gpos_91', 1),
])
def test_max_ctx_calc_features_ttx(file_name, max_context):
ttx_path = os.path.join(os.path.dirname(__file__),
'data', '{}.ttx'.format(file_name))
font = TTFont()
font.importXML(ttx_path)
assert maxCtxFont(font) == max_context

View File

@ -485,6 +485,24 @@ class SubsetTest(unittest.TestCase):
subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"])
self.assertLess(modified, TTFont(subsetpath)['head'].modified)
def test_recalc_max_context(self):
ttxpath = self.getpath("Lobster.subset.ttx")
font = TTFont()
font.importXML(ttxpath)
max_context = font['OS/2'].usMaxContext
_, fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
# by default, the subsetter does not recalculate the usMaxContext
subset.main([fontpath, "--drop-tables+=GSUB,GPOS",
"--output-file=%s" % subsetpath])
self.assertEqual(max_context, TTFont(subsetpath)['OS/2'].usMaxContext)
subset.main([fontpath, "--recalc-max-context",
"--drop-tables+=GSUB,GPOS",
"--output-file=%s" % subsetpath])
self.assertEqual(0, TTFont(subsetpath)['OS/2'].usMaxContext)
def test_retain_gids_ttf(self):
_, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
font = TTFont(fontpath)