Merge pull request #1582 from fonttools/max_ctx_recalc
(Re)calculate OS/2.usMaxContext value
This commit is contained in:
commit
6edf6257e6
@ -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:
|
||||
|
@ -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,
|
||||
)
|
||||
|
101
Lib/fontTools/otlLib/maxContextCalc.py
Normal file
101
Lib/fontTools/otlLib/maxContextCalc.py
Normal 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)
|
@ -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):
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -105,7 +105,7 @@
|
||||
<sCapHeight value="0"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="2"/>
|
||||
<usMaxContext value="0"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
|
@ -119,7 +119,7 @@
|
||||
<sCapHeight value="0"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="2"/>
|
||||
<usMaxContext value="0"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
|
@ -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()
|
||||
|
||||
|
99
Tests/otlLib/data/gpos_91.ttx
Normal file
99
Tests/otlLib/data/gpos_91.ttx
Normal 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>
|
146
Tests/otlLib/data/gsub_51.ttx
Normal file
146
Tests/otlLib/data/gsub_51.ttx
Normal 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>
|
150
Tests/otlLib/data/gsub_52.ttx
Normal file
150
Tests/otlLib/data/gsub_52.ttx
Normal 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>
|
99
Tests/otlLib/data/gsub_71.ttx
Normal file
99
Tests/otlLib/data/gsub_71.ttx
Normal 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>
|
76
Tests/otlLib/maxContextCalc_test.py
Normal file
76
Tests/otlLib/maxContextCalc_test.py
Normal 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
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user