1*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.error import FeatureLibError 2*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.location import FeatureLibLocation 3*e1fe3e4aSElliott Hughesfrom fontTools.misc.encodingTools import getEncoding 4*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import byteord, tobytes 5*e1fe3e4aSElliott Hughesfrom collections import OrderedDict 6*e1fe3e4aSElliott Hughesimport itertools 7*e1fe3e4aSElliott Hughes 8*e1fe3e4aSElliott HughesSHIFT = " " * 4 9*e1fe3e4aSElliott Hughes 10*e1fe3e4aSElliott Hughes__all__ = [ 11*e1fe3e4aSElliott Hughes "Element", 12*e1fe3e4aSElliott Hughes "FeatureFile", 13*e1fe3e4aSElliott Hughes "Comment", 14*e1fe3e4aSElliott Hughes "GlyphName", 15*e1fe3e4aSElliott Hughes "GlyphClass", 16*e1fe3e4aSElliott Hughes "GlyphClassName", 17*e1fe3e4aSElliott Hughes "MarkClassName", 18*e1fe3e4aSElliott Hughes "AnonymousBlock", 19*e1fe3e4aSElliott Hughes "Block", 20*e1fe3e4aSElliott Hughes "FeatureBlock", 21*e1fe3e4aSElliott Hughes "NestedBlock", 22*e1fe3e4aSElliott Hughes "LookupBlock", 23*e1fe3e4aSElliott Hughes "GlyphClassDefinition", 24*e1fe3e4aSElliott Hughes "GlyphClassDefStatement", 25*e1fe3e4aSElliott Hughes "MarkClass", 26*e1fe3e4aSElliott Hughes "MarkClassDefinition", 27*e1fe3e4aSElliott Hughes "AlternateSubstStatement", 28*e1fe3e4aSElliott Hughes "Anchor", 29*e1fe3e4aSElliott Hughes "AnchorDefinition", 30*e1fe3e4aSElliott Hughes "AttachStatement", 31*e1fe3e4aSElliott Hughes "AxisValueLocationStatement", 32*e1fe3e4aSElliott Hughes "BaseAxis", 33*e1fe3e4aSElliott Hughes "CVParametersNameStatement", 34*e1fe3e4aSElliott Hughes "ChainContextPosStatement", 35*e1fe3e4aSElliott Hughes "ChainContextSubstStatement", 36*e1fe3e4aSElliott Hughes "CharacterStatement", 37*e1fe3e4aSElliott Hughes "ConditionsetStatement", 38*e1fe3e4aSElliott Hughes "CursivePosStatement", 39*e1fe3e4aSElliott Hughes "ElidedFallbackName", 40*e1fe3e4aSElliott Hughes "ElidedFallbackNameID", 41*e1fe3e4aSElliott Hughes "Expression", 42*e1fe3e4aSElliott Hughes "FeatureNameStatement", 43*e1fe3e4aSElliott Hughes "FeatureReferenceStatement", 44*e1fe3e4aSElliott Hughes "FontRevisionStatement", 45*e1fe3e4aSElliott Hughes "HheaField", 46*e1fe3e4aSElliott Hughes "IgnorePosStatement", 47*e1fe3e4aSElliott Hughes "IgnoreSubstStatement", 48*e1fe3e4aSElliott Hughes "IncludeStatement", 49*e1fe3e4aSElliott Hughes "LanguageStatement", 50*e1fe3e4aSElliott Hughes "LanguageSystemStatement", 51*e1fe3e4aSElliott Hughes "LigatureCaretByIndexStatement", 52*e1fe3e4aSElliott Hughes "LigatureCaretByPosStatement", 53*e1fe3e4aSElliott Hughes "LigatureSubstStatement", 54*e1fe3e4aSElliott Hughes "LookupFlagStatement", 55*e1fe3e4aSElliott Hughes "LookupReferenceStatement", 56*e1fe3e4aSElliott Hughes "MarkBasePosStatement", 57*e1fe3e4aSElliott Hughes "MarkLigPosStatement", 58*e1fe3e4aSElliott Hughes "MarkMarkPosStatement", 59*e1fe3e4aSElliott Hughes "MultipleSubstStatement", 60*e1fe3e4aSElliott Hughes "NameRecord", 61*e1fe3e4aSElliott Hughes "OS2Field", 62*e1fe3e4aSElliott Hughes "PairPosStatement", 63*e1fe3e4aSElliott Hughes "ReverseChainSingleSubstStatement", 64*e1fe3e4aSElliott Hughes "ScriptStatement", 65*e1fe3e4aSElliott Hughes "SinglePosStatement", 66*e1fe3e4aSElliott Hughes "SingleSubstStatement", 67*e1fe3e4aSElliott Hughes "SizeParameters", 68*e1fe3e4aSElliott Hughes "Statement", 69*e1fe3e4aSElliott Hughes "STATAxisValueStatement", 70*e1fe3e4aSElliott Hughes "STATDesignAxisStatement", 71*e1fe3e4aSElliott Hughes "STATNameStatement", 72*e1fe3e4aSElliott Hughes "SubtableStatement", 73*e1fe3e4aSElliott Hughes "TableBlock", 74*e1fe3e4aSElliott Hughes "ValueRecord", 75*e1fe3e4aSElliott Hughes "ValueRecordDefinition", 76*e1fe3e4aSElliott Hughes "VheaField", 77*e1fe3e4aSElliott Hughes] 78*e1fe3e4aSElliott Hughes 79*e1fe3e4aSElliott Hughes 80*e1fe3e4aSElliott Hughesdef deviceToString(device): 81*e1fe3e4aSElliott Hughes if device is None: 82*e1fe3e4aSElliott Hughes return "<device NULL>" 83*e1fe3e4aSElliott Hughes else: 84*e1fe3e4aSElliott Hughes return "<device %s>" % ", ".join("%d %d" % t for t in device) 85*e1fe3e4aSElliott Hughes 86*e1fe3e4aSElliott Hughes 87*e1fe3e4aSElliott Hughesfea_keywords = set( 88*e1fe3e4aSElliott Hughes [ 89*e1fe3e4aSElliott Hughes "anchor", 90*e1fe3e4aSElliott Hughes "anchordef", 91*e1fe3e4aSElliott Hughes "anon", 92*e1fe3e4aSElliott Hughes "anonymous", 93*e1fe3e4aSElliott Hughes "by", 94*e1fe3e4aSElliott Hughes "contour", 95*e1fe3e4aSElliott Hughes "cursive", 96*e1fe3e4aSElliott Hughes "device", 97*e1fe3e4aSElliott Hughes "enum", 98*e1fe3e4aSElliott Hughes "enumerate", 99*e1fe3e4aSElliott Hughes "excludedflt", 100*e1fe3e4aSElliott Hughes "exclude_dflt", 101*e1fe3e4aSElliott Hughes "feature", 102*e1fe3e4aSElliott Hughes "from", 103*e1fe3e4aSElliott Hughes "ignore", 104*e1fe3e4aSElliott Hughes "ignorebaseglyphs", 105*e1fe3e4aSElliott Hughes "ignoreligatures", 106*e1fe3e4aSElliott Hughes "ignoremarks", 107*e1fe3e4aSElliott Hughes "include", 108*e1fe3e4aSElliott Hughes "includedflt", 109*e1fe3e4aSElliott Hughes "include_dflt", 110*e1fe3e4aSElliott Hughes "language", 111*e1fe3e4aSElliott Hughes "languagesystem", 112*e1fe3e4aSElliott Hughes "lookup", 113*e1fe3e4aSElliott Hughes "lookupflag", 114*e1fe3e4aSElliott Hughes "mark", 115*e1fe3e4aSElliott Hughes "markattachmenttype", 116*e1fe3e4aSElliott Hughes "markclass", 117*e1fe3e4aSElliott Hughes "nameid", 118*e1fe3e4aSElliott Hughes "null", 119*e1fe3e4aSElliott Hughes "parameters", 120*e1fe3e4aSElliott Hughes "pos", 121*e1fe3e4aSElliott Hughes "position", 122*e1fe3e4aSElliott Hughes "required", 123*e1fe3e4aSElliott Hughes "righttoleft", 124*e1fe3e4aSElliott Hughes "reversesub", 125*e1fe3e4aSElliott Hughes "rsub", 126*e1fe3e4aSElliott Hughes "script", 127*e1fe3e4aSElliott Hughes "sub", 128*e1fe3e4aSElliott Hughes "substitute", 129*e1fe3e4aSElliott Hughes "subtable", 130*e1fe3e4aSElliott Hughes "table", 131*e1fe3e4aSElliott Hughes "usemarkfilteringset", 132*e1fe3e4aSElliott Hughes "useextension", 133*e1fe3e4aSElliott Hughes "valuerecorddef", 134*e1fe3e4aSElliott Hughes "base", 135*e1fe3e4aSElliott Hughes "gdef", 136*e1fe3e4aSElliott Hughes "head", 137*e1fe3e4aSElliott Hughes "hhea", 138*e1fe3e4aSElliott Hughes "name", 139*e1fe3e4aSElliott Hughes "vhea", 140*e1fe3e4aSElliott Hughes "vmtx", 141*e1fe3e4aSElliott Hughes ] 142*e1fe3e4aSElliott Hughes) 143*e1fe3e4aSElliott Hughes 144*e1fe3e4aSElliott Hughes 145*e1fe3e4aSElliott Hughesdef asFea(g): 146*e1fe3e4aSElliott Hughes if hasattr(g, "asFea"): 147*e1fe3e4aSElliott Hughes return g.asFea() 148*e1fe3e4aSElliott Hughes elif isinstance(g, tuple) and len(g) == 2: 149*e1fe3e4aSElliott Hughes return asFea(g[0]) + " - " + asFea(g[1]) # a range 150*e1fe3e4aSElliott Hughes elif g.lower() in fea_keywords: 151*e1fe3e4aSElliott Hughes return "\\" + g 152*e1fe3e4aSElliott Hughes else: 153*e1fe3e4aSElliott Hughes return g 154*e1fe3e4aSElliott Hughes 155*e1fe3e4aSElliott Hughes 156*e1fe3e4aSElliott Hughesclass Element(object): 157*e1fe3e4aSElliott Hughes """A base class representing "something" in a feature file.""" 158*e1fe3e4aSElliott Hughes 159*e1fe3e4aSElliott Hughes def __init__(self, location=None): 160*e1fe3e4aSElliott Hughes #: location of this element as a `FeatureLibLocation` object. 161*e1fe3e4aSElliott Hughes if location and not isinstance(location, FeatureLibLocation): 162*e1fe3e4aSElliott Hughes location = FeatureLibLocation(*location) 163*e1fe3e4aSElliott Hughes self.location = location 164*e1fe3e4aSElliott Hughes 165*e1fe3e4aSElliott Hughes def build(self, builder): 166*e1fe3e4aSElliott Hughes pass 167*e1fe3e4aSElliott Hughes 168*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 169*e1fe3e4aSElliott Hughes """Returns this element as a string of feature code. For block-type 170*e1fe3e4aSElliott Hughes elements (such as :class:`FeatureBlock`), the `indent` string is 171*e1fe3e4aSElliott Hughes added to the start of each line in the output.""" 172*e1fe3e4aSElliott Hughes raise NotImplementedError 173*e1fe3e4aSElliott Hughes 174*e1fe3e4aSElliott Hughes def __str__(self): 175*e1fe3e4aSElliott Hughes return self.asFea() 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes 178*e1fe3e4aSElliott Hughesclass Statement(Element): 179*e1fe3e4aSElliott Hughes pass 180*e1fe3e4aSElliott Hughes 181*e1fe3e4aSElliott Hughes 182*e1fe3e4aSElliott Hughesclass Expression(Element): 183*e1fe3e4aSElliott Hughes pass 184*e1fe3e4aSElliott Hughes 185*e1fe3e4aSElliott Hughes 186*e1fe3e4aSElliott Hughesclass Comment(Element): 187*e1fe3e4aSElliott Hughes """A comment in a feature file.""" 188*e1fe3e4aSElliott Hughes 189*e1fe3e4aSElliott Hughes def __init__(self, text, location=None): 190*e1fe3e4aSElliott Hughes super(Comment, self).__init__(location) 191*e1fe3e4aSElliott Hughes #: Text of the comment 192*e1fe3e4aSElliott Hughes self.text = text 193*e1fe3e4aSElliott Hughes 194*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 195*e1fe3e4aSElliott Hughes return self.text 196*e1fe3e4aSElliott Hughes 197*e1fe3e4aSElliott Hughes 198*e1fe3e4aSElliott Hughesclass NullGlyph(Expression): 199*e1fe3e4aSElliott Hughes """The NULL glyph, used in glyph deletion substitutions.""" 200*e1fe3e4aSElliott Hughes 201*e1fe3e4aSElliott Hughes def __init__(self, location=None): 202*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 203*e1fe3e4aSElliott Hughes #: The name itself as a string 204*e1fe3e4aSElliott Hughes 205*e1fe3e4aSElliott Hughes def glyphSet(self): 206*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 207*e1fe3e4aSElliott Hughes return () 208*e1fe3e4aSElliott Hughes 209*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 210*e1fe3e4aSElliott Hughes return "NULL" 211*e1fe3e4aSElliott Hughes 212*e1fe3e4aSElliott Hughes 213*e1fe3e4aSElliott Hughesclass GlyphName(Expression): 214*e1fe3e4aSElliott Hughes """A single glyph name, such as ``cedilla``.""" 215*e1fe3e4aSElliott Hughes 216*e1fe3e4aSElliott Hughes def __init__(self, glyph, location=None): 217*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 218*e1fe3e4aSElliott Hughes #: The name itself as a string 219*e1fe3e4aSElliott Hughes self.glyph = glyph 220*e1fe3e4aSElliott Hughes 221*e1fe3e4aSElliott Hughes def glyphSet(self): 222*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 223*e1fe3e4aSElliott Hughes return (self.glyph,) 224*e1fe3e4aSElliott Hughes 225*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 226*e1fe3e4aSElliott Hughes return asFea(self.glyph) 227*e1fe3e4aSElliott Hughes 228*e1fe3e4aSElliott Hughes 229*e1fe3e4aSElliott Hughesclass GlyphClass(Expression): 230*e1fe3e4aSElliott Hughes """A glyph class, such as ``[acute cedilla grave]``.""" 231*e1fe3e4aSElliott Hughes 232*e1fe3e4aSElliott Hughes def __init__(self, glyphs=None, location=None): 233*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 234*e1fe3e4aSElliott Hughes #: The list of glyphs in this class, as :class:`GlyphName` objects. 235*e1fe3e4aSElliott Hughes self.glyphs = glyphs if glyphs is not None else [] 236*e1fe3e4aSElliott Hughes self.original = [] 237*e1fe3e4aSElliott Hughes self.curr = 0 238*e1fe3e4aSElliott Hughes 239*e1fe3e4aSElliott Hughes def glyphSet(self): 240*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 241*e1fe3e4aSElliott Hughes return tuple(self.glyphs) 242*e1fe3e4aSElliott Hughes 243*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 244*e1fe3e4aSElliott Hughes if len(self.original): 245*e1fe3e4aSElliott Hughes if self.curr < len(self.glyphs): 246*e1fe3e4aSElliott Hughes self.original.extend(self.glyphs[self.curr :]) 247*e1fe3e4aSElliott Hughes self.curr = len(self.glyphs) 248*e1fe3e4aSElliott Hughes return "[" + " ".join(map(asFea, self.original)) + "]" 249*e1fe3e4aSElliott Hughes else: 250*e1fe3e4aSElliott Hughes return "[" + " ".join(map(asFea, self.glyphs)) + "]" 251*e1fe3e4aSElliott Hughes 252*e1fe3e4aSElliott Hughes def extend(self, glyphs): 253*e1fe3e4aSElliott Hughes """Add a list of :class:`GlyphName` objects to the class.""" 254*e1fe3e4aSElliott Hughes self.glyphs.extend(glyphs) 255*e1fe3e4aSElliott Hughes 256*e1fe3e4aSElliott Hughes def append(self, glyph): 257*e1fe3e4aSElliott Hughes """Add a single :class:`GlyphName` object to the class.""" 258*e1fe3e4aSElliott Hughes self.glyphs.append(glyph) 259*e1fe3e4aSElliott Hughes 260*e1fe3e4aSElliott Hughes def add_range(self, start, end, glyphs): 261*e1fe3e4aSElliott Hughes """Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end`` 262*e1fe3e4aSElliott Hughes are either :class:`GlyphName` objects or strings representing the 263*e1fe3e4aSElliott Hughes start and end glyphs in the class, and ``glyphs`` is the full list of 264*e1fe3e4aSElliott Hughes :class:`GlyphName` objects in the range.""" 265*e1fe3e4aSElliott Hughes if self.curr < len(self.glyphs): 266*e1fe3e4aSElliott Hughes self.original.extend(self.glyphs[self.curr :]) 267*e1fe3e4aSElliott Hughes self.original.append((start, end)) 268*e1fe3e4aSElliott Hughes self.glyphs.extend(glyphs) 269*e1fe3e4aSElliott Hughes self.curr = len(self.glyphs) 270*e1fe3e4aSElliott Hughes 271*e1fe3e4aSElliott Hughes def add_cid_range(self, start, end, glyphs): 272*e1fe3e4aSElliott Hughes """Add a range to the class by glyph ID. ``start`` and ``end`` are the 273*e1fe3e4aSElliott Hughes initial and final IDs, and ``glyphs`` is the full list of 274*e1fe3e4aSElliott Hughes :class:`GlyphName` objects in the range.""" 275*e1fe3e4aSElliott Hughes if self.curr < len(self.glyphs): 276*e1fe3e4aSElliott Hughes self.original.extend(self.glyphs[self.curr :]) 277*e1fe3e4aSElliott Hughes self.original.append(("\\{}".format(start), "\\{}".format(end))) 278*e1fe3e4aSElliott Hughes self.glyphs.extend(glyphs) 279*e1fe3e4aSElliott Hughes self.curr = len(self.glyphs) 280*e1fe3e4aSElliott Hughes 281*e1fe3e4aSElliott Hughes def add_class(self, gc): 282*e1fe3e4aSElliott Hughes """Add glyphs from the given :class:`GlyphClassName` object to the 283*e1fe3e4aSElliott Hughes class.""" 284*e1fe3e4aSElliott Hughes if self.curr < len(self.glyphs): 285*e1fe3e4aSElliott Hughes self.original.extend(self.glyphs[self.curr :]) 286*e1fe3e4aSElliott Hughes self.original.append(gc) 287*e1fe3e4aSElliott Hughes self.glyphs.extend(gc.glyphSet()) 288*e1fe3e4aSElliott Hughes self.curr = len(self.glyphs) 289*e1fe3e4aSElliott Hughes 290*e1fe3e4aSElliott Hughes 291*e1fe3e4aSElliott Hughesclass GlyphClassName(Expression): 292*e1fe3e4aSElliott Hughes """A glyph class name, such as ``@FRENCH_MARKS``. This must be instantiated 293*e1fe3e4aSElliott Hughes with a :class:`GlyphClassDefinition` object.""" 294*e1fe3e4aSElliott Hughes 295*e1fe3e4aSElliott Hughes def __init__(self, glyphclass, location=None): 296*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 297*e1fe3e4aSElliott Hughes assert isinstance(glyphclass, GlyphClassDefinition) 298*e1fe3e4aSElliott Hughes self.glyphclass = glyphclass 299*e1fe3e4aSElliott Hughes 300*e1fe3e4aSElliott Hughes def glyphSet(self): 301*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 302*e1fe3e4aSElliott Hughes return tuple(self.glyphclass.glyphSet()) 303*e1fe3e4aSElliott Hughes 304*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 305*e1fe3e4aSElliott Hughes return "@" + self.glyphclass.name 306*e1fe3e4aSElliott Hughes 307*e1fe3e4aSElliott Hughes 308*e1fe3e4aSElliott Hughesclass MarkClassName(Expression): 309*e1fe3e4aSElliott Hughes """A mark class name, such as ``@FRENCH_MARKS`` defined with ``markClass``. 310*e1fe3e4aSElliott Hughes This must be instantiated with a :class:`MarkClass` object.""" 311*e1fe3e4aSElliott Hughes 312*e1fe3e4aSElliott Hughes def __init__(self, markClass, location=None): 313*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 314*e1fe3e4aSElliott Hughes assert isinstance(markClass, MarkClass) 315*e1fe3e4aSElliott Hughes self.markClass = markClass 316*e1fe3e4aSElliott Hughes 317*e1fe3e4aSElliott Hughes def glyphSet(self): 318*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 319*e1fe3e4aSElliott Hughes return self.markClass.glyphSet() 320*e1fe3e4aSElliott Hughes 321*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 322*e1fe3e4aSElliott Hughes return "@" + self.markClass.name 323*e1fe3e4aSElliott Hughes 324*e1fe3e4aSElliott Hughes 325*e1fe3e4aSElliott Hughesclass AnonymousBlock(Statement): 326*e1fe3e4aSElliott Hughes """An anonymous data block.""" 327*e1fe3e4aSElliott Hughes 328*e1fe3e4aSElliott Hughes def __init__(self, tag, content, location=None): 329*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 330*e1fe3e4aSElliott Hughes self.tag = tag #: string containing the block's "tag" 331*e1fe3e4aSElliott Hughes self.content = content #: block data as string 332*e1fe3e4aSElliott Hughes 333*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 334*e1fe3e4aSElliott Hughes res = "anon {} {{\n".format(self.tag) 335*e1fe3e4aSElliott Hughes res += self.content 336*e1fe3e4aSElliott Hughes res += "}} {};\n\n".format(self.tag) 337*e1fe3e4aSElliott Hughes return res 338*e1fe3e4aSElliott Hughes 339*e1fe3e4aSElliott Hughes 340*e1fe3e4aSElliott Hughesclass Block(Statement): 341*e1fe3e4aSElliott Hughes """A block of statements: feature, lookup, etc.""" 342*e1fe3e4aSElliott Hughes 343*e1fe3e4aSElliott Hughes def __init__(self, location=None): 344*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 345*e1fe3e4aSElliott Hughes self.statements = [] #: Statements contained in the block 346*e1fe3e4aSElliott Hughes 347*e1fe3e4aSElliott Hughes def build(self, builder): 348*e1fe3e4aSElliott Hughes """When handed a 'builder' object of comparable interface to 349*e1fe3e4aSElliott Hughes :class:`fontTools.feaLib.builder`, walks the statements in this 350*e1fe3e4aSElliott Hughes block, calling the builder callbacks.""" 351*e1fe3e4aSElliott Hughes for s in self.statements: 352*e1fe3e4aSElliott Hughes s.build(builder) 353*e1fe3e4aSElliott Hughes 354*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 355*e1fe3e4aSElliott Hughes indent += SHIFT 356*e1fe3e4aSElliott Hughes return ( 357*e1fe3e4aSElliott Hughes indent 358*e1fe3e4aSElliott Hughes + ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements]) 359*e1fe3e4aSElliott Hughes + "\n" 360*e1fe3e4aSElliott Hughes ) 361*e1fe3e4aSElliott Hughes 362*e1fe3e4aSElliott Hughes 363*e1fe3e4aSElliott Hughesclass FeatureFile(Block): 364*e1fe3e4aSElliott Hughes """The top-level element of the syntax tree, containing the whole feature 365*e1fe3e4aSElliott Hughes file in its ``statements`` attribute.""" 366*e1fe3e4aSElliott Hughes 367*e1fe3e4aSElliott Hughes def __init__(self): 368*e1fe3e4aSElliott Hughes Block.__init__(self, location=None) 369*e1fe3e4aSElliott Hughes self.markClasses = {} # name --> ast.MarkClass 370*e1fe3e4aSElliott Hughes 371*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 372*e1fe3e4aSElliott Hughes return "\n".join(s.asFea(indent=indent) for s in self.statements) 373*e1fe3e4aSElliott Hughes 374*e1fe3e4aSElliott Hughes 375*e1fe3e4aSElliott Hughesclass FeatureBlock(Block): 376*e1fe3e4aSElliott Hughes """A named feature block.""" 377*e1fe3e4aSElliott Hughes 378*e1fe3e4aSElliott Hughes def __init__(self, name, use_extension=False, location=None): 379*e1fe3e4aSElliott Hughes Block.__init__(self, location) 380*e1fe3e4aSElliott Hughes self.name, self.use_extension = name, use_extension 381*e1fe3e4aSElliott Hughes 382*e1fe3e4aSElliott Hughes def build(self, builder): 383*e1fe3e4aSElliott Hughes """Call the ``start_feature`` callback on the builder object, visit 384*e1fe3e4aSElliott Hughes all the statements in this feature, and then call ``end_feature``.""" 385*e1fe3e4aSElliott Hughes # TODO(sascha): Handle use_extension. 386*e1fe3e4aSElliott Hughes builder.start_feature(self.location, self.name) 387*e1fe3e4aSElliott Hughes # language exclude_dflt statements modify builder.features_ 388*e1fe3e4aSElliott Hughes # limit them to this block with temporary builder.features_ 389*e1fe3e4aSElliott Hughes features = builder.features_ 390*e1fe3e4aSElliott Hughes builder.features_ = {} 391*e1fe3e4aSElliott Hughes Block.build(self, builder) 392*e1fe3e4aSElliott Hughes for key, value in builder.features_.items(): 393*e1fe3e4aSElliott Hughes features.setdefault(key, []).extend(value) 394*e1fe3e4aSElliott Hughes builder.features_ = features 395*e1fe3e4aSElliott Hughes builder.end_feature() 396*e1fe3e4aSElliott Hughes 397*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 398*e1fe3e4aSElliott Hughes res = indent + "feature %s " % self.name.strip() 399*e1fe3e4aSElliott Hughes if self.use_extension: 400*e1fe3e4aSElliott Hughes res += "useExtension " 401*e1fe3e4aSElliott Hughes res += "{\n" 402*e1fe3e4aSElliott Hughes res += Block.asFea(self, indent=indent) 403*e1fe3e4aSElliott Hughes res += indent + "} %s;\n" % self.name.strip() 404*e1fe3e4aSElliott Hughes return res 405*e1fe3e4aSElliott Hughes 406*e1fe3e4aSElliott Hughes 407*e1fe3e4aSElliott Hughesclass NestedBlock(Block): 408*e1fe3e4aSElliott Hughes """A block inside another block, for example when found inside a 409*e1fe3e4aSElliott Hughes ``cvParameters`` block.""" 410*e1fe3e4aSElliott Hughes 411*e1fe3e4aSElliott Hughes def __init__(self, tag, block_name, location=None): 412*e1fe3e4aSElliott Hughes Block.__init__(self, location) 413*e1fe3e4aSElliott Hughes self.tag = tag 414*e1fe3e4aSElliott Hughes self.block_name = block_name 415*e1fe3e4aSElliott Hughes 416*e1fe3e4aSElliott Hughes def build(self, builder): 417*e1fe3e4aSElliott Hughes Block.build(self, builder) 418*e1fe3e4aSElliott Hughes if self.block_name == "ParamUILabelNameID": 419*e1fe3e4aSElliott Hughes builder.add_to_cv_num_named_params(self.tag) 420*e1fe3e4aSElliott Hughes 421*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 422*e1fe3e4aSElliott Hughes res = "{}{} {{\n".format(indent, self.block_name) 423*e1fe3e4aSElliott Hughes res += Block.asFea(self, indent=indent) 424*e1fe3e4aSElliott Hughes res += "{}}};\n".format(indent) 425*e1fe3e4aSElliott Hughes return res 426*e1fe3e4aSElliott Hughes 427*e1fe3e4aSElliott Hughes 428*e1fe3e4aSElliott Hughesclass LookupBlock(Block): 429*e1fe3e4aSElliott Hughes """A named lookup, containing ``statements``.""" 430*e1fe3e4aSElliott Hughes 431*e1fe3e4aSElliott Hughes def __init__(self, name, use_extension=False, location=None): 432*e1fe3e4aSElliott Hughes Block.__init__(self, location) 433*e1fe3e4aSElliott Hughes self.name, self.use_extension = name, use_extension 434*e1fe3e4aSElliott Hughes 435*e1fe3e4aSElliott Hughes def build(self, builder): 436*e1fe3e4aSElliott Hughes # TODO(sascha): Handle use_extension. 437*e1fe3e4aSElliott Hughes builder.start_lookup_block(self.location, self.name) 438*e1fe3e4aSElliott Hughes Block.build(self, builder) 439*e1fe3e4aSElliott Hughes builder.end_lookup_block() 440*e1fe3e4aSElliott Hughes 441*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 442*e1fe3e4aSElliott Hughes res = "lookup {} ".format(self.name) 443*e1fe3e4aSElliott Hughes if self.use_extension: 444*e1fe3e4aSElliott Hughes res += "useExtension " 445*e1fe3e4aSElliott Hughes res += "{\n" 446*e1fe3e4aSElliott Hughes res += Block.asFea(self, indent=indent) 447*e1fe3e4aSElliott Hughes res += "{}}} {};\n".format(indent, self.name) 448*e1fe3e4aSElliott Hughes return res 449*e1fe3e4aSElliott Hughes 450*e1fe3e4aSElliott Hughes 451*e1fe3e4aSElliott Hughesclass TableBlock(Block): 452*e1fe3e4aSElliott Hughes """A ``table ... { }`` block.""" 453*e1fe3e4aSElliott Hughes 454*e1fe3e4aSElliott Hughes def __init__(self, name, location=None): 455*e1fe3e4aSElliott Hughes Block.__init__(self, location) 456*e1fe3e4aSElliott Hughes self.name = name 457*e1fe3e4aSElliott Hughes 458*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 459*e1fe3e4aSElliott Hughes res = "table {} {{\n".format(self.name.strip()) 460*e1fe3e4aSElliott Hughes res += super(TableBlock, self).asFea(indent=indent) 461*e1fe3e4aSElliott Hughes res += "}} {};\n".format(self.name.strip()) 462*e1fe3e4aSElliott Hughes return res 463*e1fe3e4aSElliott Hughes 464*e1fe3e4aSElliott Hughes 465*e1fe3e4aSElliott Hughesclass GlyphClassDefinition(Statement): 466*e1fe3e4aSElliott Hughes """Example: ``@UPPERCASE = [A-Z];``.""" 467*e1fe3e4aSElliott Hughes 468*e1fe3e4aSElliott Hughes def __init__(self, name, glyphs, location=None): 469*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 470*e1fe3e4aSElliott Hughes self.name = name #: class name as a string, without initial ``@`` 471*e1fe3e4aSElliott Hughes self.glyphs = glyphs #: a :class:`GlyphClass` object 472*e1fe3e4aSElliott Hughes 473*e1fe3e4aSElliott Hughes def glyphSet(self): 474*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 475*e1fe3e4aSElliott Hughes return tuple(self.glyphs.glyphSet()) 476*e1fe3e4aSElliott Hughes 477*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 478*e1fe3e4aSElliott Hughes return "@" + self.name + " = " + self.glyphs.asFea() + ";" 479*e1fe3e4aSElliott Hughes 480*e1fe3e4aSElliott Hughes 481*e1fe3e4aSElliott Hughesclass GlyphClassDefStatement(Statement): 482*e1fe3e4aSElliott Hughes """Example: ``GlyphClassDef @UPPERCASE, [B], [C], [D];``. The parameters 483*e1fe3e4aSElliott Hughes must be either :class:`GlyphClass` or :class:`GlyphClassName` objects, or 484*e1fe3e4aSElliott Hughes ``None``.""" 485*e1fe3e4aSElliott Hughes 486*e1fe3e4aSElliott Hughes def __init__( 487*e1fe3e4aSElliott Hughes self, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs, location=None 488*e1fe3e4aSElliott Hughes ): 489*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 490*e1fe3e4aSElliott Hughes self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs) 491*e1fe3e4aSElliott Hughes self.ligatureGlyphs = ligatureGlyphs 492*e1fe3e4aSElliott Hughes self.componentGlyphs = componentGlyphs 493*e1fe3e4aSElliott Hughes 494*e1fe3e4aSElliott Hughes def build(self, builder): 495*e1fe3e4aSElliott Hughes """Calls the builder's ``add_glyphClassDef`` callback.""" 496*e1fe3e4aSElliott Hughes base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple() 497*e1fe3e4aSElliott Hughes liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else tuple() 498*e1fe3e4aSElliott Hughes mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple() 499*e1fe3e4aSElliott Hughes comp = self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple() 500*e1fe3e4aSElliott Hughes builder.add_glyphClassDef(self.location, base, liga, mark, comp) 501*e1fe3e4aSElliott Hughes 502*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 503*e1fe3e4aSElliott Hughes return "GlyphClassDef {}, {}, {}, {};".format( 504*e1fe3e4aSElliott Hughes self.baseGlyphs.asFea() if self.baseGlyphs else "", 505*e1fe3e4aSElliott Hughes self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "", 506*e1fe3e4aSElliott Hughes self.markGlyphs.asFea() if self.markGlyphs else "", 507*e1fe3e4aSElliott Hughes self.componentGlyphs.asFea() if self.componentGlyphs else "", 508*e1fe3e4aSElliott Hughes ) 509*e1fe3e4aSElliott Hughes 510*e1fe3e4aSElliott Hughes 511*e1fe3e4aSElliott Hughesclass MarkClass(object): 512*e1fe3e4aSElliott Hughes """One `or more` ``markClass`` statements for the same mark class. 513*e1fe3e4aSElliott Hughes 514*e1fe3e4aSElliott Hughes While glyph classes can be defined only once, the feature file format 515*e1fe3e4aSElliott Hughes allows expanding mark classes with multiple definitions, each using 516*e1fe3e4aSElliott Hughes different glyphs and anchors. The following are two ``MarkClassDefinitions`` 517*e1fe3e4aSElliott Hughes for the same ``MarkClass``:: 518*e1fe3e4aSElliott Hughes 519*e1fe3e4aSElliott Hughes markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS; 520*e1fe3e4aSElliott Hughes markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS; 521*e1fe3e4aSElliott Hughes 522*e1fe3e4aSElliott Hughes The ``MarkClass`` object is therefore just a container for a list of 523*e1fe3e4aSElliott Hughes :class:`MarkClassDefinition` statements. 524*e1fe3e4aSElliott Hughes """ 525*e1fe3e4aSElliott Hughes 526*e1fe3e4aSElliott Hughes def __init__(self, name): 527*e1fe3e4aSElliott Hughes self.name = name 528*e1fe3e4aSElliott Hughes self.definitions = [] 529*e1fe3e4aSElliott Hughes self.glyphs = OrderedDict() # glyph --> ast.MarkClassDefinitions 530*e1fe3e4aSElliott Hughes 531*e1fe3e4aSElliott Hughes def addDefinition(self, definition): 532*e1fe3e4aSElliott Hughes """Add a :class:`MarkClassDefinition` statement to this mark class.""" 533*e1fe3e4aSElliott Hughes assert isinstance(definition, MarkClassDefinition) 534*e1fe3e4aSElliott Hughes self.definitions.append(definition) 535*e1fe3e4aSElliott Hughes for glyph in definition.glyphSet(): 536*e1fe3e4aSElliott Hughes if glyph in self.glyphs: 537*e1fe3e4aSElliott Hughes otherLoc = self.glyphs[glyph].location 538*e1fe3e4aSElliott Hughes if otherLoc is None: 539*e1fe3e4aSElliott Hughes end = "" 540*e1fe3e4aSElliott Hughes else: 541*e1fe3e4aSElliott Hughes end = f" at {otherLoc}" 542*e1fe3e4aSElliott Hughes raise FeatureLibError( 543*e1fe3e4aSElliott Hughes "Glyph %s already defined%s" % (glyph, end), definition.location 544*e1fe3e4aSElliott Hughes ) 545*e1fe3e4aSElliott Hughes self.glyphs[glyph] = definition 546*e1fe3e4aSElliott Hughes 547*e1fe3e4aSElliott Hughes def glyphSet(self): 548*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 549*e1fe3e4aSElliott Hughes return tuple(self.glyphs.keys()) 550*e1fe3e4aSElliott Hughes 551*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 552*e1fe3e4aSElliott Hughes res = "\n".join(d.asFea() for d in self.definitions) 553*e1fe3e4aSElliott Hughes return res 554*e1fe3e4aSElliott Hughes 555*e1fe3e4aSElliott Hughes 556*e1fe3e4aSElliott Hughesclass MarkClassDefinition(Statement): 557*e1fe3e4aSElliott Hughes """A single ``markClass`` statement. The ``markClass`` should be a 558*e1fe3e4aSElliott Hughes :class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object, 559*e1fe3e4aSElliott Hughes and the ``glyphs`` parameter should be a `glyph-containing object`_ . 560*e1fe3e4aSElliott Hughes 561*e1fe3e4aSElliott Hughes Example: 562*e1fe3e4aSElliott Hughes 563*e1fe3e4aSElliott Hughes .. code:: python 564*e1fe3e4aSElliott Hughes 565*e1fe3e4aSElliott Hughes mc = MarkClass("FRENCH_ACCENTS") 566*e1fe3e4aSElliott Hughes mc.addDefinition( MarkClassDefinition(mc, Anchor(350, 800), 567*e1fe3e4aSElliott Hughes GlyphClass([ GlyphName("acute"), GlyphName("grave") ]) 568*e1fe3e4aSElliott Hughes ) ) 569*e1fe3e4aSElliott Hughes mc.addDefinition( MarkClassDefinition(mc, Anchor(350, -200), 570*e1fe3e4aSElliott Hughes GlyphClass([ GlyphName("cedilla") ]) 571*e1fe3e4aSElliott Hughes ) ) 572*e1fe3e4aSElliott Hughes 573*e1fe3e4aSElliott Hughes mc.asFea() 574*e1fe3e4aSElliott Hughes # markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS; 575*e1fe3e4aSElliott Hughes # markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS; 576*e1fe3e4aSElliott Hughes 577*e1fe3e4aSElliott Hughes """ 578*e1fe3e4aSElliott Hughes 579*e1fe3e4aSElliott Hughes def __init__(self, markClass, anchor, glyphs, location=None): 580*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 581*e1fe3e4aSElliott Hughes assert isinstance(markClass, MarkClass) 582*e1fe3e4aSElliott Hughes assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression) 583*e1fe3e4aSElliott Hughes self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs 584*e1fe3e4aSElliott Hughes 585*e1fe3e4aSElliott Hughes def glyphSet(self): 586*e1fe3e4aSElliott Hughes """The glyphs in this class as a tuple of :class:`GlyphName` objects.""" 587*e1fe3e4aSElliott Hughes return self.glyphs.glyphSet() 588*e1fe3e4aSElliott Hughes 589*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 590*e1fe3e4aSElliott Hughes return "markClass {} {} @{};".format( 591*e1fe3e4aSElliott Hughes self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name 592*e1fe3e4aSElliott Hughes ) 593*e1fe3e4aSElliott Hughes 594*e1fe3e4aSElliott Hughes 595*e1fe3e4aSElliott Hughesclass AlternateSubstStatement(Statement): 596*e1fe3e4aSElliott Hughes """A ``sub ... from ...`` statement. 597*e1fe3e4aSElliott Hughes 598*e1fe3e4aSElliott Hughes ``prefix``, ``glyph``, ``suffix`` and ``replacement`` should be lists of 599*e1fe3e4aSElliott Hughes `glyph-containing objects`_. ``glyph`` should be a `one element list`.""" 600*e1fe3e4aSElliott Hughes 601*e1fe3e4aSElliott Hughes def __init__(self, prefix, glyph, suffix, replacement, location=None): 602*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 603*e1fe3e4aSElliott Hughes self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix) 604*e1fe3e4aSElliott Hughes self.replacement = replacement 605*e1fe3e4aSElliott Hughes 606*e1fe3e4aSElliott Hughes def build(self, builder): 607*e1fe3e4aSElliott Hughes """Calls the builder's ``add_alternate_subst`` callback.""" 608*e1fe3e4aSElliott Hughes glyph = self.glyph.glyphSet() 609*e1fe3e4aSElliott Hughes assert len(glyph) == 1, glyph 610*e1fe3e4aSElliott Hughes glyph = list(glyph)[0] 611*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 612*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 613*e1fe3e4aSElliott Hughes replacement = self.replacement.glyphSet() 614*e1fe3e4aSElliott Hughes builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement) 615*e1fe3e4aSElliott Hughes 616*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 617*e1fe3e4aSElliott Hughes res = "sub " 618*e1fe3e4aSElliott Hughes if len(self.prefix) or len(self.suffix): 619*e1fe3e4aSElliott Hughes if len(self.prefix): 620*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, self.prefix)) + " " 621*e1fe3e4aSElliott Hughes res += asFea(self.glyph) + "'" # even though we really only use 1 622*e1fe3e4aSElliott Hughes if len(self.suffix): 623*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, self.suffix)) 624*e1fe3e4aSElliott Hughes else: 625*e1fe3e4aSElliott Hughes res += asFea(self.glyph) 626*e1fe3e4aSElliott Hughes res += " from " 627*e1fe3e4aSElliott Hughes res += asFea(self.replacement) 628*e1fe3e4aSElliott Hughes res += ";" 629*e1fe3e4aSElliott Hughes return res 630*e1fe3e4aSElliott Hughes 631*e1fe3e4aSElliott Hughes 632*e1fe3e4aSElliott Hughesclass Anchor(Expression): 633*e1fe3e4aSElliott Hughes """An ``Anchor`` element, used inside a ``pos`` rule. 634*e1fe3e4aSElliott Hughes 635*e1fe3e4aSElliott Hughes If a ``name`` is given, this will be used in preference to the coordinates. 636*e1fe3e4aSElliott Hughes Other values should be integer. 637*e1fe3e4aSElliott Hughes """ 638*e1fe3e4aSElliott Hughes 639*e1fe3e4aSElliott Hughes def __init__( 640*e1fe3e4aSElliott Hughes self, 641*e1fe3e4aSElliott Hughes x, 642*e1fe3e4aSElliott Hughes y, 643*e1fe3e4aSElliott Hughes name=None, 644*e1fe3e4aSElliott Hughes contourpoint=None, 645*e1fe3e4aSElliott Hughes xDeviceTable=None, 646*e1fe3e4aSElliott Hughes yDeviceTable=None, 647*e1fe3e4aSElliott Hughes location=None, 648*e1fe3e4aSElliott Hughes ): 649*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 650*e1fe3e4aSElliott Hughes self.name = name 651*e1fe3e4aSElliott Hughes self.x, self.y, self.contourpoint = x, y, contourpoint 652*e1fe3e4aSElliott Hughes self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable 653*e1fe3e4aSElliott Hughes 654*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 655*e1fe3e4aSElliott Hughes if self.name is not None: 656*e1fe3e4aSElliott Hughes return "<anchor {}>".format(self.name) 657*e1fe3e4aSElliott Hughes res = "<anchor {} {}".format(self.x, self.y) 658*e1fe3e4aSElliott Hughes if self.contourpoint: 659*e1fe3e4aSElliott Hughes res += " contourpoint {}".format(self.contourpoint) 660*e1fe3e4aSElliott Hughes if self.xDeviceTable or self.yDeviceTable: 661*e1fe3e4aSElliott Hughes res += " " 662*e1fe3e4aSElliott Hughes res += deviceToString(self.xDeviceTable) 663*e1fe3e4aSElliott Hughes res += " " 664*e1fe3e4aSElliott Hughes res += deviceToString(self.yDeviceTable) 665*e1fe3e4aSElliott Hughes res += ">" 666*e1fe3e4aSElliott Hughes return res 667*e1fe3e4aSElliott Hughes 668*e1fe3e4aSElliott Hughes 669*e1fe3e4aSElliott Hughesclass AnchorDefinition(Statement): 670*e1fe3e4aSElliott Hughes """A named anchor definition. (2.e.viii). ``name`` should be a string.""" 671*e1fe3e4aSElliott Hughes 672*e1fe3e4aSElliott Hughes def __init__(self, name, x, y, contourpoint=None, location=None): 673*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 674*e1fe3e4aSElliott Hughes self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint 675*e1fe3e4aSElliott Hughes 676*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 677*e1fe3e4aSElliott Hughes res = "anchorDef {} {}".format(self.x, self.y) 678*e1fe3e4aSElliott Hughes if self.contourpoint: 679*e1fe3e4aSElliott Hughes res += " contourpoint {}".format(self.contourpoint) 680*e1fe3e4aSElliott Hughes res += " {};".format(self.name) 681*e1fe3e4aSElliott Hughes return res 682*e1fe3e4aSElliott Hughes 683*e1fe3e4aSElliott Hughes 684*e1fe3e4aSElliott Hughesclass AttachStatement(Statement): 685*e1fe3e4aSElliott Hughes """A ``GDEF`` table ``Attach`` statement.""" 686*e1fe3e4aSElliott Hughes 687*e1fe3e4aSElliott Hughes def __init__(self, glyphs, contourPoints, location=None): 688*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 689*e1fe3e4aSElliott Hughes self.glyphs = glyphs #: A `glyph-containing object`_ 690*e1fe3e4aSElliott Hughes self.contourPoints = contourPoints #: A list of integer contour points 691*e1fe3e4aSElliott Hughes 692*e1fe3e4aSElliott Hughes def build(self, builder): 693*e1fe3e4aSElliott Hughes """Calls the builder's ``add_attach_points`` callback.""" 694*e1fe3e4aSElliott Hughes glyphs = self.glyphs.glyphSet() 695*e1fe3e4aSElliott Hughes builder.add_attach_points(self.location, glyphs, self.contourPoints) 696*e1fe3e4aSElliott Hughes 697*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 698*e1fe3e4aSElliott Hughes return "Attach {} {};".format( 699*e1fe3e4aSElliott Hughes self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints) 700*e1fe3e4aSElliott Hughes ) 701*e1fe3e4aSElliott Hughes 702*e1fe3e4aSElliott Hughes 703*e1fe3e4aSElliott Hughesclass ChainContextPosStatement(Statement): 704*e1fe3e4aSElliott Hughes r"""A chained contextual positioning statement. 705*e1fe3e4aSElliott Hughes 706*e1fe3e4aSElliott Hughes ``prefix``, ``glyphs``, and ``suffix`` should be lists of 707*e1fe3e4aSElliott Hughes `glyph-containing objects`_ . 708*e1fe3e4aSElliott Hughes 709*e1fe3e4aSElliott Hughes ``lookups`` should be a list of elements representing what lookups 710*e1fe3e4aSElliott Hughes to apply at each glyph position. Each element should be a 711*e1fe3e4aSElliott Hughes :class:`LookupBlock` to apply a single chaining lookup at the given 712*e1fe3e4aSElliott Hughes position, a list of :class:`LookupBlock`\ s to apply multiple 713*e1fe3e4aSElliott Hughes lookups, or ``None`` to apply no lookup. The length of the outer 714*e1fe3e4aSElliott Hughes list should equal the length of ``glyphs``; the inner lists can be 715*e1fe3e4aSElliott Hughes of variable length.""" 716*e1fe3e4aSElliott Hughes 717*e1fe3e4aSElliott Hughes def __init__(self, prefix, glyphs, suffix, lookups, location=None): 718*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 719*e1fe3e4aSElliott Hughes self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix 720*e1fe3e4aSElliott Hughes self.lookups = list(lookups) 721*e1fe3e4aSElliott Hughes for i, lookup in enumerate(lookups): 722*e1fe3e4aSElliott Hughes if lookup: 723*e1fe3e4aSElliott Hughes try: 724*e1fe3e4aSElliott Hughes (_ for _ in lookup) 725*e1fe3e4aSElliott Hughes except TypeError: 726*e1fe3e4aSElliott Hughes self.lookups[i] = [lookup] 727*e1fe3e4aSElliott Hughes 728*e1fe3e4aSElliott Hughes def build(self, builder): 729*e1fe3e4aSElliott Hughes """Calls the builder's ``add_chain_context_pos`` callback.""" 730*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 731*e1fe3e4aSElliott Hughes glyphs = [g.glyphSet() for g in self.glyphs] 732*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 733*e1fe3e4aSElliott Hughes builder.add_chain_context_pos( 734*e1fe3e4aSElliott Hughes self.location, prefix, glyphs, suffix, self.lookups 735*e1fe3e4aSElliott Hughes ) 736*e1fe3e4aSElliott Hughes 737*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 738*e1fe3e4aSElliott Hughes res = "pos " 739*e1fe3e4aSElliott Hughes if ( 740*e1fe3e4aSElliott Hughes len(self.prefix) 741*e1fe3e4aSElliott Hughes or len(self.suffix) 742*e1fe3e4aSElliott Hughes or any([x is not None for x in self.lookups]) 743*e1fe3e4aSElliott Hughes ): 744*e1fe3e4aSElliott Hughes if len(self.prefix): 745*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() for g in self.prefix) + " " 746*e1fe3e4aSElliott Hughes for i, g in enumerate(self.glyphs): 747*e1fe3e4aSElliott Hughes res += g.asFea() + "'" 748*e1fe3e4aSElliott Hughes if self.lookups[i]: 749*e1fe3e4aSElliott Hughes for lu in self.lookups[i]: 750*e1fe3e4aSElliott Hughes res += " lookup " + lu.name 751*e1fe3e4aSElliott Hughes if i < len(self.glyphs) - 1: 752*e1fe3e4aSElliott Hughes res += " " 753*e1fe3e4aSElliott Hughes if len(self.suffix): 754*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, self.suffix)) 755*e1fe3e4aSElliott Hughes else: 756*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, self.glyph)) 757*e1fe3e4aSElliott Hughes res += ";" 758*e1fe3e4aSElliott Hughes return res 759*e1fe3e4aSElliott Hughes 760*e1fe3e4aSElliott Hughes 761*e1fe3e4aSElliott Hughesclass ChainContextSubstStatement(Statement): 762*e1fe3e4aSElliott Hughes r"""A chained contextual substitution statement. 763*e1fe3e4aSElliott Hughes 764*e1fe3e4aSElliott Hughes ``prefix``, ``glyphs``, and ``suffix`` should be lists of 765*e1fe3e4aSElliott Hughes `glyph-containing objects`_ . 766*e1fe3e4aSElliott Hughes 767*e1fe3e4aSElliott Hughes ``lookups`` should be a list of elements representing what lookups 768*e1fe3e4aSElliott Hughes to apply at each glyph position. Each element should be a 769*e1fe3e4aSElliott Hughes :class:`LookupBlock` to apply a single chaining lookup at the given 770*e1fe3e4aSElliott Hughes position, a list of :class:`LookupBlock`\ s to apply multiple 771*e1fe3e4aSElliott Hughes lookups, or ``None`` to apply no lookup. The length of the outer 772*e1fe3e4aSElliott Hughes list should equal the length of ``glyphs``; the inner lists can be 773*e1fe3e4aSElliott Hughes of variable length.""" 774*e1fe3e4aSElliott Hughes 775*e1fe3e4aSElliott Hughes def __init__(self, prefix, glyphs, suffix, lookups, location=None): 776*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 777*e1fe3e4aSElliott Hughes self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix 778*e1fe3e4aSElliott Hughes self.lookups = list(lookups) 779*e1fe3e4aSElliott Hughes for i, lookup in enumerate(lookups): 780*e1fe3e4aSElliott Hughes if lookup: 781*e1fe3e4aSElliott Hughes try: 782*e1fe3e4aSElliott Hughes (_ for _ in lookup) 783*e1fe3e4aSElliott Hughes except TypeError: 784*e1fe3e4aSElliott Hughes self.lookups[i] = [lookup] 785*e1fe3e4aSElliott Hughes 786*e1fe3e4aSElliott Hughes def build(self, builder): 787*e1fe3e4aSElliott Hughes """Calls the builder's ``add_chain_context_subst`` callback.""" 788*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 789*e1fe3e4aSElliott Hughes glyphs = [g.glyphSet() for g in self.glyphs] 790*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 791*e1fe3e4aSElliott Hughes builder.add_chain_context_subst( 792*e1fe3e4aSElliott Hughes self.location, prefix, glyphs, suffix, self.lookups 793*e1fe3e4aSElliott Hughes ) 794*e1fe3e4aSElliott Hughes 795*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 796*e1fe3e4aSElliott Hughes res = "sub " 797*e1fe3e4aSElliott Hughes if ( 798*e1fe3e4aSElliott Hughes len(self.prefix) 799*e1fe3e4aSElliott Hughes or len(self.suffix) 800*e1fe3e4aSElliott Hughes or any([x is not None for x in self.lookups]) 801*e1fe3e4aSElliott Hughes ): 802*e1fe3e4aSElliott Hughes if len(self.prefix): 803*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() for g in self.prefix) + " " 804*e1fe3e4aSElliott Hughes for i, g in enumerate(self.glyphs): 805*e1fe3e4aSElliott Hughes res += g.asFea() + "'" 806*e1fe3e4aSElliott Hughes if self.lookups[i]: 807*e1fe3e4aSElliott Hughes for lu in self.lookups[i]: 808*e1fe3e4aSElliott Hughes res += " lookup " + lu.name 809*e1fe3e4aSElliott Hughes if i < len(self.glyphs) - 1: 810*e1fe3e4aSElliott Hughes res += " " 811*e1fe3e4aSElliott Hughes if len(self.suffix): 812*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, self.suffix)) 813*e1fe3e4aSElliott Hughes else: 814*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, self.glyph)) 815*e1fe3e4aSElliott Hughes res += ";" 816*e1fe3e4aSElliott Hughes return res 817*e1fe3e4aSElliott Hughes 818*e1fe3e4aSElliott Hughes 819*e1fe3e4aSElliott Hughesclass CursivePosStatement(Statement): 820*e1fe3e4aSElliott Hughes """A cursive positioning statement. Entry and exit anchors can either 821*e1fe3e4aSElliott Hughes be :class:`Anchor` objects or ``None``.""" 822*e1fe3e4aSElliott Hughes 823*e1fe3e4aSElliott Hughes def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None): 824*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 825*e1fe3e4aSElliott Hughes self.glyphclass = glyphclass 826*e1fe3e4aSElliott Hughes self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor 827*e1fe3e4aSElliott Hughes 828*e1fe3e4aSElliott Hughes def build(self, builder): 829*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_cursive_pos`` callback.""" 830*e1fe3e4aSElliott Hughes builder.add_cursive_pos( 831*e1fe3e4aSElliott Hughes self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor 832*e1fe3e4aSElliott Hughes ) 833*e1fe3e4aSElliott Hughes 834*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 835*e1fe3e4aSElliott Hughes entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>" 836*e1fe3e4aSElliott Hughes exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>" 837*e1fe3e4aSElliott Hughes return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit) 838*e1fe3e4aSElliott Hughes 839*e1fe3e4aSElliott Hughes 840*e1fe3e4aSElliott Hughesclass FeatureReferenceStatement(Statement): 841*e1fe3e4aSElliott Hughes """Example: ``feature salt;``""" 842*e1fe3e4aSElliott Hughes 843*e1fe3e4aSElliott Hughes def __init__(self, featureName, location=None): 844*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 845*e1fe3e4aSElliott Hughes self.location, self.featureName = (location, featureName) 846*e1fe3e4aSElliott Hughes 847*e1fe3e4aSElliott Hughes def build(self, builder): 848*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_feature_reference`` callback.""" 849*e1fe3e4aSElliott Hughes builder.add_feature_reference(self.location, self.featureName) 850*e1fe3e4aSElliott Hughes 851*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 852*e1fe3e4aSElliott Hughes return "feature {};".format(self.featureName) 853*e1fe3e4aSElliott Hughes 854*e1fe3e4aSElliott Hughes 855*e1fe3e4aSElliott Hughesclass IgnorePosStatement(Statement): 856*e1fe3e4aSElliott Hughes """An ``ignore pos`` statement, containing `one or more` contexts to ignore. 857*e1fe3e4aSElliott Hughes 858*e1fe3e4aSElliott Hughes ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples, 859*e1fe3e4aSElliott Hughes with each of ``prefix``, ``glyphs`` and ``suffix`` being 860*e1fe3e4aSElliott Hughes `glyph-containing objects`_ .""" 861*e1fe3e4aSElliott Hughes 862*e1fe3e4aSElliott Hughes def __init__(self, chainContexts, location=None): 863*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 864*e1fe3e4aSElliott Hughes self.chainContexts = chainContexts 865*e1fe3e4aSElliott Hughes 866*e1fe3e4aSElliott Hughes def build(self, builder): 867*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_chain_context_pos`` callback on each 868*e1fe3e4aSElliott Hughes rule context.""" 869*e1fe3e4aSElliott Hughes for prefix, glyphs, suffix in self.chainContexts: 870*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in prefix] 871*e1fe3e4aSElliott Hughes glyphs = [g.glyphSet() for g in glyphs] 872*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in suffix] 873*e1fe3e4aSElliott Hughes builder.add_chain_context_pos(self.location, prefix, glyphs, suffix, []) 874*e1fe3e4aSElliott Hughes 875*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 876*e1fe3e4aSElliott Hughes contexts = [] 877*e1fe3e4aSElliott Hughes for prefix, glyphs, suffix in self.chainContexts: 878*e1fe3e4aSElliott Hughes res = "" 879*e1fe3e4aSElliott Hughes if len(prefix) or len(suffix): 880*e1fe3e4aSElliott Hughes if len(prefix): 881*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, prefix)) + " " 882*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() + "'" for g in glyphs) 883*e1fe3e4aSElliott Hughes if len(suffix): 884*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, suffix)) 885*e1fe3e4aSElliott Hughes else: 886*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, glyphs)) 887*e1fe3e4aSElliott Hughes contexts.append(res) 888*e1fe3e4aSElliott Hughes return "ignore pos " + ", ".join(contexts) + ";" 889*e1fe3e4aSElliott Hughes 890*e1fe3e4aSElliott Hughes 891*e1fe3e4aSElliott Hughesclass IgnoreSubstStatement(Statement): 892*e1fe3e4aSElliott Hughes """An ``ignore sub`` statement, containing `one or more` contexts to ignore. 893*e1fe3e4aSElliott Hughes 894*e1fe3e4aSElliott Hughes ``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples, 895*e1fe3e4aSElliott Hughes with each of ``prefix``, ``glyphs`` and ``suffix`` being 896*e1fe3e4aSElliott Hughes `glyph-containing objects`_ .""" 897*e1fe3e4aSElliott Hughes 898*e1fe3e4aSElliott Hughes def __init__(self, chainContexts, location=None): 899*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 900*e1fe3e4aSElliott Hughes self.chainContexts = chainContexts 901*e1fe3e4aSElliott Hughes 902*e1fe3e4aSElliott Hughes def build(self, builder): 903*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_chain_context_subst`` callback on 904*e1fe3e4aSElliott Hughes each rule context.""" 905*e1fe3e4aSElliott Hughes for prefix, glyphs, suffix in self.chainContexts: 906*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in prefix] 907*e1fe3e4aSElliott Hughes glyphs = [g.glyphSet() for g in glyphs] 908*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in suffix] 909*e1fe3e4aSElliott Hughes builder.add_chain_context_subst(self.location, prefix, glyphs, suffix, []) 910*e1fe3e4aSElliott Hughes 911*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 912*e1fe3e4aSElliott Hughes contexts = [] 913*e1fe3e4aSElliott Hughes for prefix, glyphs, suffix in self.chainContexts: 914*e1fe3e4aSElliott Hughes res = "" 915*e1fe3e4aSElliott Hughes if len(prefix): 916*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, prefix)) + " " 917*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() + "'" for g in glyphs) 918*e1fe3e4aSElliott Hughes if len(suffix): 919*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, suffix)) 920*e1fe3e4aSElliott Hughes contexts.append(res) 921*e1fe3e4aSElliott Hughes return "ignore sub " + ", ".join(contexts) + ";" 922*e1fe3e4aSElliott Hughes 923*e1fe3e4aSElliott Hughes 924*e1fe3e4aSElliott Hughesclass IncludeStatement(Statement): 925*e1fe3e4aSElliott Hughes """An ``include()`` statement.""" 926*e1fe3e4aSElliott Hughes 927*e1fe3e4aSElliott Hughes def __init__(self, filename, location=None): 928*e1fe3e4aSElliott Hughes super(IncludeStatement, self).__init__(location) 929*e1fe3e4aSElliott Hughes self.filename = filename #: String containing name of file to include 930*e1fe3e4aSElliott Hughes 931*e1fe3e4aSElliott Hughes def build(self): 932*e1fe3e4aSElliott Hughes # TODO: consider lazy-loading the including parser/lexer? 933*e1fe3e4aSElliott Hughes raise FeatureLibError( 934*e1fe3e4aSElliott Hughes "Building an include statement is not implemented yet. " 935*e1fe3e4aSElliott Hughes "Instead, use Parser(..., followIncludes=True) for building.", 936*e1fe3e4aSElliott Hughes self.location, 937*e1fe3e4aSElliott Hughes ) 938*e1fe3e4aSElliott Hughes 939*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 940*e1fe3e4aSElliott Hughes return indent + "include(%s);" % self.filename 941*e1fe3e4aSElliott Hughes 942*e1fe3e4aSElliott Hughes 943*e1fe3e4aSElliott Hughesclass LanguageStatement(Statement): 944*e1fe3e4aSElliott Hughes """A ``language`` statement within a feature.""" 945*e1fe3e4aSElliott Hughes 946*e1fe3e4aSElliott Hughes def __init__(self, language, include_default=True, required=False, location=None): 947*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 948*e1fe3e4aSElliott Hughes assert len(language) == 4 949*e1fe3e4aSElliott Hughes self.language = language #: A four-character language tag 950*e1fe3e4aSElliott Hughes self.include_default = include_default #: If false, "exclude_dflt" 951*e1fe3e4aSElliott Hughes self.required = required 952*e1fe3e4aSElliott Hughes 953*e1fe3e4aSElliott Hughes def build(self, builder): 954*e1fe3e4aSElliott Hughes """Call the builder object's ``set_language`` callback.""" 955*e1fe3e4aSElliott Hughes builder.set_language( 956*e1fe3e4aSElliott Hughes location=self.location, 957*e1fe3e4aSElliott Hughes language=self.language, 958*e1fe3e4aSElliott Hughes include_default=self.include_default, 959*e1fe3e4aSElliott Hughes required=self.required, 960*e1fe3e4aSElliott Hughes ) 961*e1fe3e4aSElliott Hughes 962*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 963*e1fe3e4aSElliott Hughes res = "language {}".format(self.language.strip()) 964*e1fe3e4aSElliott Hughes if not self.include_default: 965*e1fe3e4aSElliott Hughes res += " exclude_dflt" 966*e1fe3e4aSElliott Hughes if self.required: 967*e1fe3e4aSElliott Hughes res += " required" 968*e1fe3e4aSElliott Hughes res += ";" 969*e1fe3e4aSElliott Hughes return res 970*e1fe3e4aSElliott Hughes 971*e1fe3e4aSElliott Hughes 972*e1fe3e4aSElliott Hughesclass LanguageSystemStatement(Statement): 973*e1fe3e4aSElliott Hughes """A top-level ``languagesystem`` statement.""" 974*e1fe3e4aSElliott Hughes 975*e1fe3e4aSElliott Hughes def __init__(self, script, language, location=None): 976*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 977*e1fe3e4aSElliott Hughes self.script, self.language = (script, language) 978*e1fe3e4aSElliott Hughes 979*e1fe3e4aSElliott Hughes def build(self, builder): 980*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_language_system`` callback.""" 981*e1fe3e4aSElliott Hughes builder.add_language_system(self.location, self.script, self.language) 982*e1fe3e4aSElliott Hughes 983*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 984*e1fe3e4aSElliott Hughes return "languagesystem {} {};".format(self.script, self.language.strip()) 985*e1fe3e4aSElliott Hughes 986*e1fe3e4aSElliott Hughes 987*e1fe3e4aSElliott Hughesclass FontRevisionStatement(Statement): 988*e1fe3e4aSElliott Hughes """A ``head`` table ``FontRevision`` statement. ``revision`` should be a 989*e1fe3e4aSElliott Hughes number, and will be formatted to three significant decimal places.""" 990*e1fe3e4aSElliott Hughes 991*e1fe3e4aSElliott Hughes def __init__(self, revision, location=None): 992*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 993*e1fe3e4aSElliott Hughes self.revision = revision 994*e1fe3e4aSElliott Hughes 995*e1fe3e4aSElliott Hughes def build(self, builder): 996*e1fe3e4aSElliott Hughes builder.set_font_revision(self.location, self.revision) 997*e1fe3e4aSElliott Hughes 998*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 999*e1fe3e4aSElliott Hughes return "FontRevision {:.3f};".format(self.revision) 1000*e1fe3e4aSElliott Hughes 1001*e1fe3e4aSElliott Hughes 1002*e1fe3e4aSElliott Hughesclass LigatureCaretByIndexStatement(Statement): 1003*e1fe3e4aSElliott Hughes """A ``GDEF`` table ``LigatureCaretByIndex`` statement. ``glyphs`` should be 1004*e1fe3e4aSElliott Hughes a `glyph-containing object`_, and ``carets`` should be a list of integers.""" 1005*e1fe3e4aSElliott Hughes 1006*e1fe3e4aSElliott Hughes def __init__(self, glyphs, carets, location=None): 1007*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1008*e1fe3e4aSElliott Hughes self.glyphs, self.carets = (glyphs, carets) 1009*e1fe3e4aSElliott Hughes 1010*e1fe3e4aSElliott Hughes def build(self, builder): 1011*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_ligatureCaretByIndex_`` callback.""" 1012*e1fe3e4aSElliott Hughes glyphs = self.glyphs.glyphSet() 1013*e1fe3e4aSElliott Hughes builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets)) 1014*e1fe3e4aSElliott Hughes 1015*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1016*e1fe3e4aSElliott Hughes return "LigatureCaretByIndex {} {};".format( 1017*e1fe3e4aSElliott Hughes self.glyphs.asFea(), " ".join(str(x) for x in self.carets) 1018*e1fe3e4aSElliott Hughes ) 1019*e1fe3e4aSElliott Hughes 1020*e1fe3e4aSElliott Hughes 1021*e1fe3e4aSElliott Hughesclass LigatureCaretByPosStatement(Statement): 1022*e1fe3e4aSElliott Hughes """A ``GDEF`` table ``LigatureCaretByPos`` statement. ``glyphs`` should be 1023*e1fe3e4aSElliott Hughes a `glyph-containing object`_, and ``carets`` should be a list of integers.""" 1024*e1fe3e4aSElliott Hughes 1025*e1fe3e4aSElliott Hughes def __init__(self, glyphs, carets, location=None): 1026*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1027*e1fe3e4aSElliott Hughes self.glyphs, self.carets = (glyphs, carets) 1028*e1fe3e4aSElliott Hughes 1029*e1fe3e4aSElliott Hughes def build(self, builder): 1030*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_ligatureCaretByPos_`` callback.""" 1031*e1fe3e4aSElliott Hughes glyphs = self.glyphs.glyphSet() 1032*e1fe3e4aSElliott Hughes builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets)) 1033*e1fe3e4aSElliott Hughes 1034*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1035*e1fe3e4aSElliott Hughes return "LigatureCaretByPos {} {};".format( 1036*e1fe3e4aSElliott Hughes self.glyphs.asFea(), " ".join(str(x) for x in self.carets) 1037*e1fe3e4aSElliott Hughes ) 1038*e1fe3e4aSElliott Hughes 1039*e1fe3e4aSElliott Hughes 1040*e1fe3e4aSElliott Hughesclass LigatureSubstStatement(Statement): 1041*e1fe3e4aSElliott Hughes """A chained contextual substitution statement. 1042*e1fe3e4aSElliott Hughes 1043*e1fe3e4aSElliott Hughes ``prefix``, ``glyphs``, and ``suffix`` should be lists of 1044*e1fe3e4aSElliott Hughes `glyph-containing objects`_; ``replacement`` should be a single 1045*e1fe3e4aSElliott Hughes `glyph-containing object`_. 1046*e1fe3e4aSElliott Hughes 1047*e1fe3e4aSElliott Hughes If ``forceChain`` is True, this is expressed as a chaining rule 1048*e1fe3e4aSElliott Hughes (e.g. ``sub f' i' by f_i``) even when no context is given.""" 1049*e1fe3e4aSElliott Hughes 1050*e1fe3e4aSElliott Hughes def __init__(self, prefix, glyphs, suffix, replacement, forceChain, location=None): 1051*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1052*e1fe3e4aSElliott Hughes self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix) 1053*e1fe3e4aSElliott Hughes self.replacement, self.forceChain = replacement, forceChain 1054*e1fe3e4aSElliott Hughes 1055*e1fe3e4aSElliott Hughes def build(self, builder): 1056*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 1057*e1fe3e4aSElliott Hughes glyphs = [g.glyphSet() for g in self.glyphs] 1058*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 1059*e1fe3e4aSElliott Hughes builder.add_ligature_subst( 1060*e1fe3e4aSElliott Hughes self.location, prefix, glyphs, suffix, self.replacement, self.forceChain 1061*e1fe3e4aSElliott Hughes ) 1062*e1fe3e4aSElliott Hughes 1063*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1064*e1fe3e4aSElliott Hughes res = "sub " 1065*e1fe3e4aSElliott Hughes if len(self.prefix) or len(self.suffix) or self.forceChain: 1066*e1fe3e4aSElliott Hughes if len(self.prefix): 1067*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() for g in self.prefix) + " " 1068*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() + "'" for g in self.glyphs) 1069*e1fe3e4aSElliott Hughes if len(self.suffix): 1070*e1fe3e4aSElliott Hughes res += " " + " ".join(g.asFea() for g in self.suffix) 1071*e1fe3e4aSElliott Hughes else: 1072*e1fe3e4aSElliott Hughes res += " ".join(g.asFea() for g in self.glyphs) 1073*e1fe3e4aSElliott Hughes res += " by " 1074*e1fe3e4aSElliott Hughes res += asFea(self.replacement) 1075*e1fe3e4aSElliott Hughes res += ";" 1076*e1fe3e4aSElliott Hughes return res 1077*e1fe3e4aSElliott Hughes 1078*e1fe3e4aSElliott Hughes 1079*e1fe3e4aSElliott Hughesclass LookupFlagStatement(Statement): 1080*e1fe3e4aSElliott Hughes """A ``lookupflag`` statement. The ``value`` should be an integer value 1081*e1fe3e4aSElliott Hughes representing the flags in use, but not including the ``markAttachment`` 1082*e1fe3e4aSElliott Hughes class and ``markFilteringSet`` values, which must be specified as 1083*e1fe3e4aSElliott Hughes glyph-containing objects.""" 1084*e1fe3e4aSElliott Hughes 1085*e1fe3e4aSElliott Hughes def __init__( 1086*e1fe3e4aSElliott Hughes self, value=0, markAttachment=None, markFilteringSet=None, location=None 1087*e1fe3e4aSElliott Hughes ): 1088*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1089*e1fe3e4aSElliott Hughes self.value = value 1090*e1fe3e4aSElliott Hughes self.markAttachment = markAttachment 1091*e1fe3e4aSElliott Hughes self.markFilteringSet = markFilteringSet 1092*e1fe3e4aSElliott Hughes 1093*e1fe3e4aSElliott Hughes def build(self, builder): 1094*e1fe3e4aSElliott Hughes """Calls the builder object's ``set_lookup_flag`` callback.""" 1095*e1fe3e4aSElliott Hughes markAttach = None 1096*e1fe3e4aSElliott Hughes if self.markAttachment is not None: 1097*e1fe3e4aSElliott Hughes markAttach = self.markAttachment.glyphSet() 1098*e1fe3e4aSElliott Hughes markFilter = None 1099*e1fe3e4aSElliott Hughes if self.markFilteringSet is not None: 1100*e1fe3e4aSElliott Hughes markFilter = self.markFilteringSet.glyphSet() 1101*e1fe3e4aSElliott Hughes builder.set_lookup_flag(self.location, self.value, markAttach, markFilter) 1102*e1fe3e4aSElliott Hughes 1103*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1104*e1fe3e4aSElliott Hughes res = [] 1105*e1fe3e4aSElliott Hughes flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"] 1106*e1fe3e4aSElliott Hughes curr = 1 1107*e1fe3e4aSElliott Hughes for i in range(len(flags)): 1108*e1fe3e4aSElliott Hughes if self.value & curr != 0: 1109*e1fe3e4aSElliott Hughes res.append(flags[i]) 1110*e1fe3e4aSElliott Hughes curr = curr << 1 1111*e1fe3e4aSElliott Hughes if self.markAttachment is not None: 1112*e1fe3e4aSElliott Hughes res.append("MarkAttachmentType {}".format(self.markAttachment.asFea())) 1113*e1fe3e4aSElliott Hughes if self.markFilteringSet is not None: 1114*e1fe3e4aSElliott Hughes res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea())) 1115*e1fe3e4aSElliott Hughes if not res: 1116*e1fe3e4aSElliott Hughes res = ["0"] 1117*e1fe3e4aSElliott Hughes return "lookupflag {};".format(" ".join(res)) 1118*e1fe3e4aSElliott Hughes 1119*e1fe3e4aSElliott Hughes 1120*e1fe3e4aSElliott Hughesclass LookupReferenceStatement(Statement): 1121*e1fe3e4aSElliott Hughes """Represents a ``lookup ...;`` statement to include a lookup in a feature. 1122*e1fe3e4aSElliott Hughes 1123*e1fe3e4aSElliott Hughes The ``lookup`` should be a :class:`LookupBlock` object.""" 1124*e1fe3e4aSElliott Hughes 1125*e1fe3e4aSElliott Hughes def __init__(self, lookup, location=None): 1126*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1127*e1fe3e4aSElliott Hughes self.location, self.lookup = (location, lookup) 1128*e1fe3e4aSElliott Hughes 1129*e1fe3e4aSElliott Hughes def build(self, builder): 1130*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_lookup_call`` callback.""" 1131*e1fe3e4aSElliott Hughes builder.add_lookup_call(self.lookup.name) 1132*e1fe3e4aSElliott Hughes 1133*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1134*e1fe3e4aSElliott Hughes return "lookup {};".format(self.lookup.name) 1135*e1fe3e4aSElliott Hughes 1136*e1fe3e4aSElliott Hughes 1137*e1fe3e4aSElliott Hughesclass MarkBasePosStatement(Statement): 1138*e1fe3e4aSElliott Hughes """A mark-to-base positioning rule. The ``base`` should be a 1139*e1fe3e4aSElliott Hughes `glyph-containing object`_. The ``marks`` should be a list of 1140*e1fe3e4aSElliott Hughes (:class:`Anchor`, :class:`MarkClass`) tuples.""" 1141*e1fe3e4aSElliott Hughes 1142*e1fe3e4aSElliott Hughes def __init__(self, base, marks, location=None): 1143*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1144*e1fe3e4aSElliott Hughes self.base, self.marks = base, marks 1145*e1fe3e4aSElliott Hughes 1146*e1fe3e4aSElliott Hughes def build(self, builder): 1147*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_mark_base_pos`` callback.""" 1148*e1fe3e4aSElliott Hughes builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks) 1149*e1fe3e4aSElliott Hughes 1150*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1151*e1fe3e4aSElliott Hughes res = "pos base {}".format(self.base.asFea()) 1152*e1fe3e4aSElliott Hughes for a, m in self.marks: 1153*e1fe3e4aSElliott Hughes res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name) 1154*e1fe3e4aSElliott Hughes res += ";" 1155*e1fe3e4aSElliott Hughes return res 1156*e1fe3e4aSElliott Hughes 1157*e1fe3e4aSElliott Hughes 1158*e1fe3e4aSElliott Hughesclass MarkLigPosStatement(Statement): 1159*e1fe3e4aSElliott Hughes """A mark-to-ligature positioning rule. The ``ligatures`` must be a 1160*e1fe3e4aSElliott Hughes `glyph-containing object`_. The ``marks`` should be a list of lists: each 1161*e1fe3e4aSElliott Hughes element in the top-level list represents a component glyph, and is made 1162*e1fe3e4aSElliott Hughes up of a list of (:class:`Anchor`, :class:`MarkClass`) tuples representing 1163*e1fe3e4aSElliott Hughes mark attachment points for that position. 1164*e1fe3e4aSElliott Hughes 1165*e1fe3e4aSElliott Hughes Example:: 1166*e1fe3e4aSElliott Hughes 1167*e1fe3e4aSElliott Hughes m1 = MarkClass("TOP_MARKS") 1168*e1fe3e4aSElliott Hughes m2 = MarkClass("BOTTOM_MARKS") 1169*e1fe3e4aSElliott Hughes # ... add definitions to mark classes... 1170*e1fe3e4aSElliott Hughes 1171*e1fe3e4aSElliott Hughes glyph = GlyphName("lam_meem_jeem") 1172*e1fe3e4aSElliott Hughes marks = [ 1173*e1fe3e4aSElliott Hughes [ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam) 1174*e1fe3e4aSElliott Hughes [ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem) 1175*e1fe3e4aSElliott Hughes [ ] # No attachments on the jeem 1176*e1fe3e4aSElliott Hughes ] 1177*e1fe3e4aSElliott Hughes mlp = MarkLigPosStatement(glyph, marks) 1178*e1fe3e4aSElliott Hughes 1179*e1fe3e4aSElliott Hughes mlp.asFea() 1180*e1fe3e4aSElliott Hughes # pos ligature lam_meem_jeem <anchor 625 1800> mark @TOP_MARKS 1181*e1fe3e4aSElliott Hughes # ligComponent <anchor 376 -378> mark @BOTTOM_MARKS; 1182*e1fe3e4aSElliott Hughes 1183*e1fe3e4aSElliott Hughes """ 1184*e1fe3e4aSElliott Hughes 1185*e1fe3e4aSElliott Hughes def __init__(self, ligatures, marks, location=None): 1186*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1187*e1fe3e4aSElliott Hughes self.ligatures, self.marks = ligatures, marks 1188*e1fe3e4aSElliott Hughes 1189*e1fe3e4aSElliott Hughes def build(self, builder): 1190*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_mark_lig_pos`` callback.""" 1191*e1fe3e4aSElliott Hughes builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks) 1192*e1fe3e4aSElliott Hughes 1193*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1194*e1fe3e4aSElliott Hughes res = "pos ligature {}".format(self.ligatures.asFea()) 1195*e1fe3e4aSElliott Hughes ligs = [] 1196*e1fe3e4aSElliott Hughes for l in self.marks: 1197*e1fe3e4aSElliott Hughes temp = "" 1198*e1fe3e4aSElliott Hughes if l is None or not len(l): 1199*e1fe3e4aSElliott Hughes temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>" 1200*e1fe3e4aSElliott Hughes else: 1201*e1fe3e4aSElliott Hughes for a, m in l: 1202*e1fe3e4aSElliott Hughes temp += ( 1203*e1fe3e4aSElliott Hughes "\n" 1204*e1fe3e4aSElliott Hughes + indent 1205*e1fe3e4aSElliott Hughes + SHIFT * 2 1206*e1fe3e4aSElliott Hughes + "{} mark @{}".format(a.asFea(), m.name) 1207*e1fe3e4aSElliott Hughes ) 1208*e1fe3e4aSElliott Hughes ligs.append(temp) 1209*e1fe3e4aSElliott Hughes res += ("\n" + indent + SHIFT + "ligComponent").join(ligs) 1210*e1fe3e4aSElliott Hughes res += ";" 1211*e1fe3e4aSElliott Hughes return res 1212*e1fe3e4aSElliott Hughes 1213*e1fe3e4aSElliott Hughes 1214*e1fe3e4aSElliott Hughesclass MarkMarkPosStatement(Statement): 1215*e1fe3e4aSElliott Hughes """A mark-to-mark positioning rule. The ``baseMarks`` must be a 1216*e1fe3e4aSElliott Hughes `glyph-containing object`_. The ``marks`` should be a list of 1217*e1fe3e4aSElliott Hughes (:class:`Anchor`, :class:`MarkClass`) tuples.""" 1218*e1fe3e4aSElliott Hughes 1219*e1fe3e4aSElliott Hughes def __init__(self, baseMarks, marks, location=None): 1220*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1221*e1fe3e4aSElliott Hughes self.baseMarks, self.marks = baseMarks, marks 1222*e1fe3e4aSElliott Hughes 1223*e1fe3e4aSElliott Hughes def build(self, builder): 1224*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_mark_mark_pos`` callback.""" 1225*e1fe3e4aSElliott Hughes builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks) 1226*e1fe3e4aSElliott Hughes 1227*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1228*e1fe3e4aSElliott Hughes res = "pos mark {}".format(self.baseMarks.asFea()) 1229*e1fe3e4aSElliott Hughes for a, m in self.marks: 1230*e1fe3e4aSElliott Hughes res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name) 1231*e1fe3e4aSElliott Hughes res += ";" 1232*e1fe3e4aSElliott Hughes return res 1233*e1fe3e4aSElliott Hughes 1234*e1fe3e4aSElliott Hughes 1235*e1fe3e4aSElliott Hughesclass MultipleSubstStatement(Statement): 1236*e1fe3e4aSElliott Hughes """A multiple substitution statement. 1237*e1fe3e4aSElliott Hughes 1238*e1fe3e4aSElliott Hughes Args: 1239*e1fe3e4aSElliott Hughes prefix: a list of `glyph-containing objects`_. 1240*e1fe3e4aSElliott Hughes glyph: a single glyph-containing object. 1241*e1fe3e4aSElliott Hughes suffix: a list of glyph-containing objects. 1242*e1fe3e4aSElliott Hughes replacement: a list of glyph-containing objects. 1243*e1fe3e4aSElliott Hughes forceChain: If true, the statement is expressed as a chaining rule 1244*e1fe3e4aSElliott Hughes (e.g. ``sub f' i' by f_i``) even when no context is given. 1245*e1fe3e4aSElliott Hughes """ 1246*e1fe3e4aSElliott Hughes 1247*e1fe3e4aSElliott Hughes def __init__( 1248*e1fe3e4aSElliott Hughes self, prefix, glyph, suffix, replacement, forceChain=False, location=None 1249*e1fe3e4aSElliott Hughes ): 1250*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1251*e1fe3e4aSElliott Hughes self.prefix, self.glyph, self.suffix = prefix, glyph, suffix 1252*e1fe3e4aSElliott Hughes self.replacement = replacement 1253*e1fe3e4aSElliott Hughes self.forceChain = forceChain 1254*e1fe3e4aSElliott Hughes 1255*e1fe3e4aSElliott Hughes def build(self, builder): 1256*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_multiple_subst`` callback.""" 1257*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 1258*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 1259*e1fe3e4aSElliott Hughes if hasattr(self.glyph, "glyphSet"): 1260*e1fe3e4aSElliott Hughes originals = self.glyph.glyphSet() 1261*e1fe3e4aSElliott Hughes else: 1262*e1fe3e4aSElliott Hughes originals = [self.glyph] 1263*e1fe3e4aSElliott Hughes count = len(originals) 1264*e1fe3e4aSElliott Hughes replaces = [] 1265*e1fe3e4aSElliott Hughes for r in self.replacement: 1266*e1fe3e4aSElliott Hughes if hasattr(r, "glyphSet"): 1267*e1fe3e4aSElliott Hughes replace = r.glyphSet() 1268*e1fe3e4aSElliott Hughes else: 1269*e1fe3e4aSElliott Hughes replace = [r] 1270*e1fe3e4aSElliott Hughes if len(replace) == 1 and len(replace) != count: 1271*e1fe3e4aSElliott Hughes replace = replace * count 1272*e1fe3e4aSElliott Hughes replaces.append(replace) 1273*e1fe3e4aSElliott Hughes replaces = list(zip(*replaces)) 1274*e1fe3e4aSElliott Hughes 1275*e1fe3e4aSElliott Hughes seen_originals = set() 1276*e1fe3e4aSElliott Hughes for i, original in enumerate(originals): 1277*e1fe3e4aSElliott Hughes if original not in seen_originals: 1278*e1fe3e4aSElliott Hughes seen_originals.add(original) 1279*e1fe3e4aSElliott Hughes builder.add_multiple_subst( 1280*e1fe3e4aSElliott Hughes self.location, 1281*e1fe3e4aSElliott Hughes prefix, 1282*e1fe3e4aSElliott Hughes original, 1283*e1fe3e4aSElliott Hughes suffix, 1284*e1fe3e4aSElliott Hughes replaces and replaces[i] or (), 1285*e1fe3e4aSElliott Hughes self.forceChain, 1286*e1fe3e4aSElliott Hughes ) 1287*e1fe3e4aSElliott Hughes 1288*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1289*e1fe3e4aSElliott Hughes res = "sub " 1290*e1fe3e4aSElliott Hughes if len(self.prefix) or len(self.suffix) or self.forceChain: 1291*e1fe3e4aSElliott Hughes if len(self.prefix): 1292*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, self.prefix)) + " " 1293*e1fe3e4aSElliott Hughes res += asFea(self.glyph) + "'" 1294*e1fe3e4aSElliott Hughes if len(self.suffix): 1295*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, self.suffix)) 1296*e1fe3e4aSElliott Hughes else: 1297*e1fe3e4aSElliott Hughes res += asFea(self.glyph) 1298*e1fe3e4aSElliott Hughes replacement = self.replacement or [NullGlyph()] 1299*e1fe3e4aSElliott Hughes res += " by " 1300*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, replacement)) 1301*e1fe3e4aSElliott Hughes res += ";" 1302*e1fe3e4aSElliott Hughes return res 1303*e1fe3e4aSElliott Hughes 1304*e1fe3e4aSElliott Hughes 1305*e1fe3e4aSElliott Hughesclass PairPosStatement(Statement): 1306*e1fe3e4aSElliott Hughes """A pair positioning statement. 1307*e1fe3e4aSElliott Hughes 1308*e1fe3e4aSElliott Hughes ``glyphs1`` and ``glyphs2`` should be `glyph-containing objects`_. 1309*e1fe3e4aSElliott Hughes ``valuerecord1`` should be a :class:`ValueRecord` object; 1310*e1fe3e4aSElliott Hughes ``valuerecord2`` should be either a :class:`ValueRecord` object or ``None``. 1311*e1fe3e4aSElliott Hughes If ``enumerated`` is true, then this is expressed as an 1312*e1fe3e4aSElliott Hughes `enumerated pair <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#6.b.ii>`_. 1313*e1fe3e4aSElliott Hughes """ 1314*e1fe3e4aSElliott Hughes 1315*e1fe3e4aSElliott Hughes def __init__( 1316*e1fe3e4aSElliott Hughes self, 1317*e1fe3e4aSElliott Hughes glyphs1, 1318*e1fe3e4aSElliott Hughes valuerecord1, 1319*e1fe3e4aSElliott Hughes glyphs2, 1320*e1fe3e4aSElliott Hughes valuerecord2, 1321*e1fe3e4aSElliott Hughes enumerated=False, 1322*e1fe3e4aSElliott Hughes location=None, 1323*e1fe3e4aSElliott Hughes ): 1324*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1325*e1fe3e4aSElliott Hughes self.enumerated = enumerated 1326*e1fe3e4aSElliott Hughes self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1 1327*e1fe3e4aSElliott Hughes self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2 1328*e1fe3e4aSElliott Hughes 1329*e1fe3e4aSElliott Hughes def build(self, builder): 1330*e1fe3e4aSElliott Hughes """Calls a callback on the builder object: 1331*e1fe3e4aSElliott Hughes 1332*e1fe3e4aSElliott Hughes * If the rule is enumerated, calls ``add_specific_pair_pos`` on each 1333*e1fe3e4aSElliott Hughes combination of first and second glyphs. 1334*e1fe3e4aSElliott Hughes * If the glyphs are both single :class:`GlyphName` objects, calls 1335*e1fe3e4aSElliott Hughes ``add_specific_pair_pos``. 1336*e1fe3e4aSElliott Hughes * Else, calls ``add_class_pair_pos``. 1337*e1fe3e4aSElliott Hughes """ 1338*e1fe3e4aSElliott Hughes if self.enumerated: 1339*e1fe3e4aSElliott Hughes g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()] 1340*e1fe3e4aSElliott Hughes seen_pair = False 1341*e1fe3e4aSElliott Hughes for glyph1, glyph2 in itertools.product(*g): 1342*e1fe3e4aSElliott Hughes seen_pair = True 1343*e1fe3e4aSElliott Hughes builder.add_specific_pair_pos( 1344*e1fe3e4aSElliott Hughes self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2 1345*e1fe3e4aSElliott Hughes ) 1346*e1fe3e4aSElliott Hughes if not seen_pair: 1347*e1fe3e4aSElliott Hughes raise FeatureLibError( 1348*e1fe3e4aSElliott Hughes "Empty glyph class in positioning rule", self.location 1349*e1fe3e4aSElliott Hughes ) 1350*e1fe3e4aSElliott Hughes return 1351*e1fe3e4aSElliott Hughes 1352*e1fe3e4aSElliott Hughes is_specific = isinstance(self.glyphs1, GlyphName) and isinstance( 1353*e1fe3e4aSElliott Hughes self.glyphs2, GlyphName 1354*e1fe3e4aSElliott Hughes ) 1355*e1fe3e4aSElliott Hughes if is_specific: 1356*e1fe3e4aSElliott Hughes builder.add_specific_pair_pos( 1357*e1fe3e4aSElliott Hughes self.location, 1358*e1fe3e4aSElliott Hughes self.glyphs1.glyph, 1359*e1fe3e4aSElliott Hughes self.valuerecord1, 1360*e1fe3e4aSElliott Hughes self.glyphs2.glyph, 1361*e1fe3e4aSElliott Hughes self.valuerecord2, 1362*e1fe3e4aSElliott Hughes ) 1363*e1fe3e4aSElliott Hughes else: 1364*e1fe3e4aSElliott Hughes builder.add_class_pair_pos( 1365*e1fe3e4aSElliott Hughes self.location, 1366*e1fe3e4aSElliott Hughes self.glyphs1.glyphSet(), 1367*e1fe3e4aSElliott Hughes self.valuerecord1, 1368*e1fe3e4aSElliott Hughes self.glyphs2.glyphSet(), 1369*e1fe3e4aSElliott Hughes self.valuerecord2, 1370*e1fe3e4aSElliott Hughes ) 1371*e1fe3e4aSElliott Hughes 1372*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1373*e1fe3e4aSElliott Hughes res = "enum " if self.enumerated else "" 1374*e1fe3e4aSElliott Hughes if self.valuerecord2: 1375*e1fe3e4aSElliott Hughes res += "pos {} {} {} {};".format( 1376*e1fe3e4aSElliott Hughes self.glyphs1.asFea(), 1377*e1fe3e4aSElliott Hughes self.valuerecord1.asFea(), 1378*e1fe3e4aSElliott Hughes self.glyphs2.asFea(), 1379*e1fe3e4aSElliott Hughes self.valuerecord2.asFea(), 1380*e1fe3e4aSElliott Hughes ) 1381*e1fe3e4aSElliott Hughes else: 1382*e1fe3e4aSElliott Hughes res += "pos {} {} {};".format( 1383*e1fe3e4aSElliott Hughes self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.asFea() 1384*e1fe3e4aSElliott Hughes ) 1385*e1fe3e4aSElliott Hughes return res 1386*e1fe3e4aSElliott Hughes 1387*e1fe3e4aSElliott Hughes 1388*e1fe3e4aSElliott Hughesclass ReverseChainSingleSubstStatement(Statement): 1389*e1fe3e4aSElliott Hughes """A reverse chaining substitution statement. You don't see those every day. 1390*e1fe3e4aSElliott Hughes 1391*e1fe3e4aSElliott Hughes Note the unusual argument order: ``suffix`` comes `before` ``glyphs``. 1392*e1fe3e4aSElliott Hughes ``old_prefix``, ``old_suffix``, ``glyphs`` and ``replacements`` should be 1393*e1fe3e4aSElliott Hughes lists of `glyph-containing objects`_. ``glyphs`` and ``replacements`` should 1394*e1fe3e4aSElliott Hughes be one-item lists. 1395*e1fe3e4aSElliott Hughes """ 1396*e1fe3e4aSElliott Hughes 1397*e1fe3e4aSElliott Hughes def __init__(self, old_prefix, old_suffix, glyphs, replacements, location=None): 1398*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1399*e1fe3e4aSElliott Hughes self.old_prefix, self.old_suffix = old_prefix, old_suffix 1400*e1fe3e4aSElliott Hughes self.glyphs = glyphs 1401*e1fe3e4aSElliott Hughes self.replacements = replacements 1402*e1fe3e4aSElliott Hughes 1403*e1fe3e4aSElliott Hughes def build(self, builder): 1404*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.old_prefix] 1405*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.old_suffix] 1406*e1fe3e4aSElliott Hughes originals = self.glyphs[0].glyphSet() 1407*e1fe3e4aSElliott Hughes replaces = self.replacements[0].glyphSet() 1408*e1fe3e4aSElliott Hughes if len(replaces) == 1: 1409*e1fe3e4aSElliott Hughes replaces = replaces * len(originals) 1410*e1fe3e4aSElliott Hughes builder.add_reverse_chain_single_subst( 1411*e1fe3e4aSElliott Hughes self.location, prefix, suffix, dict(zip(originals, replaces)) 1412*e1fe3e4aSElliott Hughes ) 1413*e1fe3e4aSElliott Hughes 1414*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1415*e1fe3e4aSElliott Hughes res = "rsub " 1416*e1fe3e4aSElliott Hughes if len(self.old_prefix) or len(self.old_suffix): 1417*e1fe3e4aSElliott Hughes if len(self.old_prefix): 1418*e1fe3e4aSElliott Hughes res += " ".join(asFea(g) for g in self.old_prefix) + " " 1419*e1fe3e4aSElliott Hughes res += " ".join(asFea(g) + "'" for g in self.glyphs) 1420*e1fe3e4aSElliott Hughes if len(self.old_suffix): 1421*e1fe3e4aSElliott Hughes res += " " + " ".join(asFea(g) for g in self.old_suffix) 1422*e1fe3e4aSElliott Hughes else: 1423*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, self.glyphs)) 1424*e1fe3e4aSElliott Hughes res += " by {};".format(" ".join(asFea(g) for g in self.replacements)) 1425*e1fe3e4aSElliott Hughes return res 1426*e1fe3e4aSElliott Hughes 1427*e1fe3e4aSElliott Hughes 1428*e1fe3e4aSElliott Hughesclass SingleSubstStatement(Statement): 1429*e1fe3e4aSElliott Hughes """A single substitution statement. 1430*e1fe3e4aSElliott Hughes 1431*e1fe3e4aSElliott Hughes Note the unusual argument order: ``prefix`` and suffix come `after` 1432*e1fe3e4aSElliott Hughes the replacement ``glyphs``. ``prefix``, ``suffix``, ``glyphs`` and 1433*e1fe3e4aSElliott Hughes ``replace`` should be lists of `glyph-containing objects`_. ``glyphs`` and 1434*e1fe3e4aSElliott Hughes ``replace`` should be one-item lists. 1435*e1fe3e4aSElliott Hughes """ 1436*e1fe3e4aSElliott Hughes 1437*e1fe3e4aSElliott Hughes def __init__(self, glyphs, replace, prefix, suffix, forceChain, location=None): 1438*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1439*e1fe3e4aSElliott Hughes self.prefix, self.suffix = prefix, suffix 1440*e1fe3e4aSElliott Hughes self.forceChain = forceChain 1441*e1fe3e4aSElliott Hughes self.glyphs = glyphs 1442*e1fe3e4aSElliott Hughes self.replacements = replace 1443*e1fe3e4aSElliott Hughes 1444*e1fe3e4aSElliott Hughes def build(self, builder): 1445*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_single_subst`` callback.""" 1446*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 1447*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 1448*e1fe3e4aSElliott Hughes originals = self.glyphs[0].glyphSet() 1449*e1fe3e4aSElliott Hughes replaces = self.replacements[0].glyphSet() 1450*e1fe3e4aSElliott Hughes if len(replaces) == 1: 1451*e1fe3e4aSElliott Hughes replaces = replaces * len(originals) 1452*e1fe3e4aSElliott Hughes builder.add_single_subst( 1453*e1fe3e4aSElliott Hughes self.location, 1454*e1fe3e4aSElliott Hughes prefix, 1455*e1fe3e4aSElliott Hughes suffix, 1456*e1fe3e4aSElliott Hughes OrderedDict(zip(originals, replaces)), 1457*e1fe3e4aSElliott Hughes self.forceChain, 1458*e1fe3e4aSElliott Hughes ) 1459*e1fe3e4aSElliott Hughes 1460*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1461*e1fe3e4aSElliott Hughes res = "sub " 1462*e1fe3e4aSElliott Hughes if len(self.prefix) or len(self.suffix) or self.forceChain: 1463*e1fe3e4aSElliott Hughes if len(self.prefix): 1464*e1fe3e4aSElliott Hughes res += " ".join(asFea(g) for g in self.prefix) + " " 1465*e1fe3e4aSElliott Hughes res += " ".join(asFea(g) + "'" for g in self.glyphs) 1466*e1fe3e4aSElliott Hughes if len(self.suffix): 1467*e1fe3e4aSElliott Hughes res += " " + " ".join(asFea(g) for g in self.suffix) 1468*e1fe3e4aSElliott Hughes else: 1469*e1fe3e4aSElliott Hughes res += " ".join(asFea(g) for g in self.glyphs) 1470*e1fe3e4aSElliott Hughes res += " by {};".format(" ".join(asFea(g) for g in self.replacements)) 1471*e1fe3e4aSElliott Hughes return res 1472*e1fe3e4aSElliott Hughes 1473*e1fe3e4aSElliott Hughes 1474*e1fe3e4aSElliott Hughesclass ScriptStatement(Statement): 1475*e1fe3e4aSElliott Hughes """A ``script`` statement.""" 1476*e1fe3e4aSElliott Hughes 1477*e1fe3e4aSElliott Hughes def __init__(self, script, location=None): 1478*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1479*e1fe3e4aSElliott Hughes self.script = script #: the script code 1480*e1fe3e4aSElliott Hughes 1481*e1fe3e4aSElliott Hughes def build(self, builder): 1482*e1fe3e4aSElliott Hughes """Calls the builder's ``set_script`` callback.""" 1483*e1fe3e4aSElliott Hughes builder.set_script(self.location, self.script) 1484*e1fe3e4aSElliott Hughes 1485*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1486*e1fe3e4aSElliott Hughes return "script {};".format(self.script.strip()) 1487*e1fe3e4aSElliott Hughes 1488*e1fe3e4aSElliott Hughes 1489*e1fe3e4aSElliott Hughesclass SinglePosStatement(Statement): 1490*e1fe3e4aSElliott Hughes """A single position statement. ``prefix`` and ``suffix`` should be 1491*e1fe3e4aSElliott Hughes lists of `glyph-containing objects`_. 1492*e1fe3e4aSElliott Hughes 1493*e1fe3e4aSElliott Hughes ``pos`` should be a one-element list containing a (`glyph-containing object`_, 1494*e1fe3e4aSElliott Hughes :class:`ValueRecord`) tuple.""" 1495*e1fe3e4aSElliott Hughes 1496*e1fe3e4aSElliott Hughes def __init__(self, pos, prefix, suffix, forceChain, location=None): 1497*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1498*e1fe3e4aSElliott Hughes self.pos, self.prefix, self.suffix = pos, prefix, suffix 1499*e1fe3e4aSElliott Hughes self.forceChain = forceChain 1500*e1fe3e4aSElliott Hughes 1501*e1fe3e4aSElliott Hughes def build(self, builder): 1502*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_single_pos`` callback.""" 1503*e1fe3e4aSElliott Hughes prefix = [p.glyphSet() for p in self.prefix] 1504*e1fe3e4aSElliott Hughes suffix = [s.glyphSet() for s in self.suffix] 1505*e1fe3e4aSElliott Hughes pos = [(g.glyphSet(), value) for g, value in self.pos] 1506*e1fe3e4aSElliott Hughes builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain) 1507*e1fe3e4aSElliott Hughes 1508*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1509*e1fe3e4aSElliott Hughes res = "pos " 1510*e1fe3e4aSElliott Hughes if len(self.prefix) or len(self.suffix) or self.forceChain: 1511*e1fe3e4aSElliott Hughes if len(self.prefix): 1512*e1fe3e4aSElliott Hughes res += " ".join(map(asFea, self.prefix)) + " " 1513*e1fe3e4aSElliott Hughes res += " ".join( 1514*e1fe3e4aSElliott Hughes [ 1515*e1fe3e4aSElliott Hughes asFea(x[0]) + "'" + ((" " + x[1].asFea()) if x[1] else "") 1516*e1fe3e4aSElliott Hughes for x in self.pos 1517*e1fe3e4aSElliott Hughes ] 1518*e1fe3e4aSElliott Hughes ) 1519*e1fe3e4aSElliott Hughes if len(self.suffix): 1520*e1fe3e4aSElliott Hughes res += " " + " ".join(map(asFea, self.suffix)) 1521*e1fe3e4aSElliott Hughes else: 1522*e1fe3e4aSElliott Hughes res += " ".join( 1523*e1fe3e4aSElliott Hughes [asFea(x[0]) + " " + (x[1].asFea() if x[1] else "") for x in self.pos] 1524*e1fe3e4aSElliott Hughes ) 1525*e1fe3e4aSElliott Hughes res += ";" 1526*e1fe3e4aSElliott Hughes return res 1527*e1fe3e4aSElliott Hughes 1528*e1fe3e4aSElliott Hughes 1529*e1fe3e4aSElliott Hughesclass SubtableStatement(Statement): 1530*e1fe3e4aSElliott Hughes """Represents a subtable break.""" 1531*e1fe3e4aSElliott Hughes 1532*e1fe3e4aSElliott Hughes def __init__(self, location=None): 1533*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1534*e1fe3e4aSElliott Hughes 1535*e1fe3e4aSElliott Hughes def build(self, builder): 1536*e1fe3e4aSElliott Hughes """Calls the builder objects's ``add_subtable_break`` callback.""" 1537*e1fe3e4aSElliott Hughes builder.add_subtable_break(self.location) 1538*e1fe3e4aSElliott Hughes 1539*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1540*e1fe3e4aSElliott Hughes return "subtable;" 1541*e1fe3e4aSElliott Hughes 1542*e1fe3e4aSElliott Hughes 1543*e1fe3e4aSElliott Hughesclass ValueRecord(Expression): 1544*e1fe3e4aSElliott Hughes """Represents a value record.""" 1545*e1fe3e4aSElliott Hughes 1546*e1fe3e4aSElliott Hughes def __init__( 1547*e1fe3e4aSElliott Hughes self, 1548*e1fe3e4aSElliott Hughes xPlacement=None, 1549*e1fe3e4aSElliott Hughes yPlacement=None, 1550*e1fe3e4aSElliott Hughes xAdvance=None, 1551*e1fe3e4aSElliott Hughes yAdvance=None, 1552*e1fe3e4aSElliott Hughes xPlaDevice=None, 1553*e1fe3e4aSElliott Hughes yPlaDevice=None, 1554*e1fe3e4aSElliott Hughes xAdvDevice=None, 1555*e1fe3e4aSElliott Hughes yAdvDevice=None, 1556*e1fe3e4aSElliott Hughes vertical=False, 1557*e1fe3e4aSElliott Hughes location=None, 1558*e1fe3e4aSElliott Hughes ): 1559*e1fe3e4aSElliott Hughes Expression.__init__(self, location) 1560*e1fe3e4aSElliott Hughes self.xPlacement, self.yPlacement = (xPlacement, yPlacement) 1561*e1fe3e4aSElliott Hughes self.xAdvance, self.yAdvance = (xAdvance, yAdvance) 1562*e1fe3e4aSElliott Hughes self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice) 1563*e1fe3e4aSElliott Hughes self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice) 1564*e1fe3e4aSElliott Hughes self.vertical = vertical 1565*e1fe3e4aSElliott Hughes 1566*e1fe3e4aSElliott Hughes def __eq__(self, other): 1567*e1fe3e4aSElliott Hughes return ( 1568*e1fe3e4aSElliott Hughes self.xPlacement == other.xPlacement 1569*e1fe3e4aSElliott Hughes and self.yPlacement == other.yPlacement 1570*e1fe3e4aSElliott Hughes and self.xAdvance == other.xAdvance 1571*e1fe3e4aSElliott Hughes and self.yAdvance == other.yAdvance 1572*e1fe3e4aSElliott Hughes and self.xPlaDevice == other.xPlaDevice 1573*e1fe3e4aSElliott Hughes and self.xAdvDevice == other.xAdvDevice 1574*e1fe3e4aSElliott Hughes ) 1575*e1fe3e4aSElliott Hughes 1576*e1fe3e4aSElliott Hughes def __ne__(self, other): 1577*e1fe3e4aSElliott Hughes return not self.__eq__(other) 1578*e1fe3e4aSElliott Hughes 1579*e1fe3e4aSElliott Hughes def __hash__(self): 1580*e1fe3e4aSElliott Hughes return ( 1581*e1fe3e4aSElliott Hughes hash(self.xPlacement) 1582*e1fe3e4aSElliott Hughes ^ hash(self.yPlacement) 1583*e1fe3e4aSElliott Hughes ^ hash(self.xAdvance) 1584*e1fe3e4aSElliott Hughes ^ hash(self.yAdvance) 1585*e1fe3e4aSElliott Hughes ^ hash(self.xPlaDevice) 1586*e1fe3e4aSElliott Hughes ^ hash(self.yPlaDevice) 1587*e1fe3e4aSElliott Hughes ^ hash(self.xAdvDevice) 1588*e1fe3e4aSElliott Hughes ^ hash(self.yAdvDevice) 1589*e1fe3e4aSElliott Hughes ) 1590*e1fe3e4aSElliott Hughes 1591*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1592*e1fe3e4aSElliott Hughes if not self: 1593*e1fe3e4aSElliott Hughes return "<NULL>" 1594*e1fe3e4aSElliott Hughes 1595*e1fe3e4aSElliott Hughes x, y = self.xPlacement, self.yPlacement 1596*e1fe3e4aSElliott Hughes xAdvance, yAdvance = self.xAdvance, self.yAdvance 1597*e1fe3e4aSElliott Hughes xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice 1598*e1fe3e4aSElliott Hughes xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice 1599*e1fe3e4aSElliott Hughes vertical = self.vertical 1600*e1fe3e4aSElliott Hughes 1601*e1fe3e4aSElliott Hughes # Try format A, if possible. 1602*e1fe3e4aSElliott Hughes if x is None and y is None: 1603*e1fe3e4aSElliott Hughes if xAdvance is None and vertical: 1604*e1fe3e4aSElliott Hughes return str(yAdvance) 1605*e1fe3e4aSElliott Hughes elif yAdvance is None and not vertical: 1606*e1fe3e4aSElliott Hughes return str(xAdvance) 1607*e1fe3e4aSElliott Hughes 1608*e1fe3e4aSElliott Hughes # Make any remaining None value 0 to avoid generating invalid records. 1609*e1fe3e4aSElliott Hughes x = x or 0 1610*e1fe3e4aSElliott Hughes y = y or 0 1611*e1fe3e4aSElliott Hughes xAdvance = xAdvance or 0 1612*e1fe3e4aSElliott Hughes yAdvance = yAdvance or 0 1613*e1fe3e4aSElliott Hughes 1614*e1fe3e4aSElliott Hughes # Try format B, if possible. 1615*e1fe3e4aSElliott Hughes if ( 1616*e1fe3e4aSElliott Hughes xPlaDevice is None 1617*e1fe3e4aSElliott Hughes and yPlaDevice is None 1618*e1fe3e4aSElliott Hughes and xAdvDevice is None 1619*e1fe3e4aSElliott Hughes and yAdvDevice is None 1620*e1fe3e4aSElliott Hughes ): 1621*e1fe3e4aSElliott Hughes return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance) 1622*e1fe3e4aSElliott Hughes 1623*e1fe3e4aSElliott Hughes # Last resort is format C. 1624*e1fe3e4aSElliott Hughes return "<%s %s %s %s %s %s %s %s>" % ( 1625*e1fe3e4aSElliott Hughes x, 1626*e1fe3e4aSElliott Hughes y, 1627*e1fe3e4aSElliott Hughes xAdvance, 1628*e1fe3e4aSElliott Hughes yAdvance, 1629*e1fe3e4aSElliott Hughes deviceToString(xPlaDevice), 1630*e1fe3e4aSElliott Hughes deviceToString(yPlaDevice), 1631*e1fe3e4aSElliott Hughes deviceToString(xAdvDevice), 1632*e1fe3e4aSElliott Hughes deviceToString(yAdvDevice), 1633*e1fe3e4aSElliott Hughes ) 1634*e1fe3e4aSElliott Hughes 1635*e1fe3e4aSElliott Hughes def __bool__(self): 1636*e1fe3e4aSElliott Hughes return any( 1637*e1fe3e4aSElliott Hughes getattr(self, v) is not None 1638*e1fe3e4aSElliott Hughes for v in [ 1639*e1fe3e4aSElliott Hughes "xPlacement", 1640*e1fe3e4aSElliott Hughes "yPlacement", 1641*e1fe3e4aSElliott Hughes "xAdvance", 1642*e1fe3e4aSElliott Hughes "yAdvance", 1643*e1fe3e4aSElliott Hughes "xPlaDevice", 1644*e1fe3e4aSElliott Hughes "yPlaDevice", 1645*e1fe3e4aSElliott Hughes "xAdvDevice", 1646*e1fe3e4aSElliott Hughes "yAdvDevice", 1647*e1fe3e4aSElliott Hughes ] 1648*e1fe3e4aSElliott Hughes ) 1649*e1fe3e4aSElliott Hughes 1650*e1fe3e4aSElliott Hughes __nonzero__ = __bool__ 1651*e1fe3e4aSElliott Hughes 1652*e1fe3e4aSElliott Hughes 1653*e1fe3e4aSElliott Hughesclass ValueRecordDefinition(Statement): 1654*e1fe3e4aSElliott Hughes """Represents a named value record definition.""" 1655*e1fe3e4aSElliott Hughes 1656*e1fe3e4aSElliott Hughes def __init__(self, name, value, location=None): 1657*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1658*e1fe3e4aSElliott Hughes self.name = name #: Value record name as string 1659*e1fe3e4aSElliott Hughes self.value = value #: :class:`ValueRecord` object 1660*e1fe3e4aSElliott Hughes 1661*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1662*e1fe3e4aSElliott Hughes return "valueRecordDef {} {};".format(self.value.asFea(), self.name) 1663*e1fe3e4aSElliott Hughes 1664*e1fe3e4aSElliott Hughes 1665*e1fe3e4aSElliott Hughesdef simplify_name_attributes(pid, eid, lid): 1666*e1fe3e4aSElliott Hughes if pid == 3 and eid == 1 and lid == 1033: 1667*e1fe3e4aSElliott Hughes return "" 1668*e1fe3e4aSElliott Hughes elif pid == 1 and eid == 0 and lid == 0: 1669*e1fe3e4aSElliott Hughes return "1" 1670*e1fe3e4aSElliott Hughes else: 1671*e1fe3e4aSElliott Hughes return "{} {} {}".format(pid, eid, lid) 1672*e1fe3e4aSElliott Hughes 1673*e1fe3e4aSElliott Hughes 1674*e1fe3e4aSElliott Hughesclass NameRecord(Statement): 1675*e1fe3e4aSElliott Hughes """Represents a name record. (`Section 9.e. <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.e>`_)""" 1676*e1fe3e4aSElliott Hughes 1677*e1fe3e4aSElliott Hughes def __init__(self, nameID, platformID, platEncID, langID, string, location=None): 1678*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1679*e1fe3e4aSElliott Hughes self.nameID = nameID #: Name ID as integer (e.g. 9 for designer's name) 1680*e1fe3e4aSElliott Hughes self.platformID = platformID #: Platform ID as integer 1681*e1fe3e4aSElliott Hughes self.platEncID = platEncID #: Platform encoding ID as integer 1682*e1fe3e4aSElliott Hughes self.langID = langID #: Language ID as integer 1683*e1fe3e4aSElliott Hughes self.string = string #: Name record value 1684*e1fe3e4aSElliott Hughes 1685*e1fe3e4aSElliott Hughes def build(self, builder): 1686*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_name_record`` callback.""" 1687*e1fe3e4aSElliott Hughes builder.add_name_record( 1688*e1fe3e4aSElliott Hughes self.location, 1689*e1fe3e4aSElliott Hughes self.nameID, 1690*e1fe3e4aSElliott Hughes self.platformID, 1691*e1fe3e4aSElliott Hughes self.platEncID, 1692*e1fe3e4aSElliott Hughes self.langID, 1693*e1fe3e4aSElliott Hughes self.string, 1694*e1fe3e4aSElliott Hughes ) 1695*e1fe3e4aSElliott Hughes 1696*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1697*e1fe3e4aSElliott Hughes def escape(c, escape_pattern): 1698*e1fe3e4aSElliott Hughes # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS 1699*e1fe3e4aSElliott Hughes if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C): 1700*e1fe3e4aSElliott Hughes return chr(c) 1701*e1fe3e4aSElliott Hughes else: 1702*e1fe3e4aSElliott Hughes return escape_pattern % c 1703*e1fe3e4aSElliott Hughes 1704*e1fe3e4aSElliott Hughes encoding = getEncoding(self.platformID, self.platEncID, self.langID) 1705*e1fe3e4aSElliott Hughes if encoding is None: 1706*e1fe3e4aSElliott Hughes raise FeatureLibError("Unsupported encoding", self.location) 1707*e1fe3e4aSElliott Hughes s = tobytes(self.string, encoding=encoding) 1708*e1fe3e4aSElliott Hughes if encoding == "utf_16_be": 1709*e1fe3e4aSElliott Hughes escaped_string = "".join( 1710*e1fe3e4aSElliott Hughes [ 1711*e1fe3e4aSElliott Hughes escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x") 1712*e1fe3e4aSElliott Hughes for i in range(0, len(s), 2) 1713*e1fe3e4aSElliott Hughes ] 1714*e1fe3e4aSElliott Hughes ) 1715*e1fe3e4aSElliott Hughes else: 1716*e1fe3e4aSElliott Hughes escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s]) 1717*e1fe3e4aSElliott Hughes plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID) 1718*e1fe3e4aSElliott Hughes if plat != "": 1719*e1fe3e4aSElliott Hughes plat += " " 1720*e1fe3e4aSElliott Hughes return 'nameid {} {}"{}";'.format(self.nameID, plat, escaped_string) 1721*e1fe3e4aSElliott Hughes 1722*e1fe3e4aSElliott Hughes 1723*e1fe3e4aSElliott Hughesclass FeatureNameStatement(NameRecord): 1724*e1fe3e4aSElliott Hughes """Represents a ``sizemenuname`` or ``name`` statement.""" 1725*e1fe3e4aSElliott Hughes 1726*e1fe3e4aSElliott Hughes def build(self, builder): 1727*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_featureName`` callback.""" 1728*e1fe3e4aSElliott Hughes NameRecord.build(self, builder) 1729*e1fe3e4aSElliott Hughes builder.add_featureName(self.nameID) 1730*e1fe3e4aSElliott Hughes 1731*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1732*e1fe3e4aSElliott Hughes if self.nameID == "size": 1733*e1fe3e4aSElliott Hughes tag = "sizemenuname" 1734*e1fe3e4aSElliott Hughes else: 1735*e1fe3e4aSElliott Hughes tag = "name" 1736*e1fe3e4aSElliott Hughes plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID) 1737*e1fe3e4aSElliott Hughes if plat != "": 1738*e1fe3e4aSElliott Hughes plat += " " 1739*e1fe3e4aSElliott Hughes return '{} {}"{}";'.format(tag, plat, self.string) 1740*e1fe3e4aSElliott Hughes 1741*e1fe3e4aSElliott Hughes 1742*e1fe3e4aSElliott Hughesclass STATNameStatement(NameRecord): 1743*e1fe3e4aSElliott Hughes """Represents a STAT table ``name`` statement.""" 1744*e1fe3e4aSElliott Hughes 1745*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1746*e1fe3e4aSElliott Hughes plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID) 1747*e1fe3e4aSElliott Hughes if plat != "": 1748*e1fe3e4aSElliott Hughes plat += " " 1749*e1fe3e4aSElliott Hughes return 'name {}"{}";'.format(plat, self.string) 1750*e1fe3e4aSElliott Hughes 1751*e1fe3e4aSElliott Hughes 1752*e1fe3e4aSElliott Hughesclass SizeParameters(Statement): 1753*e1fe3e4aSElliott Hughes """A ``parameters`` statement.""" 1754*e1fe3e4aSElliott Hughes 1755*e1fe3e4aSElliott Hughes def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, location=None): 1756*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1757*e1fe3e4aSElliott Hughes self.DesignSize = DesignSize 1758*e1fe3e4aSElliott Hughes self.SubfamilyID = SubfamilyID 1759*e1fe3e4aSElliott Hughes self.RangeStart = RangeStart 1760*e1fe3e4aSElliott Hughes self.RangeEnd = RangeEnd 1761*e1fe3e4aSElliott Hughes 1762*e1fe3e4aSElliott Hughes def build(self, builder): 1763*e1fe3e4aSElliott Hughes """Calls the builder object's ``set_size_parameters`` callback.""" 1764*e1fe3e4aSElliott Hughes builder.set_size_parameters( 1765*e1fe3e4aSElliott Hughes self.location, 1766*e1fe3e4aSElliott Hughes self.DesignSize, 1767*e1fe3e4aSElliott Hughes self.SubfamilyID, 1768*e1fe3e4aSElliott Hughes self.RangeStart, 1769*e1fe3e4aSElliott Hughes self.RangeEnd, 1770*e1fe3e4aSElliott Hughes ) 1771*e1fe3e4aSElliott Hughes 1772*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1773*e1fe3e4aSElliott Hughes res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID) 1774*e1fe3e4aSElliott Hughes if self.RangeStart != 0 or self.RangeEnd != 0: 1775*e1fe3e4aSElliott Hughes res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10)) 1776*e1fe3e4aSElliott Hughes return res + ";" 1777*e1fe3e4aSElliott Hughes 1778*e1fe3e4aSElliott Hughes 1779*e1fe3e4aSElliott Hughesclass CVParametersNameStatement(NameRecord): 1780*e1fe3e4aSElliott Hughes """Represent a name statement inside a ``cvParameters`` block.""" 1781*e1fe3e4aSElliott Hughes 1782*e1fe3e4aSElliott Hughes def __init__( 1783*e1fe3e4aSElliott Hughes self, nameID, platformID, platEncID, langID, string, block_name, location=None 1784*e1fe3e4aSElliott Hughes ): 1785*e1fe3e4aSElliott Hughes NameRecord.__init__( 1786*e1fe3e4aSElliott Hughes self, nameID, platformID, platEncID, langID, string, location=location 1787*e1fe3e4aSElliott Hughes ) 1788*e1fe3e4aSElliott Hughes self.block_name = block_name 1789*e1fe3e4aSElliott Hughes 1790*e1fe3e4aSElliott Hughes def build(self, builder): 1791*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_cv_parameter`` callback.""" 1792*e1fe3e4aSElliott Hughes item = "" 1793*e1fe3e4aSElliott Hughes if self.block_name == "ParamUILabelNameID": 1794*e1fe3e4aSElliott Hughes item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0)) 1795*e1fe3e4aSElliott Hughes builder.add_cv_parameter(self.nameID) 1796*e1fe3e4aSElliott Hughes self.nameID = (self.nameID, self.block_name + item) 1797*e1fe3e4aSElliott Hughes NameRecord.build(self, builder) 1798*e1fe3e4aSElliott Hughes 1799*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1800*e1fe3e4aSElliott Hughes plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID) 1801*e1fe3e4aSElliott Hughes if plat != "": 1802*e1fe3e4aSElliott Hughes plat += " " 1803*e1fe3e4aSElliott Hughes return 'name {}"{}";'.format(plat, self.string) 1804*e1fe3e4aSElliott Hughes 1805*e1fe3e4aSElliott Hughes 1806*e1fe3e4aSElliott Hughesclass CharacterStatement(Statement): 1807*e1fe3e4aSElliott Hughes """ 1808*e1fe3e4aSElliott Hughes Statement used in cvParameters blocks of Character Variant features (cvXX). 1809*e1fe3e4aSElliott Hughes The Unicode value may be written with either decimal or hexadecimal 1810*e1fe3e4aSElliott Hughes notation. The value must be preceded by '0x' if it is a hexadecimal value. 1811*e1fe3e4aSElliott Hughes The largest Unicode value allowed is 0xFFFFFF. 1812*e1fe3e4aSElliott Hughes """ 1813*e1fe3e4aSElliott Hughes 1814*e1fe3e4aSElliott Hughes def __init__(self, character, tag, location=None): 1815*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1816*e1fe3e4aSElliott Hughes self.character = character 1817*e1fe3e4aSElliott Hughes self.tag = tag 1818*e1fe3e4aSElliott Hughes 1819*e1fe3e4aSElliott Hughes def build(self, builder): 1820*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_cv_character`` callback.""" 1821*e1fe3e4aSElliott Hughes builder.add_cv_character(self.character, self.tag) 1822*e1fe3e4aSElliott Hughes 1823*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1824*e1fe3e4aSElliott Hughes return "Character {:#x};".format(self.character) 1825*e1fe3e4aSElliott Hughes 1826*e1fe3e4aSElliott Hughes 1827*e1fe3e4aSElliott Hughesclass BaseAxis(Statement): 1828*e1fe3e4aSElliott Hughes """An axis definition, being either a ``VertAxis.BaseTagList/BaseScriptList`` 1829*e1fe3e4aSElliott Hughes pair or a ``HorizAxis.BaseTagList/BaseScriptList`` pair.""" 1830*e1fe3e4aSElliott Hughes 1831*e1fe3e4aSElliott Hughes def __init__(self, bases, scripts, vertical, location=None): 1832*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1833*e1fe3e4aSElliott Hughes self.bases = bases #: A list of baseline tag names as strings 1834*e1fe3e4aSElliott Hughes self.scripts = scripts #: A list of script record tuplets (script tag, default baseline tag, base coordinate) 1835*e1fe3e4aSElliott Hughes self.vertical = vertical #: Boolean; VertAxis if True, HorizAxis if False 1836*e1fe3e4aSElliott Hughes 1837*e1fe3e4aSElliott Hughes def build(self, builder): 1838*e1fe3e4aSElliott Hughes """Calls the builder object's ``set_base_axis`` callback.""" 1839*e1fe3e4aSElliott Hughes builder.set_base_axis(self.bases, self.scripts, self.vertical) 1840*e1fe3e4aSElliott Hughes 1841*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1842*e1fe3e4aSElliott Hughes direction = "Vert" if self.vertical else "Horiz" 1843*e1fe3e4aSElliott Hughes scripts = [ 1844*e1fe3e4aSElliott Hughes "{} {} {}".format(a[0], a[1], " ".join(map(str, a[2]))) 1845*e1fe3e4aSElliott Hughes for a in self.scripts 1846*e1fe3e4aSElliott Hughes ] 1847*e1fe3e4aSElliott Hughes return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format( 1848*e1fe3e4aSElliott Hughes direction, " ".join(self.bases), indent, direction, ", ".join(scripts) 1849*e1fe3e4aSElliott Hughes ) 1850*e1fe3e4aSElliott Hughes 1851*e1fe3e4aSElliott Hughes 1852*e1fe3e4aSElliott Hughesclass OS2Field(Statement): 1853*e1fe3e4aSElliott Hughes """An entry in the ``OS/2`` table. Most ``values`` should be numbers or 1854*e1fe3e4aSElliott Hughes strings, apart from when the key is ``UnicodeRange``, ``CodePageRange`` 1855*e1fe3e4aSElliott Hughes or ``Panose``, in which case it should be an array of integers.""" 1856*e1fe3e4aSElliott Hughes 1857*e1fe3e4aSElliott Hughes def __init__(self, key, value, location=None): 1858*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1859*e1fe3e4aSElliott Hughes self.key = key 1860*e1fe3e4aSElliott Hughes self.value = value 1861*e1fe3e4aSElliott Hughes 1862*e1fe3e4aSElliott Hughes def build(self, builder): 1863*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_os2_field`` callback.""" 1864*e1fe3e4aSElliott Hughes builder.add_os2_field(self.key, self.value) 1865*e1fe3e4aSElliott Hughes 1866*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1867*e1fe3e4aSElliott Hughes def intarr2str(x): 1868*e1fe3e4aSElliott Hughes return " ".join(map(str, x)) 1869*e1fe3e4aSElliott Hughes 1870*e1fe3e4aSElliott Hughes numbers = ( 1871*e1fe3e4aSElliott Hughes "FSType", 1872*e1fe3e4aSElliott Hughes "TypoAscender", 1873*e1fe3e4aSElliott Hughes "TypoDescender", 1874*e1fe3e4aSElliott Hughes "TypoLineGap", 1875*e1fe3e4aSElliott Hughes "winAscent", 1876*e1fe3e4aSElliott Hughes "winDescent", 1877*e1fe3e4aSElliott Hughes "XHeight", 1878*e1fe3e4aSElliott Hughes "CapHeight", 1879*e1fe3e4aSElliott Hughes "WeightClass", 1880*e1fe3e4aSElliott Hughes "WidthClass", 1881*e1fe3e4aSElliott Hughes "LowerOpSize", 1882*e1fe3e4aSElliott Hughes "UpperOpSize", 1883*e1fe3e4aSElliott Hughes ) 1884*e1fe3e4aSElliott Hughes ranges = ("UnicodeRange", "CodePageRange") 1885*e1fe3e4aSElliott Hughes keywords = dict([(x.lower(), [x, str]) for x in numbers]) 1886*e1fe3e4aSElliott Hughes keywords.update([(x.lower(), [x, intarr2str]) for x in ranges]) 1887*e1fe3e4aSElliott Hughes keywords["panose"] = ["Panose", intarr2str] 1888*e1fe3e4aSElliott Hughes keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)] 1889*e1fe3e4aSElliott Hughes if self.key in keywords: 1890*e1fe3e4aSElliott Hughes return "{} {};".format( 1891*e1fe3e4aSElliott Hughes keywords[self.key][0], keywords[self.key][1](self.value) 1892*e1fe3e4aSElliott Hughes ) 1893*e1fe3e4aSElliott Hughes return "" # should raise exception 1894*e1fe3e4aSElliott Hughes 1895*e1fe3e4aSElliott Hughes 1896*e1fe3e4aSElliott Hughesclass HheaField(Statement): 1897*e1fe3e4aSElliott Hughes """An entry in the ``hhea`` table.""" 1898*e1fe3e4aSElliott Hughes 1899*e1fe3e4aSElliott Hughes def __init__(self, key, value, location=None): 1900*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1901*e1fe3e4aSElliott Hughes self.key = key 1902*e1fe3e4aSElliott Hughes self.value = value 1903*e1fe3e4aSElliott Hughes 1904*e1fe3e4aSElliott Hughes def build(self, builder): 1905*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_hhea_field`` callback.""" 1906*e1fe3e4aSElliott Hughes builder.add_hhea_field(self.key, self.value) 1907*e1fe3e4aSElliott Hughes 1908*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1909*e1fe3e4aSElliott Hughes fields = ("CaretOffset", "Ascender", "Descender", "LineGap") 1910*e1fe3e4aSElliott Hughes keywords = dict([(x.lower(), x) for x in fields]) 1911*e1fe3e4aSElliott Hughes return "{} {};".format(keywords[self.key], self.value) 1912*e1fe3e4aSElliott Hughes 1913*e1fe3e4aSElliott Hughes 1914*e1fe3e4aSElliott Hughesclass VheaField(Statement): 1915*e1fe3e4aSElliott Hughes """An entry in the ``vhea`` table.""" 1916*e1fe3e4aSElliott Hughes 1917*e1fe3e4aSElliott Hughes def __init__(self, key, value, location=None): 1918*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1919*e1fe3e4aSElliott Hughes self.key = key 1920*e1fe3e4aSElliott Hughes self.value = value 1921*e1fe3e4aSElliott Hughes 1922*e1fe3e4aSElliott Hughes def build(self, builder): 1923*e1fe3e4aSElliott Hughes """Calls the builder object's ``add_vhea_field`` callback.""" 1924*e1fe3e4aSElliott Hughes builder.add_vhea_field(self.key, self.value) 1925*e1fe3e4aSElliott Hughes 1926*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1927*e1fe3e4aSElliott Hughes fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap") 1928*e1fe3e4aSElliott Hughes keywords = dict([(x.lower(), x) for x in fields]) 1929*e1fe3e4aSElliott Hughes return "{} {};".format(keywords[self.key], self.value) 1930*e1fe3e4aSElliott Hughes 1931*e1fe3e4aSElliott Hughes 1932*e1fe3e4aSElliott Hughesclass STATDesignAxisStatement(Statement): 1933*e1fe3e4aSElliott Hughes """A STAT table Design Axis 1934*e1fe3e4aSElliott Hughes 1935*e1fe3e4aSElliott Hughes Args: 1936*e1fe3e4aSElliott Hughes tag (str): a 4 letter axis tag 1937*e1fe3e4aSElliott Hughes axisOrder (int): an int 1938*e1fe3e4aSElliott Hughes names (list): a list of :class:`STATNameStatement` objects 1939*e1fe3e4aSElliott Hughes """ 1940*e1fe3e4aSElliott Hughes 1941*e1fe3e4aSElliott Hughes def __init__(self, tag, axisOrder, names, location=None): 1942*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1943*e1fe3e4aSElliott Hughes self.tag = tag 1944*e1fe3e4aSElliott Hughes self.axisOrder = axisOrder 1945*e1fe3e4aSElliott Hughes self.names = names 1946*e1fe3e4aSElliott Hughes self.location = location 1947*e1fe3e4aSElliott Hughes 1948*e1fe3e4aSElliott Hughes def build(self, builder): 1949*e1fe3e4aSElliott Hughes builder.addDesignAxis(self, self.location) 1950*e1fe3e4aSElliott Hughes 1951*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1952*e1fe3e4aSElliott Hughes indent += SHIFT 1953*e1fe3e4aSElliott Hughes res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n" 1954*e1fe3e4aSElliott Hughes res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n" 1955*e1fe3e4aSElliott Hughes res += "};" 1956*e1fe3e4aSElliott Hughes return res 1957*e1fe3e4aSElliott Hughes 1958*e1fe3e4aSElliott Hughes 1959*e1fe3e4aSElliott Hughesclass ElidedFallbackName(Statement): 1960*e1fe3e4aSElliott Hughes """STAT table ElidedFallbackName 1961*e1fe3e4aSElliott Hughes 1962*e1fe3e4aSElliott Hughes Args: 1963*e1fe3e4aSElliott Hughes names: a list of :class:`STATNameStatement` objects 1964*e1fe3e4aSElliott Hughes """ 1965*e1fe3e4aSElliott Hughes 1966*e1fe3e4aSElliott Hughes def __init__(self, names, location=None): 1967*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1968*e1fe3e4aSElliott Hughes self.names = names 1969*e1fe3e4aSElliott Hughes self.location = location 1970*e1fe3e4aSElliott Hughes 1971*e1fe3e4aSElliott Hughes def build(self, builder): 1972*e1fe3e4aSElliott Hughes builder.setElidedFallbackName(self.names, self.location) 1973*e1fe3e4aSElliott Hughes 1974*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1975*e1fe3e4aSElliott Hughes indent += SHIFT 1976*e1fe3e4aSElliott Hughes res = "ElidedFallbackName { \n" 1977*e1fe3e4aSElliott Hughes res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n" 1978*e1fe3e4aSElliott Hughes res += "};" 1979*e1fe3e4aSElliott Hughes return res 1980*e1fe3e4aSElliott Hughes 1981*e1fe3e4aSElliott Hughes 1982*e1fe3e4aSElliott Hughesclass ElidedFallbackNameID(Statement): 1983*e1fe3e4aSElliott Hughes """STAT table ElidedFallbackNameID 1984*e1fe3e4aSElliott Hughes 1985*e1fe3e4aSElliott Hughes Args: 1986*e1fe3e4aSElliott Hughes value: an int pointing to an existing name table name ID 1987*e1fe3e4aSElliott Hughes """ 1988*e1fe3e4aSElliott Hughes 1989*e1fe3e4aSElliott Hughes def __init__(self, value, location=None): 1990*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 1991*e1fe3e4aSElliott Hughes self.value = value 1992*e1fe3e4aSElliott Hughes self.location = location 1993*e1fe3e4aSElliott Hughes 1994*e1fe3e4aSElliott Hughes def build(self, builder): 1995*e1fe3e4aSElliott Hughes builder.setElidedFallbackName(self.value, self.location) 1996*e1fe3e4aSElliott Hughes 1997*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 1998*e1fe3e4aSElliott Hughes return f"ElidedFallbackNameID {self.value};" 1999*e1fe3e4aSElliott Hughes 2000*e1fe3e4aSElliott Hughes 2001*e1fe3e4aSElliott Hughesclass STATAxisValueStatement(Statement): 2002*e1fe3e4aSElliott Hughes """A STAT table Axis Value Record 2003*e1fe3e4aSElliott Hughes 2004*e1fe3e4aSElliott Hughes Args: 2005*e1fe3e4aSElliott Hughes names (list): a list of :class:`STATNameStatement` objects 2006*e1fe3e4aSElliott Hughes locations (list): a list of :class:`AxisValueLocationStatement` objects 2007*e1fe3e4aSElliott Hughes flags (int): an int 2008*e1fe3e4aSElliott Hughes """ 2009*e1fe3e4aSElliott Hughes 2010*e1fe3e4aSElliott Hughes def __init__(self, names, locations, flags, location=None): 2011*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 2012*e1fe3e4aSElliott Hughes self.names = names 2013*e1fe3e4aSElliott Hughes self.locations = locations 2014*e1fe3e4aSElliott Hughes self.flags = flags 2015*e1fe3e4aSElliott Hughes 2016*e1fe3e4aSElliott Hughes def build(self, builder): 2017*e1fe3e4aSElliott Hughes builder.addAxisValueRecord(self, self.location) 2018*e1fe3e4aSElliott Hughes 2019*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 2020*e1fe3e4aSElliott Hughes res = "AxisValue {\n" 2021*e1fe3e4aSElliott Hughes for location in self.locations: 2022*e1fe3e4aSElliott Hughes res += location.asFea() 2023*e1fe3e4aSElliott Hughes 2024*e1fe3e4aSElliott Hughes for nameRecord in self.names: 2025*e1fe3e4aSElliott Hughes res += nameRecord.asFea() 2026*e1fe3e4aSElliott Hughes res += "\n" 2027*e1fe3e4aSElliott Hughes 2028*e1fe3e4aSElliott Hughes if self.flags: 2029*e1fe3e4aSElliott Hughes flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"] 2030*e1fe3e4aSElliott Hughes flagStrings = [] 2031*e1fe3e4aSElliott Hughes curr = 1 2032*e1fe3e4aSElliott Hughes for i in range(len(flags)): 2033*e1fe3e4aSElliott Hughes if self.flags & curr != 0: 2034*e1fe3e4aSElliott Hughes flagStrings.append(flags[i]) 2035*e1fe3e4aSElliott Hughes curr = curr << 1 2036*e1fe3e4aSElliott Hughes res += f"flag {' '.join(flagStrings)};\n" 2037*e1fe3e4aSElliott Hughes res += "};" 2038*e1fe3e4aSElliott Hughes return res 2039*e1fe3e4aSElliott Hughes 2040*e1fe3e4aSElliott Hughes 2041*e1fe3e4aSElliott Hughesclass AxisValueLocationStatement(Statement): 2042*e1fe3e4aSElliott Hughes """ 2043*e1fe3e4aSElliott Hughes A STAT table Axis Value Location 2044*e1fe3e4aSElliott Hughes 2045*e1fe3e4aSElliott Hughes Args: 2046*e1fe3e4aSElliott Hughes tag (str): a 4 letter axis tag 2047*e1fe3e4aSElliott Hughes values (list): a list of ints and/or floats 2048*e1fe3e4aSElliott Hughes """ 2049*e1fe3e4aSElliott Hughes 2050*e1fe3e4aSElliott Hughes def __init__(self, tag, values, location=None): 2051*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 2052*e1fe3e4aSElliott Hughes self.tag = tag 2053*e1fe3e4aSElliott Hughes self.values = values 2054*e1fe3e4aSElliott Hughes 2055*e1fe3e4aSElliott Hughes def asFea(self, res=""): 2056*e1fe3e4aSElliott Hughes res += f"location {self.tag} " 2057*e1fe3e4aSElliott Hughes res += f"{' '.join(str(i) for i in self.values)};\n" 2058*e1fe3e4aSElliott Hughes return res 2059*e1fe3e4aSElliott Hughes 2060*e1fe3e4aSElliott Hughes 2061*e1fe3e4aSElliott Hughesclass ConditionsetStatement(Statement): 2062*e1fe3e4aSElliott Hughes """ 2063*e1fe3e4aSElliott Hughes A variable layout conditionset 2064*e1fe3e4aSElliott Hughes 2065*e1fe3e4aSElliott Hughes Args: 2066*e1fe3e4aSElliott Hughes name (str): the name of this conditionset 2067*e1fe3e4aSElliott Hughes conditions (dict): a dictionary mapping axis tags to a 2068*e1fe3e4aSElliott Hughes tuple of (min,max) userspace coordinates. 2069*e1fe3e4aSElliott Hughes """ 2070*e1fe3e4aSElliott Hughes 2071*e1fe3e4aSElliott Hughes def __init__(self, name, conditions, location=None): 2072*e1fe3e4aSElliott Hughes Statement.__init__(self, location) 2073*e1fe3e4aSElliott Hughes self.name = name 2074*e1fe3e4aSElliott Hughes self.conditions = conditions 2075*e1fe3e4aSElliott Hughes 2076*e1fe3e4aSElliott Hughes def build(self, builder): 2077*e1fe3e4aSElliott Hughes builder.add_conditionset(self.location, self.name, self.conditions) 2078*e1fe3e4aSElliott Hughes 2079*e1fe3e4aSElliott Hughes def asFea(self, res="", indent=""): 2080*e1fe3e4aSElliott Hughes res += indent + f"conditionset {self.name} " + "{\n" 2081*e1fe3e4aSElliott Hughes for tag, (minvalue, maxvalue) in self.conditions.items(): 2082*e1fe3e4aSElliott Hughes res += indent + SHIFT + f"{tag} {minvalue} {maxvalue};\n" 2083*e1fe3e4aSElliott Hughes res += indent + "}" + f" {self.name};\n" 2084*e1fe3e4aSElliott Hughes return res 2085*e1fe3e4aSElliott Hughes 2086*e1fe3e4aSElliott Hughes 2087*e1fe3e4aSElliott Hughesclass VariationBlock(Block): 2088*e1fe3e4aSElliott Hughes """A variation feature block, applicable in a given set of conditions.""" 2089*e1fe3e4aSElliott Hughes 2090*e1fe3e4aSElliott Hughes def __init__(self, name, conditionset, use_extension=False, location=None): 2091*e1fe3e4aSElliott Hughes Block.__init__(self, location) 2092*e1fe3e4aSElliott Hughes self.name, self.conditionset, self.use_extension = ( 2093*e1fe3e4aSElliott Hughes name, 2094*e1fe3e4aSElliott Hughes conditionset, 2095*e1fe3e4aSElliott Hughes use_extension, 2096*e1fe3e4aSElliott Hughes ) 2097*e1fe3e4aSElliott Hughes 2098*e1fe3e4aSElliott Hughes def build(self, builder): 2099*e1fe3e4aSElliott Hughes """Call the ``start_feature`` callback on the builder object, visit 2100*e1fe3e4aSElliott Hughes all the statements in this feature, and then call ``end_feature``.""" 2101*e1fe3e4aSElliott Hughes builder.start_feature(self.location, self.name) 2102*e1fe3e4aSElliott Hughes if ( 2103*e1fe3e4aSElliott Hughes self.conditionset != "NULL" 2104*e1fe3e4aSElliott Hughes and self.conditionset not in builder.conditionsets_ 2105*e1fe3e4aSElliott Hughes ): 2106*e1fe3e4aSElliott Hughes raise FeatureLibError( 2107*e1fe3e4aSElliott Hughes f"variation block used undefined conditionset {self.conditionset}", 2108*e1fe3e4aSElliott Hughes self.location, 2109*e1fe3e4aSElliott Hughes ) 2110*e1fe3e4aSElliott Hughes 2111*e1fe3e4aSElliott Hughes # language exclude_dflt statements modify builder.features_ 2112*e1fe3e4aSElliott Hughes # limit them to this block with temporary builder.features_ 2113*e1fe3e4aSElliott Hughes features = builder.features_ 2114*e1fe3e4aSElliott Hughes builder.features_ = {} 2115*e1fe3e4aSElliott Hughes Block.build(self, builder) 2116*e1fe3e4aSElliott Hughes for key, value in builder.features_.items(): 2117*e1fe3e4aSElliott Hughes items = builder.feature_variations_.setdefault(key, {}).setdefault( 2118*e1fe3e4aSElliott Hughes self.conditionset, [] 2119*e1fe3e4aSElliott Hughes ) 2120*e1fe3e4aSElliott Hughes items.extend(value) 2121*e1fe3e4aSElliott Hughes if key not in features: 2122*e1fe3e4aSElliott Hughes features[key] = [] # Ensure we make a feature record 2123*e1fe3e4aSElliott Hughes builder.features_ = features 2124*e1fe3e4aSElliott Hughes builder.end_feature() 2125*e1fe3e4aSElliott Hughes 2126*e1fe3e4aSElliott Hughes def asFea(self, indent=""): 2127*e1fe3e4aSElliott Hughes res = indent + "variation %s " % self.name.strip() 2128*e1fe3e4aSElliott Hughes res += self.conditionset + " " 2129*e1fe3e4aSElliott Hughes if self.use_extension: 2130*e1fe3e4aSElliott Hughes res += "useExtension " 2131*e1fe3e4aSElliott Hughes res += "{\n" 2132*e1fe3e4aSElliott Hughes res += Block.asFea(self, indent=indent) 2133*e1fe3e4aSElliott Hughes res += indent + "} %s;\n" % self.name.strip() 2134*e1fe3e4aSElliott Hughes return res 2135