1from __future__ import print_function, division, absolute_import 2from __future__ import unicode_literals 3from fontTools.misc.py23 import * 4from fontTools.feaLib.error import FeatureLibError 5from fontTools.misc.encodingTools import getEncoding 6from collections import OrderedDict 7import itertools 8 9SHIFT = " " * 4 10 11__all__ = [ 12 'AlternateSubstStatement', 13 'Anchor', 14 'AnchorDefinition', 15 'AnonymousBlock', 16 'AttachStatement', 17 'BaseAxis', 18 'Block', 19 'BytesIO', 20 'CVParametersNameStatement', 21 'ChainContextPosStatement', 22 'ChainContextSubstStatement', 23 'CharacterStatement', 24 'Comment', 25 'CursivePosStatement', 26 'Element', 27 'Expression', 28 'FeatureBlock', 29 'FeatureFile', 30 'FeatureLibError', 31 'FeatureNameStatement', 32 'FeatureReferenceStatement', 33 'FontRevisionStatement', 34 'GlyphClass', 35 'GlyphClassDefStatement', 36 'GlyphClassDefinition', 37 'GlyphClassName', 38 'GlyphName', 39 'HheaField', 40 'IgnorePosStatement', 41 'IgnoreSubstStatement', 42 'IncludeStatement', 43 'LanguageStatement', 44 'LanguageSystemStatement', 45 'LigatureCaretByIndexStatement', 46 'LigatureCaretByPosStatement', 47 'LigatureSubstStatement', 48 'LookupBlock', 49 'LookupFlagStatement', 50 'LookupReferenceStatement', 51 'MarkBasePosStatement', 52 'MarkClass', 53 'MarkClassDefinition', 54 'MarkClassName', 55 'MarkLigPosStatement', 56 'MarkMarkPosStatement', 57 'MultipleSubstStatement', 58 'NameRecord', 59 'NestedBlock', 60 'OS2Field', 61 'OrderedDict', 62 'PairPosStatement', 63 'Py23Error', 64 'ReverseChainSingleSubstStatement', 65 'ScriptStatement', 66 'SimpleNamespace', 67 'SinglePosStatement', 68 'SingleSubstStatement', 69 'SizeParameters', 70 'Statement', 71 'StringIO', 72 'SubtableStatement', 73 'TableBlock', 74 'Tag', 75 'UnicodeIO', 76 'ValueRecord', 77 'ValueRecordDefinition', 78 'VheaField', 79] 80 81 82def deviceToString(device): 83 if device is None: 84 return "<device NULL>" 85 else: 86 return "<device %s>" % ", ".join("%d %d" % t for t in device) 87 88 89fea_keywords = set([ 90 "anchor", "anchordef", "anon", "anonymous", 91 "by", 92 "contour", "cursive", 93 "device", 94 "enum", "enumerate", "excludedflt", "exclude_dflt", 95 "feature", "from", 96 "ignore", "ignorebaseglyphs", "ignoreligatures", "ignoremarks", 97 "include", "includedflt", "include_dflt", 98 "language", "languagesystem", "lookup", "lookupflag", 99 "mark", "markattachmenttype", "markclass", 100 "nameid", "null", 101 "parameters", "pos", "position", 102 "required", "righttoleft", "reversesub", "rsub", 103 "script", "sub", "substitute", "subtable", 104 "table", 105 "usemarkfilteringset", "useextension", "valuerecorddef", 106 "base", "gdef", "head", "hhea", "name", "vhea", "vmtx"] 107) 108 109 110def asFea(g): 111 if hasattr(g, 'asFea'): 112 return g.asFea() 113 elif isinstance(g, tuple) and len(g) == 2: 114 return asFea(g[0]) + "-" + asFea(g[1]) # a range 115 elif g.lower() in fea_keywords: 116 return "\\" + g 117 else: 118 return g 119 120 121class Element(object): 122 123 def __init__(self, location=None): 124 self.location = location 125 126 def build(self, builder): 127 pass 128 129 def asFea(self, indent=""): 130 raise NotImplementedError 131 132 def __str__(self): 133 return self.asFea() 134 135 136class Statement(Element): 137 pass 138 139 140class Expression(Element): 141 pass 142 143 144class Comment(Element): 145 def __init__(self, text, location=None): 146 super(Comment, self).__init__(location) 147 self.text = text 148 149 def asFea(self, indent=""): 150 return self.text 151 152 153class GlyphName(Expression): 154 """A single glyph name, such as cedilla.""" 155 def __init__(self, glyph, location=None): 156 Expression.__init__(self, location) 157 self.glyph = glyph 158 159 def glyphSet(self): 160 return (self.glyph,) 161 162 def asFea(self, indent=""): 163 return asFea(self.glyph) 164 165 166class GlyphClass(Expression): 167 """A glyph class, such as [acute cedilla grave].""" 168 def __init__(self, glyphs=None, location=None): 169 Expression.__init__(self, location) 170 self.glyphs = glyphs if glyphs is not None else [] 171 self.original = [] 172 self.curr = 0 173 174 def glyphSet(self): 175 return tuple(self.glyphs) 176 177 def asFea(self, indent=""): 178 if len(self.original): 179 if self.curr < len(self.glyphs): 180 self.original.extend(self.glyphs[self.curr:]) 181 self.curr = len(self.glyphs) 182 return "[" + " ".join(map(asFea, self.original)) + "]" 183 else: 184 return "[" + " ".join(map(asFea, self.glyphs)) + "]" 185 186 def extend(self, glyphs): 187 self.glyphs.extend(glyphs) 188 189 def append(self, glyph): 190 self.glyphs.append(glyph) 191 192 def add_range(self, start, end, glyphs): 193 if self.curr < len(self.glyphs): 194 self.original.extend(self.glyphs[self.curr:]) 195 self.original.append((start, end)) 196 self.glyphs.extend(glyphs) 197 self.curr = len(self.glyphs) 198 199 def add_cid_range(self, start, end, glyphs): 200 if self.curr < len(self.glyphs): 201 self.original.extend(self.glyphs[self.curr:]) 202 self.original.append(("cid{:05d}".format(start), "cid{:05d}".format(end))) 203 self.glyphs.extend(glyphs) 204 self.curr = len(self.glyphs) 205 206 def add_class(self, gc): 207 if self.curr < len(self.glyphs): 208 self.original.extend(self.glyphs[self.curr:]) 209 self.original.append(gc) 210 self.glyphs.extend(gc.glyphSet()) 211 self.curr = len(self.glyphs) 212 213 214class GlyphClassName(Expression): 215 """A glyph class name, such as @FRENCH_MARKS.""" 216 def __init__(self, glyphclass, location=None): 217 Expression.__init__(self, location) 218 assert isinstance(glyphclass, GlyphClassDefinition) 219 self.glyphclass = glyphclass 220 221 def glyphSet(self): 222 return tuple(self.glyphclass.glyphSet()) 223 224 def asFea(self, indent=""): 225 return "@" + self.glyphclass.name 226 227 228class MarkClassName(Expression): 229 """A mark class name, such as @FRENCH_MARKS defined with markClass.""" 230 def __init__(self, markClass, location=None): 231 Expression.__init__(self, location) 232 assert isinstance(markClass, MarkClass) 233 self.markClass = markClass 234 235 def glyphSet(self): 236 return self.markClass.glyphSet() 237 238 def asFea(self, indent=""): 239 return "@" + self.markClass.name 240 241 242class AnonymousBlock(Statement): 243 def __init__(self, tag, content, location=None): 244 Statement.__init__(self, location) 245 self.tag, self.content = tag, content 246 247 def asFea(self, indent=""): 248 res = "anon {} {{\n".format(self.tag) 249 res += self.content 250 res += "}} {};\n\n".format(self.tag) 251 return res 252 253 254class Block(Statement): 255 def __init__(self, location=None): 256 Statement.__init__(self, location) 257 self.statements = [] 258 259 def build(self, builder): 260 for s in self.statements: 261 s.build(builder) 262 263 def asFea(self, indent=""): 264 indent += SHIFT 265 return indent + ("\n" + indent).join( 266 [s.asFea(indent=indent) for s in self.statements]) + "\n" 267 268 269class FeatureFile(Block): 270 def __init__(self): 271 Block.__init__(self, location=None) 272 self.markClasses = {} # name --> ast.MarkClass 273 274 def asFea(self, indent=""): 275 return "\n".join(s.asFea(indent=indent) for s in self.statements) 276 277 278class FeatureBlock(Block): 279 def __init__(self, name, use_extension=False, location=None): 280 Block.__init__(self, location) 281 self.name, self.use_extension = name, use_extension 282 283 def build(self, builder): 284 # TODO(sascha): Handle use_extension. 285 builder.start_feature(self.location, self.name) 286 # language exclude_dflt statements modify builder.features_ 287 # limit them to this block with temporary builder.features_ 288 features = builder.features_ 289 builder.features_ = {} 290 Block.build(self, builder) 291 for key, value in builder.features_.items(): 292 features.setdefault(key, []).extend(value) 293 builder.features_ = features 294 builder.end_feature() 295 296 def asFea(self, indent=""): 297 res = indent + "feature %s " % self.name.strip() 298 if self.use_extension: 299 res += "useExtension " 300 res += "{\n" 301 res += Block.asFea(self, indent=indent) 302 res += indent + "} %s;\n" % self.name.strip() 303 return res 304 305 306class NestedBlock(Block): 307 def __init__(self, tag, block_name, location=None): 308 Block.__init__(self, location) 309 self.tag = tag 310 self.block_name = block_name 311 312 def build(self, builder): 313 Block.build(self, builder) 314 if self.block_name == "ParamUILabelNameID": 315 builder.add_to_cv_num_named_params(self.tag) 316 317 def asFea(self, indent=""): 318 res = "{}{} {{\n".format(indent, self.block_name) 319 res += Block.asFea(self, indent=indent) 320 res += "{}}};\n".format(indent) 321 return res 322 323 324class LookupBlock(Block): 325 def __init__(self, name, use_extension=False, location=None): 326 Block.__init__(self, location) 327 self.name, self.use_extension = name, use_extension 328 329 def build(self, builder): 330 # TODO(sascha): Handle use_extension. 331 builder.start_lookup_block(self.location, self.name) 332 Block.build(self, builder) 333 builder.end_lookup_block() 334 335 def asFea(self, indent=""): 336 res = "lookup {} ".format(self.name) 337 if self.use_extension: 338 res += "useExtension " 339 res += "{\n" 340 res += Block.asFea(self, indent=indent) 341 res += "{}}} {};\n".format(indent, self.name) 342 return res 343 344 345class TableBlock(Block): 346 def __init__(self, name, location=None): 347 Block.__init__(self, location) 348 self.name = name 349 350 def asFea(self, indent=""): 351 res = "table {} {{\n".format(self.name.strip()) 352 res += super(TableBlock, self).asFea(indent=indent) 353 res += "}} {};\n".format(self.name.strip()) 354 return res 355 356 357class GlyphClassDefinition(Statement): 358 """Example: @UPPERCASE = [A-Z];""" 359 def __init__(self, name, glyphs, location=None): 360 Statement.__init__(self, location) 361 self.name = name 362 self.glyphs = glyphs 363 364 def glyphSet(self): 365 return tuple(self.glyphs.glyphSet()) 366 367 def asFea(self, indent=""): 368 return "@" + self.name + " = " + self.glyphs.asFea() + ";" 369 370 371class GlyphClassDefStatement(Statement): 372 """Example: GlyphClassDef @UPPERCASE, [B], [C], [D];""" 373 def __init__(self, baseGlyphs, markGlyphs, ligatureGlyphs, 374 componentGlyphs, location=None): 375 Statement.__init__(self, location) 376 self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs) 377 self.ligatureGlyphs = ligatureGlyphs 378 self.componentGlyphs = componentGlyphs 379 380 def build(self, builder): 381 base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple() 382 liga = self.ligatureGlyphs.glyphSet() \ 383 if self.ligatureGlyphs else tuple() 384 mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple() 385 comp = (self.componentGlyphs.glyphSet() 386 if self.componentGlyphs else tuple()) 387 builder.add_glyphClassDef(self.location, base, liga, mark, comp) 388 389 def asFea(self, indent=""): 390 return "GlyphClassDef {}, {}, {}, {};".format( 391 self.baseGlyphs.asFea() if self.baseGlyphs else "", 392 self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "", 393 self.markGlyphs.asFea() if self.markGlyphs else "", 394 self.componentGlyphs.asFea() if self.componentGlyphs else "") 395 396 397# While glyph classes can be defined only once, the feature file format 398# allows expanding mark classes with multiple definitions, each using 399# different glyphs and anchors. The following are two MarkClassDefinitions 400# for the same MarkClass: 401# markClass [acute grave] <anchor 350 800> @FRENCH_ACCENTS; 402# markClass [cedilla] <anchor 350 -200> @FRENCH_ACCENTS; 403class MarkClass(object): 404 def __init__(self, name): 405 self.name = name 406 self.definitions = [] 407 self.glyphs = OrderedDict() # glyph --> ast.MarkClassDefinitions 408 409 def addDefinition(self, definition): 410 assert isinstance(definition, MarkClassDefinition) 411 self.definitions.append(definition) 412 for glyph in definition.glyphSet(): 413 if glyph in self.glyphs: 414 otherLoc = self.glyphs[glyph].location 415 if otherLoc is None: 416 end = "" 417 else: 418 end = " at %s:%d:%d" % ( 419 otherLoc[0], otherLoc[1], otherLoc[2]) 420 raise FeatureLibError( 421 "Glyph %s already defined%s" % (glyph, end), 422 definition.location) 423 self.glyphs[glyph] = definition 424 425 def glyphSet(self): 426 return tuple(self.glyphs.keys()) 427 428 def asFea(self, indent=""): 429 res = "\n".join(d.asFea() for d in self.definitions) 430 return res 431 432 433class MarkClassDefinition(Statement): 434 def __init__(self, markClass, anchor, glyphs, location=None): 435 Statement.__init__(self, location) 436 assert isinstance(markClass, MarkClass) 437 assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression) 438 self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs 439 440 def glyphSet(self): 441 return self.glyphs.glyphSet() 442 443 def asFea(self, indent=""): 444 return "markClass {} {} @{};".format( 445 self.glyphs.asFea(), self.anchor.asFea(), 446 self.markClass.name) 447 448 449class AlternateSubstStatement(Statement): 450 def __init__(self, prefix, glyph, suffix, replacement, location=None): 451 Statement.__init__(self, location) 452 self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix) 453 self.replacement = replacement 454 455 def build(self, builder): 456 glyph = self.glyph.glyphSet() 457 assert len(glyph) == 1, glyph 458 glyph = list(glyph)[0] 459 prefix = [p.glyphSet() for p in self.prefix] 460 suffix = [s.glyphSet() for s in self.suffix] 461 replacement = self.replacement.glyphSet() 462 builder.add_alternate_subst(self.location, prefix, glyph, suffix, 463 replacement) 464 465 def asFea(self, indent=""): 466 res = "sub " 467 if len(self.prefix) or len(self.suffix): 468 if len(self.prefix): 469 res += " ".join(map(asFea, self.prefix)) + " " 470 res += asFea(self.glyph) + "'" # even though we really only use 1 471 if len(self.suffix): 472 res += " " + " ".join(map(asFea, self.suffix)) 473 else: 474 res += asFea(self.glyph) 475 res += " from " 476 res += asFea(self.replacement) 477 res += ";" 478 return res 479 480 481class Anchor(Expression): 482 def __init__(self, x, y, name=None, contourpoint=None, 483 xDeviceTable=None, yDeviceTable=None, location=None): 484 Expression.__init__(self, location) 485 self.name = name 486 self.x, self.y, self.contourpoint = x, y, contourpoint 487 self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable 488 489 def asFea(self, indent=""): 490 if self.name is not None: 491 return "<anchor {}>".format(self.name) 492 res = "<anchor {} {}".format(self.x, self.y) 493 if self.contourpoint: 494 res += " contourpoint {}".format(self.contourpoint) 495 if self.xDeviceTable or self.yDeviceTable: 496 res += " " 497 res += deviceToString(self.xDeviceTable) 498 res += " " 499 res += deviceToString(self.yDeviceTable) 500 res += ">" 501 return res 502 503 504class AnchorDefinition(Statement): 505 def __init__(self, name, x, y, contourpoint=None, location=None): 506 Statement.__init__(self, location) 507 self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint 508 509 def asFea(self, indent=""): 510 res = "anchorDef {} {}".format(self.x, self.y) 511 if self.contourpoint: 512 res += " contourpoint {}".format(self.contourpoint) 513 res += " {};".format(self.name) 514 return res 515 516 517class AttachStatement(Statement): 518 def __init__(self, glyphs, contourPoints, location=None): 519 Statement.__init__(self, location) 520 self.glyphs, self.contourPoints = (glyphs, contourPoints) 521 522 def build(self, builder): 523 glyphs = self.glyphs.glyphSet() 524 builder.add_attach_points(self.location, glyphs, self.contourPoints) 525 526 def asFea(self, indent=""): 527 return "Attach {} {};".format( 528 self.glyphs.asFea(), " ".join(str(c) for c in self.contourPoints)) 529 530 531class ChainContextPosStatement(Statement): 532 def __init__(self, prefix, glyphs, suffix, lookups, location=None): 533 Statement.__init__(self, location) 534 self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix 535 self.lookups = lookups 536 537 def build(self, builder): 538 prefix = [p.glyphSet() for p in self.prefix] 539 glyphs = [g.glyphSet() for g in self.glyphs] 540 suffix = [s.glyphSet() for s in self.suffix] 541 builder.add_chain_context_pos( 542 self.location, prefix, glyphs, suffix, self.lookups) 543 544 def asFea(self, indent=""): 545 res = "pos " 546 if len(self.prefix) or len(self.suffix) or any([x is not None for x in self.lookups]): 547 if len(self.prefix): 548 res += " ".join(g.asFea() for g in self.prefix) + " " 549 for i, g in enumerate(self.glyphs): 550 res += g.asFea() + "'" 551 if self.lookups[i] is not None: 552 res += " lookup " + self.lookups[i].name 553 if i < len(self.glyphs) - 1: 554 res += " " 555 if len(self.suffix): 556 res += " " + " ".join(map(asFea, self.suffix)) 557 else: 558 res += " ".join(map(asFea, self.glyph)) 559 res += ";" 560 return res 561 562 563class ChainContextSubstStatement(Statement): 564 def __init__(self, prefix, glyphs, suffix, lookups, location=None): 565 Statement.__init__(self, location) 566 self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix 567 self.lookups = lookups 568 569 def build(self, builder): 570 prefix = [p.glyphSet() for p in self.prefix] 571 glyphs = [g.glyphSet() for g in self.glyphs] 572 suffix = [s.glyphSet() for s in self.suffix] 573 builder.add_chain_context_subst( 574 self.location, prefix, glyphs, suffix, self.lookups) 575 576 def asFea(self, indent=""): 577 res = "sub " 578 if len(self.prefix) or len(self.suffix) or any([x is not None for x in self.lookups]): 579 if len(self.prefix): 580 res += " ".join(g.asFea() for g in self.prefix) + " " 581 for i, g in enumerate(self.glyphs): 582 res += g.asFea() + "'" 583 if self.lookups[i] is not None: 584 res += " lookup " + self.lookups[i].name 585 if i < len(self.glyphs) - 1: 586 res += " " 587 if len(self.suffix): 588 res += " " + " ".join(map(asFea, self.suffix)) 589 else: 590 res += " ".join(map(asFea, self.glyph)) 591 res += ";" 592 return res 593 594 595class CursivePosStatement(Statement): 596 def __init__(self, glyphclass, entryAnchor, exitAnchor, location=None): 597 Statement.__init__(self, location) 598 self.glyphclass = glyphclass 599 self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor 600 601 def build(self, builder): 602 builder.add_cursive_pos( 603 self.location, self.glyphclass.glyphSet(), self.entryAnchor, self.exitAnchor) 604 605 def asFea(self, indent=""): 606 entry = self.entryAnchor.asFea() if self.entryAnchor else "<anchor NULL>" 607 exit = self.exitAnchor.asFea() if self.exitAnchor else "<anchor NULL>" 608 return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit) 609 610 611class FeatureReferenceStatement(Statement): 612 """Example: feature salt;""" 613 def __init__(self, featureName, location=None): 614 Statement.__init__(self, location) 615 self.location, self.featureName = (location, featureName) 616 617 def build(self, builder): 618 builder.add_feature_reference(self.location, self.featureName) 619 620 def asFea(self, indent=""): 621 return "feature {};".format(self.featureName) 622 623 624class IgnorePosStatement(Statement): 625 def __init__(self, chainContexts, location=None): 626 Statement.__init__(self, location) 627 self.chainContexts = chainContexts 628 629 def build(self, builder): 630 for prefix, glyphs, suffix in self.chainContexts: 631 prefix = [p.glyphSet() for p in prefix] 632 glyphs = [g.glyphSet() for g in glyphs] 633 suffix = [s.glyphSet() for s in suffix] 634 builder.add_chain_context_pos( 635 self.location, prefix, glyphs, suffix, []) 636 637 def asFea(self, indent=""): 638 contexts = [] 639 for prefix, glyphs, suffix in self.chainContexts: 640 res = "" 641 if len(prefix) or len(suffix): 642 if len(prefix): 643 res += " ".join(map(asFea, prefix)) + " " 644 res += " ".join(g.asFea() + "'" for g in glyphs) 645 if len(suffix): 646 res += " " + " ".join(map(asFea, suffix)) 647 else: 648 res += " ".join(map(asFea, glyphs)) 649 contexts.append(res) 650 return "ignore pos " + ", ".join(contexts) + ";" 651 652 653class IgnoreSubstStatement(Statement): 654 def __init__(self, chainContexts, location=None): 655 Statement.__init__(self, location) 656 self.chainContexts = chainContexts 657 658 def build(self, builder): 659 for prefix, glyphs, suffix in self.chainContexts: 660 prefix = [p.glyphSet() for p in prefix] 661 glyphs = [g.glyphSet() for g in glyphs] 662 suffix = [s.glyphSet() for s in suffix] 663 builder.add_chain_context_subst( 664 self.location, prefix, glyphs, suffix, []) 665 666 def asFea(self, indent=""): 667 contexts = [] 668 for prefix, glyphs, suffix in self.chainContexts: 669 res = "" 670 if len(prefix) or len(suffix): 671 if len(prefix): 672 res += " ".join(map(asFea, prefix)) + " " 673 res += " ".join(g.asFea() + "'" for g in glyphs) 674 if len(suffix): 675 res += " " + " ".join(map(asFea, suffix)) 676 else: 677 res += " ".join(map(asFea, glyphs)) 678 contexts.append(res) 679 return "ignore sub " + ", ".join(contexts) + ";" 680 681 682class IncludeStatement(Statement): 683 def __init__(self, filename, location=None): 684 super(IncludeStatement, self).__init__(location) 685 self.filename = filename 686 687 def build(self): 688 # TODO: consider lazy-loading the including parser/lexer? 689 raise FeatureLibError( 690 "Building an include statement is not implemented yet. " 691 "Instead, use Parser(..., followIncludes=True) for building.", 692 self.location) 693 694 def asFea(self, indent=""): 695 return indent + "include(%s);" % self.filename 696 697 698class LanguageStatement(Statement): 699 def __init__(self, language, include_default=True, required=False, 700 location=None): 701 Statement.__init__(self, location) 702 assert(len(language) == 4) 703 self.language = language 704 self.include_default = include_default 705 self.required = required 706 707 def build(self, builder): 708 builder.set_language(location=self.location, language=self.language, 709 include_default=self.include_default, 710 required=self.required) 711 712 def asFea(self, indent=""): 713 res = "language {}".format(self.language.strip()) 714 if not self.include_default: 715 res += " exclude_dflt" 716 if self.required: 717 res += " required" 718 res += ";" 719 return res 720 721 722class LanguageSystemStatement(Statement): 723 def __init__(self, script, language, location=None): 724 Statement.__init__(self, location) 725 self.script, self.language = (script, language) 726 727 def build(self, builder): 728 builder.add_language_system(self.location, self.script, self.language) 729 730 def asFea(self, indent=""): 731 return "languagesystem {} {};".format(self.script, self.language.strip()) 732 733 734class FontRevisionStatement(Statement): 735 def __init__(self, revision, location=None): 736 Statement.__init__(self, location) 737 self.revision = revision 738 739 def build(self, builder): 740 builder.set_font_revision(self.location, self.revision) 741 742 def asFea(self, indent=""): 743 return "FontRevision {:.3f};".format(self.revision) 744 745 746class LigatureCaretByIndexStatement(Statement): 747 def __init__(self, glyphs, carets, location=None): 748 Statement.__init__(self, location) 749 self.glyphs, self.carets = (glyphs, carets) 750 751 def build(self, builder): 752 glyphs = self.glyphs.glyphSet() 753 builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets)) 754 755 def asFea(self, indent=""): 756 return "LigatureCaretByIndex {} {};".format( 757 self.glyphs.asFea(), " ".join(str(x) for x in self.carets)) 758 759 760class LigatureCaretByPosStatement(Statement): 761 def __init__(self, glyphs, carets, location=None): 762 Statement.__init__(self, location) 763 self.glyphs, self.carets = (glyphs, carets) 764 765 def build(self, builder): 766 glyphs = self.glyphs.glyphSet() 767 builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets)) 768 769 def asFea(self, indent=""): 770 return "LigatureCaretByPos {} {};".format( 771 self.glyphs.asFea(), " ".join(str(x) for x in self.carets)) 772 773 774class LigatureSubstStatement(Statement): 775 def __init__(self, prefix, glyphs, suffix, replacement, 776 forceChain, location=None): 777 Statement.__init__(self, location) 778 self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix) 779 self.replacement, self.forceChain = replacement, forceChain 780 781 def build(self, builder): 782 prefix = [p.glyphSet() for p in self.prefix] 783 glyphs = [g.glyphSet() for g in self.glyphs] 784 suffix = [s.glyphSet() for s in self.suffix] 785 builder.add_ligature_subst( 786 self.location, prefix, glyphs, suffix, self.replacement, 787 self.forceChain) 788 789 def asFea(self, indent=""): 790 res = "sub " 791 if len(self.prefix) or len(self.suffix) or self.forceChain: 792 if len(self.prefix): 793 res += " ".join(g.asFea() for g in self.prefix) + " " 794 res += " ".join(g.asFea() + "'" for g in self.glyphs) 795 if len(self.suffix): 796 res += " " + " ".join(g.asFea() for g in self.suffix) 797 else: 798 res += " ".join(g.asFea() for g in self.glyphs) 799 res += " by " 800 res += asFea(self.replacement) 801 res += ";" 802 return res 803 804 805class LookupFlagStatement(Statement): 806 def __init__(self, value=0, markAttachment=None, markFilteringSet=None, 807 location=None): 808 Statement.__init__(self, location) 809 self.value = value 810 self.markAttachment = markAttachment 811 self.markFilteringSet = markFilteringSet 812 813 def build(self, builder): 814 markAttach = None 815 if self.markAttachment is not None: 816 markAttach = self.markAttachment.glyphSet() 817 markFilter = None 818 if self.markFilteringSet is not None: 819 markFilter = self.markFilteringSet.glyphSet() 820 builder.set_lookup_flag(self.location, self.value, 821 markAttach, markFilter) 822 823 def asFea(self, indent=""): 824 res = [] 825 flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"] 826 curr = 1 827 for i in range(len(flags)): 828 if self.value & curr != 0: 829 res.append(flags[i]) 830 curr = curr << 1 831 if self.markAttachment is not None: 832 res.append("MarkAttachmentType {}".format(self.markAttachment.asFea())) 833 if self.markFilteringSet is not None: 834 res.append("UseMarkFilteringSet {}".format(self.markFilteringSet.asFea())) 835 if not res: 836 res = ["0"] 837 return "lookupflag {};".format(" ".join(res)) 838 839 840class LookupReferenceStatement(Statement): 841 def __init__(self, lookup, location=None): 842 Statement.__init__(self, location) 843 self.location, self.lookup = (location, lookup) 844 845 def build(self, builder): 846 builder.add_lookup_call(self.lookup.name) 847 848 def asFea(self, indent=""): 849 return "lookup {};".format(self.lookup.name) 850 851 852class MarkBasePosStatement(Statement): 853 def __init__(self, base, marks, location=None): 854 Statement.__init__(self, location) 855 self.base, self.marks = base, marks 856 857 def build(self, builder): 858 builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks) 859 860 def asFea(self, indent=""): 861 res = "pos base {}".format(self.base.asFea()) 862 for a, m in self.marks: 863 res += " {} mark @{}".format(a.asFea(), m.name) 864 res += ";" 865 return res 866 867 868class MarkLigPosStatement(Statement): 869 def __init__(self, ligatures, marks, location=None): 870 Statement.__init__(self, location) 871 self.ligatures, self.marks = ligatures, marks 872 873 def build(self, builder): 874 builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks) 875 876 def asFea(self, indent=""): 877 res = "pos ligature {}".format(self.ligatures.asFea()) 878 ligs = [] 879 for l in self.marks: 880 temp = "" 881 if l is None or not len(l): 882 temp = " <anchor NULL>" 883 else: 884 for a, m in l: 885 temp += " {} mark @{}".format(a.asFea(), m.name) 886 ligs.append(temp) 887 res += ("\n" + indent + SHIFT + "ligComponent").join(ligs) 888 res += ";" 889 return res 890 891 892class MarkMarkPosStatement(Statement): 893 def __init__(self, baseMarks, marks, location=None): 894 Statement.__init__(self, location) 895 self.baseMarks, self.marks = baseMarks, marks 896 897 def build(self, builder): 898 builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks) 899 900 def asFea(self, indent=""): 901 res = "pos mark {}".format(self.baseMarks.asFea()) 902 for a, m in self.marks: 903 res += " {} mark @{}".format(a.asFea(), m.name) 904 res += ";" 905 return res 906 907 908class MultipleSubstStatement(Statement): 909 def __init__( 910 self, prefix, glyph, suffix, replacement, forceChain=False, location=None 911 ): 912 Statement.__init__(self, location) 913 self.prefix, self.glyph, self.suffix = prefix, glyph, suffix 914 self.replacement = replacement 915 self.forceChain = forceChain 916 917 def build(self, builder): 918 prefix = [p.glyphSet() for p in self.prefix] 919 suffix = [s.glyphSet() for s in self.suffix] 920 builder.add_multiple_subst( 921 self.location, prefix, self.glyph, suffix, self.replacement, 922 self.forceChain) 923 924 def asFea(self, indent=""): 925 res = "sub " 926 if len(self.prefix) or len(self.suffix) or self.forceChain: 927 if len(self.prefix): 928 res += " ".join(map(asFea, self.prefix)) + " " 929 res += asFea(self.glyph) + "'" 930 if len(self.suffix): 931 res += " " + " ".join(map(asFea, self.suffix)) 932 else: 933 res += asFea(self.glyph) 934 res += " by " 935 res += " ".join(map(asFea, self.replacement)) 936 res += ";" 937 return res 938 939 940class PairPosStatement(Statement): 941 def __init__(self, glyphs1, valuerecord1, glyphs2, valuerecord2, 942 enumerated=False, location=None): 943 Statement.__init__(self, location) 944 self.enumerated = enumerated 945 self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1 946 self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2 947 948 def build(self, builder): 949 if self.enumerated: 950 g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()] 951 for glyph1, glyph2 in itertools.product(*g): 952 builder.add_specific_pair_pos( 953 self.location, glyph1, self.valuerecord1, 954 glyph2, self.valuerecord2) 955 return 956 957 is_specific = (isinstance(self.glyphs1, GlyphName) and 958 isinstance(self.glyphs2, GlyphName)) 959 if is_specific: 960 builder.add_specific_pair_pos( 961 self.location, self.glyphs1.glyph, self.valuerecord1, 962 self.glyphs2.glyph, self.valuerecord2) 963 else: 964 builder.add_class_pair_pos( 965 self.location, self.glyphs1.glyphSet(), self.valuerecord1, 966 self.glyphs2.glyphSet(), self.valuerecord2) 967 968 def asFea(self, indent=""): 969 res = "enum " if self.enumerated else "" 970 if self.valuerecord2: 971 res += "pos {} {} {} {};".format( 972 self.glyphs1.asFea(), self.valuerecord1.asFea(), 973 self.glyphs2.asFea(), self.valuerecord2.asFea()) 974 else: 975 res += "pos {} {} {};".format( 976 self.glyphs1.asFea(), self.glyphs2.asFea(), 977 self.valuerecord1.asFea()) 978 return res 979 980 981class ReverseChainSingleSubstStatement(Statement): 982 def __init__(self, old_prefix, old_suffix, glyphs, replacements, 983 location=None): 984 Statement.__init__(self, location) 985 self.old_prefix, self.old_suffix = old_prefix, old_suffix 986 self.glyphs = glyphs 987 self.replacements = replacements 988 989 def build(self, builder): 990 prefix = [p.glyphSet() for p in self.old_prefix] 991 suffix = [s.glyphSet() for s in self.old_suffix] 992 originals = self.glyphs[0].glyphSet() 993 replaces = self.replacements[0].glyphSet() 994 if len(replaces) == 1: 995 replaces = replaces * len(originals) 996 builder.add_reverse_chain_single_subst( 997 self.location, prefix, suffix, dict(zip(originals, replaces))) 998 999 def asFea(self, indent=""): 1000 res = "rsub " 1001 if len(self.old_prefix) or len(self.old_suffix): 1002 if len(self.old_prefix): 1003 res += " ".join(asFea(g) for g in self.old_prefix) + " " 1004 res += " ".join(asFea(g) + "'" for g in self.glyphs) 1005 if len(self.old_suffix): 1006 res += " " + " ".join(asFea(g) for g in self.old_suffix) 1007 else: 1008 res += " ".join(map(asFea, self.glyphs)) 1009 res += " by {};".format(" ".join(asFea(g) for g in self.replacements)) 1010 return res 1011 1012 1013class SingleSubstStatement(Statement): 1014 def __init__(self, glyphs, replace, prefix, suffix, forceChain, 1015 location=None): 1016 Statement.__init__(self, location) 1017 self.prefix, self.suffix = prefix, suffix 1018 self.forceChain = forceChain 1019 self.glyphs = glyphs 1020 self.replacements = replace 1021 1022 def build(self, builder): 1023 prefix = [p.glyphSet() for p in self.prefix] 1024 suffix = [s.glyphSet() for s in self.suffix] 1025 originals = self.glyphs[0].glyphSet() 1026 replaces = self.replacements[0].glyphSet() 1027 if len(replaces) == 1: 1028 replaces = replaces * len(originals) 1029 builder.add_single_subst(self.location, prefix, suffix, 1030 OrderedDict(zip(originals, replaces)), 1031 self.forceChain) 1032 1033 def asFea(self, indent=""): 1034 res = "sub " 1035 if len(self.prefix) or len(self.suffix) or self.forceChain: 1036 if len(self.prefix): 1037 res += " ".join(asFea(g) for g in self.prefix) + " " 1038 res += " ".join(asFea(g) + "'" for g in self.glyphs) 1039 if len(self.suffix): 1040 res += " " + " ".join(asFea(g) for g in self.suffix) 1041 else: 1042 res += " ".join(asFea(g) for g in self.glyphs) 1043 res += " by {};".format(" ".join(asFea(g) for g in self.replacements)) 1044 return res 1045 1046 1047class ScriptStatement(Statement): 1048 def __init__(self, script, location=None): 1049 Statement.__init__(self, location) 1050 self.script = script 1051 1052 def build(self, builder): 1053 builder.set_script(self.location, self.script) 1054 1055 def asFea(self, indent=""): 1056 return "script {};".format(self.script.strip()) 1057 1058 1059class SinglePosStatement(Statement): 1060 def __init__(self, pos, prefix, suffix, forceChain, location=None): 1061 Statement.__init__(self, location) 1062 self.pos, self.prefix, self.suffix = pos, prefix, suffix 1063 self.forceChain = forceChain 1064 1065 def build(self, builder): 1066 prefix = [p.glyphSet() for p in self.prefix] 1067 suffix = [s.glyphSet() for s in self.suffix] 1068 pos = [(g.glyphSet(), value) for g, value in self.pos] 1069 builder.add_single_pos(self.location, prefix, suffix, 1070 pos, self.forceChain) 1071 1072 def asFea(self, indent=""): 1073 res = "pos " 1074 if len(self.prefix) or len(self.suffix) or self.forceChain: 1075 if len(self.prefix): 1076 res += " ".join(map(asFea, self.prefix)) + " " 1077 res += " ".join([asFea(x[0]) + "'" + ( 1078 (" " + x[1].asFea()) if x[1] else "") for x in self.pos]) 1079 if len(self.suffix): 1080 res += " " + " ".join(map(asFea, self.suffix)) 1081 else: 1082 res += " ".join([asFea(x[0]) + " " + 1083 (x[1].asFea() if x[1] else "") for x in self.pos]) 1084 res += ";" 1085 return res 1086 1087 1088class SubtableStatement(Statement): 1089 def __init__(self, location=None): 1090 Statement.__init__(self, location) 1091 1092 def build(self, builder): 1093 builder.add_subtable_break(self.location) 1094 1095 def asFea(self, indent=""): 1096 return "subtable;" 1097 1098 1099class ValueRecord(Expression): 1100 def __init__(self, xPlacement=None, yPlacement=None, 1101 xAdvance=None, yAdvance=None, 1102 xPlaDevice=None, yPlaDevice=None, 1103 xAdvDevice=None, yAdvDevice=None, 1104 vertical=False, location=None): 1105 Expression.__init__(self, location) 1106 self.xPlacement, self.yPlacement = (xPlacement, yPlacement) 1107 self.xAdvance, self.yAdvance = (xAdvance, yAdvance) 1108 self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice) 1109 self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice) 1110 self.vertical = vertical 1111 1112 def __eq__(self, other): 1113 return (self.xPlacement == other.xPlacement and 1114 self.yPlacement == other.yPlacement and 1115 self.xAdvance == other.xAdvance and 1116 self.yAdvance == other.yAdvance and 1117 self.xPlaDevice == other.xPlaDevice and 1118 self.xAdvDevice == other.xAdvDevice) 1119 1120 def __ne__(self, other): 1121 return not self.__eq__(other) 1122 1123 def __hash__(self): 1124 return (hash(self.xPlacement) ^ hash(self.yPlacement) ^ 1125 hash(self.xAdvance) ^ hash(self.yAdvance) ^ 1126 hash(self.xPlaDevice) ^ hash(self.yPlaDevice) ^ 1127 hash(self.xAdvDevice) ^ hash(self.yAdvDevice)) 1128 1129 def asFea(self, indent=""): 1130 if not self: 1131 return "<NULL>" 1132 1133 x, y = self.xPlacement, self.yPlacement 1134 xAdvance, yAdvance = self.xAdvance, self.yAdvance 1135 xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice 1136 xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice 1137 vertical = self.vertical 1138 1139 # Try format A, if possible. 1140 if x is None and y is None: 1141 if xAdvance is None and vertical: 1142 return str(yAdvance) 1143 elif yAdvance is None and not vertical: 1144 return str(xAdvance) 1145 1146 # Try format B, if possible. 1147 if (xPlaDevice is None and yPlaDevice is None and 1148 xAdvDevice is None and yAdvDevice is None): 1149 return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance) 1150 1151 # Last resort is format C. 1152 return "<%s %s %s %s %s %s %s %s>" % ( 1153 x, y, xAdvance, yAdvance, 1154 deviceToString(xPlaDevice), deviceToString(yPlaDevice), 1155 deviceToString(xAdvDevice), deviceToString(yAdvDevice)) 1156 1157 def __bool__(self): 1158 return any( 1159 getattr(self, v) is not None 1160 for v in [ 1161 "xPlacement", 1162 "yPlacement", 1163 "xAdvance", 1164 "yAdvance", 1165 "xPlaDevice", 1166 "yPlaDevice", 1167 "xAdvDevice", 1168 "yAdvDevice", 1169 ] 1170 ) 1171 1172 __nonzero__ = __bool__ 1173 1174 1175class ValueRecordDefinition(Statement): 1176 def __init__(self, name, value, location=None): 1177 Statement.__init__(self, location) 1178 self.name = name 1179 self.value = value 1180 1181 def asFea(self, indent=""): 1182 return "valueRecordDef {} {};".format(self.value.asFea(), self.name) 1183 1184 1185def simplify_name_attributes(pid, eid, lid): 1186 if pid == 3 and eid == 1 and lid == 1033: 1187 return "" 1188 elif pid == 1 and eid == 0 and lid == 0: 1189 return "1" 1190 else: 1191 return "{} {} {}".format(pid, eid, lid) 1192 1193 1194class NameRecord(Statement): 1195 def __init__(self, nameID, platformID, platEncID, langID, string, 1196 location=None): 1197 Statement.__init__(self, location) 1198 self.nameID = nameID 1199 self.platformID = platformID 1200 self.platEncID = platEncID 1201 self.langID = langID 1202 self.string = string 1203 1204 def build(self, builder): 1205 builder.add_name_record( 1206 self.location, self.nameID, self.platformID, 1207 self.platEncID, self.langID, self.string) 1208 1209 def asFea(self, indent=""): 1210 def escape(c, escape_pattern): 1211 # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS 1212 if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C): 1213 return unichr(c) 1214 else: 1215 return escape_pattern % c 1216 encoding = getEncoding(self.platformID, self.platEncID, self.langID) 1217 if encoding is None: 1218 raise FeatureLibError("Unsupported encoding", self.location) 1219 s = tobytes(self.string, encoding=encoding) 1220 if encoding == "utf_16_be": 1221 escaped_string = "".join([ 1222 escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x") 1223 for i in range(0, len(s), 2)]) 1224 else: 1225 escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s]) 1226 plat = simplify_name_attributes( 1227 self.platformID, self.platEncID, self.langID) 1228 if plat != "": 1229 plat += " " 1230 return "nameid {} {}\"{}\";".format(self.nameID, plat, escaped_string) 1231 1232 1233class FeatureNameStatement(NameRecord): 1234 def build(self, builder): 1235 NameRecord.build(self, builder) 1236 builder.add_featureName(self.nameID) 1237 1238 def asFea(self, indent=""): 1239 if self.nameID == "size": 1240 tag = "sizemenuname" 1241 else: 1242 tag = "name" 1243 plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID) 1244 if plat != "": 1245 plat += " " 1246 return "{} {}\"{}\";".format(tag, plat, self.string) 1247 1248 1249class SizeParameters(Statement): 1250 def __init__(self, DesignSize, SubfamilyID, RangeStart, RangeEnd, 1251 location=None): 1252 Statement.__init__(self, location) 1253 self.DesignSize = DesignSize 1254 self.SubfamilyID = SubfamilyID 1255 self.RangeStart = RangeStart 1256 self.RangeEnd = RangeEnd 1257 1258 def build(self, builder): 1259 builder.set_size_parameters(self.location, self.DesignSize, 1260 self.SubfamilyID, self.RangeStart, self.RangeEnd) 1261 1262 def asFea(self, indent=""): 1263 res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID) 1264 if self.RangeStart != 0 or self.RangeEnd != 0: 1265 res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10)) 1266 return res + ";" 1267 1268 1269class CVParametersNameStatement(NameRecord): 1270 def __init__(self, nameID, platformID, platEncID, langID, string, 1271 block_name, location=None): 1272 NameRecord.__init__(self, nameID, platformID, platEncID, langID, 1273 string, location=location) 1274 self.block_name = block_name 1275 1276 def build(self, builder): 1277 item = "" 1278 if self.block_name == "ParamUILabelNameID": 1279 item = "_{}".format(builder.cv_num_named_params_.get(self.nameID, 0)) 1280 builder.add_cv_parameter(self.nameID) 1281 self.nameID = (self.nameID, self.block_name + item) 1282 NameRecord.build(self, builder) 1283 1284 def asFea(self, indent=""): 1285 plat = simplify_name_attributes(self.platformID, self.platEncID, 1286 self.langID) 1287 if plat != "": 1288 plat += " " 1289 return "name {}\"{}\";".format(plat, self.string) 1290 1291 1292class CharacterStatement(Statement): 1293 """ 1294 Statement used in cvParameters blocks of Character Variant features (cvXX). 1295 The Unicode value may be written with either decimal or hexadecimal 1296 notation. The value must be preceded by '0x' if it is a hexadecimal value. 1297 The largest Unicode value allowed is 0xFFFFFF. 1298 """ 1299 def __init__(self, character, tag, location=None): 1300 Statement.__init__(self, location) 1301 self.character = character 1302 self.tag = tag 1303 1304 def build(self, builder): 1305 builder.add_cv_character(self.character, self.tag) 1306 1307 def asFea(self, indent=""): 1308 return "Character {:#x};".format(self.character) 1309 1310 1311class BaseAxis(Statement): 1312 def __init__(self, bases, scripts, vertical, location=None): 1313 Statement.__init__(self, location) 1314 self.bases = bases 1315 self.scripts = scripts 1316 self.vertical = vertical 1317 1318 def build(self, builder): 1319 builder.set_base_axis(self.bases, self.scripts, self.vertical) 1320 1321 def asFea(self, indent=""): 1322 direction = "Vert" if self.vertical else "Horiz" 1323 scripts = ["{} {} {}".format(a[0], a[1], " ".join(map(str, a[2]))) for a in self.scripts] 1324 return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format( 1325 direction, " ".join(self.bases), indent, direction, ", ".join(scripts)) 1326 1327 1328class OS2Field(Statement): 1329 def __init__(self, key, value, location=None): 1330 Statement.__init__(self, location) 1331 self.key = key 1332 self.value = value 1333 1334 def build(self, builder): 1335 builder.add_os2_field(self.key, self.value) 1336 1337 def asFea(self, indent=""): 1338 def intarr2str(x): 1339 return " ".join(map(str, x)) 1340 numbers = ("FSType", "TypoAscender", "TypoDescender", "TypoLineGap", 1341 "winAscent", "winDescent", "XHeight", "CapHeight", 1342 "WeightClass", "WidthClass", "LowerOpSize", "UpperOpSize") 1343 ranges = ("UnicodeRange", "CodePageRange") 1344 keywords = dict([(x.lower(), [x, str]) for x in numbers]) 1345 keywords.update([(x.lower(), [x, intarr2str]) for x in ranges]) 1346 keywords["panose"] = ["Panose", intarr2str] 1347 keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)] 1348 if self.key in keywords: 1349 return "{} {};".format(keywords[self.key][0], keywords[self.key][1](self.value)) 1350 return "" # should raise exception 1351 1352 1353class HheaField(Statement): 1354 def __init__(self, key, value, location=None): 1355 Statement.__init__(self, location) 1356 self.key = key 1357 self.value = value 1358 1359 def build(self, builder): 1360 builder.add_hhea_field(self.key, self.value) 1361 1362 def asFea(self, indent=""): 1363 fields = ("CaretOffset", "Ascender", "Descender", "LineGap") 1364 keywords = dict([(x.lower(), x) for x in fields]) 1365 return "{} {};".format(keywords[self.key], self.value) 1366 1367 1368class VheaField(Statement): 1369 def __init__(self, key, value, location=None): 1370 Statement.__init__(self, location) 1371 self.key = key 1372 self.value = value 1373 1374 def build(self, builder): 1375 builder.add_vhea_field(self.key, self.value) 1376 1377 def asFea(self, indent=""): 1378 fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap") 1379 keywords = dict([(x.lower(), x) for x in fields]) 1380 return "{} {};".format(keywords[self.key], self.value) 1381