1*e1fe3e4aSElliott Hughesfrom collections import namedtuple, OrderedDict 2*e1fe3e4aSElliott Hughesimport os 3*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import fixedToFloat 4*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import otRound 5*e1fe3e4aSElliott Hughesfrom fontTools import ttLib 6*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otTables as ot 7*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.otBase import ( 8*e1fe3e4aSElliott Hughes ValueRecord, 9*e1fe3e4aSElliott Hughes valueRecordFormatDict, 10*e1fe3e4aSElliott Hughes OTLOffsetOverflowError, 11*e1fe3e4aSElliott Hughes OTTableWriter, 12*e1fe3e4aSElliott Hughes CountReference, 13*e1fe3e4aSElliott Hughes) 14*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otBase 15*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.ast import STATNameStatement 16*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.optimize.gpos import ( 17*e1fe3e4aSElliott Hughes _compression_level_from_env, 18*e1fe3e4aSElliott Hughes compact_lookup, 19*e1fe3e4aSElliott Hughes) 20*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.error import OpenTypeLibError 21*e1fe3e4aSElliott Hughesfrom functools import reduce 22*e1fe3e4aSElliott Hughesimport logging 23*e1fe3e4aSElliott Hughesimport copy 24*e1fe3e4aSElliott Hughes 25*e1fe3e4aSElliott Hughes 26*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__) 27*e1fe3e4aSElliott Hughes 28*e1fe3e4aSElliott Hughes 29*e1fe3e4aSElliott Hughesdef buildCoverage(glyphs, glyphMap): 30*e1fe3e4aSElliott Hughes """Builds a coverage table. 31*e1fe3e4aSElliott Hughes 32*e1fe3e4aSElliott Hughes Coverage tables (as defined in the `OpenType spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#coverage-table>`__) 33*e1fe3e4aSElliott Hughes are used in all OpenType Layout lookups apart from the Extension type, and 34*e1fe3e4aSElliott Hughes define the glyphs involved in a layout subtable. This allows shaping engines 35*e1fe3e4aSElliott Hughes to compare the glyph stream with the coverage table and quickly determine 36*e1fe3e4aSElliott Hughes whether a subtable should be involved in a shaping operation. 37*e1fe3e4aSElliott Hughes 38*e1fe3e4aSElliott Hughes This function takes a list of glyphs and a glyphname-to-ID map, and 39*e1fe3e4aSElliott Hughes returns a ``Coverage`` object representing the coverage table. 40*e1fe3e4aSElliott Hughes 41*e1fe3e4aSElliott Hughes Example:: 42*e1fe3e4aSElliott Hughes 43*e1fe3e4aSElliott Hughes glyphMap = font.getReverseGlyphMap() 44*e1fe3e4aSElliott Hughes glyphs = [ "A", "B", "C" ] 45*e1fe3e4aSElliott Hughes coverage = buildCoverage(glyphs, glyphMap) 46*e1fe3e4aSElliott Hughes 47*e1fe3e4aSElliott Hughes Args: 48*e1fe3e4aSElliott Hughes glyphs: a sequence of glyph names. 49*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 50*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 51*e1fe3e4aSElliott Hughes 52*e1fe3e4aSElliott Hughes Returns: 53*e1fe3e4aSElliott Hughes An ``otTables.Coverage`` object or ``None`` if there are no glyphs 54*e1fe3e4aSElliott Hughes supplied. 55*e1fe3e4aSElliott Hughes """ 56*e1fe3e4aSElliott Hughes 57*e1fe3e4aSElliott Hughes if not glyphs: 58*e1fe3e4aSElliott Hughes return None 59*e1fe3e4aSElliott Hughes self = ot.Coverage() 60*e1fe3e4aSElliott Hughes try: 61*e1fe3e4aSElliott Hughes self.glyphs = sorted(set(glyphs), key=glyphMap.__getitem__) 62*e1fe3e4aSElliott Hughes except KeyError as e: 63*e1fe3e4aSElliott Hughes raise ValueError(f"Could not find glyph {e} in font") from e 64*e1fe3e4aSElliott Hughes 65*e1fe3e4aSElliott Hughes return self 66*e1fe3e4aSElliott Hughes 67*e1fe3e4aSElliott Hughes 68*e1fe3e4aSElliott HughesLOOKUP_FLAG_RIGHT_TO_LEFT = 0x0001 69*e1fe3e4aSElliott HughesLOOKUP_FLAG_IGNORE_BASE_GLYPHS = 0x0002 70*e1fe3e4aSElliott HughesLOOKUP_FLAG_IGNORE_LIGATURES = 0x0004 71*e1fe3e4aSElliott HughesLOOKUP_FLAG_IGNORE_MARKS = 0x0008 72*e1fe3e4aSElliott HughesLOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010 73*e1fe3e4aSElliott Hughes 74*e1fe3e4aSElliott Hughes 75*e1fe3e4aSElliott Hughesdef buildLookup(subtables, flags=0, markFilterSet=None): 76*e1fe3e4aSElliott Hughes """Turns a collection of rules into a lookup. 77*e1fe3e4aSElliott Hughes 78*e1fe3e4aSElliott Hughes A Lookup (as defined in the `OpenType Spec <https://docs.microsoft.com/en-gb/typography/opentype/spec/chapter2#lookupTbl>`__) 79*e1fe3e4aSElliott Hughes wraps the individual rules in a layout operation (substitution or 80*e1fe3e4aSElliott Hughes positioning) in a data structure expressing their overall lookup type - 81*e1fe3e4aSElliott Hughes for example, single substitution, mark-to-base attachment, and so on - 82*e1fe3e4aSElliott Hughes as well as the lookup flags and any mark filtering sets. You may import 83*e1fe3e4aSElliott Hughes the following constants to express lookup flags: 84*e1fe3e4aSElliott Hughes 85*e1fe3e4aSElliott Hughes - ``LOOKUP_FLAG_RIGHT_TO_LEFT`` 86*e1fe3e4aSElliott Hughes - ``LOOKUP_FLAG_IGNORE_BASE_GLYPHS`` 87*e1fe3e4aSElliott Hughes - ``LOOKUP_FLAG_IGNORE_LIGATURES`` 88*e1fe3e4aSElliott Hughes - ``LOOKUP_FLAG_IGNORE_MARKS`` 89*e1fe3e4aSElliott Hughes - ``LOOKUP_FLAG_USE_MARK_FILTERING_SET`` 90*e1fe3e4aSElliott Hughes 91*e1fe3e4aSElliott Hughes Args: 92*e1fe3e4aSElliott Hughes subtables: A list of layout subtable objects (e.g. 93*e1fe3e4aSElliott Hughes ``MultipleSubst``, ``PairPos``, etc.) or ``None``. 94*e1fe3e4aSElliott Hughes flags (int): This lookup's flags. 95*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 96*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 97*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 98*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 99*e1fe3e4aSElliott Hughes flags. 100*e1fe3e4aSElliott Hughes 101*e1fe3e4aSElliott Hughes Returns: 102*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object or ``None`` if there are no subtables 103*e1fe3e4aSElliott Hughes supplied. 104*e1fe3e4aSElliott Hughes """ 105*e1fe3e4aSElliott Hughes if subtables is None: 106*e1fe3e4aSElliott Hughes return None 107*e1fe3e4aSElliott Hughes subtables = [st for st in subtables if st is not None] 108*e1fe3e4aSElliott Hughes if not subtables: 109*e1fe3e4aSElliott Hughes return None 110*e1fe3e4aSElliott Hughes assert all( 111*e1fe3e4aSElliott Hughes t.LookupType == subtables[0].LookupType for t in subtables 112*e1fe3e4aSElliott Hughes ), "all subtables must have the same LookupType; got %s" % repr( 113*e1fe3e4aSElliott Hughes [t.LookupType for t in subtables] 114*e1fe3e4aSElliott Hughes ) 115*e1fe3e4aSElliott Hughes self = ot.Lookup() 116*e1fe3e4aSElliott Hughes self.LookupType = subtables[0].LookupType 117*e1fe3e4aSElliott Hughes self.LookupFlag = flags 118*e1fe3e4aSElliott Hughes self.SubTable = subtables 119*e1fe3e4aSElliott Hughes self.SubTableCount = len(self.SubTable) 120*e1fe3e4aSElliott Hughes if markFilterSet is not None: 121*e1fe3e4aSElliott Hughes self.LookupFlag |= LOOKUP_FLAG_USE_MARK_FILTERING_SET 122*e1fe3e4aSElliott Hughes assert isinstance(markFilterSet, int), markFilterSet 123*e1fe3e4aSElliott Hughes self.MarkFilteringSet = markFilterSet 124*e1fe3e4aSElliott Hughes else: 125*e1fe3e4aSElliott Hughes assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, ( 126*e1fe3e4aSElliott Hughes "if markFilterSet is None, flags must not set " 127*e1fe3e4aSElliott Hughes "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags 128*e1fe3e4aSElliott Hughes ) 129*e1fe3e4aSElliott Hughes return self 130*e1fe3e4aSElliott Hughes 131*e1fe3e4aSElliott Hughes 132*e1fe3e4aSElliott Hughesclass LookupBuilder(object): 133*e1fe3e4aSElliott Hughes SUBTABLE_BREAK_ = "SUBTABLE_BREAK" 134*e1fe3e4aSElliott Hughes 135*e1fe3e4aSElliott Hughes def __init__(self, font, location, table, lookup_type): 136*e1fe3e4aSElliott Hughes self.font = font 137*e1fe3e4aSElliott Hughes self.glyphMap = font.getReverseGlyphMap() 138*e1fe3e4aSElliott Hughes self.location = location 139*e1fe3e4aSElliott Hughes self.table, self.lookup_type = table, lookup_type 140*e1fe3e4aSElliott Hughes self.lookupflag = 0 141*e1fe3e4aSElliott Hughes self.markFilterSet = None 142*e1fe3e4aSElliott Hughes self.lookup_index = None # assigned when making final tables 143*e1fe3e4aSElliott Hughes assert table in ("GPOS", "GSUB") 144*e1fe3e4aSElliott Hughes 145*e1fe3e4aSElliott Hughes def equals(self, other): 146*e1fe3e4aSElliott Hughes return ( 147*e1fe3e4aSElliott Hughes isinstance(other, self.__class__) 148*e1fe3e4aSElliott Hughes and self.table == other.table 149*e1fe3e4aSElliott Hughes and self.lookupflag == other.lookupflag 150*e1fe3e4aSElliott Hughes and self.markFilterSet == other.markFilterSet 151*e1fe3e4aSElliott Hughes ) 152*e1fe3e4aSElliott Hughes 153*e1fe3e4aSElliott Hughes def inferGlyphClasses(self): 154*e1fe3e4aSElliott Hughes """Infers glyph glasses for the GDEF table, such as {"cedilla":3}.""" 155*e1fe3e4aSElliott Hughes return {} 156*e1fe3e4aSElliott Hughes 157*e1fe3e4aSElliott Hughes def getAlternateGlyphs(self): 158*e1fe3e4aSElliott Hughes """Helper for building 'aalt' features.""" 159*e1fe3e4aSElliott Hughes return {} 160*e1fe3e4aSElliott Hughes 161*e1fe3e4aSElliott Hughes def buildLookup_(self, subtables): 162*e1fe3e4aSElliott Hughes return buildLookup(subtables, self.lookupflag, self.markFilterSet) 163*e1fe3e4aSElliott Hughes 164*e1fe3e4aSElliott Hughes def buildMarkClasses_(self, marks): 165*e1fe3e4aSElliott Hughes """{"cedilla": ("BOTTOM", ast.Anchor), ...} --> {"BOTTOM":0, "TOP":1} 166*e1fe3e4aSElliott Hughes 167*e1fe3e4aSElliott Hughes Helper for MarkBasePostBuilder, MarkLigPosBuilder, and 168*e1fe3e4aSElliott Hughes MarkMarkPosBuilder. Seems to return the same numeric IDs 169*e1fe3e4aSElliott Hughes for mark classes as the AFDKO makeotf tool. 170*e1fe3e4aSElliott Hughes """ 171*e1fe3e4aSElliott Hughes ids = {} 172*e1fe3e4aSElliott Hughes for mark in sorted(marks.keys(), key=self.font.getGlyphID): 173*e1fe3e4aSElliott Hughes markClassName, _markAnchor = marks[mark] 174*e1fe3e4aSElliott Hughes if markClassName not in ids: 175*e1fe3e4aSElliott Hughes ids[markClassName] = len(ids) 176*e1fe3e4aSElliott Hughes return ids 177*e1fe3e4aSElliott Hughes 178*e1fe3e4aSElliott Hughes def setBacktrackCoverage_(self, prefix, subtable): 179*e1fe3e4aSElliott Hughes subtable.BacktrackGlyphCount = len(prefix) 180*e1fe3e4aSElliott Hughes subtable.BacktrackCoverage = [] 181*e1fe3e4aSElliott Hughes for p in reversed(prefix): 182*e1fe3e4aSElliott Hughes coverage = buildCoverage(p, self.glyphMap) 183*e1fe3e4aSElliott Hughes subtable.BacktrackCoverage.append(coverage) 184*e1fe3e4aSElliott Hughes 185*e1fe3e4aSElliott Hughes def setLookAheadCoverage_(self, suffix, subtable): 186*e1fe3e4aSElliott Hughes subtable.LookAheadGlyphCount = len(suffix) 187*e1fe3e4aSElliott Hughes subtable.LookAheadCoverage = [] 188*e1fe3e4aSElliott Hughes for s in suffix: 189*e1fe3e4aSElliott Hughes coverage = buildCoverage(s, self.glyphMap) 190*e1fe3e4aSElliott Hughes subtable.LookAheadCoverage.append(coverage) 191*e1fe3e4aSElliott Hughes 192*e1fe3e4aSElliott Hughes def setInputCoverage_(self, glyphs, subtable): 193*e1fe3e4aSElliott Hughes subtable.InputGlyphCount = len(glyphs) 194*e1fe3e4aSElliott Hughes subtable.InputCoverage = [] 195*e1fe3e4aSElliott Hughes for g in glyphs: 196*e1fe3e4aSElliott Hughes coverage = buildCoverage(g, self.glyphMap) 197*e1fe3e4aSElliott Hughes subtable.InputCoverage.append(coverage) 198*e1fe3e4aSElliott Hughes 199*e1fe3e4aSElliott Hughes def setCoverage_(self, glyphs, subtable): 200*e1fe3e4aSElliott Hughes subtable.GlyphCount = len(glyphs) 201*e1fe3e4aSElliott Hughes subtable.Coverage = [] 202*e1fe3e4aSElliott Hughes for g in glyphs: 203*e1fe3e4aSElliott Hughes coverage = buildCoverage(g, self.glyphMap) 204*e1fe3e4aSElliott Hughes subtable.Coverage.append(coverage) 205*e1fe3e4aSElliott Hughes 206*e1fe3e4aSElliott Hughes def build_subst_subtables(self, mapping, klass): 207*e1fe3e4aSElliott Hughes substitutions = [{}] 208*e1fe3e4aSElliott Hughes for key in mapping: 209*e1fe3e4aSElliott Hughes if key[0] == self.SUBTABLE_BREAK_: 210*e1fe3e4aSElliott Hughes substitutions.append({}) 211*e1fe3e4aSElliott Hughes else: 212*e1fe3e4aSElliott Hughes substitutions[-1][key] = mapping[key] 213*e1fe3e4aSElliott Hughes subtables = [klass(s) for s in substitutions] 214*e1fe3e4aSElliott Hughes return subtables 215*e1fe3e4aSElliott Hughes 216*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 217*e1fe3e4aSElliott Hughes """Add an explicit subtable break. 218*e1fe3e4aSElliott Hughes 219*e1fe3e4aSElliott Hughes Args: 220*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the 221*e1fe3e4aSElliott Hughes original source which produced this break, or ``None`` if 222*e1fe3e4aSElliott Hughes no location is provided. 223*e1fe3e4aSElliott Hughes """ 224*e1fe3e4aSElliott Hughes log.warning( 225*e1fe3e4aSElliott Hughes OpenTypeLibError( 226*e1fe3e4aSElliott Hughes 'unsupported "subtable" statement for lookup type', location 227*e1fe3e4aSElliott Hughes ) 228*e1fe3e4aSElliott Hughes ) 229*e1fe3e4aSElliott Hughes 230*e1fe3e4aSElliott Hughes 231*e1fe3e4aSElliott Hughesclass AlternateSubstBuilder(LookupBuilder): 232*e1fe3e4aSElliott Hughes """Builds an Alternate Substitution (GSUB3) lookup. 233*e1fe3e4aSElliott Hughes 234*e1fe3e4aSElliott Hughes Users are expected to manually add alternate glyph substitutions to 235*e1fe3e4aSElliott Hughes the ``alternates`` attribute after the object has been initialized, 236*e1fe3e4aSElliott Hughes e.g.:: 237*e1fe3e4aSElliott Hughes 238*e1fe3e4aSElliott Hughes builder.alternates["A"] = ["A.alt1", "A.alt2"] 239*e1fe3e4aSElliott Hughes 240*e1fe3e4aSElliott Hughes Attributes: 241*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 242*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 243*e1fe3e4aSElliott Hughes source which produced this lookup. 244*e1fe3e4aSElliott Hughes alternates: An ordered dictionary of alternates, mapping glyph names 245*e1fe3e4aSElliott Hughes to a list of names of alternates. 246*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 247*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 248*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 249*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 250*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 251*e1fe3e4aSElliott Hughes flags. 252*e1fe3e4aSElliott Hughes """ 253*e1fe3e4aSElliott Hughes 254*e1fe3e4aSElliott Hughes def __init__(self, font, location): 255*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GSUB", 3) 256*e1fe3e4aSElliott Hughes self.alternates = OrderedDict() 257*e1fe3e4aSElliott Hughes 258*e1fe3e4aSElliott Hughes def equals(self, other): 259*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.alternates == other.alternates 260*e1fe3e4aSElliott Hughes 261*e1fe3e4aSElliott Hughes def build(self): 262*e1fe3e4aSElliott Hughes """Build the lookup. 263*e1fe3e4aSElliott Hughes 264*e1fe3e4aSElliott Hughes Returns: 265*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the alternate 266*e1fe3e4aSElliott Hughes substitution lookup. 267*e1fe3e4aSElliott Hughes """ 268*e1fe3e4aSElliott Hughes subtables = self.build_subst_subtables( 269*e1fe3e4aSElliott Hughes self.alternates, buildAlternateSubstSubtable 270*e1fe3e4aSElliott Hughes ) 271*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 272*e1fe3e4aSElliott Hughes 273*e1fe3e4aSElliott Hughes def getAlternateGlyphs(self): 274*e1fe3e4aSElliott Hughes return self.alternates 275*e1fe3e4aSElliott Hughes 276*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 277*e1fe3e4aSElliott Hughes self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ 278*e1fe3e4aSElliott Hughes 279*e1fe3e4aSElliott Hughes 280*e1fe3e4aSElliott Hughesclass ChainContextualRule( 281*e1fe3e4aSElliott Hughes namedtuple("ChainContextualRule", ["prefix", "glyphs", "suffix", "lookups"]) 282*e1fe3e4aSElliott Hughes): 283*e1fe3e4aSElliott Hughes @property 284*e1fe3e4aSElliott Hughes def is_subtable_break(self): 285*e1fe3e4aSElliott Hughes return self.prefix == LookupBuilder.SUBTABLE_BREAK_ 286*e1fe3e4aSElliott Hughes 287*e1fe3e4aSElliott Hughes 288*e1fe3e4aSElliott Hughesclass ChainContextualRuleset: 289*e1fe3e4aSElliott Hughes def __init__(self): 290*e1fe3e4aSElliott Hughes self.rules = [] 291*e1fe3e4aSElliott Hughes 292*e1fe3e4aSElliott Hughes def addRule(self, rule): 293*e1fe3e4aSElliott Hughes self.rules.append(rule) 294*e1fe3e4aSElliott Hughes 295*e1fe3e4aSElliott Hughes @property 296*e1fe3e4aSElliott Hughes def hasPrefixOrSuffix(self): 297*e1fe3e4aSElliott Hughes # Do we have any prefixes/suffixes? If this is False for all 298*e1fe3e4aSElliott Hughes # rulesets, we can express the whole lookup as GPOS5/GSUB7. 299*e1fe3e4aSElliott Hughes for rule in self.rules: 300*e1fe3e4aSElliott Hughes if len(rule.prefix) > 0 or len(rule.suffix) > 0: 301*e1fe3e4aSElliott Hughes return True 302*e1fe3e4aSElliott Hughes return False 303*e1fe3e4aSElliott Hughes 304*e1fe3e4aSElliott Hughes @property 305*e1fe3e4aSElliott Hughes def hasAnyGlyphClasses(self): 306*e1fe3e4aSElliott Hughes # Do we use glyph classes anywhere in the rules? If this is False 307*e1fe3e4aSElliott Hughes # we can express this subtable as a Format 1. 308*e1fe3e4aSElliott Hughes for rule in self.rules: 309*e1fe3e4aSElliott Hughes for coverage in (rule.prefix, rule.glyphs, rule.suffix): 310*e1fe3e4aSElliott Hughes if any(len(x) > 1 for x in coverage): 311*e1fe3e4aSElliott Hughes return True 312*e1fe3e4aSElliott Hughes return False 313*e1fe3e4aSElliott Hughes 314*e1fe3e4aSElliott Hughes def format2ClassDefs(self): 315*e1fe3e4aSElliott Hughes PREFIX, GLYPHS, SUFFIX = 0, 1, 2 316*e1fe3e4aSElliott Hughes classDefBuilders = [] 317*e1fe3e4aSElliott Hughes for ix in [PREFIX, GLYPHS, SUFFIX]: 318*e1fe3e4aSElliott Hughes context = [] 319*e1fe3e4aSElliott Hughes for r in self.rules: 320*e1fe3e4aSElliott Hughes context.append(r[ix]) 321*e1fe3e4aSElliott Hughes classes = self._classBuilderForContext(context) 322*e1fe3e4aSElliott Hughes if not classes: 323*e1fe3e4aSElliott Hughes return None 324*e1fe3e4aSElliott Hughes classDefBuilders.append(classes) 325*e1fe3e4aSElliott Hughes return classDefBuilders 326*e1fe3e4aSElliott Hughes 327*e1fe3e4aSElliott Hughes def _classBuilderForContext(self, context): 328*e1fe3e4aSElliott Hughes classdefbuilder = ClassDefBuilder(useClass0=False) 329*e1fe3e4aSElliott Hughes for position in context: 330*e1fe3e4aSElliott Hughes for glyphset in position: 331*e1fe3e4aSElliott Hughes glyphs = set(glyphset) 332*e1fe3e4aSElliott Hughes if not classdefbuilder.canAdd(glyphs): 333*e1fe3e4aSElliott Hughes return None 334*e1fe3e4aSElliott Hughes classdefbuilder.add(glyphs) 335*e1fe3e4aSElliott Hughes return classdefbuilder 336*e1fe3e4aSElliott Hughes 337*e1fe3e4aSElliott Hughes 338*e1fe3e4aSElliott Hughesclass ChainContextualBuilder(LookupBuilder): 339*e1fe3e4aSElliott Hughes def equals(self, other): 340*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.rules == other.rules 341*e1fe3e4aSElliott Hughes 342*e1fe3e4aSElliott Hughes def rulesets(self): 343*e1fe3e4aSElliott Hughes # Return a list of ChainContextRuleset objects, taking explicit 344*e1fe3e4aSElliott Hughes # subtable breaks into account 345*e1fe3e4aSElliott Hughes ruleset = [ChainContextualRuleset()] 346*e1fe3e4aSElliott Hughes for rule in self.rules: 347*e1fe3e4aSElliott Hughes if rule.is_subtable_break: 348*e1fe3e4aSElliott Hughes ruleset.append(ChainContextualRuleset()) 349*e1fe3e4aSElliott Hughes continue 350*e1fe3e4aSElliott Hughes ruleset[-1].addRule(rule) 351*e1fe3e4aSElliott Hughes # Squish any empty subtables 352*e1fe3e4aSElliott Hughes return [x for x in ruleset if len(x.rules) > 0] 353*e1fe3e4aSElliott Hughes 354*e1fe3e4aSElliott Hughes def getCompiledSize_(self, subtables): 355*e1fe3e4aSElliott Hughes if not subtables: 356*e1fe3e4aSElliott Hughes return 0 357*e1fe3e4aSElliott Hughes # We need to make a copy here because compiling 358*e1fe3e4aSElliott Hughes # modifies the subtable (finalizing formats etc.) 359*e1fe3e4aSElliott Hughes table = self.buildLookup_(copy.deepcopy(subtables)) 360*e1fe3e4aSElliott Hughes w = OTTableWriter() 361*e1fe3e4aSElliott Hughes table.compile(w, self.font) 362*e1fe3e4aSElliott Hughes size = len(w.getAllData()) 363*e1fe3e4aSElliott Hughes return size 364*e1fe3e4aSElliott Hughes 365*e1fe3e4aSElliott Hughes def build(self): 366*e1fe3e4aSElliott Hughes """Build the lookup. 367*e1fe3e4aSElliott Hughes 368*e1fe3e4aSElliott Hughes Returns: 369*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the chained 370*e1fe3e4aSElliott Hughes contextual positioning lookup. 371*e1fe3e4aSElliott Hughes """ 372*e1fe3e4aSElliott Hughes subtables = [] 373*e1fe3e4aSElliott Hughes 374*e1fe3e4aSElliott Hughes rulesets = self.rulesets() 375*e1fe3e4aSElliott Hughes chaining = any(ruleset.hasPrefixOrSuffix for ruleset in rulesets) 376*e1fe3e4aSElliott Hughes 377*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2539 378*e1fe3e4aSElliott Hughes # 379*e1fe3e4aSElliott Hughes # Unfortunately, as of 2022-03-07, Apple's CoreText renderer does not 380*e1fe3e4aSElliott Hughes # correctly process GPOS7 lookups, so for now we force contextual 381*e1fe3e4aSElliott Hughes # positioning lookups to be chaining (GPOS8). 382*e1fe3e4aSElliott Hughes # 383*e1fe3e4aSElliott Hughes # This seems to be fixed as of macOS 13.2, but we keep disabling this 384*e1fe3e4aSElliott Hughes # for now until we are no longer concerned about old macOS versions. 385*e1fe3e4aSElliott Hughes # But we allow people to opt-out of this with the config key below. 386*e1fe3e4aSElliott Hughes write_gpos7 = self.font.cfg.get("fontTools.otlLib.builder:WRITE_GPOS7") 387*e1fe3e4aSElliott Hughes # horrible separation of concerns breach 388*e1fe3e4aSElliott Hughes if not write_gpos7 and self.subtable_type == "Pos": 389*e1fe3e4aSElliott Hughes chaining = True 390*e1fe3e4aSElliott Hughes 391*e1fe3e4aSElliott Hughes for ruleset in rulesets: 392*e1fe3e4aSElliott Hughes # Determine format strategy. We try to build formats 1, 2 and 3 393*e1fe3e4aSElliott Hughes # subtables and then work out which is best. candidates list holds 394*e1fe3e4aSElliott Hughes # the subtables in each format for this ruleset (including a dummy 395*e1fe3e4aSElliott Hughes # "format 0" to make the addressing match the format numbers). 396*e1fe3e4aSElliott Hughes 397*e1fe3e4aSElliott Hughes # We can always build a format 3 lookup by accumulating each of 398*e1fe3e4aSElliott Hughes # the rules into a list, so start with that. 399*e1fe3e4aSElliott Hughes candidates = [None, None, None, []] 400*e1fe3e4aSElliott Hughes for rule in ruleset.rules: 401*e1fe3e4aSElliott Hughes candidates[3].append(self.buildFormat3Subtable(rule, chaining)) 402*e1fe3e4aSElliott Hughes 403*e1fe3e4aSElliott Hughes # Can we express the whole ruleset as a format 2 subtable? 404*e1fe3e4aSElliott Hughes classdefs = ruleset.format2ClassDefs() 405*e1fe3e4aSElliott Hughes if classdefs: 406*e1fe3e4aSElliott Hughes candidates[2] = [ 407*e1fe3e4aSElliott Hughes self.buildFormat2Subtable(ruleset, classdefs, chaining) 408*e1fe3e4aSElliott Hughes ] 409*e1fe3e4aSElliott Hughes 410*e1fe3e4aSElliott Hughes if not ruleset.hasAnyGlyphClasses: 411*e1fe3e4aSElliott Hughes candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)] 412*e1fe3e4aSElliott Hughes 413*e1fe3e4aSElliott Hughes candidates_by_size = [] 414*e1fe3e4aSElliott Hughes for i in [1, 2, 3]: 415*e1fe3e4aSElliott Hughes if candidates[i]: 416*e1fe3e4aSElliott Hughes try: 417*e1fe3e4aSElliott Hughes size = self.getCompiledSize_(candidates[i]) 418*e1fe3e4aSElliott Hughes except OTLOffsetOverflowError as e: 419*e1fe3e4aSElliott Hughes log.warning( 420*e1fe3e4aSElliott Hughes "Contextual format %i at %s overflowed (%s)" 421*e1fe3e4aSElliott Hughes % (i, str(self.location), e) 422*e1fe3e4aSElliott Hughes ) 423*e1fe3e4aSElliott Hughes else: 424*e1fe3e4aSElliott Hughes candidates_by_size.append((size, candidates[i])) 425*e1fe3e4aSElliott Hughes 426*e1fe3e4aSElliott Hughes if not candidates_by_size: 427*e1fe3e4aSElliott Hughes raise OpenTypeLibError("All candidates overflowed", self.location) 428*e1fe3e4aSElliott Hughes 429*e1fe3e4aSElliott Hughes _min_size, winner = min(candidates_by_size, key=lambda x: x[0]) 430*e1fe3e4aSElliott Hughes subtables.extend(winner) 431*e1fe3e4aSElliott Hughes 432*e1fe3e4aSElliott Hughes # If we are not chaining, lookup type will be automatically fixed by 433*e1fe3e4aSElliott Hughes # buildLookup_ 434*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 435*e1fe3e4aSElliott Hughes 436*e1fe3e4aSElliott Hughes def buildFormat1Subtable(self, ruleset, chaining=True): 437*e1fe3e4aSElliott Hughes st = self.newSubtable_(chaining=chaining) 438*e1fe3e4aSElliott Hughes st.Format = 1 439*e1fe3e4aSElliott Hughes st.populateDefaults() 440*e1fe3e4aSElliott Hughes coverage = set() 441*e1fe3e4aSElliott Hughes rulesetsByFirstGlyph = {} 442*e1fe3e4aSElliott Hughes ruleAttr = self.ruleAttr_(format=1, chaining=chaining) 443*e1fe3e4aSElliott Hughes 444*e1fe3e4aSElliott Hughes for rule in ruleset.rules: 445*e1fe3e4aSElliott Hughes ruleAsSubtable = self.newRule_(format=1, chaining=chaining) 446*e1fe3e4aSElliott Hughes 447*e1fe3e4aSElliott Hughes if chaining: 448*e1fe3e4aSElliott Hughes ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix) 449*e1fe3e4aSElliott Hughes ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix) 450*e1fe3e4aSElliott Hughes ruleAsSubtable.Backtrack = [list(x)[0] for x in reversed(rule.prefix)] 451*e1fe3e4aSElliott Hughes ruleAsSubtable.LookAhead = [list(x)[0] for x in rule.suffix] 452*e1fe3e4aSElliott Hughes 453*e1fe3e4aSElliott Hughes ruleAsSubtable.InputGlyphCount = len(rule.glyphs) 454*e1fe3e4aSElliott Hughes else: 455*e1fe3e4aSElliott Hughes ruleAsSubtable.GlyphCount = len(rule.glyphs) 456*e1fe3e4aSElliott Hughes 457*e1fe3e4aSElliott Hughes ruleAsSubtable.Input = [list(x)[0] for x in rule.glyphs[1:]] 458*e1fe3e4aSElliott Hughes 459*e1fe3e4aSElliott Hughes self.buildLookupList(rule, ruleAsSubtable) 460*e1fe3e4aSElliott Hughes 461*e1fe3e4aSElliott Hughes firstGlyph = list(rule.glyphs[0])[0] 462*e1fe3e4aSElliott Hughes if firstGlyph not in rulesetsByFirstGlyph: 463*e1fe3e4aSElliott Hughes coverage.add(firstGlyph) 464*e1fe3e4aSElliott Hughes rulesetsByFirstGlyph[firstGlyph] = [] 465*e1fe3e4aSElliott Hughes rulesetsByFirstGlyph[firstGlyph].append(ruleAsSubtable) 466*e1fe3e4aSElliott Hughes 467*e1fe3e4aSElliott Hughes st.Coverage = buildCoverage(coverage, self.glyphMap) 468*e1fe3e4aSElliott Hughes ruleSets = [] 469*e1fe3e4aSElliott Hughes for g in st.Coverage.glyphs: 470*e1fe3e4aSElliott Hughes ruleSet = self.newRuleSet_(format=1, chaining=chaining) 471*e1fe3e4aSElliott Hughes setattr(ruleSet, ruleAttr, rulesetsByFirstGlyph[g]) 472*e1fe3e4aSElliott Hughes setattr(ruleSet, f"{ruleAttr}Count", len(rulesetsByFirstGlyph[g])) 473*e1fe3e4aSElliott Hughes ruleSets.append(ruleSet) 474*e1fe3e4aSElliott Hughes 475*e1fe3e4aSElliott Hughes setattr(st, self.ruleSetAttr_(format=1, chaining=chaining), ruleSets) 476*e1fe3e4aSElliott Hughes setattr( 477*e1fe3e4aSElliott Hughes st, self.ruleSetAttr_(format=1, chaining=chaining) + "Count", len(ruleSets) 478*e1fe3e4aSElliott Hughes ) 479*e1fe3e4aSElliott Hughes 480*e1fe3e4aSElliott Hughes return st 481*e1fe3e4aSElliott Hughes 482*e1fe3e4aSElliott Hughes def buildFormat2Subtable(self, ruleset, classdefs, chaining=True): 483*e1fe3e4aSElliott Hughes st = self.newSubtable_(chaining=chaining) 484*e1fe3e4aSElliott Hughes st.Format = 2 485*e1fe3e4aSElliott Hughes st.populateDefaults() 486*e1fe3e4aSElliott Hughes 487*e1fe3e4aSElliott Hughes if chaining: 488*e1fe3e4aSElliott Hughes ( 489*e1fe3e4aSElliott Hughes st.BacktrackClassDef, 490*e1fe3e4aSElliott Hughes st.InputClassDef, 491*e1fe3e4aSElliott Hughes st.LookAheadClassDef, 492*e1fe3e4aSElliott Hughes ) = [c.build() for c in classdefs] 493*e1fe3e4aSElliott Hughes else: 494*e1fe3e4aSElliott Hughes st.ClassDef = classdefs[1].build() 495*e1fe3e4aSElliott Hughes 496*e1fe3e4aSElliott Hughes inClasses = classdefs[1].classes() 497*e1fe3e4aSElliott Hughes 498*e1fe3e4aSElliott Hughes classSets = [] 499*e1fe3e4aSElliott Hughes for _ in inClasses: 500*e1fe3e4aSElliott Hughes classSet = self.newRuleSet_(format=2, chaining=chaining) 501*e1fe3e4aSElliott Hughes classSets.append(classSet) 502*e1fe3e4aSElliott Hughes 503*e1fe3e4aSElliott Hughes coverage = set() 504*e1fe3e4aSElliott Hughes classRuleAttr = self.ruleAttr_(format=2, chaining=chaining) 505*e1fe3e4aSElliott Hughes 506*e1fe3e4aSElliott Hughes for rule in ruleset.rules: 507*e1fe3e4aSElliott Hughes ruleAsSubtable = self.newRule_(format=2, chaining=chaining) 508*e1fe3e4aSElliott Hughes if chaining: 509*e1fe3e4aSElliott Hughes ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix) 510*e1fe3e4aSElliott Hughes ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix) 511*e1fe3e4aSElliott Hughes # The glyphs in the rule may be list, tuple, odict_keys... 512*e1fe3e4aSElliott Hughes # Order is not important anyway because they are guaranteed 513*e1fe3e4aSElliott Hughes # to be members of the same class. 514*e1fe3e4aSElliott Hughes ruleAsSubtable.Backtrack = [ 515*e1fe3e4aSElliott Hughes st.BacktrackClassDef.classDefs[list(x)[0]] 516*e1fe3e4aSElliott Hughes for x in reversed(rule.prefix) 517*e1fe3e4aSElliott Hughes ] 518*e1fe3e4aSElliott Hughes ruleAsSubtable.LookAhead = [ 519*e1fe3e4aSElliott Hughes st.LookAheadClassDef.classDefs[list(x)[0]] for x in rule.suffix 520*e1fe3e4aSElliott Hughes ] 521*e1fe3e4aSElliott Hughes 522*e1fe3e4aSElliott Hughes ruleAsSubtable.InputGlyphCount = len(rule.glyphs) 523*e1fe3e4aSElliott Hughes ruleAsSubtable.Input = [ 524*e1fe3e4aSElliott Hughes st.InputClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:] 525*e1fe3e4aSElliott Hughes ] 526*e1fe3e4aSElliott Hughes setForThisRule = classSets[ 527*e1fe3e4aSElliott Hughes st.InputClassDef.classDefs[list(rule.glyphs[0])[0]] 528*e1fe3e4aSElliott Hughes ] 529*e1fe3e4aSElliott Hughes else: 530*e1fe3e4aSElliott Hughes ruleAsSubtable.GlyphCount = len(rule.glyphs) 531*e1fe3e4aSElliott Hughes ruleAsSubtable.Class = [ # The spec calls this InputSequence 532*e1fe3e4aSElliott Hughes st.ClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:] 533*e1fe3e4aSElliott Hughes ] 534*e1fe3e4aSElliott Hughes setForThisRule = classSets[ 535*e1fe3e4aSElliott Hughes st.ClassDef.classDefs[list(rule.glyphs[0])[0]] 536*e1fe3e4aSElliott Hughes ] 537*e1fe3e4aSElliott Hughes 538*e1fe3e4aSElliott Hughes self.buildLookupList(rule, ruleAsSubtable) 539*e1fe3e4aSElliott Hughes coverage |= set(rule.glyphs[0]) 540*e1fe3e4aSElliott Hughes 541*e1fe3e4aSElliott Hughes getattr(setForThisRule, classRuleAttr).append(ruleAsSubtable) 542*e1fe3e4aSElliott Hughes setattr( 543*e1fe3e4aSElliott Hughes setForThisRule, 544*e1fe3e4aSElliott Hughes f"{classRuleAttr}Count", 545*e1fe3e4aSElliott Hughes getattr(setForThisRule, f"{classRuleAttr}Count") + 1, 546*e1fe3e4aSElliott Hughes ) 547*e1fe3e4aSElliott Hughes setattr(st, self.ruleSetAttr_(format=2, chaining=chaining), classSets) 548*e1fe3e4aSElliott Hughes setattr( 549*e1fe3e4aSElliott Hughes st, self.ruleSetAttr_(format=2, chaining=chaining) + "Count", len(classSets) 550*e1fe3e4aSElliott Hughes ) 551*e1fe3e4aSElliott Hughes st.Coverage = buildCoverage(coverage, self.glyphMap) 552*e1fe3e4aSElliott Hughes return st 553*e1fe3e4aSElliott Hughes 554*e1fe3e4aSElliott Hughes def buildFormat3Subtable(self, rule, chaining=True): 555*e1fe3e4aSElliott Hughes st = self.newSubtable_(chaining=chaining) 556*e1fe3e4aSElliott Hughes st.Format = 3 557*e1fe3e4aSElliott Hughes if chaining: 558*e1fe3e4aSElliott Hughes self.setBacktrackCoverage_(rule.prefix, st) 559*e1fe3e4aSElliott Hughes self.setLookAheadCoverage_(rule.suffix, st) 560*e1fe3e4aSElliott Hughes self.setInputCoverage_(rule.glyphs, st) 561*e1fe3e4aSElliott Hughes else: 562*e1fe3e4aSElliott Hughes self.setCoverage_(rule.glyphs, st) 563*e1fe3e4aSElliott Hughes self.buildLookupList(rule, st) 564*e1fe3e4aSElliott Hughes return st 565*e1fe3e4aSElliott Hughes 566*e1fe3e4aSElliott Hughes def buildLookupList(self, rule, st): 567*e1fe3e4aSElliott Hughes for sequenceIndex, lookupList in enumerate(rule.lookups): 568*e1fe3e4aSElliott Hughes if lookupList is not None: 569*e1fe3e4aSElliott Hughes if not isinstance(lookupList, list): 570*e1fe3e4aSElliott Hughes # Can happen with synthesised lookups 571*e1fe3e4aSElliott Hughes lookupList = [lookupList] 572*e1fe3e4aSElliott Hughes for l in lookupList: 573*e1fe3e4aSElliott Hughes if l.lookup_index is None: 574*e1fe3e4aSElliott Hughes if isinstance(self, ChainContextPosBuilder): 575*e1fe3e4aSElliott Hughes other = "substitution" 576*e1fe3e4aSElliott Hughes else: 577*e1fe3e4aSElliott Hughes other = "positioning" 578*e1fe3e4aSElliott Hughes raise OpenTypeLibError( 579*e1fe3e4aSElliott Hughes "Missing index of the specified " 580*e1fe3e4aSElliott Hughes f"lookup, might be a {other} lookup", 581*e1fe3e4aSElliott Hughes self.location, 582*e1fe3e4aSElliott Hughes ) 583*e1fe3e4aSElliott Hughes rec = self.newLookupRecord_(st) 584*e1fe3e4aSElliott Hughes rec.SequenceIndex = sequenceIndex 585*e1fe3e4aSElliott Hughes rec.LookupListIndex = l.lookup_index 586*e1fe3e4aSElliott Hughes 587*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 588*e1fe3e4aSElliott Hughes self.rules.append( 589*e1fe3e4aSElliott Hughes ChainContextualRule( 590*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 591*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 592*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 593*e1fe3e4aSElliott Hughes [self.SUBTABLE_BREAK_], 594*e1fe3e4aSElliott Hughes ) 595*e1fe3e4aSElliott Hughes ) 596*e1fe3e4aSElliott Hughes 597*e1fe3e4aSElliott Hughes def newSubtable_(self, chaining=True): 598*e1fe3e4aSElliott Hughes subtablename = f"Context{self.subtable_type}" 599*e1fe3e4aSElliott Hughes if chaining: 600*e1fe3e4aSElliott Hughes subtablename = "Chain" + subtablename 601*e1fe3e4aSElliott Hughes st = getattr(ot, subtablename)() # ot.ChainContextPos()/ot.ChainSubst()/etc. 602*e1fe3e4aSElliott Hughes setattr(st, f"{self.subtable_type}Count", 0) 603*e1fe3e4aSElliott Hughes setattr(st, f"{self.subtable_type}LookupRecord", []) 604*e1fe3e4aSElliott Hughes return st 605*e1fe3e4aSElliott Hughes 606*e1fe3e4aSElliott Hughes # Format 1 and format 2 GSUB5/GSUB6/GPOS7/GPOS8 rulesets and rules form a family: 607*e1fe3e4aSElliott Hughes # 608*e1fe3e4aSElliott Hughes # format 1 ruleset format 1 rule format 2 ruleset format 2 rule 609*e1fe3e4aSElliott Hughes # GSUB5 SubRuleSet SubRule SubClassSet SubClassRule 610*e1fe3e4aSElliott Hughes # GSUB6 ChainSubRuleSet ChainSubRule ChainSubClassSet ChainSubClassRule 611*e1fe3e4aSElliott Hughes # GPOS7 PosRuleSet PosRule PosClassSet PosClassRule 612*e1fe3e4aSElliott Hughes # GPOS8 ChainPosRuleSet ChainPosRule ChainPosClassSet ChainPosClassRule 613*e1fe3e4aSElliott Hughes # 614*e1fe3e4aSElliott Hughes # The following functions generate the attribute names and subtables according 615*e1fe3e4aSElliott Hughes # to this naming convention. 616*e1fe3e4aSElliott Hughes def ruleSetAttr_(self, format=1, chaining=True): 617*e1fe3e4aSElliott Hughes if format == 1: 618*e1fe3e4aSElliott Hughes formatType = "Rule" 619*e1fe3e4aSElliott Hughes elif format == 2: 620*e1fe3e4aSElliott Hughes formatType = "Class" 621*e1fe3e4aSElliott Hughes else: 622*e1fe3e4aSElliott Hughes raise AssertionError(formatType) 623*e1fe3e4aSElliott Hughes subtablename = f"{self.subtable_type[0:3]}{formatType}Set" # Sub, not Subst. 624*e1fe3e4aSElliott Hughes if chaining: 625*e1fe3e4aSElliott Hughes subtablename = "Chain" + subtablename 626*e1fe3e4aSElliott Hughes return subtablename 627*e1fe3e4aSElliott Hughes 628*e1fe3e4aSElliott Hughes def ruleAttr_(self, format=1, chaining=True): 629*e1fe3e4aSElliott Hughes if format == 1: 630*e1fe3e4aSElliott Hughes formatType = "" 631*e1fe3e4aSElliott Hughes elif format == 2: 632*e1fe3e4aSElliott Hughes formatType = "Class" 633*e1fe3e4aSElliott Hughes else: 634*e1fe3e4aSElliott Hughes raise AssertionError(formatType) 635*e1fe3e4aSElliott Hughes subtablename = f"{self.subtable_type[0:3]}{formatType}Rule" # Sub, not Subst. 636*e1fe3e4aSElliott Hughes if chaining: 637*e1fe3e4aSElliott Hughes subtablename = "Chain" + subtablename 638*e1fe3e4aSElliott Hughes return subtablename 639*e1fe3e4aSElliott Hughes 640*e1fe3e4aSElliott Hughes def newRuleSet_(self, format=1, chaining=True): 641*e1fe3e4aSElliott Hughes st = getattr( 642*e1fe3e4aSElliott Hughes ot, self.ruleSetAttr_(format, chaining) 643*e1fe3e4aSElliott Hughes )() # ot.ChainPosRuleSet()/ot.SubRuleSet()/etc. 644*e1fe3e4aSElliott Hughes st.populateDefaults() 645*e1fe3e4aSElliott Hughes return st 646*e1fe3e4aSElliott Hughes 647*e1fe3e4aSElliott Hughes def newRule_(self, format=1, chaining=True): 648*e1fe3e4aSElliott Hughes st = getattr( 649*e1fe3e4aSElliott Hughes ot, self.ruleAttr_(format, chaining) 650*e1fe3e4aSElliott Hughes )() # ot.ChainPosClassRule()/ot.SubClassRule()/etc. 651*e1fe3e4aSElliott Hughes st.populateDefaults() 652*e1fe3e4aSElliott Hughes return st 653*e1fe3e4aSElliott Hughes 654*e1fe3e4aSElliott Hughes def attachSubtableWithCount_( 655*e1fe3e4aSElliott Hughes self, st, subtable_name, count_name, existing=None, index=None, chaining=False 656*e1fe3e4aSElliott Hughes ): 657*e1fe3e4aSElliott Hughes if chaining: 658*e1fe3e4aSElliott Hughes subtable_name = "Chain" + subtable_name 659*e1fe3e4aSElliott Hughes count_name = "Chain" + count_name 660*e1fe3e4aSElliott Hughes 661*e1fe3e4aSElliott Hughes if not hasattr(st, count_name): 662*e1fe3e4aSElliott Hughes setattr(st, count_name, 0) 663*e1fe3e4aSElliott Hughes setattr(st, subtable_name, []) 664*e1fe3e4aSElliott Hughes 665*e1fe3e4aSElliott Hughes if existing: 666*e1fe3e4aSElliott Hughes new_subtable = existing 667*e1fe3e4aSElliott Hughes else: 668*e1fe3e4aSElliott Hughes # Create a new, empty subtable from otTables 669*e1fe3e4aSElliott Hughes new_subtable = getattr(ot, subtable_name)() 670*e1fe3e4aSElliott Hughes 671*e1fe3e4aSElliott Hughes setattr(st, count_name, getattr(st, count_name) + 1) 672*e1fe3e4aSElliott Hughes 673*e1fe3e4aSElliott Hughes if index: 674*e1fe3e4aSElliott Hughes getattr(st, subtable_name).insert(index, new_subtable) 675*e1fe3e4aSElliott Hughes else: 676*e1fe3e4aSElliott Hughes getattr(st, subtable_name).append(new_subtable) 677*e1fe3e4aSElliott Hughes 678*e1fe3e4aSElliott Hughes return new_subtable 679*e1fe3e4aSElliott Hughes 680*e1fe3e4aSElliott Hughes def newLookupRecord_(self, st): 681*e1fe3e4aSElliott Hughes return self.attachSubtableWithCount_( 682*e1fe3e4aSElliott Hughes st, 683*e1fe3e4aSElliott Hughes f"{self.subtable_type}LookupRecord", 684*e1fe3e4aSElliott Hughes f"{self.subtable_type}Count", 685*e1fe3e4aSElliott Hughes chaining=False, 686*e1fe3e4aSElliott Hughes ) # Oddly, it isn't ChainSubstLookupRecord 687*e1fe3e4aSElliott Hughes 688*e1fe3e4aSElliott Hughes 689*e1fe3e4aSElliott Hughesclass ChainContextPosBuilder(ChainContextualBuilder): 690*e1fe3e4aSElliott Hughes """Builds a Chained Contextual Positioning (GPOS8) lookup. 691*e1fe3e4aSElliott Hughes 692*e1fe3e4aSElliott Hughes Users are expected to manually add rules to the ``rules`` attribute after 693*e1fe3e4aSElliott Hughes the object has been initialized, e.g.:: 694*e1fe3e4aSElliott Hughes 695*e1fe3e4aSElliott Hughes # pos [A B] [C D] x' lookup lu1 y' z' lookup lu2 E; 696*e1fe3e4aSElliott Hughes 697*e1fe3e4aSElliott Hughes prefix = [ ["A", "B"], ["C", "D"] ] 698*e1fe3e4aSElliott Hughes suffix = [ ["E"] ] 699*e1fe3e4aSElliott Hughes glyphs = [ ["x"], ["y"], ["z"] ] 700*e1fe3e4aSElliott Hughes lookups = [ [lu1], None, [lu2] ] 701*e1fe3e4aSElliott Hughes builder.rules.append( (prefix, glyphs, suffix, lookups) ) 702*e1fe3e4aSElliott Hughes 703*e1fe3e4aSElliott Hughes Attributes: 704*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 705*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 706*e1fe3e4aSElliott Hughes source which produced this lookup. 707*e1fe3e4aSElliott Hughes rules: A list of tuples representing the rules in this lookup. 708*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 709*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 710*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 711*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 712*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 713*e1fe3e4aSElliott Hughes flags. 714*e1fe3e4aSElliott Hughes """ 715*e1fe3e4aSElliott Hughes 716*e1fe3e4aSElliott Hughes def __init__(self, font, location): 717*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 8) 718*e1fe3e4aSElliott Hughes self.rules = [] 719*e1fe3e4aSElliott Hughes self.subtable_type = "Pos" 720*e1fe3e4aSElliott Hughes 721*e1fe3e4aSElliott Hughes def find_chainable_single_pos(self, lookups, glyphs, value): 722*e1fe3e4aSElliott Hughes """Helper for add_single_pos_chained_()""" 723*e1fe3e4aSElliott Hughes res = None 724*e1fe3e4aSElliott Hughes for lookup in lookups[::-1]: 725*e1fe3e4aSElliott Hughes if lookup == self.SUBTABLE_BREAK_: 726*e1fe3e4aSElliott Hughes return res 727*e1fe3e4aSElliott Hughes if isinstance(lookup, SinglePosBuilder) and all( 728*e1fe3e4aSElliott Hughes lookup.can_add(glyph, value) for glyph in glyphs 729*e1fe3e4aSElliott Hughes ): 730*e1fe3e4aSElliott Hughes res = lookup 731*e1fe3e4aSElliott Hughes return res 732*e1fe3e4aSElliott Hughes 733*e1fe3e4aSElliott Hughes 734*e1fe3e4aSElliott Hughesclass ChainContextSubstBuilder(ChainContextualBuilder): 735*e1fe3e4aSElliott Hughes """Builds a Chained Contextual Substitution (GSUB6) lookup. 736*e1fe3e4aSElliott Hughes 737*e1fe3e4aSElliott Hughes Users are expected to manually add rules to the ``rules`` attribute after 738*e1fe3e4aSElliott Hughes the object has been initialized, e.g.:: 739*e1fe3e4aSElliott Hughes 740*e1fe3e4aSElliott Hughes # sub [A B] [C D] x' lookup lu1 y' z' lookup lu2 E; 741*e1fe3e4aSElliott Hughes 742*e1fe3e4aSElliott Hughes prefix = [ ["A", "B"], ["C", "D"] ] 743*e1fe3e4aSElliott Hughes suffix = [ ["E"] ] 744*e1fe3e4aSElliott Hughes glyphs = [ ["x"], ["y"], ["z"] ] 745*e1fe3e4aSElliott Hughes lookups = [ [lu1], None, [lu2] ] 746*e1fe3e4aSElliott Hughes builder.rules.append( (prefix, glyphs, suffix, lookups) ) 747*e1fe3e4aSElliott Hughes 748*e1fe3e4aSElliott Hughes Attributes: 749*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 750*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 751*e1fe3e4aSElliott Hughes source which produced this lookup. 752*e1fe3e4aSElliott Hughes rules: A list of tuples representing the rules in this lookup. 753*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 754*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 755*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 756*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 757*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 758*e1fe3e4aSElliott Hughes flags. 759*e1fe3e4aSElliott Hughes """ 760*e1fe3e4aSElliott Hughes 761*e1fe3e4aSElliott Hughes def __init__(self, font, location): 762*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GSUB", 6) 763*e1fe3e4aSElliott Hughes self.rules = [] # (prefix, input, suffix, lookups) 764*e1fe3e4aSElliott Hughes self.subtable_type = "Subst" 765*e1fe3e4aSElliott Hughes 766*e1fe3e4aSElliott Hughes def getAlternateGlyphs(self): 767*e1fe3e4aSElliott Hughes result = {} 768*e1fe3e4aSElliott Hughes for rule in self.rules: 769*e1fe3e4aSElliott Hughes if rule.is_subtable_break: 770*e1fe3e4aSElliott Hughes continue 771*e1fe3e4aSElliott Hughes for lookups in rule.lookups: 772*e1fe3e4aSElliott Hughes if not isinstance(lookups, list): 773*e1fe3e4aSElliott Hughes lookups = [lookups] 774*e1fe3e4aSElliott Hughes for lookup in lookups: 775*e1fe3e4aSElliott Hughes if lookup is not None: 776*e1fe3e4aSElliott Hughes alts = lookup.getAlternateGlyphs() 777*e1fe3e4aSElliott Hughes for glyph, replacements in alts.items(): 778*e1fe3e4aSElliott Hughes alts_for_glyph = result.setdefault(glyph, []) 779*e1fe3e4aSElliott Hughes alts_for_glyph.extend( 780*e1fe3e4aSElliott Hughes g for g in replacements if g not in alts_for_glyph 781*e1fe3e4aSElliott Hughes ) 782*e1fe3e4aSElliott Hughes return result 783*e1fe3e4aSElliott Hughes 784*e1fe3e4aSElliott Hughes def find_chainable_single_subst(self, mapping): 785*e1fe3e4aSElliott Hughes """Helper for add_single_subst_chained_()""" 786*e1fe3e4aSElliott Hughes res = None 787*e1fe3e4aSElliott Hughes for rule in self.rules[::-1]: 788*e1fe3e4aSElliott Hughes if rule.is_subtable_break: 789*e1fe3e4aSElliott Hughes return res 790*e1fe3e4aSElliott Hughes for sub in rule.lookups: 791*e1fe3e4aSElliott Hughes if isinstance(sub, SingleSubstBuilder) and not any( 792*e1fe3e4aSElliott Hughes g in mapping and mapping[g] != sub.mapping[g] for g in sub.mapping 793*e1fe3e4aSElliott Hughes ): 794*e1fe3e4aSElliott Hughes res = sub 795*e1fe3e4aSElliott Hughes return res 796*e1fe3e4aSElliott Hughes 797*e1fe3e4aSElliott Hughes 798*e1fe3e4aSElliott Hughesclass LigatureSubstBuilder(LookupBuilder): 799*e1fe3e4aSElliott Hughes """Builds a Ligature Substitution (GSUB4) lookup. 800*e1fe3e4aSElliott Hughes 801*e1fe3e4aSElliott Hughes Users are expected to manually add ligatures to the ``ligatures`` 802*e1fe3e4aSElliott Hughes attribute after the object has been initialized, e.g.:: 803*e1fe3e4aSElliott Hughes 804*e1fe3e4aSElliott Hughes # sub f i by f_i; 805*e1fe3e4aSElliott Hughes builder.ligatures[("f","f","i")] = "f_f_i" 806*e1fe3e4aSElliott Hughes 807*e1fe3e4aSElliott Hughes Attributes: 808*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 809*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 810*e1fe3e4aSElliott Hughes source which produced this lookup. 811*e1fe3e4aSElliott Hughes ligatures: An ordered dictionary mapping a tuple of glyph names to the 812*e1fe3e4aSElliott Hughes ligature glyphname. 813*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 814*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 815*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 816*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 817*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 818*e1fe3e4aSElliott Hughes flags. 819*e1fe3e4aSElliott Hughes """ 820*e1fe3e4aSElliott Hughes 821*e1fe3e4aSElliott Hughes def __init__(self, font, location): 822*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GSUB", 4) 823*e1fe3e4aSElliott Hughes self.ligatures = OrderedDict() # {('f','f','i'): 'f_f_i'} 824*e1fe3e4aSElliott Hughes 825*e1fe3e4aSElliott Hughes def equals(self, other): 826*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.ligatures == other.ligatures 827*e1fe3e4aSElliott Hughes 828*e1fe3e4aSElliott Hughes def build(self): 829*e1fe3e4aSElliott Hughes """Build the lookup. 830*e1fe3e4aSElliott Hughes 831*e1fe3e4aSElliott Hughes Returns: 832*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the ligature 833*e1fe3e4aSElliott Hughes substitution lookup. 834*e1fe3e4aSElliott Hughes """ 835*e1fe3e4aSElliott Hughes subtables = self.build_subst_subtables( 836*e1fe3e4aSElliott Hughes self.ligatures, buildLigatureSubstSubtable 837*e1fe3e4aSElliott Hughes ) 838*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 839*e1fe3e4aSElliott Hughes 840*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 841*e1fe3e4aSElliott Hughes self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ 842*e1fe3e4aSElliott Hughes 843*e1fe3e4aSElliott Hughes 844*e1fe3e4aSElliott Hughesclass MultipleSubstBuilder(LookupBuilder): 845*e1fe3e4aSElliott Hughes """Builds a Multiple Substitution (GSUB2) lookup. 846*e1fe3e4aSElliott Hughes 847*e1fe3e4aSElliott Hughes Users are expected to manually add substitutions to the ``mapping`` 848*e1fe3e4aSElliott Hughes attribute after the object has been initialized, e.g.:: 849*e1fe3e4aSElliott Hughes 850*e1fe3e4aSElliott Hughes # sub uni06C0 by uni06D5.fina hamza.above; 851*e1fe3e4aSElliott Hughes builder.mapping["uni06C0"] = [ "uni06D5.fina", "hamza.above"] 852*e1fe3e4aSElliott Hughes 853*e1fe3e4aSElliott Hughes Attributes: 854*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 855*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 856*e1fe3e4aSElliott Hughes source which produced this lookup. 857*e1fe3e4aSElliott Hughes mapping: An ordered dictionary mapping a glyph name to a list of 858*e1fe3e4aSElliott Hughes substituted glyph names. 859*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 860*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 861*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 862*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 863*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 864*e1fe3e4aSElliott Hughes flags. 865*e1fe3e4aSElliott Hughes """ 866*e1fe3e4aSElliott Hughes 867*e1fe3e4aSElliott Hughes def __init__(self, font, location): 868*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GSUB", 2) 869*e1fe3e4aSElliott Hughes self.mapping = OrderedDict() 870*e1fe3e4aSElliott Hughes 871*e1fe3e4aSElliott Hughes def equals(self, other): 872*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.mapping == other.mapping 873*e1fe3e4aSElliott Hughes 874*e1fe3e4aSElliott Hughes def build(self): 875*e1fe3e4aSElliott Hughes subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable) 876*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 877*e1fe3e4aSElliott Hughes 878*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 879*e1fe3e4aSElliott Hughes self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ 880*e1fe3e4aSElliott Hughes 881*e1fe3e4aSElliott Hughes 882*e1fe3e4aSElliott Hughesclass CursivePosBuilder(LookupBuilder): 883*e1fe3e4aSElliott Hughes """Builds a Cursive Positioning (GPOS3) lookup. 884*e1fe3e4aSElliott Hughes 885*e1fe3e4aSElliott Hughes Attributes: 886*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 887*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 888*e1fe3e4aSElliott Hughes source which produced this lookup. 889*e1fe3e4aSElliott Hughes attachments: An ordered dictionary mapping a glyph name to a two-element 890*e1fe3e4aSElliott Hughes tuple of ``otTables.Anchor`` objects. 891*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 892*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 893*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 894*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 895*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 896*e1fe3e4aSElliott Hughes flags. 897*e1fe3e4aSElliott Hughes """ 898*e1fe3e4aSElliott Hughes 899*e1fe3e4aSElliott Hughes def __init__(self, font, location): 900*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 3) 901*e1fe3e4aSElliott Hughes self.attachments = {} 902*e1fe3e4aSElliott Hughes 903*e1fe3e4aSElliott Hughes def equals(self, other): 904*e1fe3e4aSElliott Hughes return ( 905*e1fe3e4aSElliott Hughes LookupBuilder.equals(self, other) and self.attachments == other.attachments 906*e1fe3e4aSElliott Hughes ) 907*e1fe3e4aSElliott Hughes 908*e1fe3e4aSElliott Hughes def add_attachment(self, location, glyphs, entryAnchor, exitAnchor): 909*e1fe3e4aSElliott Hughes """Adds attachment information to the cursive positioning lookup. 910*e1fe3e4aSElliott Hughes 911*e1fe3e4aSElliott Hughes Args: 912*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the 913*e1fe3e4aSElliott Hughes original source which produced this lookup. (Unused.) 914*e1fe3e4aSElliott Hughes glyphs: A list of glyph names sharing these entry and exit 915*e1fe3e4aSElliott Hughes anchor locations. 916*e1fe3e4aSElliott Hughes entryAnchor: A ``otTables.Anchor`` object representing the 917*e1fe3e4aSElliott Hughes entry anchor, or ``None`` if no entry anchor is present. 918*e1fe3e4aSElliott Hughes exitAnchor: A ``otTables.Anchor`` object representing the 919*e1fe3e4aSElliott Hughes exit anchor, or ``None`` if no exit anchor is present. 920*e1fe3e4aSElliott Hughes """ 921*e1fe3e4aSElliott Hughes for glyph in glyphs: 922*e1fe3e4aSElliott Hughes self.attachments[glyph] = (entryAnchor, exitAnchor) 923*e1fe3e4aSElliott Hughes 924*e1fe3e4aSElliott Hughes def build(self): 925*e1fe3e4aSElliott Hughes """Build the lookup. 926*e1fe3e4aSElliott Hughes 927*e1fe3e4aSElliott Hughes Returns: 928*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the cursive 929*e1fe3e4aSElliott Hughes positioning lookup. 930*e1fe3e4aSElliott Hughes """ 931*e1fe3e4aSElliott Hughes st = buildCursivePosSubtable(self.attachments, self.glyphMap) 932*e1fe3e4aSElliott Hughes return self.buildLookup_([st]) 933*e1fe3e4aSElliott Hughes 934*e1fe3e4aSElliott Hughes 935*e1fe3e4aSElliott Hughesclass MarkBasePosBuilder(LookupBuilder): 936*e1fe3e4aSElliott Hughes """Builds a Mark-To-Base Positioning (GPOS4) lookup. 937*e1fe3e4aSElliott Hughes 938*e1fe3e4aSElliott Hughes Users are expected to manually add marks and bases to the ``marks`` 939*e1fe3e4aSElliott Hughes and ``bases`` attributes after the object has been initialized, e.g.:: 940*e1fe3e4aSElliott Hughes 941*e1fe3e4aSElliott Hughes builder.marks["acute"] = (0, a1) 942*e1fe3e4aSElliott Hughes builder.marks["grave"] = (0, a1) 943*e1fe3e4aSElliott Hughes builder.marks["cedilla"] = (1, a2) 944*e1fe3e4aSElliott Hughes builder.bases["a"] = {0: a3, 1: a5} 945*e1fe3e4aSElliott Hughes builder.bases["b"] = {0: a4, 1: a5} 946*e1fe3e4aSElliott Hughes 947*e1fe3e4aSElliott Hughes Attributes: 948*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 949*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 950*e1fe3e4aSElliott Hughes source which produced this lookup. 951*e1fe3e4aSElliott Hughes marks: An dictionary mapping a glyph name to a two-element 952*e1fe3e4aSElliott Hughes tuple containing a mark class ID and ``otTables.Anchor`` object. 953*e1fe3e4aSElliott Hughes bases: An dictionary mapping a glyph name to a dictionary of 954*e1fe3e4aSElliott Hughes mark class IDs and ``otTables.Anchor`` object. 955*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 956*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 957*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 958*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 959*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 960*e1fe3e4aSElliott Hughes flags. 961*e1fe3e4aSElliott Hughes """ 962*e1fe3e4aSElliott Hughes 963*e1fe3e4aSElliott Hughes def __init__(self, font, location): 964*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 4) 965*e1fe3e4aSElliott Hughes self.marks = {} # glyphName -> (markClassName, anchor) 966*e1fe3e4aSElliott Hughes self.bases = {} # glyphName -> {markClassName: anchor} 967*e1fe3e4aSElliott Hughes 968*e1fe3e4aSElliott Hughes def equals(self, other): 969*e1fe3e4aSElliott Hughes return ( 970*e1fe3e4aSElliott Hughes LookupBuilder.equals(self, other) 971*e1fe3e4aSElliott Hughes and self.marks == other.marks 972*e1fe3e4aSElliott Hughes and self.bases == other.bases 973*e1fe3e4aSElliott Hughes ) 974*e1fe3e4aSElliott Hughes 975*e1fe3e4aSElliott Hughes def inferGlyphClasses(self): 976*e1fe3e4aSElliott Hughes result = {glyph: 1 for glyph in self.bases} 977*e1fe3e4aSElliott Hughes result.update({glyph: 3 for glyph in self.marks}) 978*e1fe3e4aSElliott Hughes return result 979*e1fe3e4aSElliott Hughes 980*e1fe3e4aSElliott Hughes def build(self): 981*e1fe3e4aSElliott Hughes """Build the lookup. 982*e1fe3e4aSElliott Hughes 983*e1fe3e4aSElliott Hughes Returns: 984*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the mark-to-base 985*e1fe3e4aSElliott Hughes positioning lookup. 986*e1fe3e4aSElliott Hughes """ 987*e1fe3e4aSElliott Hughes markClasses = self.buildMarkClasses_(self.marks) 988*e1fe3e4aSElliott Hughes marks = {} 989*e1fe3e4aSElliott Hughes for mark, (mc, anchor) in self.marks.items(): 990*e1fe3e4aSElliott Hughes if mc not in markClasses: 991*e1fe3e4aSElliott Hughes raise ValueError( 992*e1fe3e4aSElliott Hughes "Mark class %s not found for mark glyph %s" % (mc, mark) 993*e1fe3e4aSElliott Hughes ) 994*e1fe3e4aSElliott Hughes marks[mark] = (markClasses[mc], anchor) 995*e1fe3e4aSElliott Hughes bases = {} 996*e1fe3e4aSElliott Hughes for glyph, anchors in self.bases.items(): 997*e1fe3e4aSElliott Hughes bases[glyph] = {} 998*e1fe3e4aSElliott Hughes for mc, anchor in anchors.items(): 999*e1fe3e4aSElliott Hughes if mc not in markClasses: 1000*e1fe3e4aSElliott Hughes raise ValueError( 1001*e1fe3e4aSElliott Hughes "Mark class %s not found for base glyph %s" % (mc, glyph) 1002*e1fe3e4aSElliott Hughes ) 1003*e1fe3e4aSElliott Hughes bases[glyph][markClasses[mc]] = anchor 1004*e1fe3e4aSElliott Hughes subtables = buildMarkBasePos(marks, bases, self.glyphMap) 1005*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 1006*e1fe3e4aSElliott Hughes 1007*e1fe3e4aSElliott Hughes 1008*e1fe3e4aSElliott Hughesclass MarkLigPosBuilder(LookupBuilder): 1009*e1fe3e4aSElliott Hughes """Builds a Mark-To-Ligature Positioning (GPOS5) lookup. 1010*e1fe3e4aSElliott Hughes 1011*e1fe3e4aSElliott Hughes Users are expected to manually add marks and bases to the ``marks`` 1012*e1fe3e4aSElliott Hughes and ``ligatures`` attributes after the object has been initialized, e.g.:: 1013*e1fe3e4aSElliott Hughes 1014*e1fe3e4aSElliott Hughes builder.marks["acute"] = (0, a1) 1015*e1fe3e4aSElliott Hughes builder.marks["grave"] = (0, a1) 1016*e1fe3e4aSElliott Hughes builder.marks["cedilla"] = (1, a2) 1017*e1fe3e4aSElliott Hughes builder.ligatures["f_i"] = [ 1018*e1fe3e4aSElliott Hughes { 0: a3, 1: a5 }, # f 1019*e1fe3e4aSElliott Hughes { 0: a4, 1: a5 } # i 1020*e1fe3e4aSElliott Hughes ] 1021*e1fe3e4aSElliott Hughes 1022*e1fe3e4aSElliott Hughes Attributes: 1023*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 1024*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 1025*e1fe3e4aSElliott Hughes source which produced this lookup. 1026*e1fe3e4aSElliott Hughes marks: An dictionary mapping a glyph name to a two-element 1027*e1fe3e4aSElliott Hughes tuple containing a mark class ID and ``otTables.Anchor`` object. 1028*e1fe3e4aSElliott Hughes ligatures: An dictionary mapping a glyph name to an array with one 1029*e1fe3e4aSElliott Hughes element for each ligature component. Each array element should be 1030*e1fe3e4aSElliott Hughes a dictionary mapping mark class IDs to ``otTables.Anchor`` objects. 1031*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 1032*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 1033*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 1034*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 1035*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 1036*e1fe3e4aSElliott Hughes flags. 1037*e1fe3e4aSElliott Hughes """ 1038*e1fe3e4aSElliott Hughes 1039*e1fe3e4aSElliott Hughes def __init__(self, font, location): 1040*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 5) 1041*e1fe3e4aSElliott Hughes self.marks = {} # glyphName -> (markClassName, anchor) 1042*e1fe3e4aSElliott Hughes self.ligatures = {} # glyphName -> [{markClassName: anchor}, ...] 1043*e1fe3e4aSElliott Hughes 1044*e1fe3e4aSElliott Hughes def equals(self, other): 1045*e1fe3e4aSElliott Hughes return ( 1046*e1fe3e4aSElliott Hughes LookupBuilder.equals(self, other) 1047*e1fe3e4aSElliott Hughes and self.marks == other.marks 1048*e1fe3e4aSElliott Hughes and self.ligatures == other.ligatures 1049*e1fe3e4aSElliott Hughes ) 1050*e1fe3e4aSElliott Hughes 1051*e1fe3e4aSElliott Hughes def inferGlyphClasses(self): 1052*e1fe3e4aSElliott Hughes result = {glyph: 2 for glyph in self.ligatures} 1053*e1fe3e4aSElliott Hughes result.update({glyph: 3 for glyph in self.marks}) 1054*e1fe3e4aSElliott Hughes return result 1055*e1fe3e4aSElliott Hughes 1056*e1fe3e4aSElliott Hughes def build(self): 1057*e1fe3e4aSElliott Hughes """Build the lookup. 1058*e1fe3e4aSElliott Hughes 1059*e1fe3e4aSElliott Hughes Returns: 1060*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the mark-to-ligature 1061*e1fe3e4aSElliott Hughes positioning lookup. 1062*e1fe3e4aSElliott Hughes """ 1063*e1fe3e4aSElliott Hughes markClasses = self.buildMarkClasses_(self.marks) 1064*e1fe3e4aSElliott Hughes marks = { 1065*e1fe3e4aSElliott Hughes mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items() 1066*e1fe3e4aSElliott Hughes } 1067*e1fe3e4aSElliott Hughes ligs = {} 1068*e1fe3e4aSElliott Hughes for lig, components in self.ligatures.items(): 1069*e1fe3e4aSElliott Hughes ligs[lig] = [] 1070*e1fe3e4aSElliott Hughes for c in components: 1071*e1fe3e4aSElliott Hughes ligs[lig].append({markClasses[mc]: a for mc, a in c.items()}) 1072*e1fe3e4aSElliott Hughes subtables = buildMarkLigPos(marks, ligs, self.glyphMap) 1073*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 1074*e1fe3e4aSElliott Hughes 1075*e1fe3e4aSElliott Hughes 1076*e1fe3e4aSElliott Hughesclass MarkMarkPosBuilder(LookupBuilder): 1077*e1fe3e4aSElliott Hughes """Builds a Mark-To-Mark Positioning (GPOS6) lookup. 1078*e1fe3e4aSElliott Hughes 1079*e1fe3e4aSElliott Hughes Users are expected to manually add marks and bases to the ``marks`` 1080*e1fe3e4aSElliott Hughes and ``baseMarks`` attributes after the object has been initialized, e.g.:: 1081*e1fe3e4aSElliott Hughes 1082*e1fe3e4aSElliott Hughes builder.marks["acute"] = (0, a1) 1083*e1fe3e4aSElliott Hughes builder.marks["grave"] = (0, a1) 1084*e1fe3e4aSElliott Hughes builder.marks["cedilla"] = (1, a2) 1085*e1fe3e4aSElliott Hughes builder.baseMarks["acute"] = {0: a3} 1086*e1fe3e4aSElliott Hughes 1087*e1fe3e4aSElliott Hughes Attributes: 1088*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 1089*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 1090*e1fe3e4aSElliott Hughes source which produced this lookup. 1091*e1fe3e4aSElliott Hughes marks: An dictionary mapping a glyph name to a two-element 1092*e1fe3e4aSElliott Hughes tuple containing a mark class ID and ``otTables.Anchor`` object. 1093*e1fe3e4aSElliott Hughes baseMarks: An dictionary mapping a glyph name to a dictionary 1094*e1fe3e4aSElliott Hughes containing one item: a mark class ID and a ``otTables.Anchor`` object. 1095*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 1096*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 1097*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 1098*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 1099*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 1100*e1fe3e4aSElliott Hughes flags. 1101*e1fe3e4aSElliott Hughes """ 1102*e1fe3e4aSElliott Hughes 1103*e1fe3e4aSElliott Hughes def __init__(self, font, location): 1104*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 6) 1105*e1fe3e4aSElliott Hughes self.marks = {} # glyphName -> (markClassName, anchor) 1106*e1fe3e4aSElliott Hughes self.baseMarks = {} # glyphName -> {markClassName: anchor} 1107*e1fe3e4aSElliott Hughes 1108*e1fe3e4aSElliott Hughes def equals(self, other): 1109*e1fe3e4aSElliott Hughes return ( 1110*e1fe3e4aSElliott Hughes LookupBuilder.equals(self, other) 1111*e1fe3e4aSElliott Hughes and self.marks == other.marks 1112*e1fe3e4aSElliott Hughes and self.baseMarks == other.baseMarks 1113*e1fe3e4aSElliott Hughes ) 1114*e1fe3e4aSElliott Hughes 1115*e1fe3e4aSElliott Hughes def inferGlyphClasses(self): 1116*e1fe3e4aSElliott Hughes result = {glyph: 3 for glyph in self.baseMarks} 1117*e1fe3e4aSElliott Hughes result.update({glyph: 3 for glyph in self.marks}) 1118*e1fe3e4aSElliott Hughes return result 1119*e1fe3e4aSElliott Hughes 1120*e1fe3e4aSElliott Hughes def build(self): 1121*e1fe3e4aSElliott Hughes """Build the lookup. 1122*e1fe3e4aSElliott Hughes 1123*e1fe3e4aSElliott Hughes Returns: 1124*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the mark-to-mark 1125*e1fe3e4aSElliott Hughes positioning lookup. 1126*e1fe3e4aSElliott Hughes """ 1127*e1fe3e4aSElliott Hughes markClasses = self.buildMarkClasses_(self.marks) 1128*e1fe3e4aSElliott Hughes markClassList = sorted(markClasses.keys(), key=markClasses.get) 1129*e1fe3e4aSElliott Hughes marks = { 1130*e1fe3e4aSElliott Hughes mark: (markClasses[mc], anchor) for mark, (mc, anchor) in self.marks.items() 1131*e1fe3e4aSElliott Hughes } 1132*e1fe3e4aSElliott Hughes 1133*e1fe3e4aSElliott Hughes st = ot.MarkMarkPos() 1134*e1fe3e4aSElliott Hughes st.Format = 1 1135*e1fe3e4aSElliott Hughes st.ClassCount = len(markClasses) 1136*e1fe3e4aSElliott Hughes st.Mark1Coverage = buildCoverage(marks, self.glyphMap) 1137*e1fe3e4aSElliott Hughes st.Mark2Coverage = buildCoverage(self.baseMarks, self.glyphMap) 1138*e1fe3e4aSElliott Hughes st.Mark1Array = buildMarkArray(marks, self.glyphMap) 1139*e1fe3e4aSElliott Hughes st.Mark2Array = ot.Mark2Array() 1140*e1fe3e4aSElliott Hughes st.Mark2Array.Mark2Count = len(st.Mark2Coverage.glyphs) 1141*e1fe3e4aSElliott Hughes st.Mark2Array.Mark2Record = [] 1142*e1fe3e4aSElliott Hughes for base in st.Mark2Coverage.glyphs: 1143*e1fe3e4aSElliott Hughes anchors = [self.baseMarks[base].get(mc) for mc in markClassList] 1144*e1fe3e4aSElliott Hughes st.Mark2Array.Mark2Record.append(buildMark2Record(anchors)) 1145*e1fe3e4aSElliott Hughes return self.buildLookup_([st]) 1146*e1fe3e4aSElliott Hughes 1147*e1fe3e4aSElliott Hughes 1148*e1fe3e4aSElliott Hughesclass ReverseChainSingleSubstBuilder(LookupBuilder): 1149*e1fe3e4aSElliott Hughes """Builds a Reverse Chaining Contextual Single Substitution (GSUB8) lookup. 1150*e1fe3e4aSElliott Hughes 1151*e1fe3e4aSElliott Hughes Users are expected to manually add substitutions to the ``substitutions`` 1152*e1fe3e4aSElliott Hughes attribute after the object has been initialized, e.g.:: 1153*e1fe3e4aSElliott Hughes 1154*e1fe3e4aSElliott Hughes # reversesub [a e n] d' by d.alt; 1155*e1fe3e4aSElliott Hughes prefix = [ ["a", "e", "n"] ] 1156*e1fe3e4aSElliott Hughes suffix = [] 1157*e1fe3e4aSElliott Hughes mapping = { "d": "d.alt" } 1158*e1fe3e4aSElliott Hughes builder.substitutions.append( (prefix, suffix, mapping) ) 1159*e1fe3e4aSElliott Hughes 1160*e1fe3e4aSElliott Hughes Attributes: 1161*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 1162*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 1163*e1fe3e4aSElliott Hughes source which produced this lookup. 1164*e1fe3e4aSElliott Hughes substitutions: A three-element tuple consisting of a prefix sequence, 1165*e1fe3e4aSElliott Hughes a suffix sequence, and a dictionary of single substitutions. 1166*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 1167*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 1168*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 1169*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 1170*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 1171*e1fe3e4aSElliott Hughes flags. 1172*e1fe3e4aSElliott Hughes """ 1173*e1fe3e4aSElliott Hughes 1174*e1fe3e4aSElliott Hughes def __init__(self, font, location): 1175*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GSUB", 8) 1176*e1fe3e4aSElliott Hughes self.rules = [] # (prefix, suffix, mapping) 1177*e1fe3e4aSElliott Hughes 1178*e1fe3e4aSElliott Hughes def equals(self, other): 1179*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.rules == other.rules 1180*e1fe3e4aSElliott Hughes 1181*e1fe3e4aSElliott Hughes def build(self): 1182*e1fe3e4aSElliott Hughes """Build the lookup. 1183*e1fe3e4aSElliott Hughes 1184*e1fe3e4aSElliott Hughes Returns: 1185*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the chained 1186*e1fe3e4aSElliott Hughes contextual substitution lookup. 1187*e1fe3e4aSElliott Hughes """ 1188*e1fe3e4aSElliott Hughes subtables = [] 1189*e1fe3e4aSElliott Hughes for prefix, suffix, mapping in self.rules: 1190*e1fe3e4aSElliott Hughes st = ot.ReverseChainSingleSubst() 1191*e1fe3e4aSElliott Hughes st.Format = 1 1192*e1fe3e4aSElliott Hughes self.setBacktrackCoverage_(prefix, st) 1193*e1fe3e4aSElliott Hughes self.setLookAheadCoverage_(suffix, st) 1194*e1fe3e4aSElliott Hughes st.Coverage = buildCoverage(mapping.keys(), self.glyphMap) 1195*e1fe3e4aSElliott Hughes st.GlyphCount = len(mapping) 1196*e1fe3e4aSElliott Hughes st.Substitute = [mapping[g] for g in st.Coverage.glyphs] 1197*e1fe3e4aSElliott Hughes subtables.append(st) 1198*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 1199*e1fe3e4aSElliott Hughes 1200*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 1201*e1fe3e4aSElliott Hughes # Nothing to do here, each substitution is in its own subtable. 1202*e1fe3e4aSElliott Hughes pass 1203*e1fe3e4aSElliott Hughes 1204*e1fe3e4aSElliott Hughes 1205*e1fe3e4aSElliott Hughesclass SingleSubstBuilder(LookupBuilder): 1206*e1fe3e4aSElliott Hughes """Builds a Single Substitution (GSUB1) lookup. 1207*e1fe3e4aSElliott Hughes 1208*e1fe3e4aSElliott Hughes Users are expected to manually add substitutions to the ``mapping`` 1209*e1fe3e4aSElliott Hughes attribute after the object has been initialized, e.g.:: 1210*e1fe3e4aSElliott Hughes 1211*e1fe3e4aSElliott Hughes # sub x by y; 1212*e1fe3e4aSElliott Hughes builder.mapping["x"] = "y" 1213*e1fe3e4aSElliott Hughes 1214*e1fe3e4aSElliott Hughes Attributes: 1215*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 1216*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 1217*e1fe3e4aSElliott Hughes source which produced this lookup. 1218*e1fe3e4aSElliott Hughes mapping: A dictionary mapping a single glyph name to another glyph name. 1219*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 1220*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 1221*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 1222*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 1223*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 1224*e1fe3e4aSElliott Hughes flags. 1225*e1fe3e4aSElliott Hughes """ 1226*e1fe3e4aSElliott Hughes 1227*e1fe3e4aSElliott Hughes def __init__(self, font, location): 1228*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GSUB", 1) 1229*e1fe3e4aSElliott Hughes self.mapping = OrderedDict() 1230*e1fe3e4aSElliott Hughes 1231*e1fe3e4aSElliott Hughes def equals(self, other): 1232*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.mapping == other.mapping 1233*e1fe3e4aSElliott Hughes 1234*e1fe3e4aSElliott Hughes def build(self): 1235*e1fe3e4aSElliott Hughes """Build the lookup. 1236*e1fe3e4aSElliott Hughes 1237*e1fe3e4aSElliott Hughes Returns: 1238*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the multiple 1239*e1fe3e4aSElliott Hughes substitution lookup. 1240*e1fe3e4aSElliott Hughes """ 1241*e1fe3e4aSElliott Hughes subtables = self.build_subst_subtables(self.mapping, buildSingleSubstSubtable) 1242*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 1243*e1fe3e4aSElliott Hughes 1244*e1fe3e4aSElliott Hughes def getAlternateGlyphs(self): 1245*e1fe3e4aSElliott Hughes return {glyph: [repl] for glyph, repl in self.mapping.items()} 1246*e1fe3e4aSElliott Hughes 1247*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 1248*e1fe3e4aSElliott Hughes self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ 1249*e1fe3e4aSElliott Hughes 1250*e1fe3e4aSElliott Hughes 1251*e1fe3e4aSElliott Hughesclass ClassPairPosSubtableBuilder(object): 1252*e1fe3e4aSElliott Hughes """Builds class-based Pair Positioning (GPOS2 format 2) subtables. 1253*e1fe3e4aSElliott Hughes 1254*e1fe3e4aSElliott Hughes Note that this does *not* build a GPOS2 ``otTables.Lookup`` directly, 1255*e1fe3e4aSElliott Hughes but builds a list of ``otTables.PairPos`` subtables. It is used by the 1256*e1fe3e4aSElliott Hughes :class:`PairPosBuilder` below. 1257*e1fe3e4aSElliott Hughes 1258*e1fe3e4aSElliott Hughes Attributes: 1259*e1fe3e4aSElliott Hughes builder (PairPosBuilder): A pair positioning lookup builder. 1260*e1fe3e4aSElliott Hughes """ 1261*e1fe3e4aSElliott Hughes 1262*e1fe3e4aSElliott Hughes def __init__(self, builder): 1263*e1fe3e4aSElliott Hughes self.builder_ = builder 1264*e1fe3e4aSElliott Hughes self.classDef1_, self.classDef2_ = None, None 1265*e1fe3e4aSElliott Hughes self.values_ = {} # (glyphclass1, glyphclass2) --> (value1, value2) 1266*e1fe3e4aSElliott Hughes self.forceSubtableBreak_ = False 1267*e1fe3e4aSElliott Hughes self.subtables_ = [] 1268*e1fe3e4aSElliott Hughes 1269*e1fe3e4aSElliott Hughes def addPair(self, gc1, value1, gc2, value2): 1270*e1fe3e4aSElliott Hughes """Add a pair positioning rule. 1271*e1fe3e4aSElliott Hughes 1272*e1fe3e4aSElliott Hughes Args: 1273*e1fe3e4aSElliott Hughes gc1: A set of glyph names for the "left" glyph 1274*e1fe3e4aSElliott Hughes value1: An ``otTables.ValueRecord`` object for the left glyph's 1275*e1fe3e4aSElliott Hughes positioning. 1276*e1fe3e4aSElliott Hughes gc2: A set of glyph names for the "right" glyph 1277*e1fe3e4aSElliott Hughes value2: An ``otTables.ValueRecord`` object for the right glyph's 1278*e1fe3e4aSElliott Hughes positioning. 1279*e1fe3e4aSElliott Hughes """ 1280*e1fe3e4aSElliott Hughes mergeable = ( 1281*e1fe3e4aSElliott Hughes not self.forceSubtableBreak_ 1282*e1fe3e4aSElliott Hughes and self.classDef1_ is not None 1283*e1fe3e4aSElliott Hughes and self.classDef1_.canAdd(gc1) 1284*e1fe3e4aSElliott Hughes and self.classDef2_ is not None 1285*e1fe3e4aSElliott Hughes and self.classDef2_.canAdd(gc2) 1286*e1fe3e4aSElliott Hughes ) 1287*e1fe3e4aSElliott Hughes if not mergeable: 1288*e1fe3e4aSElliott Hughes self.flush_() 1289*e1fe3e4aSElliott Hughes self.classDef1_ = ClassDefBuilder(useClass0=True) 1290*e1fe3e4aSElliott Hughes self.classDef2_ = ClassDefBuilder(useClass0=False) 1291*e1fe3e4aSElliott Hughes self.values_ = {} 1292*e1fe3e4aSElliott Hughes self.classDef1_.add(gc1) 1293*e1fe3e4aSElliott Hughes self.classDef2_.add(gc2) 1294*e1fe3e4aSElliott Hughes self.values_[(gc1, gc2)] = (value1, value2) 1295*e1fe3e4aSElliott Hughes 1296*e1fe3e4aSElliott Hughes def addSubtableBreak(self): 1297*e1fe3e4aSElliott Hughes """Add an explicit subtable break at this point.""" 1298*e1fe3e4aSElliott Hughes self.forceSubtableBreak_ = True 1299*e1fe3e4aSElliott Hughes 1300*e1fe3e4aSElliott Hughes def subtables(self): 1301*e1fe3e4aSElliott Hughes """Return the list of ``otTables.PairPos`` subtables constructed.""" 1302*e1fe3e4aSElliott Hughes self.flush_() 1303*e1fe3e4aSElliott Hughes return self.subtables_ 1304*e1fe3e4aSElliott Hughes 1305*e1fe3e4aSElliott Hughes def flush_(self): 1306*e1fe3e4aSElliott Hughes if self.classDef1_ is None or self.classDef2_ is None: 1307*e1fe3e4aSElliott Hughes return 1308*e1fe3e4aSElliott Hughes st = buildPairPosClassesSubtable(self.values_, self.builder_.glyphMap) 1309*e1fe3e4aSElliott Hughes if st.Coverage is None: 1310*e1fe3e4aSElliott Hughes return 1311*e1fe3e4aSElliott Hughes self.subtables_.append(st) 1312*e1fe3e4aSElliott Hughes self.forceSubtableBreak_ = False 1313*e1fe3e4aSElliott Hughes 1314*e1fe3e4aSElliott Hughes 1315*e1fe3e4aSElliott Hughesclass PairPosBuilder(LookupBuilder): 1316*e1fe3e4aSElliott Hughes """Builds a Pair Positioning (GPOS2) lookup. 1317*e1fe3e4aSElliott Hughes 1318*e1fe3e4aSElliott Hughes Attributes: 1319*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 1320*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 1321*e1fe3e4aSElliott Hughes source which produced this lookup. 1322*e1fe3e4aSElliott Hughes pairs: An array of class-based pair positioning tuples. Usually 1323*e1fe3e4aSElliott Hughes manipulated with the :meth:`addClassPair` method below. 1324*e1fe3e4aSElliott Hughes glyphPairs: A dictionary mapping a tuple of glyph names to a tuple 1325*e1fe3e4aSElliott Hughes of ``otTables.ValueRecord`` objects. Usually manipulated with the 1326*e1fe3e4aSElliott Hughes :meth:`addGlyphPair` method below. 1327*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 1328*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 1329*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 1330*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 1331*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 1332*e1fe3e4aSElliott Hughes flags. 1333*e1fe3e4aSElliott Hughes """ 1334*e1fe3e4aSElliott Hughes 1335*e1fe3e4aSElliott Hughes def __init__(self, font, location): 1336*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 2) 1337*e1fe3e4aSElliott Hughes self.pairs = [] # [(gc1, value1, gc2, value2)*] 1338*e1fe3e4aSElliott Hughes self.glyphPairs = {} # (glyph1, glyph2) --> (value1, value2) 1339*e1fe3e4aSElliott Hughes self.locations = {} # (gc1, gc2) --> (filepath, line, column) 1340*e1fe3e4aSElliott Hughes 1341*e1fe3e4aSElliott Hughes def addClassPair(self, location, glyphclass1, value1, glyphclass2, value2): 1342*e1fe3e4aSElliott Hughes """Add a class pair positioning rule to the current lookup. 1343*e1fe3e4aSElliott Hughes 1344*e1fe3e4aSElliott Hughes Args: 1345*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the 1346*e1fe3e4aSElliott Hughes original source which produced this rule. Unused. 1347*e1fe3e4aSElliott Hughes glyphclass1: A set of glyph names for the "left" glyph in the pair. 1348*e1fe3e4aSElliott Hughes value1: A ``otTables.ValueRecord`` for positioning the left glyph. 1349*e1fe3e4aSElliott Hughes glyphclass2: A set of glyph names for the "right" glyph in the pair. 1350*e1fe3e4aSElliott Hughes value2: A ``otTables.ValueRecord`` for positioning the right glyph. 1351*e1fe3e4aSElliott Hughes """ 1352*e1fe3e4aSElliott Hughes self.pairs.append((glyphclass1, value1, glyphclass2, value2)) 1353*e1fe3e4aSElliott Hughes 1354*e1fe3e4aSElliott Hughes def addGlyphPair(self, location, glyph1, value1, glyph2, value2): 1355*e1fe3e4aSElliott Hughes """Add a glyph pair positioning rule to the current lookup. 1356*e1fe3e4aSElliott Hughes 1357*e1fe3e4aSElliott Hughes Args: 1358*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the 1359*e1fe3e4aSElliott Hughes original source which produced this rule. 1360*e1fe3e4aSElliott Hughes glyph1: A glyph name for the "left" glyph in the pair. 1361*e1fe3e4aSElliott Hughes value1: A ``otTables.ValueRecord`` for positioning the left glyph. 1362*e1fe3e4aSElliott Hughes glyph2: A glyph name for the "right" glyph in the pair. 1363*e1fe3e4aSElliott Hughes value2: A ``otTables.ValueRecord`` for positioning the right glyph. 1364*e1fe3e4aSElliott Hughes """ 1365*e1fe3e4aSElliott Hughes key = (glyph1, glyph2) 1366*e1fe3e4aSElliott Hughes oldValue = self.glyphPairs.get(key, None) 1367*e1fe3e4aSElliott Hughes if oldValue is not None: 1368*e1fe3e4aSElliott Hughes # the Feature File spec explicitly allows specific pairs generated 1369*e1fe3e4aSElliott Hughes # by an 'enum' rule to be overridden by preceding single pairs 1370*e1fe3e4aSElliott Hughes otherLoc = self.locations[key] 1371*e1fe3e4aSElliott Hughes log.debug( 1372*e1fe3e4aSElliott Hughes "Already defined position for pair %s %s at %s; " 1373*e1fe3e4aSElliott Hughes "choosing the first value", 1374*e1fe3e4aSElliott Hughes glyph1, 1375*e1fe3e4aSElliott Hughes glyph2, 1376*e1fe3e4aSElliott Hughes otherLoc, 1377*e1fe3e4aSElliott Hughes ) 1378*e1fe3e4aSElliott Hughes else: 1379*e1fe3e4aSElliott Hughes self.glyphPairs[key] = (value1, value2) 1380*e1fe3e4aSElliott Hughes self.locations[key] = location 1381*e1fe3e4aSElliott Hughes 1382*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 1383*e1fe3e4aSElliott Hughes self.pairs.append( 1384*e1fe3e4aSElliott Hughes ( 1385*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 1386*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 1387*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 1388*e1fe3e4aSElliott Hughes self.SUBTABLE_BREAK_, 1389*e1fe3e4aSElliott Hughes ) 1390*e1fe3e4aSElliott Hughes ) 1391*e1fe3e4aSElliott Hughes 1392*e1fe3e4aSElliott Hughes def equals(self, other): 1393*e1fe3e4aSElliott Hughes return ( 1394*e1fe3e4aSElliott Hughes LookupBuilder.equals(self, other) 1395*e1fe3e4aSElliott Hughes and self.glyphPairs == other.glyphPairs 1396*e1fe3e4aSElliott Hughes and self.pairs == other.pairs 1397*e1fe3e4aSElliott Hughes ) 1398*e1fe3e4aSElliott Hughes 1399*e1fe3e4aSElliott Hughes def build(self): 1400*e1fe3e4aSElliott Hughes """Build the lookup. 1401*e1fe3e4aSElliott Hughes 1402*e1fe3e4aSElliott Hughes Returns: 1403*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the pair positioning 1404*e1fe3e4aSElliott Hughes lookup. 1405*e1fe3e4aSElliott Hughes """ 1406*e1fe3e4aSElliott Hughes builders = {} 1407*e1fe3e4aSElliott Hughes builder = ClassPairPosSubtableBuilder(self) 1408*e1fe3e4aSElliott Hughes for glyphclass1, value1, glyphclass2, value2 in self.pairs: 1409*e1fe3e4aSElliott Hughes if glyphclass1 is self.SUBTABLE_BREAK_: 1410*e1fe3e4aSElliott Hughes builder.addSubtableBreak() 1411*e1fe3e4aSElliott Hughes continue 1412*e1fe3e4aSElliott Hughes builder.addPair(glyphclass1, value1, glyphclass2, value2) 1413*e1fe3e4aSElliott Hughes subtables = [] 1414*e1fe3e4aSElliott Hughes if self.glyphPairs: 1415*e1fe3e4aSElliott Hughes subtables.extend(buildPairPosGlyphs(self.glyphPairs, self.glyphMap)) 1416*e1fe3e4aSElliott Hughes subtables.extend(builder.subtables()) 1417*e1fe3e4aSElliott Hughes lookup = self.buildLookup_(subtables) 1418*e1fe3e4aSElliott Hughes 1419*e1fe3e4aSElliott Hughes # Compact the lookup 1420*e1fe3e4aSElliott Hughes # This is a good moment to do it because the compaction should create 1421*e1fe3e4aSElliott Hughes # smaller subtables, which may prevent overflows from happening. 1422*e1fe3e4aSElliott Hughes # Keep reading the value from the ENV until ufo2ft switches to the config system 1423*e1fe3e4aSElliott Hughes level = self.font.cfg.get( 1424*e1fe3e4aSElliott Hughes "fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL", 1425*e1fe3e4aSElliott Hughes default=_compression_level_from_env(), 1426*e1fe3e4aSElliott Hughes ) 1427*e1fe3e4aSElliott Hughes if level != 0: 1428*e1fe3e4aSElliott Hughes log.info("Compacting GPOS...") 1429*e1fe3e4aSElliott Hughes compact_lookup(self.font, level, lookup) 1430*e1fe3e4aSElliott Hughes 1431*e1fe3e4aSElliott Hughes return lookup 1432*e1fe3e4aSElliott Hughes 1433*e1fe3e4aSElliott Hughes 1434*e1fe3e4aSElliott Hughesclass SinglePosBuilder(LookupBuilder): 1435*e1fe3e4aSElliott Hughes """Builds a Single Positioning (GPOS1) lookup. 1436*e1fe3e4aSElliott Hughes 1437*e1fe3e4aSElliott Hughes Attributes: 1438*e1fe3e4aSElliott Hughes font (``fontTools.TTLib.TTFont``): A font object. 1439*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the original 1440*e1fe3e4aSElliott Hughes source which produced this lookup. 1441*e1fe3e4aSElliott Hughes mapping: A dictionary mapping a glyph name to a ``otTables.ValueRecord`` 1442*e1fe3e4aSElliott Hughes objects. Usually manipulated with the :meth:`add_pos` method below. 1443*e1fe3e4aSElliott Hughes lookupflag (int): The lookup's flag 1444*e1fe3e4aSElliott Hughes markFilterSet: Either ``None`` if no mark filtering set is used, or 1445*e1fe3e4aSElliott Hughes an integer representing the filtering set to be used for this 1446*e1fe3e4aSElliott Hughes lookup. If a mark filtering set is provided, 1447*e1fe3e4aSElliott Hughes `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's 1448*e1fe3e4aSElliott Hughes flags. 1449*e1fe3e4aSElliott Hughes """ 1450*e1fe3e4aSElliott Hughes 1451*e1fe3e4aSElliott Hughes def __init__(self, font, location): 1452*e1fe3e4aSElliott Hughes LookupBuilder.__init__(self, font, location, "GPOS", 1) 1453*e1fe3e4aSElliott Hughes self.locations = {} # glyph -> (filename, line, column) 1454*e1fe3e4aSElliott Hughes self.mapping = {} # glyph -> ot.ValueRecord 1455*e1fe3e4aSElliott Hughes 1456*e1fe3e4aSElliott Hughes def add_pos(self, location, glyph, otValueRecord): 1457*e1fe3e4aSElliott Hughes """Add a single positioning rule. 1458*e1fe3e4aSElliott Hughes 1459*e1fe3e4aSElliott Hughes Args: 1460*e1fe3e4aSElliott Hughes location: A string or tuple representing the location in the 1461*e1fe3e4aSElliott Hughes original source which produced this lookup. 1462*e1fe3e4aSElliott Hughes glyph: A glyph name. 1463*e1fe3e4aSElliott Hughes otValueRection: A ``otTables.ValueRecord`` used to position the 1464*e1fe3e4aSElliott Hughes glyph. 1465*e1fe3e4aSElliott Hughes """ 1466*e1fe3e4aSElliott Hughes if not self.can_add(glyph, otValueRecord): 1467*e1fe3e4aSElliott Hughes otherLoc = self.locations[glyph] 1468*e1fe3e4aSElliott Hughes raise OpenTypeLibError( 1469*e1fe3e4aSElliott Hughes 'Already defined different position for glyph "%s" at %s' 1470*e1fe3e4aSElliott Hughes % (glyph, otherLoc), 1471*e1fe3e4aSElliott Hughes location, 1472*e1fe3e4aSElliott Hughes ) 1473*e1fe3e4aSElliott Hughes if otValueRecord: 1474*e1fe3e4aSElliott Hughes self.mapping[glyph] = otValueRecord 1475*e1fe3e4aSElliott Hughes self.locations[glyph] = location 1476*e1fe3e4aSElliott Hughes 1477*e1fe3e4aSElliott Hughes def can_add(self, glyph, value): 1478*e1fe3e4aSElliott Hughes assert isinstance(value, ValueRecord) 1479*e1fe3e4aSElliott Hughes curValue = self.mapping.get(glyph) 1480*e1fe3e4aSElliott Hughes return curValue is None or curValue == value 1481*e1fe3e4aSElliott Hughes 1482*e1fe3e4aSElliott Hughes def equals(self, other): 1483*e1fe3e4aSElliott Hughes return LookupBuilder.equals(self, other) and self.mapping == other.mapping 1484*e1fe3e4aSElliott Hughes 1485*e1fe3e4aSElliott Hughes def build(self): 1486*e1fe3e4aSElliott Hughes """Build the lookup. 1487*e1fe3e4aSElliott Hughes 1488*e1fe3e4aSElliott Hughes Returns: 1489*e1fe3e4aSElliott Hughes An ``otTables.Lookup`` object representing the single positioning 1490*e1fe3e4aSElliott Hughes lookup. 1491*e1fe3e4aSElliott Hughes """ 1492*e1fe3e4aSElliott Hughes subtables = buildSinglePos(self.mapping, self.glyphMap) 1493*e1fe3e4aSElliott Hughes return self.buildLookup_(subtables) 1494*e1fe3e4aSElliott Hughes 1495*e1fe3e4aSElliott Hughes 1496*e1fe3e4aSElliott Hughes# GSUB 1497*e1fe3e4aSElliott Hughes 1498*e1fe3e4aSElliott Hughes 1499*e1fe3e4aSElliott Hughesdef buildSingleSubstSubtable(mapping): 1500*e1fe3e4aSElliott Hughes """Builds a single substitution (GSUB1) subtable. 1501*e1fe3e4aSElliott Hughes 1502*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 1503*e1fe3e4aSElliott Hughes flexible to use 1504*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.SingleSubstBuilder` instead. 1505*e1fe3e4aSElliott Hughes 1506*e1fe3e4aSElliott Hughes Args: 1507*e1fe3e4aSElliott Hughes mapping: A dictionary mapping input glyph names to output glyph names. 1508*e1fe3e4aSElliott Hughes 1509*e1fe3e4aSElliott Hughes Returns: 1510*e1fe3e4aSElliott Hughes An ``otTables.SingleSubst`` object, or ``None`` if the mapping dictionary 1511*e1fe3e4aSElliott Hughes is empty. 1512*e1fe3e4aSElliott Hughes """ 1513*e1fe3e4aSElliott Hughes if not mapping: 1514*e1fe3e4aSElliott Hughes return None 1515*e1fe3e4aSElliott Hughes self = ot.SingleSubst() 1516*e1fe3e4aSElliott Hughes self.mapping = dict(mapping) 1517*e1fe3e4aSElliott Hughes return self 1518*e1fe3e4aSElliott Hughes 1519*e1fe3e4aSElliott Hughes 1520*e1fe3e4aSElliott Hughesdef buildMultipleSubstSubtable(mapping): 1521*e1fe3e4aSElliott Hughes """Builds a multiple substitution (GSUB2) subtable. 1522*e1fe3e4aSElliott Hughes 1523*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 1524*e1fe3e4aSElliott Hughes flexible to use 1525*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.MultipleSubstBuilder` instead. 1526*e1fe3e4aSElliott Hughes 1527*e1fe3e4aSElliott Hughes Example:: 1528*e1fe3e4aSElliott Hughes 1529*e1fe3e4aSElliott Hughes # sub uni06C0 by uni06D5.fina hamza.above 1530*e1fe3e4aSElliott Hughes # sub uni06C2 by uni06C1.fina hamza.above; 1531*e1fe3e4aSElliott Hughes 1532*e1fe3e4aSElliott Hughes subtable = buildMultipleSubstSubtable({ 1533*e1fe3e4aSElliott Hughes "uni06C0": [ "uni06D5.fina", "hamza.above"], 1534*e1fe3e4aSElliott Hughes "uni06C2": [ "uni06D1.fina", "hamza.above"] 1535*e1fe3e4aSElliott Hughes }) 1536*e1fe3e4aSElliott Hughes 1537*e1fe3e4aSElliott Hughes Args: 1538*e1fe3e4aSElliott Hughes mapping: A dictionary mapping input glyph names to a list of output 1539*e1fe3e4aSElliott Hughes glyph names. 1540*e1fe3e4aSElliott Hughes 1541*e1fe3e4aSElliott Hughes Returns: 1542*e1fe3e4aSElliott Hughes An ``otTables.MultipleSubst`` object or ``None`` if the mapping dictionary 1543*e1fe3e4aSElliott Hughes is empty. 1544*e1fe3e4aSElliott Hughes """ 1545*e1fe3e4aSElliott Hughes if not mapping: 1546*e1fe3e4aSElliott Hughes return None 1547*e1fe3e4aSElliott Hughes self = ot.MultipleSubst() 1548*e1fe3e4aSElliott Hughes self.mapping = dict(mapping) 1549*e1fe3e4aSElliott Hughes return self 1550*e1fe3e4aSElliott Hughes 1551*e1fe3e4aSElliott Hughes 1552*e1fe3e4aSElliott Hughesdef buildAlternateSubstSubtable(mapping): 1553*e1fe3e4aSElliott Hughes """Builds an alternate substitution (GSUB3) subtable. 1554*e1fe3e4aSElliott Hughes 1555*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 1556*e1fe3e4aSElliott Hughes flexible to use 1557*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.AlternateSubstBuilder` instead. 1558*e1fe3e4aSElliott Hughes 1559*e1fe3e4aSElliott Hughes Args: 1560*e1fe3e4aSElliott Hughes mapping: A dictionary mapping input glyph names to a list of output 1561*e1fe3e4aSElliott Hughes glyph names. 1562*e1fe3e4aSElliott Hughes 1563*e1fe3e4aSElliott Hughes Returns: 1564*e1fe3e4aSElliott Hughes An ``otTables.AlternateSubst`` object or ``None`` if the mapping dictionary 1565*e1fe3e4aSElliott Hughes is empty. 1566*e1fe3e4aSElliott Hughes """ 1567*e1fe3e4aSElliott Hughes if not mapping: 1568*e1fe3e4aSElliott Hughes return None 1569*e1fe3e4aSElliott Hughes self = ot.AlternateSubst() 1570*e1fe3e4aSElliott Hughes self.alternates = dict(mapping) 1571*e1fe3e4aSElliott Hughes return self 1572*e1fe3e4aSElliott Hughes 1573*e1fe3e4aSElliott Hughes 1574*e1fe3e4aSElliott Hughesdef buildLigatureSubstSubtable(mapping): 1575*e1fe3e4aSElliott Hughes """Builds a ligature substitution (GSUB4) subtable. 1576*e1fe3e4aSElliott Hughes 1577*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 1578*e1fe3e4aSElliott Hughes flexible to use 1579*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.LigatureSubstBuilder` instead. 1580*e1fe3e4aSElliott Hughes 1581*e1fe3e4aSElliott Hughes Example:: 1582*e1fe3e4aSElliott Hughes 1583*e1fe3e4aSElliott Hughes # sub f f i by f_f_i; 1584*e1fe3e4aSElliott Hughes # sub f i by f_i; 1585*e1fe3e4aSElliott Hughes 1586*e1fe3e4aSElliott Hughes subtable = buildLigatureSubstSubtable({ 1587*e1fe3e4aSElliott Hughes ("f", "f", "i"): "f_f_i", 1588*e1fe3e4aSElliott Hughes ("f", "i"): "f_i", 1589*e1fe3e4aSElliott Hughes }) 1590*e1fe3e4aSElliott Hughes 1591*e1fe3e4aSElliott Hughes Args: 1592*e1fe3e4aSElliott Hughes mapping: A dictionary mapping tuples of glyph names to output 1593*e1fe3e4aSElliott Hughes glyph names. 1594*e1fe3e4aSElliott Hughes 1595*e1fe3e4aSElliott Hughes Returns: 1596*e1fe3e4aSElliott Hughes An ``otTables.LigatureSubst`` object or ``None`` if the mapping dictionary 1597*e1fe3e4aSElliott Hughes is empty. 1598*e1fe3e4aSElliott Hughes """ 1599*e1fe3e4aSElliott Hughes 1600*e1fe3e4aSElliott Hughes if not mapping: 1601*e1fe3e4aSElliott Hughes return None 1602*e1fe3e4aSElliott Hughes self = ot.LigatureSubst() 1603*e1fe3e4aSElliott Hughes # The following single line can replace the rest of this function 1604*e1fe3e4aSElliott Hughes # with fontTools >= 3.1: 1605*e1fe3e4aSElliott Hughes # self.ligatures = dict(mapping) 1606*e1fe3e4aSElliott Hughes self.ligatures = {} 1607*e1fe3e4aSElliott Hughes for components in sorted(mapping.keys(), key=self._getLigatureSortKey): 1608*e1fe3e4aSElliott Hughes ligature = ot.Ligature() 1609*e1fe3e4aSElliott Hughes ligature.Component = components[1:] 1610*e1fe3e4aSElliott Hughes ligature.CompCount = len(ligature.Component) + 1 1611*e1fe3e4aSElliott Hughes ligature.LigGlyph = mapping[components] 1612*e1fe3e4aSElliott Hughes firstGlyph = components[0] 1613*e1fe3e4aSElliott Hughes self.ligatures.setdefault(firstGlyph, []).append(ligature) 1614*e1fe3e4aSElliott Hughes return self 1615*e1fe3e4aSElliott Hughes 1616*e1fe3e4aSElliott Hughes 1617*e1fe3e4aSElliott Hughes# GPOS 1618*e1fe3e4aSElliott Hughes 1619*e1fe3e4aSElliott Hughes 1620*e1fe3e4aSElliott Hughesdef buildAnchor(x, y, point=None, deviceX=None, deviceY=None): 1621*e1fe3e4aSElliott Hughes """Builds an Anchor table. 1622*e1fe3e4aSElliott Hughes 1623*e1fe3e4aSElliott Hughes This determines the appropriate anchor format based on the passed parameters. 1624*e1fe3e4aSElliott Hughes 1625*e1fe3e4aSElliott Hughes Args: 1626*e1fe3e4aSElliott Hughes x (int): X coordinate. 1627*e1fe3e4aSElliott Hughes y (int): Y coordinate. 1628*e1fe3e4aSElliott Hughes point (int): Index of glyph contour point, if provided. 1629*e1fe3e4aSElliott Hughes deviceX (``otTables.Device``): X coordinate device table, if provided. 1630*e1fe3e4aSElliott Hughes deviceY (``otTables.Device``): Y coordinate device table, if provided. 1631*e1fe3e4aSElliott Hughes 1632*e1fe3e4aSElliott Hughes Returns: 1633*e1fe3e4aSElliott Hughes An ``otTables.Anchor`` object. 1634*e1fe3e4aSElliott Hughes """ 1635*e1fe3e4aSElliott Hughes self = ot.Anchor() 1636*e1fe3e4aSElliott Hughes self.XCoordinate, self.YCoordinate = x, y 1637*e1fe3e4aSElliott Hughes self.Format = 1 1638*e1fe3e4aSElliott Hughes if point is not None: 1639*e1fe3e4aSElliott Hughes self.AnchorPoint = point 1640*e1fe3e4aSElliott Hughes self.Format = 2 1641*e1fe3e4aSElliott Hughes if deviceX is not None or deviceY is not None: 1642*e1fe3e4aSElliott Hughes assert ( 1643*e1fe3e4aSElliott Hughes self.Format == 1 1644*e1fe3e4aSElliott Hughes ), "Either point, or both of deviceX/deviceY, must be None." 1645*e1fe3e4aSElliott Hughes self.XDeviceTable = deviceX 1646*e1fe3e4aSElliott Hughes self.YDeviceTable = deviceY 1647*e1fe3e4aSElliott Hughes self.Format = 3 1648*e1fe3e4aSElliott Hughes return self 1649*e1fe3e4aSElliott Hughes 1650*e1fe3e4aSElliott Hughes 1651*e1fe3e4aSElliott Hughesdef buildBaseArray(bases, numMarkClasses, glyphMap): 1652*e1fe3e4aSElliott Hughes """Builds a base array record. 1653*e1fe3e4aSElliott Hughes 1654*e1fe3e4aSElliott Hughes As part of building mark-to-base positioning rules, you will need to define 1655*e1fe3e4aSElliott Hughes a ``BaseArray`` record, which "defines for each base glyph an array of 1656*e1fe3e4aSElliott Hughes anchors, one for each mark class." This function builds the base array 1657*e1fe3e4aSElliott Hughes subtable. 1658*e1fe3e4aSElliott Hughes 1659*e1fe3e4aSElliott Hughes Example:: 1660*e1fe3e4aSElliott Hughes 1661*e1fe3e4aSElliott Hughes bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}} 1662*e1fe3e4aSElliott Hughes basearray = buildBaseArray(bases, 2, font.getReverseGlyphMap()) 1663*e1fe3e4aSElliott Hughes 1664*e1fe3e4aSElliott Hughes Args: 1665*e1fe3e4aSElliott Hughes bases (dict): A dictionary mapping anchors to glyphs; the keys being 1666*e1fe3e4aSElliott Hughes glyph names, and the values being dictionaries mapping mark class ID 1667*e1fe3e4aSElliott Hughes to the appropriate ``otTables.Anchor`` object used for attaching marks 1668*e1fe3e4aSElliott Hughes of that class. 1669*e1fe3e4aSElliott Hughes numMarkClasses (int): The total number of mark classes for which anchors 1670*e1fe3e4aSElliott Hughes are defined. 1671*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 1672*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 1673*e1fe3e4aSElliott Hughes 1674*e1fe3e4aSElliott Hughes Returns: 1675*e1fe3e4aSElliott Hughes An ``otTables.BaseArray`` object. 1676*e1fe3e4aSElliott Hughes """ 1677*e1fe3e4aSElliott Hughes self = ot.BaseArray() 1678*e1fe3e4aSElliott Hughes self.BaseRecord = [] 1679*e1fe3e4aSElliott Hughes for base in sorted(bases, key=glyphMap.__getitem__): 1680*e1fe3e4aSElliott Hughes b = bases[base] 1681*e1fe3e4aSElliott Hughes anchors = [b.get(markClass) for markClass in range(numMarkClasses)] 1682*e1fe3e4aSElliott Hughes self.BaseRecord.append(buildBaseRecord(anchors)) 1683*e1fe3e4aSElliott Hughes self.BaseCount = len(self.BaseRecord) 1684*e1fe3e4aSElliott Hughes return self 1685*e1fe3e4aSElliott Hughes 1686*e1fe3e4aSElliott Hughes 1687*e1fe3e4aSElliott Hughesdef buildBaseRecord(anchors): 1688*e1fe3e4aSElliott Hughes # [otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord 1689*e1fe3e4aSElliott Hughes self = ot.BaseRecord() 1690*e1fe3e4aSElliott Hughes self.BaseAnchor = anchors 1691*e1fe3e4aSElliott Hughes return self 1692*e1fe3e4aSElliott Hughes 1693*e1fe3e4aSElliott Hughes 1694*e1fe3e4aSElliott Hughesdef buildComponentRecord(anchors): 1695*e1fe3e4aSElliott Hughes """Builds a component record. 1696*e1fe3e4aSElliott Hughes 1697*e1fe3e4aSElliott Hughes As part of building mark-to-ligature positioning rules, you will need to 1698*e1fe3e4aSElliott Hughes define ``ComponentRecord`` objects, which contain "an array of offsets... 1699*e1fe3e4aSElliott Hughes to the Anchor tables that define all the attachment points used to attach 1700*e1fe3e4aSElliott Hughes marks to the component." This function builds the component record. 1701*e1fe3e4aSElliott Hughes 1702*e1fe3e4aSElliott Hughes Args: 1703*e1fe3e4aSElliott Hughes anchors: A list of ``otTables.Anchor`` objects or ``None``. 1704*e1fe3e4aSElliott Hughes 1705*e1fe3e4aSElliott Hughes Returns: 1706*e1fe3e4aSElliott Hughes A ``otTables.ComponentRecord`` object or ``None`` if no anchors are 1707*e1fe3e4aSElliott Hughes supplied. 1708*e1fe3e4aSElliott Hughes """ 1709*e1fe3e4aSElliott Hughes if not anchors: 1710*e1fe3e4aSElliott Hughes return None 1711*e1fe3e4aSElliott Hughes self = ot.ComponentRecord() 1712*e1fe3e4aSElliott Hughes self.LigatureAnchor = anchors 1713*e1fe3e4aSElliott Hughes return self 1714*e1fe3e4aSElliott Hughes 1715*e1fe3e4aSElliott Hughes 1716*e1fe3e4aSElliott Hughesdef buildCursivePosSubtable(attach, glyphMap): 1717*e1fe3e4aSElliott Hughes """Builds a cursive positioning (GPOS3) subtable. 1718*e1fe3e4aSElliott Hughes 1719*e1fe3e4aSElliott Hughes Cursive positioning lookups are made up of a coverage table of glyphs, 1720*e1fe3e4aSElliott Hughes and a set of ``EntryExitRecord`` records containing the anchors for 1721*e1fe3e4aSElliott Hughes each glyph. This function builds the cursive positioning subtable. 1722*e1fe3e4aSElliott Hughes 1723*e1fe3e4aSElliott Hughes Example:: 1724*e1fe3e4aSElliott Hughes 1725*e1fe3e4aSElliott Hughes subtable = buildCursivePosSubtable({ 1726*e1fe3e4aSElliott Hughes "AlifIni": (None, buildAnchor(0, 50)), 1727*e1fe3e4aSElliott Hughes "BehMed": (buildAnchor(500,250), buildAnchor(0,50)), 1728*e1fe3e4aSElliott Hughes # ... 1729*e1fe3e4aSElliott Hughes }, font.getReverseGlyphMap()) 1730*e1fe3e4aSElliott Hughes 1731*e1fe3e4aSElliott Hughes Args: 1732*e1fe3e4aSElliott Hughes attach (dict): A mapping between glyph names and a tuple of two 1733*e1fe3e4aSElliott Hughes ``otTables.Anchor`` objects representing entry and exit anchors. 1734*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 1735*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 1736*e1fe3e4aSElliott Hughes 1737*e1fe3e4aSElliott Hughes Returns: 1738*e1fe3e4aSElliott Hughes An ``otTables.CursivePos`` object, or ``None`` if the attachment 1739*e1fe3e4aSElliott Hughes dictionary was empty. 1740*e1fe3e4aSElliott Hughes """ 1741*e1fe3e4aSElliott Hughes if not attach: 1742*e1fe3e4aSElliott Hughes return None 1743*e1fe3e4aSElliott Hughes self = ot.CursivePos() 1744*e1fe3e4aSElliott Hughes self.Format = 1 1745*e1fe3e4aSElliott Hughes self.Coverage = buildCoverage(attach.keys(), glyphMap) 1746*e1fe3e4aSElliott Hughes self.EntryExitRecord = [] 1747*e1fe3e4aSElliott Hughes for glyph in self.Coverage.glyphs: 1748*e1fe3e4aSElliott Hughes entryAnchor, exitAnchor = attach[glyph] 1749*e1fe3e4aSElliott Hughes rec = ot.EntryExitRecord() 1750*e1fe3e4aSElliott Hughes rec.EntryAnchor = entryAnchor 1751*e1fe3e4aSElliott Hughes rec.ExitAnchor = exitAnchor 1752*e1fe3e4aSElliott Hughes self.EntryExitRecord.append(rec) 1753*e1fe3e4aSElliott Hughes self.EntryExitCount = len(self.EntryExitRecord) 1754*e1fe3e4aSElliott Hughes return self 1755*e1fe3e4aSElliott Hughes 1756*e1fe3e4aSElliott Hughes 1757*e1fe3e4aSElliott Hughesdef buildDevice(deltas): 1758*e1fe3e4aSElliott Hughes """Builds a Device record as part of a ValueRecord or Anchor. 1759*e1fe3e4aSElliott Hughes 1760*e1fe3e4aSElliott Hughes Device tables specify size-specific adjustments to value records 1761*e1fe3e4aSElliott Hughes and anchors to reflect changes based on the resolution of the output. 1762*e1fe3e4aSElliott Hughes For example, one could specify that an anchor's Y position should be 1763*e1fe3e4aSElliott Hughes increased by 1 pixel when displayed at 8 pixels per em. This routine 1764*e1fe3e4aSElliott Hughes builds device records. 1765*e1fe3e4aSElliott Hughes 1766*e1fe3e4aSElliott Hughes Args: 1767*e1fe3e4aSElliott Hughes deltas: A dictionary mapping pixels-per-em sizes to the delta 1768*e1fe3e4aSElliott Hughes adjustment in pixels when the font is displayed at that size. 1769*e1fe3e4aSElliott Hughes 1770*e1fe3e4aSElliott Hughes Returns: 1771*e1fe3e4aSElliott Hughes An ``otTables.Device`` object if any deltas were supplied, or 1772*e1fe3e4aSElliott Hughes ``None`` otherwise. 1773*e1fe3e4aSElliott Hughes """ 1774*e1fe3e4aSElliott Hughes if not deltas: 1775*e1fe3e4aSElliott Hughes return None 1776*e1fe3e4aSElliott Hughes self = ot.Device() 1777*e1fe3e4aSElliott Hughes keys = deltas.keys() 1778*e1fe3e4aSElliott Hughes self.StartSize = startSize = min(keys) 1779*e1fe3e4aSElliott Hughes self.EndSize = endSize = max(keys) 1780*e1fe3e4aSElliott Hughes assert 0 <= startSize <= endSize 1781*e1fe3e4aSElliott Hughes self.DeltaValue = deltaValues = [ 1782*e1fe3e4aSElliott Hughes deltas.get(size, 0) for size in range(startSize, endSize + 1) 1783*e1fe3e4aSElliott Hughes ] 1784*e1fe3e4aSElliott Hughes maxDelta = max(deltaValues) 1785*e1fe3e4aSElliott Hughes minDelta = min(deltaValues) 1786*e1fe3e4aSElliott Hughes assert minDelta > -129 and maxDelta < 128 1787*e1fe3e4aSElliott Hughes if minDelta > -3 and maxDelta < 2: 1788*e1fe3e4aSElliott Hughes self.DeltaFormat = 1 1789*e1fe3e4aSElliott Hughes elif minDelta > -9 and maxDelta < 8: 1790*e1fe3e4aSElliott Hughes self.DeltaFormat = 2 1791*e1fe3e4aSElliott Hughes else: 1792*e1fe3e4aSElliott Hughes self.DeltaFormat = 3 1793*e1fe3e4aSElliott Hughes return self 1794*e1fe3e4aSElliott Hughes 1795*e1fe3e4aSElliott Hughes 1796*e1fe3e4aSElliott Hughesdef buildLigatureArray(ligs, numMarkClasses, glyphMap): 1797*e1fe3e4aSElliott Hughes """Builds a LigatureArray subtable. 1798*e1fe3e4aSElliott Hughes 1799*e1fe3e4aSElliott Hughes As part of building a mark-to-ligature lookup, you will need to define 1800*e1fe3e4aSElliott Hughes the set of anchors (for each mark class) on each component of the ligature 1801*e1fe3e4aSElliott Hughes where marks can be attached. For example, for an Arabic divine name ligature 1802*e1fe3e4aSElliott Hughes (lam lam heh), you may want to specify mark attachment positioning for 1803*e1fe3e4aSElliott Hughes superior marks (fatha, etc.) and inferior marks (kasra, etc.) on each glyph 1804*e1fe3e4aSElliott Hughes of the ligature. This routine builds the ligature array record. 1805*e1fe3e4aSElliott Hughes 1806*e1fe3e4aSElliott Hughes Example:: 1807*e1fe3e4aSElliott Hughes 1808*e1fe3e4aSElliott Hughes buildLigatureArray({ 1809*e1fe3e4aSElliott Hughes "lam-lam-heh": [ 1810*e1fe3e4aSElliott Hughes { 0: superiorAnchor1, 1: inferiorAnchor1 }, # attach points for lam1 1811*e1fe3e4aSElliott Hughes { 0: superiorAnchor2, 1: inferiorAnchor2 }, # attach points for lam2 1812*e1fe3e4aSElliott Hughes { 0: superiorAnchor3, 1: inferiorAnchor3 }, # attach points for heh 1813*e1fe3e4aSElliott Hughes ] 1814*e1fe3e4aSElliott Hughes }, 2, font.getReverseGlyphMap()) 1815*e1fe3e4aSElliott Hughes 1816*e1fe3e4aSElliott Hughes Args: 1817*e1fe3e4aSElliott Hughes ligs (dict): A mapping of ligature names to an array of dictionaries: 1818*e1fe3e4aSElliott Hughes for each component glyph in the ligature, an dictionary mapping 1819*e1fe3e4aSElliott Hughes mark class IDs to anchors. 1820*e1fe3e4aSElliott Hughes numMarkClasses (int): The number of mark classes. 1821*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 1822*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 1823*e1fe3e4aSElliott Hughes 1824*e1fe3e4aSElliott Hughes Returns: 1825*e1fe3e4aSElliott Hughes An ``otTables.LigatureArray`` object if deltas were supplied. 1826*e1fe3e4aSElliott Hughes """ 1827*e1fe3e4aSElliott Hughes self = ot.LigatureArray() 1828*e1fe3e4aSElliott Hughes self.LigatureAttach = [] 1829*e1fe3e4aSElliott Hughes for lig in sorted(ligs, key=glyphMap.__getitem__): 1830*e1fe3e4aSElliott Hughes anchors = [] 1831*e1fe3e4aSElliott Hughes for component in ligs[lig]: 1832*e1fe3e4aSElliott Hughes anchors.append([component.get(mc) for mc in range(numMarkClasses)]) 1833*e1fe3e4aSElliott Hughes self.LigatureAttach.append(buildLigatureAttach(anchors)) 1834*e1fe3e4aSElliott Hughes self.LigatureCount = len(self.LigatureAttach) 1835*e1fe3e4aSElliott Hughes return self 1836*e1fe3e4aSElliott Hughes 1837*e1fe3e4aSElliott Hughes 1838*e1fe3e4aSElliott Hughesdef buildLigatureAttach(components): 1839*e1fe3e4aSElliott Hughes # [[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach 1840*e1fe3e4aSElliott Hughes self = ot.LigatureAttach() 1841*e1fe3e4aSElliott Hughes self.ComponentRecord = [buildComponentRecord(c) for c in components] 1842*e1fe3e4aSElliott Hughes self.ComponentCount = len(self.ComponentRecord) 1843*e1fe3e4aSElliott Hughes return self 1844*e1fe3e4aSElliott Hughes 1845*e1fe3e4aSElliott Hughes 1846*e1fe3e4aSElliott Hughesdef buildMarkArray(marks, glyphMap): 1847*e1fe3e4aSElliott Hughes """Builds a mark array subtable. 1848*e1fe3e4aSElliott Hughes 1849*e1fe3e4aSElliott Hughes As part of building mark-to-* positioning rules, you will need to define 1850*e1fe3e4aSElliott Hughes a MarkArray subtable, which "defines the class and the anchor point 1851*e1fe3e4aSElliott Hughes for a mark glyph." This function builds the mark array subtable. 1852*e1fe3e4aSElliott Hughes 1853*e1fe3e4aSElliott Hughes Example:: 1854*e1fe3e4aSElliott Hughes 1855*e1fe3e4aSElliott Hughes mark = { 1856*e1fe3e4aSElliott Hughes "acute": (0, buildAnchor(300,712)), 1857*e1fe3e4aSElliott Hughes # ... 1858*e1fe3e4aSElliott Hughes } 1859*e1fe3e4aSElliott Hughes markarray = buildMarkArray(marks, font.getReverseGlyphMap()) 1860*e1fe3e4aSElliott Hughes 1861*e1fe3e4aSElliott Hughes Args: 1862*e1fe3e4aSElliott Hughes marks (dict): A dictionary mapping anchors to glyphs; the keys being 1863*e1fe3e4aSElliott Hughes glyph names, and the values being a tuple of mark class number and 1864*e1fe3e4aSElliott Hughes an ``otTables.Anchor`` object representing the mark's attachment 1865*e1fe3e4aSElliott Hughes point. 1866*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 1867*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 1868*e1fe3e4aSElliott Hughes 1869*e1fe3e4aSElliott Hughes Returns: 1870*e1fe3e4aSElliott Hughes An ``otTables.MarkArray`` object. 1871*e1fe3e4aSElliott Hughes """ 1872*e1fe3e4aSElliott Hughes self = ot.MarkArray() 1873*e1fe3e4aSElliott Hughes self.MarkRecord = [] 1874*e1fe3e4aSElliott Hughes for mark in sorted(marks.keys(), key=glyphMap.__getitem__): 1875*e1fe3e4aSElliott Hughes markClass, anchor = marks[mark] 1876*e1fe3e4aSElliott Hughes markrec = buildMarkRecord(markClass, anchor) 1877*e1fe3e4aSElliott Hughes self.MarkRecord.append(markrec) 1878*e1fe3e4aSElliott Hughes self.MarkCount = len(self.MarkRecord) 1879*e1fe3e4aSElliott Hughes return self 1880*e1fe3e4aSElliott Hughes 1881*e1fe3e4aSElliott Hughes 1882*e1fe3e4aSElliott Hughesdef buildMarkBasePos(marks, bases, glyphMap): 1883*e1fe3e4aSElliott Hughes """Build a list of MarkBasePos (GPOS4) subtables. 1884*e1fe3e4aSElliott Hughes 1885*e1fe3e4aSElliott Hughes This routine turns a set of marks and bases into a list of mark-to-base 1886*e1fe3e4aSElliott Hughes positioning subtables. Currently the list will contain a single subtable 1887*e1fe3e4aSElliott Hughes containing all marks and bases, although at a later date it may return the 1888*e1fe3e4aSElliott Hughes optimal list of subtables subsetting the marks and bases into groups which 1889*e1fe3e4aSElliott Hughes save space. See :func:`buildMarkBasePosSubtable` below. 1890*e1fe3e4aSElliott Hughes 1891*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 1892*e1fe3e4aSElliott Hughes flexible to use 1893*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.MarkBasePosBuilder` instead. 1894*e1fe3e4aSElliott Hughes 1895*e1fe3e4aSElliott Hughes Example:: 1896*e1fe3e4aSElliott Hughes 1897*e1fe3e4aSElliott Hughes # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 1898*e1fe3e4aSElliott Hughes 1899*e1fe3e4aSElliott Hughes marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)} 1900*e1fe3e4aSElliott Hughes bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}} 1901*e1fe3e4aSElliott Hughes markbaseposes = buildMarkBasePos(marks, bases, font.getReverseGlyphMap()) 1902*e1fe3e4aSElliott Hughes 1903*e1fe3e4aSElliott Hughes Args: 1904*e1fe3e4aSElliott Hughes marks (dict): A dictionary mapping anchors to glyphs; the keys being 1905*e1fe3e4aSElliott Hughes glyph names, and the values being a tuple of mark class number and 1906*e1fe3e4aSElliott Hughes an ``otTables.Anchor`` object representing the mark's attachment 1907*e1fe3e4aSElliott Hughes point. (See :func:`buildMarkArray`.) 1908*e1fe3e4aSElliott Hughes bases (dict): A dictionary mapping anchors to glyphs; the keys being 1909*e1fe3e4aSElliott Hughes glyph names, and the values being dictionaries mapping mark class ID 1910*e1fe3e4aSElliott Hughes to the appropriate ``otTables.Anchor`` object used for attaching marks 1911*e1fe3e4aSElliott Hughes of that class. (See :func:`buildBaseArray`.) 1912*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 1913*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 1914*e1fe3e4aSElliott Hughes 1915*e1fe3e4aSElliott Hughes Returns: 1916*e1fe3e4aSElliott Hughes A list of ``otTables.MarkBasePos`` objects. 1917*e1fe3e4aSElliott Hughes """ 1918*e1fe3e4aSElliott Hughes # TODO: Consider emitting multiple subtables to save space. 1919*e1fe3e4aSElliott Hughes # Partition the marks and bases into disjoint subsets, so that 1920*e1fe3e4aSElliott Hughes # MarkBasePos rules would only access glyphs from a single 1921*e1fe3e4aSElliott Hughes # subset. This would likely lead to smaller mark/base 1922*e1fe3e4aSElliott Hughes # matrices, so we might be able to omit many of the empty 1923*e1fe3e4aSElliott Hughes # anchor tables that we currently produce. Of course, this 1924*e1fe3e4aSElliott Hughes # would only work if the MarkBasePos rules of real-world fonts 1925*e1fe3e4aSElliott Hughes # allow partitioning into multiple subsets. We should find out 1926*e1fe3e4aSElliott Hughes # whether this is the case; if so, implement the optimization. 1927*e1fe3e4aSElliott Hughes # On the other hand, a very large number of subtables could 1928*e1fe3e4aSElliott Hughes # slow down layout engines; so this would need profiling. 1929*e1fe3e4aSElliott Hughes return [buildMarkBasePosSubtable(marks, bases, glyphMap)] 1930*e1fe3e4aSElliott Hughes 1931*e1fe3e4aSElliott Hughes 1932*e1fe3e4aSElliott Hughesdef buildMarkBasePosSubtable(marks, bases, glyphMap): 1933*e1fe3e4aSElliott Hughes """Build a single MarkBasePos (GPOS4) subtable. 1934*e1fe3e4aSElliott Hughes 1935*e1fe3e4aSElliott Hughes This builds a mark-to-base lookup subtable containing all of the referenced 1936*e1fe3e4aSElliott Hughes marks and bases. See :func:`buildMarkBasePos`. 1937*e1fe3e4aSElliott Hughes 1938*e1fe3e4aSElliott Hughes Args: 1939*e1fe3e4aSElliott Hughes marks (dict): A dictionary mapping anchors to glyphs; the keys being 1940*e1fe3e4aSElliott Hughes glyph names, and the values being a tuple of mark class number and 1941*e1fe3e4aSElliott Hughes an ``otTables.Anchor`` object representing the mark's attachment 1942*e1fe3e4aSElliott Hughes point. (See :func:`buildMarkArray`.) 1943*e1fe3e4aSElliott Hughes bases (dict): A dictionary mapping anchors to glyphs; the keys being 1944*e1fe3e4aSElliott Hughes glyph names, and the values being dictionaries mapping mark class ID 1945*e1fe3e4aSElliott Hughes to the appropriate ``otTables.Anchor`` object used for attaching marks 1946*e1fe3e4aSElliott Hughes of that class. (See :func:`buildBaseArray`.) 1947*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 1948*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 1949*e1fe3e4aSElliott Hughes 1950*e1fe3e4aSElliott Hughes Returns: 1951*e1fe3e4aSElliott Hughes A ``otTables.MarkBasePos`` object. 1952*e1fe3e4aSElliott Hughes """ 1953*e1fe3e4aSElliott Hughes self = ot.MarkBasePos() 1954*e1fe3e4aSElliott Hughes self.Format = 1 1955*e1fe3e4aSElliott Hughes self.MarkCoverage = buildCoverage(marks, glyphMap) 1956*e1fe3e4aSElliott Hughes self.MarkArray = buildMarkArray(marks, glyphMap) 1957*e1fe3e4aSElliott Hughes self.ClassCount = max([mc for mc, _ in marks.values()]) + 1 1958*e1fe3e4aSElliott Hughes self.BaseCoverage = buildCoverage(bases, glyphMap) 1959*e1fe3e4aSElliott Hughes self.BaseArray = buildBaseArray(bases, self.ClassCount, glyphMap) 1960*e1fe3e4aSElliott Hughes return self 1961*e1fe3e4aSElliott Hughes 1962*e1fe3e4aSElliott Hughes 1963*e1fe3e4aSElliott Hughesdef buildMarkLigPos(marks, ligs, glyphMap): 1964*e1fe3e4aSElliott Hughes """Build a list of MarkLigPos (GPOS5) subtables. 1965*e1fe3e4aSElliott Hughes 1966*e1fe3e4aSElliott Hughes This routine turns a set of marks and ligatures into a list of mark-to-ligature 1967*e1fe3e4aSElliott Hughes positioning subtables. Currently the list will contain a single subtable 1968*e1fe3e4aSElliott Hughes containing all marks and ligatures, although at a later date it may return 1969*e1fe3e4aSElliott Hughes the optimal list of subtables subsetting the marks and ligatures into groups 1970*e1fe3e4aSElliott Hughes which save space. See :func:`buildMarkLigPosSubtable` below. 1971*e1fe3e4aSElliott Hughes 1972*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 1973*e1fe3e4aSElliott Hughes flexible to use 1974*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.MarkLigPosBuilder` instead. 1975*e1fe3e4aSElliott Hughes 1976*e1fe3e4aSElliott Hughes Example:: 1977*e1fe3e4aSElliott Hughes 1978*e1fe3e4aSElliott Hughes # a1, a2, a3, a4, a5 = buildAnchor(500, 100), ... 1979*e1fe3e4aSElliott Hughes marks = { 1980*e1fe3e4aSElliott Hughes "acute": (0, a1), 1981*e1fe3e4aSElliott Hughes "grave": (0, a1), 1982*e1fe3e4aSElliott Hughes "cedilla": (1, a2) 1983*e1fe3e4aSElliott Hughes } 1984*e1fe3e4aSElliott Hughes ligs = { 1985*e1fe3e4aSElliott Hughes "f_i": [ 1986*e1fe3e4aSElliott Hughes { 0: a3, 1: a5 }, # f 1987*e1fe3e4aSElliott Hughes { 0: a4, 1: a5 } # i 1988*e1fe3e4aSElliott Hughes ], 1989*e1fe3e4aSElliott Hughes # "c_t": [{...}, {...}] 1990*e1fe3e4aSElliott Hughes } 1991*e1fe3e4aSElliott Hughes markligposes = buildMarkLigPos(marks, ligs, 1992*e1fe3e4aSElliott Hughes font.getReverseGlyphMap()) 1993*e1fe3e4aSElliott Hughes 1994*e1fe3e4aSElliott Hughes Args: 1995*e1fe3e4aSElliott Hughes marks (dict): A dictionary mapping anchors to glyphs; the keys being 1996*e1fe3e4aSElliott Hughes glyph names, and the values being a tuple of mark class number and 1997*e1fe3e4aSElliott Hughes an ``otTables.Anchor`` object representing the mark's attachment 1998*e1fe3e4aSElliott Hughes point. (See :func:`buildMarkArray`.) 1999*e1fe3e4aSElliott Hughes ligs (dict): A mapping of ligature names to an array of dictionaries: 2000*e1fe3e4aSElliott Hughes for each component glyph in the ligature, an dictionary mapping 2001*e1fe3e4aSElliott Hughes mark class IDs to anchors. (See :func:`buildLigatureArray`.) 2002*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2003*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2004*e1fe3e4aSElliott Hughes 2005*e1fe3e4aSElliott Hughes Returns: 2006*e1fe3e4aSElliott Hughes A list of ``otTables.MarkLigPos`` objects. 2007*e1fe3e4aSElliott Hughes 2008*e1fe3e4aSElliott Hughes """ 2009*e1fe3e4aSElliott Hughes # TODO: Consider splitting into multiple subtables to save space, 2010*e1fe3e4aSElliott Hughes # as with MarkBasePos, this would be a trade-off that would need 2011*e1fe3e4aSElliott Hughes # profiling. And, depending on how typical fonts are structured, 2012*e1fe3e4aSElliott Hughes # it might not be worth doing at all. 2013*e1fe3e4aSElliott Hughes return [buildMarkLigPosSubtable(marks, ligs, glyphMap)] 2014*e1fe3e4aSElliott Hughes 2015*e1fe3e4aSElliott Hughes 2016*e1fe3e4aSElliott Hughesdef buildMarkLigPosSubtable(marks, ligs, glyphMap): 2017*e1fe3e4aSElliott Hughes """Build a single MarkLigPos (GPOS5) subtable. 2018*e1fe3e4aSElliott Hughes 2019*e1fe3e4aSElliott Hughes This builds a mark-to-base lookup subtable containing all of the referenced 2020*e1fe3e4aSElliott Hughes marks and bases. See :func:`buildMarkLigPos`. 2021*e1fe3e4aSElliott Hughes 2022*e1fe3e4aSElliott Hughes Args: 2023*e1fe3e4aSElliott Hughes marks (dict): A dictionary mapping anchors to glyphs; the keys being 2024*e1fe3e4aSElliott Hughes glyph names, and the values being a tuple of mark class number and 2025*e1fe3e4aSElliott Hughes an ``otTables.Anchor`` object representing the mark's attachment 2026*e1fe3e4aSElliott Hughes point. (See :func:`buildMarkArray`.) 2027*e1fe3e4aSElliott Hughes ligs (dict): A mapping of ligature names to an array of dictionaries: 2028*e1fe3e4aSElliott Hughes for each component glyph in the ligature, an dictionary mapping 2029*e1fe3e4aSElliott Hughes mark class IDs to anchors. (See :func:`buildLigatureArray`.) 2030*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2031*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2032*e1fe3e4aSElliott Hughes 2033*e1fe3e4aSElliott Hughes Returns: 2034*e1fe3e4aSElliott Hughes A ``otTables.MarkLigPos`` object. 2035*e1fe3e4aSElliott Hughes """ 2036*e1fe3e4aSElliott Hughes self = ot.MarkLigPos() 2037*e1fe3e4aSElliott Hughes self.Format = 1 2038*e1fe3e4aSElliott Hughes self.MarkCoverage = buildCoverage(marks, glyphMap) 2039*e1fe3e4aSElliott Hughes self.MarkArray = buildMarkArray(marks, glyphMap) 2040*e1fe3e4aSElliott Hughes self.ClassCount = max([mc for mc, _ in marks.values()]) + 1 2041*e1fe3e4aSElliott Hughes self.LigatureCoverage = buildCoverage(ligs, glyphMap) 2042*e1fe3e4aSElliott Hughes self.LigatureArray = buildLigatureArray(ligs, self.ClassCount, glyphMap) 2043*e1fe3e4aSElliott Hughes return self 2044*e1fe3e4aSElliott Hughes 2045*e1fe3e4aSElliott Hughes 2046*e1fe3e4aSElliott Hughesdef buildMarkRecord(classID, anchor): 2047*e1fe3e4aSElliott Hughes assert isinstance(classID, int) 2048*e1fe3e4aSElliott Hughes assert isinstance(anchor, ot.Anchor) 2049*e1fe3e4aSElliott Hughes self = ot.MarkRecord() 2050*e1fe3e4aSElliott Hughes self.Class = classID 2051*e1fe3e4aSElliott Hughes self.MarkAnchor = anchor 2052*e1fe3e4aSElliott Hughes return self 2053*e1fe3e4aSElliott Hughes 2054*e1fe3e4aSElliott Hughes 2055*e1fe3e4aSElliott Hughesdef buildMark2Record(anchors): 2056*e1fe3e4aSElliott Hughes # [otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record 2057*e1fe3e4aSElliott Hughes self = ot.Mark2Record() 2058*e1fe3e4aSElliott Hughes self.Mark2Anchor = anchors 2059*e1fe3e4aSElliott Hughes return self 2060*e1fe3e4aSElliott Hughes 2061*e1fe3e4aSElliott Hughes 2062*e1fe3e4aSElliott Hughesdef _getValueFormat(f, values, i): 2063*e1fe3e4aSElliott Hughes # Helper for buildPairPos{Glyphs|Classes}Subtable. 2064*e1fe3e4aSElliott Hughes if f is not None: 2065*e1fe3e4aSElliott Hughes return f 2066*e1fe3e4aSElliott Hughes mask = 0 2067*e1fe3e4aSElliott Hughes for value in values: 2068*e1fe3e4aSElliott Hughes if value is not None and value[i] is not None: 2069*e1fe3e4aSElliott Hughes mask |= value[i].getFormat() 2070*e1fe3e4aSElliott Hughes return mask 2071*e1fe3e4aSElliott Hughes 2072*e1fe3e4aSElliott Hughes 2073*e1fe3e4aSElliott Hughesdef buildPairPosClassesSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None): 2074*e1fe3e4aSElliott Hughes """Builds a class pair adjustment (GPOS2 format 2) subtable. 2075*e1fe3e4aSElliott Hughes 2076*e1fe3e4aSElliott Hughes Kerning tables are generally expressed as pair positioning tables using 2077*e1fe3e4aSElliott Hughes class-based pair adjustments. This routine builds format 2 PairPos 2078*e1fe3e4aSElliott Hughes subtables. 2079*e1fe3e4aSElliott Hughes 2080*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 2081*e1fe3e4aSElliott Hughes flexible to use 2082*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.ClassPairPosSubtableBuilder` 2083*e1fe3e4aSElliott Hughes instead, as this takes care of ensuring that the supplied pairs can be 2084*e1fe3e4aSElliott Hughes formed into non-overlapping classes and emitting individual subtables 2085*e1fe3e4aSElliott Hughes whenever the non-overlapping requirement means that a new subtable is 2086*e1fe3e4aSElliott Hughes required. 2087*e1fe3e4aSElliott Hughes 2088*e1fe3e4aSElliott Hughes Example:: 2089*e1fe3e4aSElliott Hughes 2090*e1fe3e4aSElliott Hughes pairs = {} 2091*e1fe3e4aSElliott Hughes 2092*e1fe3e4aSElliott Hughes pairs[( 2093*e1fe3e4aSElliott Hughes [ "K", "X" ], 2094*e1fe3e4aSElliott Hughes [ "W", "V" ] 2095*e1fe3e4aSElliott Hughes )] = ( buildValue(xAdvance=+5), buildValue() ) 2096*e1fe3e4aSElliott Hughes # pairs[(... , ...)] = (..., ...) 2097*e1fe3e4aSElliott Hughes 2098*e1fe3e4aSElliott Hughes pairpos = buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap()) 2099*e1fe3e4aSElliott Hughes 2100*e1fe3e4aSElliott Hughes Args: 2101*e1fe3e4aSElliott Hughes pairs (dict): Pair positioning data; the keys being a two-element 2102*e1fe3e4aSElliott Hughes tuple of lists of glyphnames, and the values being a two-element 2103*e1fe3e4aSElliott Hughes tuple of ``otTables.ValueRecord`` objects. 2104*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2105*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2106*e1fe3e4aSElliott Hughes valueFormat1: Force the "left" value records to the given format. 2107*e1fe3e4aSElliott Hughes valueFormat2: Force the "right" value records to the given format. 2108*e1fe3e4aSElliott Hughes 2109*e1fe3e4aSElliott Hughes Returns: 2110*e1fe3e4aSElliott Hughes A ``otTables.PairPos`` object. 2111*e1fe3e4aSElliott Hughes """ 2112*e1fe3e4aSElliott Hughes coverage = set() 2113*e1fe3e4aSElliott Hughes classDef1 = ClassDefBuilder(useClass0=True) 2114*e1fe3e4aSElliott Hughes classDef2 = ClassDefBuilder(useClass0=False) 2115*e1fe3e4aSElliott Hughes for gc1, gc2 in sorted(pairs): 2116*e1fe3e4aSElliott Hughes coverage.update(gc1) 2117*e1fe3e4aSElliott Hughes classDef1.add(gc1) 2118*e1fe3e4aSElliott Hughes classDef2.add(gc2) 2119*e1fe3e4aSElliott Hughes self = ot.PairPos() 2120*e1fe3e4aSElliott Hughes self.Format = 2 2121*e1fe3e4aSElliott Hughes valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0) 2122*e1fe3e4aSElliott Hughes valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1) 2123*e1fe3e4aSElliott Hughes self.Coverage = buildCoverage(coverage, glyphMap) 2124*e1fe3e4aSElliott Hughes self.ClassDef1 = classDef1.build() 2125*e1fe3e4aSElliott Hughes self.ClassDef2 = classDef2.build() 2126*e1fe3e4aSElliott Hughes classes1 = classDef1.classes() 2127*e1fe3e4aSElliott Hughes classes2 = classDef2.classes() 2128*e1fe3e4aSElliott Hughes self.Class1Record = [] 2129*e1fe3e4aSElliott Hughes for c1 in classes1: 2130*e1fe3e4aSElliott Hughes rec1 = ot.Class1Record() 2131*e1fe3e4aSElliott Hughes rec1.Class2Record = [] 2132*e1fe3e4aSElliott Hughes self.Class1Record.append(rec1) 2133*e1fe3e4aSElliott Hughes for c2 in classes2: 2134*e1fe3e4aSElliott Hughes rec2 = ot.Class2Record() 2135*e1fe3e4aSElliott Hughes val1, val2 = pairs.get((c1, c2), (None, None)) 2136*e1fe3e4aSElliott Hughes rec2.Value1 = ( 2137*e1fe3e4aSElliott Hughes ValueRecord(src=val1, valueFormat=valueFormat1) 2138*e1fe3e4aSElliott Hughes if valueFormat1 2139*e1fe3e4aSElliott Hughes else None 2140*e1fe3e4aSElliott Hughes ) 2141*e1fe3e4aSElliott Hughes rec2.Value2 = ( 2142*e1fe3e4aSElliott Hughes ValueRecord(src=val2, valueFormat=valueFormat2) 2143*e1fe3e4aSElliott Hughes if valueFormat2 2144*e1fe3e4aSElliott Hughes else None 2145*e1fe3e4aSElliott Hughes ) 2146*e1fe3e4aSElliott Hughes rec1.Class2Record.append(rec2) 2147*e1fe3e4aSElliott Hughes self.Class1Count = len(self.Class1Record) 2148*e1fe3e4aSElliott Hughes self.Class2Count = len(classes2) 2149*e1fe3e4aSElliott Hughes return self 2150*e1fe3e4aSElliott Hughes 2151*e1fe3e4aSElliott Hughes 2152*e1fe3e4aSElliott Hughesdef buildPairPosGlyphs(pairs, glyphMap): 2153*e1fe3e4aSElliott Hughes """Builds a list of glyph-based pair adjustment (GPOS2 format 1) subtables. 2154*e1fe3e4aSElliott Hughes 2155*e1fe3e4aSElliott Hughes This organises a list of pair positioning adjustments into subtables based 2156*e1fe3e4aSElliott Hughes on common value record formats. 2157*e1fe3e4aSElliott Hughes 2158*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 2159*e1fe3e4aSElliott Hughes flexible to use 2160*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.PairPosBuilder` 2161*e1fe3e4aSElliott Hughes instead. 2162*e1fe3e4aSElliott Hughes 2163*e1fe3e4aSElliott Hughes Example:: 2164*e1fe3e4aSElliott Hughes 2165*e1fe3e4aSElliott Hughes pairs = { 2166*e1fe3e4aSElliott Hughes ("K", "W"): ( buildValue(xAdvance=+5), buildValue() ), 2167*e1fe3e4aSElliott Hughes ("K", "V"): ( buildValue(xAdvance=+5), buildValue() ), 2168*e1fe3e4aSElliott Hughes # ... 2169*e1fe3e4aSElliott Hughes } 2170*e1fe3e4aSElliott Hughes 2171*e1fe3e4aSElliott Hughes subtables = buildPairPosGlyphs(pairs, font.getReverseGlyphMap()) 2172*e1fe3e4aSElliott Hughes 2173*e1fe3e4aSElliott Hughes Args: 2174*e1fe3e4aSElliott Hughes pairs (dict): Pair positioning data; the keys being a two-element 2175*e1fe3e4aSElliott Hughes tuple of glyphnames, and the values being a two-element 2176*e1fe3e4aSElliott Hughes tuple of ``otTables.ValueRecord`` objects. 2177*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2178*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2179*e1fe3e4aSElliott Hughes 2180*e1fe3e4aSElliott Hughes Returns: 2181*e1fe3e4aSElliott Hughes A list of ``otTables.PairPos`` objects. 2182*e1fe3e4aSElliott Hughes """ 2183*e1fe3e4aSElliott Hughes 2184*e1fe3e4aSElliott Hughes p = {} # (formatA, formatB) --> {(glyphA, glyphB): (valA, valB)} 2185*e1fe3e4aSElliott Hughes for (glyphA, glyphB), (valA, valB) in pairs.items(): 2186*e1fe3e4aSElliott Hughes formatA = valA.getFormat() if valA is not None else 0 2187*e1fe3e4aSElliott Hughes formatB = valB.getFormat() if valB is not None else 0 2188*e1fe3e4aSElliott Hughes pos = p.setdefault((formatA, formatB), {}) 2189*e1fe3e4aSElliott Hughes pos[(glyphA, glyphB)] = (valA, valB) 2190*e1fe3e4aSElliott Hughes return [ 2191*e1fe3e4aSElliott Hughes buildPairPosGlyphsSubtable(pos, glyphMap, formatA, formatB) 2192*e1fe3e4aSElliott Hughes for ((formatA, formatB), pos) in sorted(p.items()) 2193*e1fe3e4aSElliott Hughes ] 2194*e1fe3e4aSElliott Hughes 2195*e1fe3e4aSElliott Hughes 2196*e1fe3e4aSElliott Hughesdef buildPairPosGlyphsSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=None): 2197*e1fe3e4aSElliott Hughes """Builds a single glyph-based pair adjustment (GPOS2 format 1) subtable. 2198*e1fe3e4aSElliott Hughes 2199*e1fe3e4aSElliott Hughes This builds a PairPos subtable from a dictionary of glyph pairs and 2200*e1fe3e4aSElliott Hughes their positioning adjustments. See also :func:`buildPairPosGlyphs`. 2201*e1fe3e4aSElliott Hughes 2202*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 2203*e1fe3e4aSElliott Hughes flexible to use 2204*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.PairPosBuilder` instead. 2205*e1fe3e4aSElliott Hughes 2206*e1fe3e4aSElliott Hughes Example:: 2207*e1fe3e4aSElliott Hughes 2208*e1fe3e4aSElliott Hughes pairs = { 2209*e1fe3e4aSElliott Hughes ("K", "W"): ( buildValue(xAdvance=+5), buildValue() ), 2210*e1fe3e4aSElliott Hughes ("K", "V"): ( buildValue(xAdvance=+5), buildValue() ), 2211*e1fe3e4aSElliott Hughes # ... 2212*e1fe3e4aSElliott Hughes } 2213*e1fe3e4aSElliott Hughes 2214*e1fe3e4aSElliott Hughes pairpos = buildPairPosGlyphsSubtable(pairs, font.getReverseGlyphMap()) 2215*e1fe3e4aSElliott Hughes 2216*e1fe3e4aSElliott Hughes Args: 2217*e1fe3e4aSElliott Hughes pairs (dict): Pair positioning data; the keys being a two-element 2218*e1fe3e4aSElliott Hughes tuple of glyphnames, and the values being a two-element 2219*e1fe3e4aSElliott Hughes tuple of ``otTables.ValueRecord`` objects. 2220*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2221*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2222*e1fe3e4aSElliott Hughes valueFormat1: Force the "left" value records to the given format. 2223*e1fe3e4aSElliott Hughes valueFormat2: Force the "right" value records to the given format. 2224*e1fe3e4aSElliott Hughes 2225*e1fe3e4aSElliott Hughes Returns: 2226*e1fe3e4aSElliott Hughes A ``otTables.PairPos`` object. 2227*e1fe3e4aSElliott Hughes """ 2228*e1fe3e4aSElliott Hughes self = ot.PairPos() 2229*e1fe3e4aSElliott Hughes self.Format = 1 2230*e1fe3e4aSElliott Hughes valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0) 2231*e1fe3e4aSElliott Hughes valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1) 2232*e1fe3e4aSElliott Hughes p = {} 2233*e1fe3e4aSElliott Hughes for (glyphA, glyphB), (valA, valB) in pairs.items(): 2234*e1fe3e4aSElliott Hughes p.setdefault(glyphA, []).append((glyphB, valA, valB)) 2235*e1fe3e4aSElliott Hughes self.Coverage = buildCoverage({g for g, _ in pairs.keys()}, glyphMap) 2236*e1fe3e4aSElliott Hughes self.PairSet = [] 2237*e1fe3e4aSElliott Hughes for glyph in self.Coverage.glyphs: 2238*e1fe3e4aSElliott Hughes ps = ot.PairSet() 2239*e1fe3e4aSElliott Hughes ps.PairValueRecord = [] 2240*e1fe3e4aSElliott Hughes self.PairSet.append(ps) 2241*e1fe3e4aSElliott Hughes for glyph2, val1, val2 in sorted(p[glyph], key=lambda x: glyphMap[x[0]]): 2242*e1fe3e4aSElliott Hughes pvr = ot.PairValueRecord() 2243*e1fe3e4aSElliott Hughes pvr.SecondGlyph = glyph2 2244*e1fe3e4aSElliott Hughes pvr.Value1 = ( 2245*e1fe3e4aSElliott Hughes ValueRecord(src=val1, valueFormat=valueFormat1) 2246*e1fe3e4aSElliott Hughes if valueFormat1 2247*e1fe3e4aSElliott Hughes else None 2248*e1fe3e4aSElliott Hughes ) 2249*e1fe3e4aSElliott Hughes pvr.Value2 = ( 2250*e1fe3e4aSElliott Hughes ValueRecord(src=val2, valueFormat=valueFormat2) 2251*e1fe3e4aSElliott Hughes if valueFormat2 2252*e1fe3e4aSElliott Hughes else None 2253*e1fe3e4aSElliott Hughes ) 2254*e1fe3e4aSElliott Hughes ps.PairValueRecord.append(pvr) 2255*e1fe3e4aSElliott Hughes ps.PairValueCount = len(ps.PairValueRecord) 2256*e1fe3e4aSElliott Hughes self.PairSetCount = len(self.PairSet) 2257*e1fe3e4aSElliott Hughes return self 2258*e1fe3e4aSElliott Hughes 2259*e1fe3e4aSElliott Hughes 2260*e1fe3e4aSElliott Hughesdef buildSinglePos(mapping, glyphMap): 2261*e1fe3e4aSElliott Hughes """Builds a list of single adjustment (GPOS1) subtables. 2262*e1fe3e4aSElliott Hughes 2263*e1fe3e4aSElliott Hughes This builds a list of SinglePos subtables from a dictionary of glyph 2264*e1fe3e4aSElliott Hughes names and their positioning adjustments. The format of the subtables are 2265*e1fe3e4aSElliott Hughes determined to optimize the size of the resulting subtables. 2266*e1fe3e4aSElliott Hughes See also :func:`buildSinglePosSubtable`. 2267*e1fe3e4aSElliott Hughes 2268*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 2269*e1fe3e4aSElliott Hughes flexible to use 2270*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.SinglePosBuilder` instead. 2271*e1fe3e4aSElliott Hughes 2272*e1fe3e4aSElliott Hughes Example:: 2273*e1fe3e4aSElliott Hughes 2274*e1fe3e4aSElliott Hughes mapping = { 2275*e1fe3e4aSElliott Hughes "V": buildValue({ "xAdvance" : +5 }), 2276*e1fe3e4aSElliott Hughes # ... 2277*e1fe3e4aSElliott Hughes } 2278*e1fe3e4aSElliott Hughes 2279*e1fe3e4aSElliott Hughes subtables = buildSinglePos(pairs, font.getReverseGlyphMap()) 2280*e1fe3e4aSElliott Hughes 2281*e1fe3e4aSElliott Hughes Args: 2282*e1fe3e4aSElliott Hughes mapping (dict): A mapping between glyphnames and 2283*e1fe3e4aSElliott Hughes ``otTables.ValueRecord`` objects. 2284*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2285*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2286*e1fe3e4aSElliott Hughes 2287*e1fe3e4aSElliott Hughes Returns: 2288*e1fe3e4aSElliott Hughes A list of ``otTables.SinglePos`` objects. 2289*e1fe3e4aSElliott Hughes """ 2290*e1fe3e4aSElliott Hughes result, handled = [], set() 2291*e1fe3e4aSElliott Hughes # In SinglePos format 1, the covered glyphs all share the same ValueRecord. 2292*e1fe3e4aSElliott Hughes # In format 2, each glyph has its own ValueRecord, but these records 2293*e1fe3e4aSElliott Hughes # all have the same properties (eg., all have an X but no Y placement). 2294*e1fe3e4aSElliott Hughes coverages, masks, values = {}, {}, {} 2295*e1fe3e4aSElliott Hughes for glyph, value in mapping.items(): 2296*e1fe3e4aSElliott Hughes key = _getSinglePosValueKey(value) 2297*e1fe3e4aSElliott Hughes coverages.setdefault(key, []).append(glyph) 2298*e1fe3e4aSElliott Hughes masks.setdefault(key[0], []).append(key) 2299*e1fe3e4aSElliott Hughes values[key] = value 2300*e1fe3e4aSElliott Hughes 2301*e1fe3e4aSElliott Hughes # If a ValueRecord is shared between multiple glyphs, we generate 2302*e1fe3e4aSElliott Hughes # a SinglePos format 1 subtable; that is the most compact form. 2303*e1fe3e4aSElliott Hughes for key, glyphs in coverages.items(): 2304*e1fe3e4aSElliott Hughes # 5 ushorts is the length of introducing another sublookup 2305*e1fe3e4aSElliott Hughes if len(glyphs) * _getSinglePosValueSize(key) > 5: 2306*e1fe3e4aSElliott Hughes format1Mapping = {g: values[key] for g in glyphs} 2307*e1fe3e4aSElliott Hughes result.append(buildSinglePosSubtable(format1Mapping, glyphMap)) 2308*e1fe3e4aSElliott Hughes handled.add(key) 2309*e1fe3e4aSElliott Hughes 2310*e1fe3e4aSElliott Hughes # In the remaining ValueRecords, look for those whose valueFormat 2311*e1fe3e4aSElliott Hughes # (the set of used properties) is shared between multiple records. 2312*e1fe3e4aSElliott Hughes # These will get encoded in format 2. 2313*e1fe3e4aSElliott Hughes for valueFormat, keys in masks.items(): 2314*e1fe3e4aSElliott Hughes f2 = [k for k in keys if k not in handled] 2315*e1fe3e4aSElliott Hughes if len(f2) > 1: 2316*e1fe3e4aSElliott Hughes format2Mapping = {} 2317*e1fe3e4aSElliott Hughes for k in f2: 2318*e1fe3e4aSElliott Hughes format2Mapping.update((g, values[k]) for g in coverages[k]) 2319*e1fe3e4aSElliott Hughes result.append(buildSinglePosSubtable(format2Mapping, glyphMap)) 2320*e1fe3e4aSElliott Hughes handled.update(f2) 2321*e1fe3e4aSElliott Hughes 2322*e1fe3e4aSElliott Hughes # The remaining ValueRecords are only used by a few glyphs, normally 2323*e1fe3e4aSElliott Hughes # one. We encode these in format 1 again. 2324*e1fe3e4aSElliott Hughes for key, glyphs in coverages.items(): 2325*e1fe3e4aSElliott Hughes if key not in handled: 2326*e1fe3e4aSElliott Hughes for g in glyphs: 2327*e1fe3e4aSElliott Hughes st = buildSinglePosSubtable({g: values[key]}, glyphMap) 2328*e1fe3e4aSElliott Hughes result.append(st) 2329*e1fe3e4aSElliott Hughes 2330*e1fe3e4aSElliott Hughes # When the OpenType layout engine traverses the subtables, it will 2331*e1fe3e4aSElliott Hughes # stop after the first matching subtable. Therefore, we sort the 2332*e1fe3e4aSElliott Hughes # resulting subtables by decreasing coverage size; this increases 2333*e1fe3e4aSElliott Hughes # the chance that the layout engine can do an early exit. (Of course, 2334*e1fe3e4aSElliott Hughes # this would only be true if all glyphs were equally frequent, which 2335*e1fe3e4aSElliott Hughes # is not really the case; but we do not know their distribution). 2336*e1fe3e4aSElliott Hughes # If two subtables cover the same number of glyphs, we sort them 2337*e1fe3e4aSElliott Hughes # by glyph ID so that our output is deterministic. 2338*e1fe3e4aSElliott Hughes result.sort(key=lambda t: _getSinglePosTableKey(t, glyphMap)) 2339*e1fe3e4aSElliott Hughes return result 2340*e1fe3e4aSElliott Hughes 2341*e1fe3e4aSElliott Hughes 2342*e1fe3e4aSElliott Hughesdef buildSinglePosSubtable(values, glyphMap): 2343*e1fe3e4aSElliott Hughes """Builds a single adjustment (GPOS1) subtable. 2344*e1fe3e4aSElliott Hughes 2345*e1fe3e4aSElliott Hughes This builds a list of SinglePos subtables from a dictionary of glyph 2346*e1fe3e4aSElliott Hughes names and their positioning adjustments. The format of the subtable is 2347*e1fe3e4aSElliott Hughes determined to optimize the size of the output. 2348*e1fe3e4aSElliott Hughes See also :func:`buildSinglePos`. 2349*e1fe3e4aSElliott Hughes 2350*e1fe3e4aSElliott Hughes Note that if you are implementing a layout compiler, you may find it more 2351*e1fe3e4aSElliott Hughes flexible to use 2352*e1fe3e4aSElliott Hughes :py:class:`fontTools.otlLib.lookupBuilders.SinglePosBuilder` instead. 2353*e1fe3e4aSElliott Hughes 2354*e1fe3e4aSElliott Hughes Example:: 2355*e1fe3e4aSElliott Hughes 2356*e1fe3e4aSElliott Hughes mapping = { 2357*e1fe3e4aSElliott Hughes "V": buildValue({ "xAdvance" : +5 }), 2358*e1fe3e4aSElliott Hughes # ... 2359*e1fe3e4aSElliott Hughes } 2360*e1fe3e4aSElliott Hughes 2361*e1fe3e4aSElliott Hughes subtable = buildSinglePos(pairs, font.getReverseGlyphMap()) 2362*e1fe3e4aSElliott Hughes 2363*e1fe3e4aSElliott Hughes Args: 2364*e1fe3e4aSElliott Hughes mapping (dict): A mapping between glyphnames and 2365*e1fe3e4aSElliott Hughes ``otTables.ValueRecord`` objects. 2366*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2367*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2368*e1fe3e4aSElliott Hughes 2369*e1fe3e4aSElliott Hughes Returns: 2370*e1fe3e4aSElliott Hughes A ``otTables.SinglePos`` object. 2371*e1fe3e4aSElliott Hughes """ 2372*e1fe3e4aSElliott Hughes self = ot.SinglePos() 2373*e1fe3e4aSElliott Hughes self.Coverage = buildCoverage(values.keys(), glyphMap) 2374*e1fe3e4aSElliott Hughes valueFormat = self.ValueFormat = reduce( 2375*e1fe3e4aSElliott Hughes int.__or__, [v.getFormat() for v in values.values()], 0 2376*e1fe3e4aSElliott Hughes ) 2377*e1fe3e4aSElliott Hughes valueRecords = [ 2378*e1fe3e4aSElliott Hughes ValueRecord(src=values[g], valueFormat=valueFormat) 2379*e1fe3e4aSElliott Hughes for g in self.Coverage.glyphs 2380*e1fe3e4aSElliott Hughes ] 2381*e1fe3e4aSElliott Hughes if all(v == valueRecords[0] for v in valueRecords): 2382*e1fe3e4aSElliott Hughes self.Format = 1 2383*e1fe3e4aSElliott Hughes if self.ValueFormat != 0: 2384*e1fe3e4aSElliott Hughes self.Value = valueRecords[0] 2385*e1fe3e4aSElliott Hughes else: 2386*e1fe3e4aSElliott Hughes self.Value = None 2387*e1fe3e4aSElliott Hughes else: 2388*e1fe3e4aSElliott Hughes self.Format = 2 2389*e1fe3e4aSElliott Hughes self.Value = valueRecords 2390*e1fe3e4aSElliott Hughes self.ValueCount = len(self.Value) 2391*e1fe3e4aSElliott Hughes return self 2392*e1fe3e4aSElliott Hughes 2393*e1fe3e4aSElliott Hughes 2394*e1fe3e4aSElliott Hughesdef _getSinglePosTableKey(subtable, glyphMap): 2395*e1fe3e4aSElliott Hughes assert isinstance(subtable, ot.SinglePos), subtable 2396*e1fe3e4aSElliott Hughes glyphs = subtable.Coverage.glyphs 2397*e1fe3e4aSElliott Hughes return (-len(glyphs), glyphMap[glyphs[0]]) 2398*e1fe3e4aSElliott Hughes 2399*e1fe3e4aSElliott Hughes 2400*e1fe3e4aSElliott Hughesdef _getSinglePosValueKey(valueRecord): 2401*e1fe3e4aSElliott Hughes # otBase.ValueRecord --> (2, ("YPlacement": 12)) 2402*e1fe3e4aSElliott Hughes assert isinstance(valueRecord, ValueRecord), valueRecord 2403*e1fe3e4aSElliott Hughes valueFormat, result = 0, [] 2404*e1fe3e4aSElliott Hughes for name, value in valueRecord.__dict__.items(): 2405*e1fe3e4aSElliott Hughes if isinstance(value, ot.Device): 2406*e1fe3e4aSElliott Hughes result.append((name, _makeDeviceTuple(value))) 2407*e1fe3e4aSElliott Hughes else: 2408*e1fe3e4aSElliott Hughes result.append((name, value)) 2409*e1fe3e4aSElliott Hughes valueFormat |= valueRecordFormatDict[name][0] 2410*e1fe3e4aSElliott Hughes result.sort() 2411*e1fe3e4aSElliott Hughes result.insert(0, valueFormat) 2412*e1fe3e4aSElliott Hughes return tuple(result) 2413*e1fe3e4aSElliott Hughes 2414*e1fe3e4aSElliott Hughes 2415*e1fe3e4aSElliott Hughes_DeviceTuple = namedtuple("_DeviceTuple", "DeltaFormat StartSize EndSize DeltaValue") 2416*e1fe3e4aSElliott Hughes 2417*e1fe3e4aSElliott Hughes 2418*e1fe3e4aSElliott Hughesdef _makeDeviceTuple(device): 2419*e1fe3e4aSElliott Hughes # otTables.Device --> tuple, for making device tables unique 2420*e1fe3e4aSElliott Hughes return _DeviceTuple( 2421*e1fe3e4aSElliott Hughes device.DeltaFormat, 2422*e1fe3e4aSElliott Hughes device.StartSize, 2423*e1fe3e4aSElliott Hughes device.EndSize, 2424*e1fe3e4aSElliott Hughes () if device.DeltaFormat & 0x8000 else tuple(device.DeltaValue), 2425*e1fe3e4aSElliott Hughes ) 2426*e1fe3e4aSElliott Hughes 2427*e1fe3e4aSElliott Hughes 2428*e1fe3e4aSElliott Hughesdef _getSinglePosValueSize(valueKey): 2429*e1fe3e4aSElliott Hughes # Returns how many ushorts this valueKey (short form of ValueRecord) takes up 2430*e1fe3e4aSElliott Hughes count = 0 2431*e1fe3e4aSElliott Hughes for _, v in valueKey[1:]: 2432*e1fe3e4aSElliott Hughes if isinstance(v, _DeviceTuple): 2433*e1fe3e4aSElliott Hughes count += len(v.DeltaValue) + 3 2434*e1fe3e4aSElliott Hughes else: 2435*e1fe3e4aSElliott Hughes count += 1 2436*e1fe3e4aSElliott Hughes return count 2437*e1fe3e4aSElliott Hughes 2438*e1fe3e4aSElliott Hughes 2439*e1fe3e4aSElliott Hughesdef buildValue(value): 2440*e1fe3e4aSElliott Hughes """Builds a positioning value record. 2441*e1fe3e4aSElliott Hughes 2442*e1fe3e4aSElliott Hughes Value records are used to specify coordinates and adjustments for 2443*e1fe3e4aSElliott Hughes positioning and attaching glyphs. Many of the positioning functions 2444*e1fe3e4aSElliott Hughes in this library take ``otTables.ValueRecord`` objects as arguments. 2445*e1fe3e4aSElliott Hughes This function builds value records from dictionaries. 2446*e1fe3e4aSElliott Hughes 2447*e1fe3e4aSElliott Hughes Args: 2448*e1fe3e4aSElliott Hughes value (dict): A dictionary with zero or more of the following keys: 2449*e1fe3e4aSElliott Hughes - ``xPlacement`` 2450*e1fe3e4aSElliott Hughes - ``yPlacement`` 2451*e1fe3e4aSElliott Hughes - ``xAdvance`` 2452*e1fe3e4aSElliott Hughes - ``yAdvance`` 2453*e1fe3e4aSElliott Hughes - ``xPlaDevice`` 2454*e1fe3e4aSElliott Hughes - ``yPlaDevice`` 2455*e1fe3e4aSElliott Hughes - ``xAdvDevice`` 2456*e1fe3e4aSElliott Hughes - ``yAdvDevice`` 2457*e1fe3e4aSElliott Hughes 2458*e1fe3e4aSElliott Hughes Returns: 2459*e1fe3e4aSElliott Hughes An ``otTables.ValueRecord`` object. 2460*e1fe3e4aSElliott Hughes """ 2461*e1fe3e4aSElliott Hughes self = ValueRecord() 2462*e1fe3e4aSElliott Hughes for k, v in value.items(): 2463*e1fe3e4aSElliott Hughes setattr(self, k, v) 2464*e1fe3e4aSElliott Hughes return self 2465*e1fe3e4aSElliott Hughes 2466*e1fe3e4aSElliott Hughes 2467*e1fe3e4aSElliott Hughes# GDEF 2468*e1fe3e4aSElliott Hughes 2469*e1fe3e4aSElliott Hughes 2470*e1fe3e4aSElliott Hughesdef buildAttachList(attachPoints, glyphMap): 2471*e1fe3e4aSElliott Hughes """Builds an AttachList subtable. 2472*e1fe3e4aSElliott Hughes 2473*e1fe3e4aSElliott Hughes A GDEF table may contain an Attachment Point List table (AttachList) 2474*e1fe3e4aSElliott Hughes which stores the contour indices of attachment points for glyphs with 2475*e1fe3e4aSElliott Hughes attachment points. This routine builds AttachList subtables. 2476*e1fe3e4aSElliott Hughes 2477*e1fe3e4aSElliott Hughes Args: 2478*e1fe3e4aSElliott Hughes attachPoints (dict): A mapping between glyph names and a list of 2479*e1fe3e4aSElliott Hughes contour indices. 2480*e1fe3e4aSElliott Hughes 2481*e1fe3e4aSElliott Hughes Returns: 2482*e1fe3e4aSElliott Hughes An ``otTables.AttachList`` object if attachment points are supplied, 2483*e1fe3e4aSElliott Hughes or ``None`` otherwise. 2484*e1fe3e4aSElliott Hughes """ 2485*e1fe3e4aSElliott Hughes if not attachPoints: 2486*e1fe3e4aSElliott Hughes return None 2487*e1fe3e4aSElliott Hughes self = ot.AttachList() 2488*e1fe3e4aSElliott Hughes self.Coverage = buildCoverage(attachPoints.keys(), glyphMap) 2489*e1fe3e4aSElliott Hughes self.AttachPoint = [buildAttachPoint(attachPoints[g]) for g in self.Coverage.glyphs] 2490*e1fe3e4aSElliott Hughes self.GlyphCount = len(self.AttachPoint) 2491*e1fe3e4aSElliott Hughes return self 2492*e1fe3e4aSElliott Hughes 2493*e1fe3e4aSElliott Hughes 2494*e1fe3e4aSElliott Hughesdef buildAttachPoint(points): 2495*e1fe3e4aSElliott Hughes # [4, 23, 41] --> otTables.AttachPoint 2496*e1fe3e4aSElliott Hughes # Only used by above. 2497*e1fe3e4aSElliott Hughes if not points: 2498*e1fe3e4aSElliott Hughes return None 2499*e1fe3e4aSElliott Hughes self = ot.AttachPoint() 2500*e1fe3e4aSElliott Hughes self.PointIndex = sorted(set(points)) 2501*e1fe3e4aSElliott Hughes self.PointCount = len(self.PointIndex) 2502*e1fe3e4aSElliott Hughes return self 2503*e1fe3e4aSElliott Hughes 2504*e1fe3e4aSElliott Hughes 2505*e1fe3e4aSElliott Hughesdef buildCaretValueForCoord(coord): 2506*e1fe3e4aSElliott Hughes # 500 --> otTables.CaretValue, format 1 2507*e1fe3e4aSElliott Hughes # (500, DeviceTable) --> otTables.CaretValue, format 3 2508*e1fe3e4aSElliott Hughes self = ot.CaretValue() 2509*e1fe3e4aSElliott Hughes if isinstance(coord, tuple): 2510*e1fe3e4aSElliott Hughes self.Format = 3 2511*e1fe3e4aSElliott Hughes self.Coordinate, self.DeviceTable = coord 2512*e1fe3e4aSElliott Hughes else: 2513*e1fe3e4aSElliott Hughes self.Format = 1 2514*e1fe3e4aSElliott Hughes self.Coordinate = coord 2515*e1fe3e4aSElliott Hughes return self 2516*e1fe3e4aSElliott Hughes 2517*e1fe3e4aSElliott Hughes 2518*e1fe3e4aSElliott Hughesdef buildCaretValueForPoint(point): 2519*e1fe3e4aSElliott Hughes # 4 --> otTables.CaretValue, format 2 2520*e1fe3e4aSElliott Hughes self = ot.CaretValue() 2521*e1fe3e4aSElliott Hughes self.Format = 2 2522*e1fe3e4aSElliott Hughes self.CaretValuePoint = point 2523*e1fe3e4aSElliott Hughes return self 2524*e1fe3e4aSElliott Hughes 2525*e1fe3e4aSElliott Hughes 2526*e1fe3e4aSElliott Hughesdef buildLigCaretList(coords, points, glyphMap): 2527*e1fe3e4aSElliott Hughes """Builds a ligature caret list table. 2528*e1fe3e4aSElliott Hughes 2529*e1fe3e4aSElliott Hughes Ligatures appear as a single glyph representing multiple characters; however 2530*e1fe3e4aSElliott Hughes when, for example, editing text containing a ``f_i`` ligature, the user may 2531*e1fe3e4aSElliott Hughes want to place the cursor between the ``f`` and the ``i``. The ligature caret 2532*e1fe3e4aSElliott Hughes list in the GDEF table specifies the position to display the "caret" (the 2533*e1fe3e4aSElliott Hughes character insertion indicator, typically a flashing vertical bar) "inside" 2534*e1fe3e4aSElliott Hughes the ligature to represent an insertion point. The insertion positions may 2535*e1fe3e4aSElliott Hughes be specified either by coordinate or by contour point. 2536*e1fe3e4aSElliott Hughes 2537*e1fe3e4aSElliott Hughes Example:: 2538*e1fe3e4aSElliott Hughes 2539*e1fe3e4aSElliott Hughes coords = { 2540*e1fe3e4aSElliott Hughes "f_f_i": [300, 600] # f|fi cursor at 300 units, ff|i cursor at 600. 2541*e1fe3e4aSElliott Hughes } 2542*e1fe3e4aSElliott Hughes points = { 2543*e1fe3e4aSElliott Hughes "c_t": [28] # c|t cursor appears at coordinate of contour point 28. 2544*e1fe3e4aSElliott Hughes } 2545*e1fe3e4aSElliott Hughes ligcaretlist = buildLigCaretList(coords, points, font.getReverseGlyphMap()) 2546*e1fe3e4aSElliott Hughes 2547*e1fe3e4aSElliott Hughes Args: 2548*e1fe3e4aSElliott Hughes coords: A mapping between glyph names and a list of coordinates for 2549*e1fe3e4aSElliott Hughes the insertion point of each ligature component after the first one. 2550*e1fe3e4aSElliott Hughes points: A mapping between glyph names and a list of contour points for 2551*e1fe3e4aSElliott Hughes the insertion point of each ligature component after the first one. 2552*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2553*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2554*e1fe3e4aSElliott Hughes 2555*e1fe3e4aSElliott Hughes Returns: 2556*e1fe3e4aSElliott Hughes A ``otTables.LigCaretList`` object if any carets are present, or 2557*e1fe3e4aSElliott Hughes ``None`` otherwise.""" 2558*e1fe3e4aSElliott Hughes glyphs = set(coords.keys()) if coords else set() 2559*e1fe3e4aSElliott Hughes if points: 2560*e1fe3e4aSElliott Hughes glyphs.update(points.keys()) 2561*e1fe3e4aSElliott Hughes carets = {g: buildLigGlyph(coords.get(g), points.get(g)) for g in glyphs} 2562*e1fe3e4aSElliott Hughes carets = {g: c for g, c in carets.items() if c is not None} 2563*e1fe3e4aSElliott Hughes if not carets: 2564*e1fe3e4aSElliott Hughes return None 2565*e1fe3e4aSElliott Hughes self = ot.LigCaretList() 2566*e1fe3e4aSElliott Hughes self.Coverage = buildCoverage(carets.keys(), glyphMap) 2567*e1fe3e4aSElliott Hughes self.LigGlyph = [carets[g] for g in self.Coverage.glyphs] 2568*e1fe3e4aSElliott Hughes self.LigGlyphCount = len(self.LigGlyph) 2569*e1fe3e4aSElliott Hughes return self 2570*e1fe3e4aSElliott Hughes 2571*e1fe3e4aSElliott Hughes 2572*e1fe3e4aSElliott Hughesdef buildLigGlyph(coords, points): 2573*e1fe3e4aSElliott Hughes # ([500], [4]) --> otTables.LigGlyph; None for empty coords/points 2574*e1fe3e4aSElliott Hughes carets = [] 2575*e1fe3e4aSElliott Hughes if coords: 2576*e1fe3e4aSElliott Hughes coords = sorted(coords, key=lambda c: c[0] if isinstance(c, tuple) else c) 2577*e1fe3e4aSElliott Hughes carets.extend([buildCaretValueForCoord(c) for c in coords]) 2578*e1fe3e4aSElliott Hughes if points: 2579*e1fe3e4aSElliott Hughes carets.extend([buildCaretValueForPoint(p) for p in sorted(points)]) 2580*e1fe3e4aSElliott Hughes if not carets: 2581*e1fe3e4aSElliott Hughes return None 2582*e1fe3e4aSElliott Hughes self = ot.LigGlyph() 2583*e1fe3e4aSElliott Hughes self.CaretValue = carets 2584*e1fe3e4aSElliott Hughes self.CaretCount = len(self.CaretValue) 2585*e1fe3e4aSElliott Hughes return self 2586*e1fe3e4aSElliott Hughes 2587*e1fe3e4aSElliott Hughes 2588*e1fe3e4aSElliott Hughesdef buildMarkGlyphSetsDef(markSets, glyphMap): 2589*e1fe3e4aSElliott Hughes """Builds a mark glyph sets definition table. 2590*e1fe3e4aSElliott Hughes 2591*e1fe3e4aSElliott Hughes OpenType Layout lookups may choose to use mark filtering sets to consider 2592*e1fe3e4aSElliott Hughes or ignore particular combinations of marks. These sets are specified by 2593*e1fe3e4aSElliott Hughes setting a flag on the lookup, but the mark filtering sets are defined in 2594*e1fe3e4aSElliott Hughes the ``GDEF`` table. This routine builds the subtable containing the mark 2595*e1fe3e4aSElliott Hughes glyph set definitions. 2596*e1fe3e4aSElliott Hughes 2597*e1fe3e4aSElliott Hughes Example:: 2598*e1fe3e4aSElliott Hughes 2599*e1fe3e4aSElliott Hughes set0 = set("acute", "grave") 2600*e1fe3e4aSElliott Hughes set1 = set("caron", "grave") 2601*e1fe3e4aSElliott Hughes 2602*e1fe3e4aSElliott Hughes markglyphsets = buildMarkGlyphSetsDef([set0, set1], font.getReverseGlyphMap()) 2603*e1fe3e4aSElliott Hughes 2604*e1fe3e4aSElliott Hughes Args: 2605*e1fe3e4aSElliott Hughes 2606*e1fe3e4aSElliott Hughes markSets: A list of sets of glyphnames. 2607*e1fe3e4aSElliott Hughes glyphMap: a glyph name to ID map, typically returned from 2608*e1fe3e4aSElliott Hughes ``font.getReverseGlyphMap()``. 2609*e1fe3e4aSElliott Hughes 2610*e1fe3e4aSElliott Hughes Returns 2611*e1fe3e4aSElliott Hughes An ``otTables.MarkGlyphSetsDef`` object. 2612*e1fe3e4aSElliott Hughes """ 2613*e1fe3e4aSElliott Hughes if not markSets: 2614*e1fe3e4aSElliott Hughes return None 2615*e1fe3e4aSElliott Hughes self = ot.MarkGlyphSetsDef() 2616*e1fe3e4aSElliott Hughes self.MarkSetTableFormat = 1 2617*e1fe3e4aSElliott Hughes self.Coverage = [buildCoverage(m, glyphMap) for m in markSets] 2618*e1fe3e4aSElliott Hughes self.MarkSetCount = len(self.Coverage) 2619*e1fe3e4aSElliott Hughes return self 2620*e1fe3e4aSElliott Hughes 2621*e1fe3e4aSElliott Hughes 2622*e1fe3e4aSElliott Hughesclass ClassDefBuilder(object): 2623*e1fe3e4aSElliott Hughes """Helper for building ClassDef tables.""" 2624*e1fe3e4aSElliott Hughes 2625*e1fe3e4aSElliott Hughes def __init__(self, useClass0): 2626*e1fe3e4aSElliott Hughes self.classes_ = set() 2627*e1fe3e4aSElliott Hughes self.glyphs_ = {} 2628*e1fe3e4aSElliott Hughes self.useClass0_ = useClass0 2629*e1fe3e4aSElliott Hughes 2630*e1fe3e4aSElliott Hughes def canAdd(self, glyphs): 2631*e1fe3e4aSElliott Hughes if isinstance(glyphs, (set, frozenset)): 2632*e1fe3e4aSElliott Hughes glyphs = sorted(glyphs) 2633*e1fe3e4aSElliott Hughes glyphs = tuple(glyphs) 2634*e1fe3e4aSElliott Hughes if glyphs in self.classes_: 2635*e1fe3e4aSElliott Hughes return True 2636*e1fe3e4aSElliott Hughes for glyph in glyphs: 2637*e1fe3e4aSElliott Hughes if glyph in self.glyphs_: 2638*e1fe3e4aSElliott Hughes return False 2639*e1fe3e4aSElliott Hughes return True 2640*e1fe3e4aSElliott Hughes 2641*e1fe3e4aSElliott Hughes def add(self, glyphs): 2642*e1fe3e4aSElliott Hughes if isinstance(glyphs, (set, frozenset)): 2643*e1fe3e4aSElliott Hughes glyphs = sorted(glyphs) 2644*e1fe3e4aSElliott Hughes glyphs = tuple(glyphs) 2645*e1fe3e4aSElliott Hughes if glyphs in self.classes_: 2646*e1fe3e4aSElliott Hughes return 2647*e1fe3e4aSElliott Hughes self.classes_.add(glyphs) 2648*e1fe3e4aSElliott Hughes for glyph in glyphs: 2649*e1fe3e4aSElliott Hughes if glyph in self.glyphs_: 2650*e1fe3e4aSElliott Hughes raise OpenTypeLibError( 2651*e1fe3e4aSElliott Hughes f"Glyph {glyph} is already present in class.", None 2652*e1fe3e4aSElliott Hughes ) 2653*e1fe3e4aSElliott Hughes self.glyphs_[glyph] = glyphs 2654*e1fe3e4aSElliott Hughes 2655*e1fe3e4aSElliott Hughes def classes(self): 2656*e1fe3e4aSElliott Hughes # In ClassDef1 tables, class id #0 does not need to be encoded 2657*e1fe3e4aSElliott Hughes # because zero is the default. Therefore, we use id #0 for the 2658*e1fe3e4aSElliott Hughes # glyph class that has the largest number of members. However, 2659*e1fe3e4aSElliott Hughes # in other tables than ClassDef1, 0 means "every other glyph" 2660*e1fe3e4aSElliott Hughes # so we should not use that ID for any real glyph classes; 2661*e1fe3e4aSElliott Hughes # we implement this by inserting an empty set at position 0. 2662*e1fe3e4aSElliott Hughes # 2663*e1fe3e4aSElliott Hughes # TODO: Instead of counting the number of glyphs in each class, 2664*e1fe3e4aSElliott Hughes # we should determine the encoded size. If the glyphs in a large 2665*e1fe3e4aSElliott Hughes # class form a contiguous range, the encoding is actually quite 2666*e1fe3e4aSElliott Hughes # compact, whereas a non-contiguous set might need a lot of bytes 2667*e1fe3e4aSElliott Hughes # in the output file. We don't get this right with the key below. 2668*e1fe3e4aSElliott Hughes result = sorted(self.classes_, key=lambda s: (-len(s), s)) 2669*e1fe3e4aSElliott Hughes if not self.useClass0_: 2670*e1fe3e4aSElliott Hughes result.insert(0, frozenset()) 2671*e1fe3e4aSElliott Hughes return result 2672*e1fe3e4aSElliott Hughes 2673*e1fe3e4aSElliott Hughes def build(self): 2674*e1fe3e4aSElliott Hughes glyphClasses = {} 2675*e1fe3e4aSElliott Hughes for classID, glyphs in enumerate(self.classes()): 2676*e1fe3e4aSElliott Hughes if classID == 0: 2677*e1fe3e4aSElliott Hughes continue 2678*e1fe3e4aSElliott Hughes for glyph in glyphs: 2679*e1fe3e4aSElliott Hughes glyphClasses[glyph] = classID 2680*e1fe3e4aSElliott Hughes classDef = ot.ClassDef() 2681*e1fe3e4aSElliott Hughes classDef.classDefs = glyphClasses 2682*e1fe3e4aSElliott Hughes return classDef 2683*e1fe3e4aSElliott Hughes 2684*e1fe3e4aSElliott Hughes 2685*e1fe3e4aSElliott HughesAXIS_VALUE_NEGATIVE_INFINITY = fixedToFloat(-0x80000000, 16) 2686*e1fe3e4aSElliott HughesAXIS_VALUE_POSITIVE_INFINITY = fixedToFloat(0x7FFFFFFF, 16) 2687*e1fe3e4aSElliott Hughes 2688*e1fe3e4aSElliott Hughes 2689*e1fe3e4aSElliott Hughesdef buildStatTable( 2690*e1fe3e4aSElliott Hughes ttFont, axes, locations=None, elidedFallbackName=2, windowsNames=True, macNames=True 2691*e1fe3e4aSElliott Hughes): 2692*e1fe3e4aSElliott Hughes """Add a 'STAT' table to 'ttFont'. 2693*e1fe3e4aSElliott Hughes 2694*e1fe3e4aSElliott Hughes 'axes' is a list of dictionaries describing axes and their 2695*e1fe3e4aSElliott Hughes values. 2696*e1fe3e4aSElliott Hughes 2697*e1fe3e4aSElliott Hughes Example:: 2698*e1fe3e4aSElliott Hughes 2699*e1fe3e4aSElliott Hughes axes = [ 2700*e1fe3e4aSElliott Hughes dict( 2701*e1fe3e4aSElliott Hughes tag="wght", 2702*e1fe3e4aSElliott Hughes name="Weight", 2703*e1fe3e4aSElliott Hughes ordering=0, # optional 2704*e1fe3e4aSElliott Hughes values=[ 2705*e1fe3e4aSElliott Hughes dict(value=100, name='Thin'), 2706*e1fe3e4aSElliott Hughes dict(value=300, name='Light'), 2707*e1fe3e4aSElliott Hughes dict(value=400, name='Regular', flags=0x2), 2708*e1fe3e4aSElliott Hughes dict(value=900, name='Black'), 2709*e1fe3e4aSElliott Hughes ], 2710*e1fe3e4aSElliott Hughes ) 2711*e1fe3e4aSElliott Hughes ] 2712*e1fe3e4aSElliott Hughes 2713*e1fe3e4aSElliott Hughes Each axis dict must have 'tag' and 'name' items. 'tag' maps 2714*e1fe3e4aSElliott Hughes to the 'AxisTag' field. 'name' can be a name ID (int), a string, 2715*e1fe3e4aSElliott Hughes or a dictionary containing multilingual names (see the 2716*e1fe3e4aSElliott Hughes addMultilingualName() name table method), and will translate to 2717*e1fe3e4aSElliott Hughes the AxisNameID field. 2718*e1fe3e4aSElliott Hughes 2719*e1fe3e4aSElliott Hughes An axis dict may contain an 'ordering' item that maps to the 2720*e1fe3e4aSElliott Hughes AxisOrdering field. If omitted, the order of the axes list is 2721*e1fe3e4aSElliott Hughes used to calculate AxisOrdering fields. 2722*e1fe3e4aSElliott Hughes 2723*e1fe3e4aSElliott Hughes The axis dict may contain a 'values' item, which is a list of 2724*e1fe3e4aSElliott Hughes dictionaries describing AxisValue records belonging to this axis. 2725*e1fe3e4aSElliott Hughes 2726*e1fe3e4aSElliott Hughes Each value dict must have a 'name' item, which can be a name ID 2727*e1fe3e4aSElliott Hughes (int), a string, or a dictionary containing multilingual names, 2728*e1fe3e4aSElliott Hughes like the axis name. It translates to the ValueNameID field. 2729*e1fe3e4aSElliott Hughes 2730*e1fe3e4aSElliott Hughes Optionally the value dict can contain a 'flags' item. It maps to 2731*e1fe3e4aSElliott Hughes the AxisValue Flags field, and will be 0 when omitted. 2732*e1fe3e4aSElliott Hughes 2733*e1fe3e4aSElliott Hughes The format of the AxisValue is determined by the remaining contents 2734*e1fe3e4aSElliott Hughes of the value dictionary: 2735*e1fe3e4aSElliott Hughes 2736*e1fe3e4aSElliott Hughes If the value dict contains a 'value' item, an AxisValue record 2737*e1fe3e4aSElliott Hughes Format 1 is created. If in addition to the 'value' item it contains 2738*e1fe3e4aSElliott Hughes a 'linkedValue' item, an AxisValue record Format 3 is built. 2739*e1fe3e4aSElliott Hughes 2740*e1fe3e4aSElliott Hughes If the value dict contains a 'nominalValue' item, an AxisValue 2741*e1fe3e4aSElliott Hughes record Format 2 is built. Optionally it may contain 'rangeMinValue' 2742*e1fe3e4aSElliott Hughes and 'rangeMaxValue' items. These map to -Infinity and +Infinity 2743*e1fe3e4aSElliott Hughes respectively if omitted. 2744*e1fe3e4aSElliott Hughes 2745*e1fe3e4aSElliott Hughes You cannot specify Format 4 AxisValue tables this way, as they are 2746*e1fe3e4aSElliott Hughes not tied to a single axis, and specify a name for a location that 2747*e1fe3e4aSElliott Hughes is defined by multiple axes values. Instead, you need to supply the 2748*e1fe3e4aSElliott Hughes 'locations' argument. 2749*e1fe3e4aSElliott Hughes 2750*e1fe3e4aSElliott Hughes The optional 'locations' argument specifies AxisValue Format 4 2751*e1fe3e4aSElliott Hughes tables. It should be a list of dicts, where each dict has a 'name' 2752*e1fe3e4aSElliott Hughes item, which works just like the value dicts above, an optional 2753*e1fe3e4aSElliott Hughes 'flags' item (defaulting to 0x0), and a 'location' dict. A 2754*e1fe3e4aSElliott Hughes location dict key is an axis tag, and the associated value is the 2755*e1fe3e4aSElliott Hughes location on the specified axis. They map to the AxisIndex and Value 2756*e1fe3e4aSElliott Hughes fields of the AxisValueRecord. 2757*e1fe3e4aSElliott Hughes 2758*e1fe3e4aSElliott Hughes Example:: 2759*e1fe3e4aSElliott Hughes 2760*e1fe3e4aSElliott Hughes locations = [ 2761*e1fe3e4aSElliott Hughes dict(name='Regular ABCD', location=dict(wght=300, ABCD=100)), 2762*e1fe3e4aSElliott Hughes dict(name='Bold ABCD XYZ', location=dict(wght=600, ABCD=200)), 2763*e1fe3e4aSElliott Hughes ] 2764*e1fe3e4aSElliott Hughes 2765*e1fe3e4aSElliott Hughes The optional 'elidedFallbackName' argument can be a name ID (int), 2766*e1fe3e4aSElliott Hughes a string, a dictionary containing multilingual names, or a list of 2767*e1fe3e4aSElliott Hughes STATNameStatements. It translates to the ElidedFallbackNameID field. 2768*e1fe3e4aSElliott Hughes 2769*e1fe3e4aSElliott Hughes The 'ttFont' argument must be a TTFont instance that already has a 2770*e1fe3e4aSElliott Hughes 'name' table. If a 'STAT' table already exists, it will be 2771*e1fe3e4aSElliott Hughes overwritten by the newly created one. 2772*e1fe3e4aSElliott Hughes """ 2773*e1fe3e4aSElliott Hughes ttFont["STAT"] = ttLib.newTable("STAT") 2774*e1fe3e4aSElliott Hughes statTable = ttFont["STAT"].table = ot.STAT() 2775*e1fe3e4aSElliott Hughes statTable.ElidedFallbackNameID = _addName( 2776*e1fe3e4aSElliott Hughes ttFont, elidedFallbackName, windows=windowsNames, mac=macNames 2777*e1fe3e4aSElliott Hughes ) 2778*e1fe3e4aSElliott Hughes 2779*e1fe3e4aSElliott Hughes # 'locations' contains data for AxisValue Format 4 2780*e1fe3e4aSElliott Hughes axisRecords, axisValues = _buildAxisRecords( 2781*e1fe3e4aSElliott Hughes axes, ttFont, windowsNames=windowsNames, macNames=macNames 2782*e1fe3e4aSElliott Hughes ) 2783*e1fe3e4aSElliott Hughes if not locations: 2784*e1fe3e4aSElliott Hughes statTable.Version = 0x00010001 2785*e1fe3e4aSElliott Hughes else: 2786*e1fe3e4aSElliott Hughes # We'll be adding Format 4 AxisValue records, which 2787*e1fe3e4aSElliott Hughes # requires a higher table version 2788*e1fe3e4aSElliott Hughes statTable.Version = 0x00010002 2789*e1fe3e4aSElliott Hughes multiAxisValues = _buildAxisValuesFormat4( 2790*e1fe3e4aSElliott Hughes locations, axes, ttFont, windowsNames=windowsNames, macNames=macNames 2791*e1fe3e4aSElliott Hughes ) 2792*e1fe3e4aSElliott Hughes axisValues = multiAxisValues + axisValues 2793*e1fe3e4aSElliott Hughes ttFont["name"].names.sort() 2794*e1fe3e4aSElliott Hughes 2795*e1fe3e4aSElliott Hughes # Store AxisRecords 2796*e1fe3e4aSElliott Hughes axisRecordArray = ot.AxisRecordArray() 2797*e1fe3e4aSElliott Hughes axisRecordArray.Axis = axisRecords 2798*e1fe3e4aSElliott Hughes # XXX these should not be hard-coded but computed automatically 2799*e1fe3e4aSElliott Hughes statTable.DesignAxisRecordSize = 8 2800*e1fe3e4aSElliott Hughes statTable.DesignAxisRecord = axisRecordArray 2801*e1fe3e4aSElliott Hughes statTable.DesignAxisCount = len(axisRecords) 2802*e1fe3e4aSElliott Hughes 2803*e1fe3e4aSElliott Hughes statTable.AxisValueCount = 0 2804*e1fe3e4aSElliott Hughes statTable.AxisValueArray = None 2805*e1fe3e4aSElliott Hughes if axisValues: 2806*e1fe3e4aSElliott Hughes # Store AxisValueRecords 2807*e1fe3e4aSElliott Hughes axisValueArray = ot.AxisValueArray() 2808*e1fe3e4aSElliott Hughes axisValueArray.AxisValue = axisValues 2809*e1fe3e4aSElliott Hughes statTable.AxisValueArray = axisValueArray 2810*e1fe3e4aSElliott Hughes statTable.AxisValueCount = len(axisValues) 2811*e1fe3e4aSElliott Hughes 2812*e1fe3e4aSElliott Hughes 2813*e1fe3e4aSElliott Hughesdef _buildAxisRecords(axes, ttFont, windowsNames=True, macNames=True): 2814*e1fe3e4aSElliott Hughes axisRecords = [] 2815*e1fe3e4aSElliott Hughes axisValues = [] 2816*e1fe3e4aSElliott Hughes for axisRecordIndex, axisDict in enumerate(axes): 2817*e1fe3e4aSElliott Hughes axis = ot.AxisRecord() 2818*e1fe3e4aSElliott Hughes axis.AxisTag = axisDict["tag"] 2819*e1fe3e4aSElliott Hughes axis.AxisNameID = _addName( 2820*e1fe3e4aSElliott Hughes ttFont, axisDict["name"], 256, windows=windowsNames, mac=macNames 2821*e1fe3e4aSElliott Hughes ) 2822*e1fe3e4aSElliott Hughes axis.AxisOrdering = axisDict.get("ordering", axisRecordIndex) 2823*e1fe3e4aSElliott Hughes axisRecords.append(axis) 2824*e1fe3e4aSElliott Hughes 2825*e1fe3e4aSElliott Hughes for axisVal in axisDict.get("values", ()): 2826*e1fe3e4aSElliott Hughes axisValRec = ot.AxisValue() 2827*e1fe3e4aSElliott Hughes axisValRec.AxisIndex = axisRecordIndex 2828*e1fe3e4aSElliott Hughes axisValRec.Flags = axisVal.get("flags", 0) 2829*e1fe3e4aSElliott Hughes axisValRec.ValueNameID = _addName( 2830*e1fe3e4aSElliott Hughes ttFont, axisVal["name"], windows=windowsNames, mac=macNames 2831*e1fe3e4aSElliott Hughes ) 2832*e1fe3e4aSElliott Hughes 2833*e1fe3e4aSElliott Hughes if "value" in axisVal: 2834*e1fe3e4aSElliott Hughes axisValRec.Value = axisVal["value"] 2835*e1fe3e4aSElliott Hughes if "linkedValue" in axisVal: 2836*e1fe3e4aSElliott Hughes axisValRec.Format = 3 2837*e1fe3e4aSElliott Hughes axisValRec.LinkedValue = axisVal["linkedValue"] 2838*e1fe3e4aSElliott Hughes else: 2839*e1fe3e4aSElliott Hughes axisValRec.Format = 1 2840*e1fe3e4aSElliott Hughes elif "nominalValue" in axisVal: 2841*e1fe3e4aSElliott Hughes axisValRec.Format = 2 2842*e1fe3e4aSElliott Hughes axisValRec.NominalValue = axisVal["nominalValue"] 2843*e1fe3e4aSElliott Hughes axisValRec.RangeMinValue = axisVal.get( 2844*e1fe3e4aSElliott Hughes "rangeMinValue", AXIS_VALUE_NEGATIVE_INFINITY 2845*e1fe3e4aSElliott Hughes ) 2846*e1fe3e4aSElliott Hughes axisValRec.RangeMaxValue = axisVal.get( 2847*e1fe3e4aSElliott Hughes "rangeMaxValue", AXIS_VALUE_POSITIVE_INFINITY 2848*e1fe3e4aSElliott Hughes ) 2849*e1fe3e4aSElliott Hughes else: 2850*e1fe3e4aSElliott Hughes raise ValueError("Can't determine format for AxisValue") 2851*e1fe3e4aSElliott Hughes 2852*e1fe3e4aSElliott Hughes axisValues.append(axisValRec) 2853*e1fe3e4aSElliott Hughes return axisRecords, axisValues 2854*e1fe3e4aSElliott Hughes 2855*e1fe3e4aSElliott Hughes 2856*e1fe3e4aSElliott Hughesdef _buildAxisValuesFormat4(locations, axes, ttFont, windowsNames=True, macNames=True): 2857*e1fe3e4aSElliott Hughes axisTagToIndex = {} 2858*e1fe3e4aSElliott Hughes for axisRecordIndex, axisDict in enumerate(axes): 2859*e1fe3e4aSElliott Hughes axisTagToIndex[axisDict["tag"]] = axisRecordIndex 2860*e1fe3e4aSElliott Hughes 2861*e1fe3e4aSElliott Hughes axisValues = [] 2862*e1fe3e4aSElliott Hughes for axisLocationDict in locations: 2863*e1fe3e4aSElliott Hughes axisValRec = ot.AxisValue() 2864*e1fe3e4aSElliott Hughes axisValRec.Format = 4 2865*e1fe3e4aSElliott Hughes axisValRec.ValueNameID = _addName( 2866*e1fe3e4aSElliott Hughes ttFont, axisLocationDict["name"], windows=windowsNames, mac=macNames 2867*e1fe3e4aSElliott Hughes ) 2868*e1fe3e4aSElliott Hughes axisValRec.Flags = axisLocationDict.get("flags", 0) 2869*e1fe3e4aSElliott Hughes axisValueRecords = [] 2870*e1fe3e4aSElliott Hughes for tag, value in axisLocationDict["location"].items(): 2871*e1fe3e4aSElliott Hughes avr = ot.AxisValueRecord() 2872*e1fe3e4aSElliott Hughes avr.AxisIndex = axisTagToIndex[tag] 2873*e1fe3e4aSElliott Hughes avr.Value = value 2874*e1fe3e4aSElliott Hughes axisValueRecords.append(avr) 2875*e1fe3e4aSElliott Hughes axisValueRecords.sort(key=lambda avr: avr.AxisIndex) 2876*e1fe3e4aSElliott Hughes axisValRec.AxisCount = len(axisValueRecords) 2877*e1fe3e4aSElliott Hughes axisValRec.AxisValueRecord = axisValueRecords 2878*e1fe3e4aSElliott Hughes axisValues.append(axisValRec) 2879*e1fe3e4aSElliott Hughes return axisValues 2880*e1fe3e4aSElliott Hughes 2881*e1fe3e4aSElliott Hughes 2882*e1fe3e4aSElliott Hughesdef _addName(ttFont, value, minNameID=0, windows=True, mac=True): 2883*e1fe3e4aSElliott Hughes nameTable = ttFont["name"] 2884*e1fe3e4aSElliott Hughes if isinstance(value, int): 2885*e1fe3e4aSElliott Hughes # Already a nameID 2886*e1fe3e4aSElliott Hughes return value 2887*e1fe3e4aSElliott Hughes if isinstance(value, str): 2888*e1fe3e4aSElliott Hughes names = dict(en=value) 2889*e1fe3e4aSElliott Hughes elif isinstance(value, dict): 2890*e1fe3e4aSElliott Hughes names = value 2891*e1fe3e4aSElliott Hughes elif isinstance(value, list): 2892*e1fe3e4aSElliott Hughes nameID = nameTable._findUnusedNameID() 2893*e1fe3e4aSElliott Hughes for nameRecord in value: 2894*e1fe3e4aSElliott Hughes if isinstance(nameRecord, STATNameStatement): 2895*e1fe3e4aSElliott Hughes nameTable.setName( 2896*e1fe3e4aSElliott Hughes nameRecord.string, 2897*e1fe3e4aSElliott Hughes nameID, 2898*e1fe3e4aSElliott Hughes nameRecord.platformID, 2899*e1fe3e4aSElliott Hughes nameRecord.platEncID, 2900*e1fe3e4aSElliott Hughes nameRecord.langID, 2901*e1fe3e4aSElliott Hughes ) 2902*e1fe3e4aSElliott Hughes else: 2903*e1fe3e4aSElliott Hughes raise TypeError("value must be a list of STATNameStatements") 2904*e1fe3e4aSElliott Hughes return nameID 2905*e1fe3e4aSElliott Hughes else: 2906*e1fe3e4aSElliott Hughes raise TypeError("value must be int, str, dict or list") 2907*e1fe3e4aSElliott Hughes return nameTable.addMultilingualName( 2908*e1fe3e4aSElliott Hughes names, ttFont=ttFont, windows=windows, mac=mac, minNameID=minNameID 2909*e1fe3e4aSElliott Hughes ) 2910*e1fe3e4aSElliott Hughes 2911*e1fe3e4aSElliott Hughes 2912*e1fe3e4aSElliott Hughesdef buildMathTable( 2913*e1fe3e4aSElliott Hughes ttFont, 2914*e1fe3e4aSElliott Hughes constants=None, 2915*e1fe3e4aSElliott Hughes italicsCorrections=None, 2916*e1fe3e4aSElliott Hughes topAccentAttachments=None, 2917*e1fe3e4aSElliott Hughes extendedShapes=None, 2918*e1fe3e4aSElliott Hughes mathKerns=None, 2919*e1fe3e4aSElliott Hughes minConnectorOverlap=0, 2920*e1fe3e4aSElliott Hughes vertGlyphVariants=None, 2921*e1fe3e4aSElliott Hughes horizGlyphVariants=None, 2922*e1fe3e4aSElliott Hughes vertGlyphAssembly=None, 2923*e1fe3e4aSElliott Hughes horizGlyphAssembly=None, 2924*e1fe3e4aSElliott Hughes): 2925*e1fe3e4aSElliott Hughes """ 2926*e1fe3e4aSElliott Hughes Add a 'MATH' table to 'ttFont'. 2927*e1fe3e4aSElliott Hughes 2928*e1fe3e4aSElliott Hughes 'constants' is a dictionary of math constants. The keys are the constant 2929*e1fe3e4aSElliott Hughes names from the MATH table specification (with capital first letter), and the 2930*e1fe3e4aSElliott Hughes values are the constant values as numbers. 2931*e1fe3e4aSElliott Hughes 2932*e1fe3e4aSElliott Hughes 'italicsCorrections' is a dictionary of italic corrections. The keys are the 2933*e1fe3e4aSElliott Hughes glyph names, and the values are the italic corrections as numbers. 2934*e1fe3e4aSElliott Hughes 2935*e1fe3e4aSElliott Hughes 'topAccentAttachments' is a dictionary of top accent attachments. The keys 2936*e1fe3e4aSElliott Hughes are the glyph names, and the values are the top accent horizontal positions 2937*e1fe3e4aSElliott Hughes as numbers. 2938*e1fe3e4aSElliott Hughes 2939*e1fe3e4aSElliott Hughes 'extendedShapes' is a set of extended shape glyphs. 2940*e1fe3e4aSElliott Hughes 2941*e1fe3e4aSElliott Hughes 'mathKerns' is a dictionary of math kerns. The keys are the glyph names, and 2942*e1fe3e4aSElliott Hughes the values are dictionaries. The keys of these dictionaries are the side 2943*e1fe3e4aSElliott Hughes names ('TopRight', 'TopLeft', 'BottomRight', 'BottomLeft'), and the values 2944*e1fe3e4aSElliott Hughes are tuples of two lists. The first list contains the correction heights as 2945*e1fe3e4aSElliott Hughes numbers, and the second list contains the kern values as numbers. 2946*e1fe3e4aSElliott Hughes 2947*e1fe3e4aSElliott Hughes 'minConnectorOverlap' is the minimum connector overlap as a number. 2948*e1fe3e4aSElliott Hughes 2949*e1fe3e4aSElliott Hughes 'vertGlyphVariants' is a dictionary of vertical glyph variants. The keys are 2950*e1fe3e4aSElliott Hughes the glyph names, and the values are tuples of glyph name and full advance height. 2951*e1fe3e4aSElliott Hughes 2952*e1fe3e4aSElliott Hughes 'horizGlyphVariants' is a dictionary of horizontal glyph variants. The keys 2953*e1fe3e4aSElliott Hughes are the glyph names, and the values are tuples of glyph name and full 2954*e1fe3e4aSElliott Hughes advance width. 2955*e1fe3e4aSElliott Hughes 2956*e1fe3e4aSElliott Hughes 'vertGlyphAssembly' is a dictionary of vertical glyph assemblies. The keys 2957*e1fe3e4aSElliott Hughes are the glyph names, and the values are tuples of assembly parts and italics 2958*e1fe3e4aSElliott Hughes correction. The assembly parts are tuples of glyph name, flags, start 2959*e1fe3e4aSElliott Hughes connector length, end connector length, and full advance height. 2960*e1fe3e4aSElliott Hughes 2961*e1fe3e4aSElliott Hughes 'horizGlyphAssembly' is a dictionary of horizontal glyph assemblies. The 2962*e1fe3e4aSElliott Hughes keys are the glyph names, and the values are tuples of assembly parts 2963*e1fe3e4aSElliott Hughes and italics correction. The assembly parts are tuples of glyph name, flags, 2964*e1fe3e4aSElliott Hughes start connector length, end connector length, and full advance width. 2965*e1fe3e4aSElliott Hughes 2966*e1fe3e4aSElliott Hughes Where a number is expected, an integer or a float can be used. The floats 2967*e1fe3e4aSElliott Hughes will be rounded. 2968*e1fe3e4aSElliott Hughes 2969*e1fe3e4aSElliott Hughes Example:: 2970*e1fe3e4aSElliott Hughes 2971*e1fe3e4aSElliott Hughes constants = { 2972*e1fe3e4aSElliott Hughes "ScriptPercentScaleDown": 70, 2973*e1fe3e4aSElliott Hughes "ScriptScriptPercentScaleDown": 50, 2974*e1fe3e4aSElliott Hughes "DelimitedSubFormulaMinHeight": 24, 2975*e1fe3e4aSElliott Hughes "DisplayOperatorMinHeight": 60, 2976*e1fe3e4aSElliott Hughes ... 2977*e1fe3e4aSElliott Hughes } 2978*e1fe3e4aSElliott Hughes italicsCorrections = { 2979*e1fe3e4aSElliott Hughes "fitalic-math": 100, 2980*e1fe3e4aSElliott Hughes "fbolditalic-math": 120, 2981*e1fe3e4aSElliott Hughes ... 2982*e1fe3e4aSElliott Hughes } 2983*e1fe3e4aSElliott Hughes topAccentAttachments = { 2984*e1fe3e4aSElliott Hughes "circumflexcomb": 500, 2985*e1fe3e4aSElliott Hughes "acutecomb": 400, 2986*e1fe3e4aSElliott Hughes "A": 300, 2987*e1fe3e4aSElliott Hughes "B": 340, 2988*e1fe3e4aSElliott Hughes ... 2989*e1fe3e4aSElliott Hughes } 2990*e1fe3e4aSElliott Hughes extendedShapes = {"parenleft", "parenright", ...} 2991*e1fe3e4aSElliott Hughes mathKerns = { 2992*e1fe3e4aSElliott Hughes "A": { 2993*e1fe3e4aSElliott Hughes "TopRight": ([-50, -100], [10, 20, 30]), 2994*e1fe3e4aSElliott Hughes "TopLeft": ([50, 100], [10, 20, 30]), 2995*e1fe3e4aSElliott Hughes ... 2996*e1fe3e4aSElliott Hughes }, 2997*e1fe3e4aSElliott Hughes ... 2998*e1fe3e4aSElliott Hughes } 2999*e1fe3e4aSElliott Hughes vertGlyphVariants = { 3000*e1fe3e4aSElliott Hughes "parenleft": [("parenleft", 700), ("parenleft.size1", 1000), ...], 3001*e1fe3e4aSElliott Hughes "parenright": [("parenright", 700), ("parenright.size1", 1000), ...], 3002*e1fe3e4aSElliott Hughes ... 3003*e1fe3e4aSElliott Hughes } 3004*e1fe3e4aSElliott Hughes vertGlyphAssembly = { 3005*e1fe3e4aSElliott Hughes "braceleft": [ 3006*e1fe3e4aSElliott Hughes ( 3007*e1fe3e4aSElliott Hughes ("braceleft.bottom", 0, 0, 200, 500), 3008*e1fe3e4aSElliott Hughes ("braceleft.extender", 1, 200, 200, 200)), 3009*e1fe3e4aSElliott Hughes ("braceleft.middle", 0, 100, 100, 700), 3010*e1fe3e4aSElliott Hughes ("braceleft.extender", 1, 200, 200, 200), 3011*e1fe3e4aSElliott Hughes ("braceleft.top", 0, 200, 0, 500), 3012*e1fe3e4aSElliott Hughes ), 3013*e1fe3e4aSElliott Hughes 100, 3014*e1fe3e4aSElliott Hughes ], 3015*e1fe3e4aSElliott Hughes ... 3016*e1fe3e4aSElliott Hughes } 3017*e1fe3e4aSElliott Hughes """ 3018*e1fe3e4aSElliott Hughes glyphMap = ttFont.getReverseGlyphMap() 3019*e1fe3e4aSElliott Hughes 3020*e1fe3e4aSElliott Hughes ttFont["MATH"] = math = ttLib.newTable("MATH") 3021*e1fe3e4aSElliott Hughes math.table = table = ot.MATH() 3022*e1fe3e4aSElliott Hughes table.Version = 0x00010000 3023*e1fe3e4aSElliott Hughes table.populateDefaults() 3024*e1fe3e4aSElliott Hughes 3025*e1fe3e4aSElliott Hughes table.MathConstants = _buildMathConstants(constants) 3026*e1fe3e4aSElliott Hughes table.MathGlyphInfo = _buildMathGlyphInfo( 3027*e1fe3e4aSElliott Hughes glyphMap, 3028*e1fe3e4aSElliott Hughes italicsCorrections, 3029*e1fe3e4aSElliott Hughes topAccentAttachments, 3030*e1fe3e4aSElliott Hughes extendedShapes, 3031*e1fe3e4aSElliott Hughes mathKerns, 3032*e1fe3e4aSElliott Hughes ) 3033*e1fe3e4aSElliott Hughes table.MathVariants = _buildMathVariants( 3034*e1fe3e4aSElliott Hughes glyphMap, 3035*e1fe3e4aSElliott Hughes minConnectorOverlap, 3036*e1fe3e4aSElliott Hughes vertGlyphVariants, 3037*e1fe3e4aSElliott Hughes horizGlyphVariants, 3038*e1fe3e4aSElliott Hughes vertGlyphAssembly, 3039*e1fe3e4aSElliott Hughes horizGlyphAssembly, 3040*e1fe3e4aSElliott Hughes ) 3041*e1fe3e4aSElliott Hughes 3042*e1fe3e4aSElliott Hughes 3043*e1fe3e4aSElliott Hughesdef _buildMathConstants(constants): 3044*e1fe3e4aSElliott Hughes if not constants: 3045*e1fe3e4aSElliott Hughes return None 3046*e1fe3e4aSElliott Hughes 3047*e1fe3e4aSElliott Hughes mathConstants = ot.MathConstants() 3048*e1fe3e4aSElliott Hughes for conv in mathConstants.getConverters(): 3049*e1fe3e4aSElliott Hughes value = otRound(constants.get(conv.name, 0)) 3050*e1fe3e4aSElliott Hughes if conv.tableClass: 3051*e1fe3e4aSElliott Hughes assert issubclass(conv.tableClass, ot.MathValueRecord) 3052*e1fe3e4aSElliott Hughes value = _mathValueRecord(value) 3053*e1fe3e4aSElliott Hughes setattr(mathConstants, conv.name, value) 3054*e1fe3e4aSElliott Hughes return mathConstants 3055*e1fe3e4aSElliott Hughes 3056*e1fe3e4aSElliott Hughes 3057*e1fe3e4aSElliott Hughesdef _buildMathGlyphInfo( 3058*e1fe3e4aSElliott Hughes glyphMap, 3059*e1fe3e4aSElliott Hughes italicsCorrections, 3060*e1fe3e4aSElliott Hughes topAccentAttachments, 3061*e1fe3e4aSElliott Hughes extendedShapes, 3062*e1fe3e4aSElliott Hughes mathKerns, 3063*e1fe3e4aSElliott Hughes): 3064*e1fe3e4aSElliott Hughes if not any([extendedShapes, italicsCorrections, topAccentAttachments, mathKerns]): 3065*e1fe3e4aSElliott Hughes return None 3066*e1fe3e4aSElliott Hughes 3067*e1fe3e4aSElliott Hughes info = ot.MathGlyphInfo() 3068*e1fe3e4aSElliott Hughes info.populateDefaults() 3069*e1fe3e4aSElliott Hughes 3070*e1fe3e4aSElliott Hughes if italicsCorrections: 3071*e1fe3e4aSElliott Hughes coverage = buildCoverage(italicsCorrections.keys(), glyphMap) 3072*e1fe3e4aSElliott Hughes info.MathItalicsCorrectionInfo = ot.MathItalicsCorrectionInfo() 3073*e1fe3e4aSElliott Hughes info.MathItalicsCorrectionInfo.Coverage = coverage 3074*e1fe3e4aSElliott Hughes info.MathItalicsCorrectionInfo.ItalicsCorrectionCount = len(coverage.glyphs) 3075*e1fe3e4aSElliott Hughes info.MathItalicsCorrectionInfo.ItalicsCorrection = [ 3076*e1fe3e4aSElliott Hughes _mathValueRecord(italicsCorrections[n]) for n in coverage.glyphs 3077*e1fe3e4aSElliott Hughes ] 3078*e1fe3e4aSElliott Hughes 3079*e1fe3e4aSElliott Hughes if topAccentAttachments: 3080*e1fe3e4aSElliott Hughes coverage = buildCoverage(topAccentAttachments.keys(), glyphMap) 3081*e1fe3e4aSElliott Hughes info.MathTopAccentAttachment = ot.MathTopAccentAttachment() 3082*e1fe3e4aSElliott Hughes info.MathTopAccentAttachment.TopAccentCoverage = coverage 3083*e1fe3e4aSElliott Hughes info.MathTopAccentAttachment.TopAccentAttachmentCount = len(coverage.glyphs) 3084*e1fe3e4aSElliott Hughes info.MathTopAccentAttachment.TopAccentAttachment = [ 3085*e1fe3e4aSElliott Hughes _mathValueRecord(topAccentAttachments[n]) for n in coverage.glyphs 3086*e1fe3e4aSElliott Hughes ] 3087*e1fe3e4aSElliott Hughes 3088*e1fe3e4aSElliott Hughes if extendedShapes: 3089*e1fe3e4aSElliott Hughes info.ExtendedShapeCoverage = buildCoverage(extendedShapes, glyphMap) 3090*e1fe3e4aSElliott Hughes 3091*e1fe3e4aSElliott Hughes if mathKerns: 3092*e1fe3e4aSElliott Hughes coverage = buildCoverage(mathKerns.keys(), glyphMap) 3093*e1fe3e4aSElliott Hughes info.MathKernInfo = ot.MathKernInfo() 3094*e1fe3e4aSElliott Hughes info.MathKernInfo.MathKernCoverage = coverage 3095*e1fe3e4aSElliott Hughes info.MathKernInfo.MathKernCount = len(coverage.glyphs) 3096*e1fe3e4aSElliott Hughes info.MathKernInfo.MathKernInfoRecords = [] 3097*e1fe3e4aSElliott Hughes for glyph in coverage.glyphs: 3098*e1fe3e4aSElliott Hughes record = ot.MathKernInfoRecord() 3099*e1fe3e4aSElliott Hughes for side in {"TopRight", "TopLeft", "BottomRight", "BottomLeft"}: 3100*e1fe3e4aSElliott Hughes if side in mathKerns[glyph]: 3101*e1fe3e4aSElliott Hughes correctionHeights, kernValues = mathKerns[glyph][side] 3102*e1fe3e4aSElliott Hughes assert len(correctionHeights) == len(kernValues) - 1 3103*e1fe3e4aSElliott Hughes kern = ot.MathKern() 3104*e1fe3e4aSElliott Hughes kern.HeightCount = len(correctionHeights) 3105*e1fe3e4aSElliott Hughes kern.CorrectionHeight = [ 3106*e1fe3e4aSElliott Hughes _mathValueRecord(h) for h in correctionHeights 3107*e1fe3e4aSElliott Hughes ] 3108*e1fe3e4aSElliott Hughes kern.KernValue = [_mathValueRecord(v) for v in kernValues] 3109*e1fe3e4aSElliott Hughes setattr(record, f"{side}MathKern", kern) 3110*e1fe3e4aSElliott Hughes info.MathKernInfo.MathKernInfoRecords.append(record) 3111*e1fe3e4aSElliott Hughes 3112*e1fe3e4aSElliott Hughes return info 3113*e1fe3e4aSElliott Hughes 3114*e1fe3e4aSElliott Hughes 3115*e1fe3e4aSElliott Hughesdef _buildMathVariants( 3116*e1fe3e4aSElliott Hughes glyphMap, 3117*e1fe3e4aSElliott Hughes minConnectorOverlap, 3118*e1fe3e4aSElliott Hughes vertGlyphVariants, 3119*e1fe3e4aSElliott Hughes horizGlyphVariants, 3120*e1fe3e4aSElliott Hughes vertGlyphAssembly, 3121*e1fe3e4aSElliott Hughes horizGlyphAssembly, 3122*e1fe3e4aSElliott Hughes): 3123*e1fe3e4aSElliott Hughes if not any( 3124*e1fe3e4aSElliott Hughes [vertGlyphVariants, horizGlyphVariants, vertGlyphAssembly, horizGlyphAssembly] 3125*e1fe3e4aSElliott Hughes ): 3126*e1fe3e4aSElliott Hughes return None 3127*e1fe3e4aSElliott Hughes 3128*e1fe3e4aSElliott Hughes variants = ot.MathVariants() 3129*e1fe3e4aSElliott Hughes variants.populateDefaults() 3130*e1fe3e4aSElliott Hughes 3131*e1fe3e4aSElliott Hughes variants.MinConnectorOverlap = minConnectorOverlap 3132*e1fe3e4aSElliott Hughes 3133*e1fe3e4aSElliott Hughes if vertGlyphVariants or vertGlyphAssembly: 3134*e1fe3e4aSElliott Hughes variants.VertGlyphCoverage, variants.VertGlyphConstruction = ( 3135*e1fe3e4aSElliott Hughes _buildMathGlyphConstruction( 3136*e1fe3e4aSElliott Hughes glyphMap, 3137*e1fe3e4aSElliott Hughes vertGlyphVariants, 3138*e1fe3e4aSElliott Hughes vertGlyphAssembly, 3139*e1fe3e4aSElliott Hughes ) 3140*e1fe3e4aSElliott Hughes ) 3141*e1fe3e4aSElliott Hughes 3142*e1fe3e4aSElliott Hughes if horizGlyphVariants or horizGlyphAssembly: 3143*e1fe3e4aSElliott Hughes variants.HorizGlyphCoverage, variants.HorizGlyphConstruction = ( 3144*e1fe3e4aSElliott Hughes _buildMathGlyphConstruction( 3145*e1fe3e4aSElliott Hughes glyphMap, 3146*e1fe3e4aSElliott Hughes horizGlyphVariants, 3147*e1fe3e4aSElliott Hughes horizGlyphAssembly, 3148*e1fe3e4aSElliott Hughes ) 3149*e1fe3e4aSElliott Hughes ) 3150*e1fe3e4aSElliott Hughes 3151*e1fe3e4aSElliott Hughes return variants 3152*e1fe3e4aSElliott Hughes 3153*e1fe3e4aSElliott Hughes 3154*e1fe3e4aSElliott Hughesdef _buildMathGlyphConstruction(glyphMap, variants, assemblies): 3155*e1fe3e4aSElliott Hughes glyphs = set() 3156*e1fe3e4aSElliott Hughes if variants: 3157*e1fe3e4aSElliott Hughes glyphs.update(variants.keys()) 3158*e1fe3e4aSElliott Hughes if assemblies: 3159*e1fe3e4aSElliott Hughes glyphs.update(assemblies.keys()) 3160*e1fe3e4aSElliott Hughes coverage = buildCoverage(glyphs, glyphMap) 3161*e1fe3e4aSElliott Hughes constructions = [] 3162*e1fe3e4aSElliott Hughes 3163*e1fe3e4aSElliott Hughes for glyphName in coverage.glyphs: 3164*e1fe3e4aSElliott Hughes construction = ot.MathGlyphConstruction() 3165*e1fe3e4aSElliott Hughes construction.populateDefaults() 3166*e1fe3e4aSElliott Hughes 3167*e1fe3e4aSElliott Hughes if variants and glyphName in variants: 3168*e1fe3e4aSElliott Hughes construction.VariantCount = len(variants[glyphName]) 3169*e1fe3e4aSElliott Hughes construction.MathGlyphVariantRecord = [] 3170*e1fe3e4aSElliott Hughes for variantName, advance in variants[glyphName]: 3171*e1fe3e4aSElliott Hughes record = ot.MathGlyphVariantRecord() 3172*e1fe3e4aSElliott Hughes record.VariantGlyph = variantName 3173*e1fe3e4aSElliott Hughes record.AdvanceMeasurement = otRound(advance) 3174*e1fe3e4aSElliott Hughes construction.MathGlyphVariantRecord.append(record) 3175*e1fe3e4aSElliott Hughes 3176*e1fe3e4aSElliott Hughes if assemblies and glyphName in assemblies: 3177*e1fe3e4aSElliott Hughes parts, ic = assemblies[glyphName] 3178*e1fe3e4aSElliott Hughes construction.GlyphAssembly = ot.GlyphAssembly() 3179*e1fe3e4aSElliott Hughes construction.GlyphAssembly.ItalicsCorrection = _mathValueRecord(ic) 3180*e1fe3e4aSElliott Hughes construction.GlyphAssembly.PartCount = len(parts) 3181*e1fe3e4aSElliott Hughes construction.GlyphAssembly.PartRecords = [] 3182*e1fe3e4aSElliott Hughes for part in parts: 3183*e1fe3e4aSElliott Hughes part_name, flags, start, end, advance = part 3184*e1fe3e4aSElliott Hughes record = ot.GlyphPartRecord() 3185*e1fe3e4aSElliott Hughes record.glyph = part_name 3186*e1fe3e4aSElliott Hughes record.PartFlags = int(flags) 3187*e1fe3e4aSElliott Hughes record.StartConnectorLength = otRound(start) 3188*e1fe3e4aSElliott Hughes record.EndConnectorLength = otRound(end) 3189*e1fe3e4aSElliott Hughes record.FullAdvance = otRound(advance) 3190*e1fe3e4aSElliott Hughes construction.GlyphAssembly.PartRecords.append(record) 3191*e1fe3e4aSElliott Hughes 3192*e1fe3e4aSElliott Hughes constructions.append(construction) 3193*e1fe3e4aSElliott Hughes 3194*e1fe3e4aSElliott Hughes return coverage, constructions 3195*e1fe3e4aSElliott Hughes 3196*e1fe3e4aSElliott Hughes 3197*e1fe3e4aSElliott Hughesdef _mathValueRecord(value): 3198*e1fe3e4aSElliott Hughes value_record = ot.MathValueRecord() 3199*e1fe3e4aSElliott Hughes value_record.Value = otRound(value) 3200*e1fe3e4aSElliott Hughes return value_record 3201