from __future__ import print_function, division, absolute_import from __future__ import unicode_literals from fontTools.misc.py23 import * from fontTools.feaLib.error import FeatureLibError from fontTools.misc.encodingTools import getEncoding from collections import OrderedDict import itertools SHIFT = " " * 4 def deviceToString(device): if device is None: return "" else: return "" % ", ".join("%d %d" % t for t in device) fea_keywords = set([ "anchor", "anchordef", "anon", "anonymous", "by", "contour", "cursive", "device", "enum", "enumerate", "excludedflt", "exclude_dflt", "feature", "from", "ignore", "ignorebaseglyphs", "ignoreligatures", "ignoremarks", "include", "includedflt", "include_dflt", "language", "languagesystem", "lookup", "lookupflag", "mark", "markattachmenttype", "markclass", "nameid", "null", "parameters", "pos", "position", "required", "righttoleft", "reversesub", "rsub", "script", "sub", "substitute", "subtable", "table", "usemarkfilteringset", "useextension", "valuerecorddef"] ) def asFea(g): if hasattr(g, 'asFea'): return g.asFea() elif isinstance(g, tuple) and len(g) == 2: return asFea(g[0]) + "-" + asFea(g[1]) # a range elif g.lower() in fea_keywords: return "\\" + g else: return g class Element(object): def __init__(self, location): self.location = location def build(self, builder): pass def asFea(self, indent=""): raise NotImplementedError def __str__(self): return self.asFea() class Statement(Element): pass class Expression(Element): pass class Comment(Element): def __init__(self, location, text): super(Comment, self).__init__(location) self.text = text def asFea(self, indent=""): return self.text class GlyphName(Expression): """A single glyph name, such as cedilla.""" def __init__(self, location, glyph): Expression.__init__(self, location) self.glyph = glyph def glyphSet(self): return (self.glyph,) def asFea(self, indent=""): return str(self.glyph) class GlyphClass(Expression): """A glyph class, such as [acute cedilla grave].""" def __init__(self, location, glyphs=None): Expression.__init__(self, location) self.glyphs = glyphs if glyphs is not None else [] self.original = [] self.curr = 0 def glyphSet(self): return tuple(self.glyphs) def asFea(self, indent=""): if len(self.original): if self.curr < len(self.glyphs): self.original.extend(self.glyphs[self.curr:]) self.curr = len(self.glyphs) return "[" + " ".join(map(asFea, self.original)) + "]" else: return "[" + " ".join(map(asFea, self.glyphs)) + "]" def extend(self, glyphs): self.glyphs.extend(glyphs) def append(self, glyph): self.glyphs.append(glyph) def add_range(self, start, end, glyphs): if self.curr < len(self.glyphs): self.original.extend(self.glyphs[self.curr:]) self.original.append((start, end)) self.glyphs.extend(glyphs) self.curr = len(self.glyphs) def add_cid_range(self, start, end, glyphs): if self.curr < len(self.glyphs): self.original.extend(self.glyphs[self.curr:]) self.original.append(("cid{:05d}".format(start), "cid{:05d}".format(end))) self.glyphs.extend(glyphs) self.curr = len(self.glyphs) def add_class(self, gc): if self.curr < len(self.glyphs): self.original.extend(self.glyphs[self.curr:]) self.original.append(gc) self.glyphs.extend(gc.glyphSet()) self.curr = len(self.glyphs) class GlyphClassName(Expression): """A glyph class name, such as @FRENCH_MARKS.""" def __init__(self, location, glyphclass): Expression.__init__(self, location) assert isinstance(glyphclass, GlyphClassDefinition) self.glyphclass = glyphclass def glyphSet(self): return tuple(self.glyphclass.glyphSet()) def asFea(self, indent=""): return "@" + self.glyphclass.name class MarkClassName(Expression): """A mark class name, such as @FRENCH_MARKS defined with markClass.""" def __init__(self, location, markClass): Expression.__init__(self, location) assert isinstance(markClass, MarkClass) self.markClass = markClass def glyphSet(self): return self.markClass.glyphSet() def asFea(self, indent=""): return "@" + self.markClass.name class AnonymousBlock(Statement): def __init__(self, tag, content, location): Statement.__init__(self, location) self.tag, self.content = tag, content def asFea(self, indent=""): res = "anon {} {{\n".format(self.tag) res += self.content res += "}} {};\n\n".format(self.tag) return res class Block(Statement): def __init__(self, location): Statement.__init__(self, location) self.statements = [] def build(self, builder): for s in self.statements: s.build(builder) def asFea(self, indent=""): indent += SHIFT return indent + ("\n" + indent).join( [s.asFea(indent=indent) for s in self.statements]) + "\n" class FeatureFile(Block): def __init__(self): Block.__init__(self, location=None) self.markClasses = {} # name --> ast.MarkClass def asFea(self, indent=""): return "\n".join(s.asFea(indent=indent) for s in self.statements) class FeatureBlock(Block): def __init__(self, location, name, use_extension): Block.__init__(self, location) self.name, self.use_extension = name, use_extension def build(self, builder): # TODO(sascha): Handle use_extension. builder.start_feature(self.location, self.name) # language exclude_dflt statements modify builder.features_ # limit them to this block with temporary builder.features_ features = builder.features_ builder.features_ = {} Block.build(self, builder) for key, value in builder.features_.items(): features.setdefault(key, []).extend(value) builder.features_ = features builder.end_feature() def asFea(self, indent=""): res = indent + "feature %s {\n" % self.name.strip() res += Block.asFea(self, indent=indent) res += indent + "} %s;\n" % self.name.strip() return res class FeatureNamesBlock(Block): def __init__(self, location): Block.__init__(self, location) def asFea(self, indent=""): res = indent + "featureNames {\n" res += Block.asFea(self, indent=indent) res += indent + "};\n" return res class LookupBlock(Block): def __init__(self, location, name, use_extension): Block.__init__(self, location) self.name, self.use_extension = name, use_extension def build(self, builder): # TODO(sascha): Handle use_extension. builder.start_lookup_block(self.location, self.name) Block.build(self, builder) builder.end_lookup_block() def asFea(self, indent=""): res = "lookup {} {{\n".format(self.name) res += Block.asFea(self, indent=indent) res += "{}}} {};\n".format(indent, self.name) return res class TableBlock(Block): def __init__(self, location, name): Block.__init__(self, location) self.name = name def asFea(self, indent=""): res = "table {} {{\n".format(self.name.strip()) res += super(TableBlock, self).asFea(indent=indent) res += "}} {};\n".format(self.name.strip()) return res class GlyphClassDefinition(Statement): """Example: @UPPERCASE = [A-Z];""" def __init__(self, location, name, glyphs): Statement.__init__(self, location) self.name = name self.glyphs = glyphs def glyphSet(self): return tuple(self.glyphs.glyphSet()) def asFea(self, indent=""): return "@" + self.name + " = " + self.glyphs.asFea() + ";" class GlyphClassDefStatement(Statement): """Example: GlyphClassDef @UPPERCASE, [B], [C], [D];""" def __init__(self, location, baseGlyphs, markGlyphs, ligatureGlyphs, componentGlyphs): Statement.__init__(self, location) self.baseGlyphs, self.markGlyphs = (baseGlyphs, markGlyphs) self.ligatureGlyphs = ligatureGlyphs self.componentGlyphs = componentGlyphs def build(self, builder): base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple() liga = self.ligatureGlyphs.glyphSet() \ if self.ligatureGlyphs else tuple() mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple() comp = (self.componentGlyphs.glyphSet() if self.componentGlyphs else tuple()) builder.add_glyphClassDef(self.location, base, liga, mark, comp) def asFea(self, indent=""): return "GlyphClassDef {}, {}, {}, {};".format( self.baseGlyphs.asFea() if self.baseGlyphs else "", self.ligatureGlyphs.asFea() if self.ligatureGlyphs else "", self.markGlyphs.asFea() if self.markGlyphs else "", self.componentGlyphs.asFea() if self.componentGlyphs else "") # While glyph classes can be defined only once, the feature file format # allows expanding mark classes with multiple definitions, each using # different glyphs and anchors. The following are two MarkClassDefinitions # for the same MarkClass: # markClass [acute grave] @FRENCH_ACCENTS; # markClass [cedilla] @FRENCH_ACCENTS; class MarkClass(object): def __init__(self, name): self.name = name self.definitions = [] self.glyphs = OrderedDict() # glyph --> ast.MarkClassDefinitions def addDefinition(self, definition): assert isinstance(definition, MarkClassDefinition) self.definitions.append(definition) for glyph in definition.glyphSet(): if glyph in self.glyphs: otherLoc = self.glyphs[glyph].location raise FeatureLibError( "Glyph %s already defined at %s:%d:%d" % ( glyph, otherLoc[0], otherLoc[1], otherLoc[2]), definition.location) self.glyphs[glyph] = definition def glyphSet(self): return tuple(self.glyphs.keys()) def asFea(self, indent=""): res = "\n".join(d.asFea(indent=indent) for d in self.definitions) return res class MarkClassDefinition(Statement): def __init__(self, location, markClass, anchor, glyphs): Statement.__init__(self, location) assert isinstance(markClass, MarkClass) assert isinstance(anchor, Anchor) and isinstance(glyphs, Expression) self.markClass, self.anchor, self.glyphs = markClass, anchor, glyphs def glyphSet(self): return self.glyphs.glyphSet() def asFea(self, indent=""): return "{}markClass {} {} @{};".format( indent, self.glyphs.asFea(), self.anchor.asFea(), self.markClass.name) class AlternateSubstStatement(Statement): def __init__(self, location, prefix, glyph, suffix, replacement): Statement.__init__(self, location) self.prefix, self.glyph, self.suffix = (prefix, glyph, suffix) self.replacement = replacement def build(self, builder): glyph = self.glyph.glyphSet() assert len(glyph) == 1, glyph glyph = list(glyph)[0] prefix = [p.glyphSet() for p in self.prefix] suffix = [s.glyphSet() for s in self.suffix] replacement = self.replacement.glyphSet() builder.add_alternate_subst(self.location, prefix, glyph, suffix, replacement) def asFea(self, indent=""): res = "sub " if len(self.prefix) or len(self.suffix): if len(self.prefix): res += " ".join(map(asFea, self.prefix)) + " " res += asFea(self.glyph) + "'" # even though we really only use 1 if len(self.suffix): res += " " + " ".join(map(asFea, self.suffix)) else: res += asFea(self.glyph) res += " from " res += asFea(self.replacement) res += ";" return res class Anchor(Expression): def __init__(self, location, name, x, y, contourpoint, xDeviceTable, yDeviceTable): Expression.__init__(self, location) self.name = name self.x, self.y, self.contourpoint = x, y, contourpoint self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable def asFea(self, indent=""): if self.name is not None: return "".format(self.name) res = "" exit = self.exitAnchor.asFea() if self.exitAnchor else "" return "pos cursive {} {} {};".format(self.glyphclass.asFea(), entry, exit) class FeatureReferenceStatement(Statement): """Example: feature salt;""" def __init__(self, location, featureName): Statement.__init__(self, location) self.location, self.featureName = (location, featureName) def build(self, builder): builder.add_feature_reference(self.location, self.featureName) def asFea(self, indent=""): return "feature {};".format(self.featureName) class IgnorePosStatement(Statement): def __init__(self, location, chainContexts): Statement.__init__(self, location) self.chainContexts = chainContexts def build(self, builder): for prefix, glyphs, suffix in self.chainContexts: prefix = [p.glyphSet() for p in prefix] glyphs = [g.glyphSet() for g in glyphs] suffix = [s.glyphSet() for s in suffix] builder.add_chain_context_pos( self.location, prefix, glyphs, suffix, []) def asFea(self, indent=""): contexts = [] for prefix, glyphs, suffix in self.chainContexts: res = "" if len(prefix) or len(suffix): if len(prefix): res += " ".join(map(asFea, prefix)) + " " res += " ".join(g.asFea() + "'" for g in glyphs) if len(suffix): res += " " + " ".join(map(asFea, suffix)) else: res += " ".join(map(asFea, glyphs)) contexts.append(res) return "ignore pos " + ", ".join(contexts) + ";" class IgnoreSubstStatement(Statement): def __init__(self, location, chainContexts): Statement.__init__(self, location) self.chainContexts = chainContexts def build(self, builder): for prefix, glyphs, suffix in self.chainContexts: prefix = [p.glyphSet() for p in prefix] glyphs = [g.glyphSet() for g in glyphs] suffix = [s.glyphSet() for s in suffix] builder.add_chain_context_subst( self.location, prefix, glyphs, suffix, []) def asFea(self, indent=""): contexts = [] for prefix, glyphs, suffix in self.chainContexts: res = "" if len(prefix) or len(suffix): if len(prefix): res += " ".join(map(asFea, prefix)) + " " res += " ".join(g.asFea() + "'" for g in glyphs) if len(suffix): res += " " + " ".join(map(asFea, suffix)) else: res += " ".join(map(asFea, glyphs)) contexts.append(res) return "ignore sub " + ", ".join(contexts) + ";" class LanguageStatement(Statement): def __init__(self, location, language, include_default, required): Statement.__init__(self, location) assert(len(language) == 4) self.language = language self.include_default = include_default self.required = required def build(self, builder): builder.set_language(location=self.location, language=self.language, include_default=self.include_default, required=self.required) def asFea(self, indent=""): res = "language {}".format(self.language.strip()) if not self.include_default: res += " exclude_dflt" if self.required: res += " required" res += ";" return res class LanguageSystemStatement(Statement): def __init__(self, location, script, language): Statement.__init__(self, location) self.script, self.language = (script, language) def build(self, builder): builder.add_language_system(self.location, self.script, self.language) def asFea(self, indent=""): return "languagesystem {} {};".format(self.script, self.language.strip()) class FontRevisionStatement(Statement): def __init__(self, location, revision): Statement.__init__(self, location) self.revision = revision def build(self, builder): builder.set_font_revision(self.location, self.revision) def asFea(self, indent=""): return "FontRevision {:.3f};".format(self.revision) class LigatureCaretByIndexStatement(Statement): def __init__(self, location, glyphs, carets): Statement.__init__(self, location) self.glyphs, self.carets = (glyphs, carets) def build(self, builder): glyphs = self.glyphs.glyphSet() builder.add_ligatureCaretByIndex_(self.location, glyphs, set(self.carets)) def asFea(self, indent=""): return "LigatureCaretByIndex {} {};".format( self.glyphs.asFea(), " ".join(str(x) for x in self.carets)) class LigatureCaretByPosStatement(Statement): def __init__(self, location, glyphs, carets): Statement.__init__(self, location) self.glyphs, self.carets = (glyphs, carets) def build(self, builder): glyphs = self.glyphs.glyphSet() builder.add_ligatureCaretByPos_(self.location, glyphs, set(self.carets)) def asFea(self, indent=""): return "LigatureCaretByPos {} {};".format( self.glyphs.asFea(), " ".join(str(x) for x in self.carets)) class LigatureSubstStatement(Statement): def __init__(self, location, prefix, glyphs, suffix, replacement, forceChain): Statement.__init__(self, location) self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix) self.replacement, self.forceChain = replacement, forceChain def build(self, builder): prefix = [p.glyphSet() for p in self.prefix] glyphs = [g.glyphSet() for g in self.glyphs] suffix = [s.glyphSet() for s in self.suffix] builder.add_ligature_subst( self.location, prefix, glyphs, suffix, self.replacement, self.forceChain) def asFea(self, indent=""): res = "sub " if len(self.prefix) or len(self.suffix) or self.forceChain: if len(self.prefix): res += " ".join(g.asFea() for g in self.prefix) + " " res += " ".join(g.asFea() + "'" for g in self.glyphs) if len(self.suffix): res += " " + " ".join(g.asFea() for g in self.suffix) else: res += " ".join(g.asFea() for g in self.glyphs) res += " by " res += asFea(self.replacement) res += ";" return res class LookupFlagStatement(Statement): def __init__(self, location, value, markAttachment, markFilteringSet): Statement.__init__(self, location) self.value = value self.markAttachment = markAttachment self.markFilteringSet = markFilteringSet def build(self, builder): markAttach = None if self.markAttachment is not None: markAttach = self.markAttachment.glyphSet() markFilter = None if self.markFilteringSet is not None: markFilter = self.markFilteringSet.glyphSet() builder.set_lookup_flag(self.location, self.value, markAttach, markFilter) def asFea(self, indent=""): res = "lookupflag" flags = ["RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks"] curr = 1 for i in range(len(flags)): if self.value & curr != 0: res += " " + flags[i] curr = curr << 1 if self.markAttachment is not None: res += " MarkAttachmentType {}".format(self.markAttachment.asFea()) if self.markFilteringSet is not None: res += " UseMarkFilteringSet {}".format(self.markFilteringSet.asFea()) res += ";" return res class LookupReferenceStatement(Statement): def __init__(self, location, lookup): Statement.__init__(self, location) self.location, self.lookup = (location, lookup) def build(self, builder): builder.add_lookup_call(self.lookup.name) def asFea(self, indent=""): return "lookup {};".format(self.lookup.name) class MarkBasePosStatement(Statement): def __init__(self, location, base, marks): Statement.__init__(self, location) self.base, self.marks = base, marks def build(self, builder): builder.add_mark_base_pos(self.location, self.base.glyphSet(), self.marks) def asFea(self, indent=""): res = "pos base {}".format(self.base.asFea()) for a, m in self.marks: res += " {} mark @{}".format(a.asFea(), m.name) res += ";" return res class MarkLigPosStatement(Statement): def __init__(self, location, ligatures, marks): Statement.__init__(self, location) self.ligatures, self.marks = ligatures, marks def build(self, builder): builder.add_mark_lig_pos(self.location, self.ligatures.glyphSet(), self.marks) def asFea(self, indent=""): res = "pos ligature {}".format(self.ligatures.asFea()) ligs = [] for l in self.marks: temp = "" if l is None or not len(l): temp = " " else: for a, m in l: temp += " {} mark @{}".format(a.asFea(), m.name) ligs.append(temp) res += ("\n" + indent + SHIFT + "ligComponent").join(ligs) res += ";" return res class MarkMarkPosStatement(Statement): def __init__(self, location, baseMarks, marks): Statement.__init__(self, location) self.baseMarks, self.marks = baseMarks, marks def build(self, builder): builder.add_mark_mark_pos(self.location, self.baseMarks.glyphSet(), self.marks) def asFea(self, indent=""): res = "pos mark {}".format(self.baseMarks.asFea()) for a, m in self.marks: res += " {} mark @{}".format(a.asFea(), m.name) res += ";" return res class MultipleSubstStatement(Statement): def __init__(self, location, prefix, glyph, suffix, replacement): Statement.__init__(self, location) self.prefix, self.glyph, self.suffix = prefix, glyph, suffix self.replacement = replacement def build(self, builder): prefix = [p.glyphSet() for p in self.prefix] suffix = [s.glyphSet() for s in self.suffix] builder.add_multiple_subst( self.location, prefix, self.glyph, suffix, self.replacement) def asFea(self, indent=""): res = "sub " if len(self.prefix) or len(self.suffix): if len(self.prefix): res += " ".join(map(asFea, self.prefix)) + " " res += asFea(self.glyph) + "'" if len(self.suffix): res += " " + " ".join(map(asFea, self.suffix)) else: res += asFea(self.glyph) res += " by " res += " ".join(map(asFea, self.replacement)) res += ";" return res class PairPosStatement(Statement): def __init__(self, location, enumerated, glyphs1, valuerecord1, glyphs2, valuerecord2): Statement.__init__(self, location) self.enumerated = enumerated self.glyphs1, self.valuerecord1 = glyphs1, valuerecord1 self.glyphs2, self.valuerecord2 = glyphs2, valuerecord2 def build(self, builder): if self.enumerated: g = [self.glyphs1.glyphSet(), self.glyphs2.glyphSet()] for glyph1, glyph2 in itertools.product(*g): builder.add_specific_pair_pos( self.location, glyph1, self.valuerecord1, glyph2, self.valuerecord2) return is_specific = (isinstance(self.glyphs1, GlyphName) and isinstance(self.glyphs2, GlyphName)) if is_specific: builder.add_specific_pair_pos( self.location, self.glyphs1.glyph, self.valuerecord1, self.glyphs2.glyph, self.valuerecord2) else: builder.add_class_pair_pos( self.location, self.glyphs1.glyphSet(), self.valuerecord1, self.glyphs2.glyphSet(), self.valuerecord2) def asFea(self, indent=""): res = "enum " if self.enumerated else "" if self.valuerecord2: res += "pos {} {} {} {};".format( self.glyphs1.asFea(), self.valuerecord1.makeString(), self.glyphs2.asFea(), self.valuerecord2.makeString()) else: res += "pos {} {} {};".format( self.glyphs1.asFea(), self.glyphs2.asFea(), self.valuerecord1.makeString()) return res class ReverseChainSingleSubstStatement(Statement): def __init__(self, location, old_prefix, old_suffix, glyphs, replacements): Statement.__init__(self, location) self.old_prefix, self.old_suffix = old_prefix, old_suffix self.glyphs = glyphs self.replacements = replacements def build(self, builder): prefix = [p.glyphSet() for p in self.old_prefix] suffix = [s.glyphSet() for s in self.old_suffix] originals = self.glyphs[0].glyphSet() replaces = self.replacements[0].glyphSet() if len(replaces) == 1: replaces = replaces * len(originals) builder.add_reverse_chain_single_subst( self.location, prefix, suffix, dict(zip(originals, replaces))) def asFea(self, indent=""): res = "rsub " if len(self.old_prefix) or len(self.old_suffix): if len(self.old_prefix): res += " ".join(asFea(g) for g in self.old_prefix) + " " res += " ".join(asFea(g) + "'" for g in self.glyphs) if len(self.old_suffix): res += " " + " ".join(asFea(g) for g in self.old_suffix) else: res += " ".join(map(asFea, self.glyphs)) res += " by {};".format(" ".join(asFea(g) for g in self.replacements)) return res class SingleSubstStatement(Statement): def __init__(self, location, glyphs, replace, prefix, suffix, forceChain): Statement.__init__(self, location) self.prefix, self.suffix = prefix, suffix self.forceChain = forceChain self.glyphs = glyphs self.replacements = replace def build(self, builder): prefix = [p.glyphSet() for p in self.prefix] suffix = [s.glyphSet() for s in self.suffix] originals = self.glyphs[0].glyphSet() replaces = self.replacements[0].glyphSet() if len(replaces) == 1: replaces = replaces * len(originals) builder.add_single_subst(self.location, prefix, suffix, OrderedDict(zip(originals, replaces)), self.forceChain) def asFea(self, indent=""): res = "sub " if len(self.prefix) or len(self.suffix) or self.forceChain: if len(self.prefix): res += " ".join(asFea(g) for g in self.prefix) + " " res += " ".join(asFea(g) + "'" for g in self.glyphs) if len(self.suffix): res += " " + " ".join(asFea(g) for g in self.suffix) else: res += " ".join(asFea(g) for g in self.glyphs) res += " by {};".format(" ".join(asFea(g) for g in self.replacements)) return res class ScriptStatement(Statement): def __init__(self, location, script): Statement.__init__(self, location) self.script = script def build(self, builder): builder.set_script(self.location, self.script) def asFea(self, indent=""): return "script {};".format(self.script.strip()) class SinglePosStatement(Statement): def __init__(self, location, pos, prefix, suffix, forceChain): Statement.__init__(self, location) self.pos, self.prefix, self.suffix = pos, prefix, suffix self.forceChain = forceChain def build(self, builder): prefix = [p.glyphSet() for p in self.prefix] suffix = [s.glyphSet() for s in self.suffix] pos = [(g.glyphSet(), value) for g, value in self.pos] builder.add_single_pos(self.location, prefix, suffix, pos, self.forceChain) def asFea(self, indent=""): res = "pos " if len(self.prefix) or len(self.suffix) or self.forceChain: if len(self.prefix): res += " ".join(map(asFea, self.prefix)) + " " res += " ".join([asFea(x[0]) + "'" + ( (" " + x[1].makeString()) if x[1] else "") for x in self.pos]) if len(self.suffix): res += " " + " ".join(map(asFea, self.suffix)) else: res += " ".join([asFea(x[0]) + " " + (x[1].makeString() if x[1] else "") for x in self.pos]) res += ";" return res class SubtableStatement(Statement): def __init__(self, location): Statement.__init__(self, location) class ValueRecord(Expression): def __init__(self, location, vertical, xPlacement, yPlacement, xAdvance, yAdvance, xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice): Expression.__init__(self, location) self.xPlacement, self.yPlacement = (xPlacement, yPlacement) self.xAdvance, self.yAdvance = (xAdvance, yAdvance) self.xPlaDevice, self.yPlaDevice = (xPlaDevice, yPlaDevice) self.xAdvDevice, self.yAdvDevice = (xAdvDevice, yAdvDevice) self.vertical = vertical def __eq__(self, other): return (self.xPlacement == other.xPlacement and self.yPlacement == other.yPlacement and self.xAdvance == other.xAdvance and self.yAdvance == other.yAdvance and self.xPlaDevice == other.xPlaDevice and self.xAdvDevice == other.xAdvDevice) def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return (hash(self.xPlacement) ^ hash(self.yPlacement) ^ hash(self.xAdvance) ^ hash(self.yAdvance) ^ hash(self.xPlaDevice) ^ hash(self.yPlaDevice) ^ hash(self.xAdvDevice) ^ hash(self.yAdvDevice)) def makeString(self, vertical=None): x, y = self.xPlacement, self.yPlacement xAdvance, yAdvance = self.xAdvance, self.yAdvance xPlaDevice, yPlaDevice = self.xPlaDevice, self.yPlaDevice xAdvDevice, yAdvDevice = self.xAdvDevice, self.yAdvDevice if vertical is None: vertical = self.vertical # Try format A, if possible. if x is None and y is None: if xAdvance is None and vertical: return str(yAdvance) elif yAdvance is None and not vertical: return str(xAdvance) # Try format B, if possible. if (xPlaDevice is None and yPlaDevice is None and xAdvDevice is None and yAdvDevice is None): return "<%s %s %s %s>" % (x, y, xAdvance, yAdvance) # Last resort is format C. return "<%s %s %s %s %s %s %s %s>" % ( x, y, xAdvance, yAdvance, deviceToString(xPlaDevice), deviceToString(yPlaDevice), deviceToString(xAdvDevice), deviceToString(yAdvDevice)) class ValueRecordDefinition(Statement): def __init__(self, location, name, value): Statement.__init__(self, location) self.name = name self.value = value def asFea(self, indent=""): return "valueRecordDef {} {};".format(self.value.asFea(), self.name) def simplify_name_attributes(pid, eid, lid): if pid == 3 and eid == 1 and lid == 1033: return "" elif pid == 1 and eid == 0 and lid == 0: return "1" else: return "{} {} {}".format(pid, eid, lid) class NameRecord(Statement): def __init__(self, location, nameID, platformID, platEncID, langID, string): Statement.__init__(self, location) self.nameID = nameID self.platformID = platformID self.platEncID = platEncID self.langID = langID self.string = string def build(self, builder): builder.add_name_record( self.location, self.nameID, self.platformID, self.platEncID, self.langID, self.string) def asFea(self, indent=""): def escape(c, escape_pattern): # Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C): return unichr(c) else: return escape_pattern % c encoding = getEncoding(self.platformID, self.platEncID, self.langID) if encoding is None: raise FeatureLibError("Unsupported encoding", self.location) s = tobytes(self.string, encoding=encoding) if encoding == "utf_16_be": escaped_string = "".join([ escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x") for i in range(0, len(s), 2)]) else: escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s]) plat = simplify_name_attributes( self.platformID, self.platEncID, self.langID) if plat != "": plat += " " return "nameid {} {}\"{}\";".format(self.nameID, plat, escaped_string) class FeatureNameStatement(NameRecord): def build(self, builder): NameRecord.build(self, builder) builder.add_featureName(self.location, self.nameID) def asFea(self, indent=""): if self.nameID == "size": tag = "sizemenuname" else: tag = "name" plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID) if plat != "": plat += " " return "{} {}\"{}\";".format(tag, plat, self.string) class SizeParameters(Statement): def __init__(self, location, DesignSize, SubfamilyID, RangeStart, RangeEnd): Statement.__init__(self, location) self.DesignSize = DesignSize self.SubfamilyID = SubfamilyID self.RangeStart = RangeStart self.RangeEnd = RangeEnd def build(self, builder): builder.set_size_parameters(self.location, self.DesignSize, self.SubfamilyID, self.RangeStart, self.RangeEnd) def asFea(self, indent=""): res = "parameters {:.1f} {}".format(self.DesignSize, self.SubfamilyID) if self.RangeStart != 0 or self.RangeEnd != 0: res += " {} {}".format(int(self.RangeStart * 10), int(self.RangeEnd * 10)) return res + ";" class BaseAxis(Statement): def __init__(self, location, bases, scripts, vertical): Statement.__init__(self, location) self.bases = bases self.scripts = scripts self.vertical = vertical def build(self, builder): builder.set_base_axis(self.bases, self.scripts, self.vertical) def asFea(self, indent=""): direction = "Vert" if self.vertical else "Horiz" scripts = ["{} {} {}".format(a[0], a[1], " ".join(map(str, a[2]))) for a in self.scripts] return "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};".format( direction, " ".join(self.bases), indent, direction, ", ".join(scripts)) class OS2Field(Statement): def __init__(self, location, key, value): Statement.__init__(self, location) self.key = key self.value = value def build(self, builder): builder.add_os2_field(self.key, self.value) def asFea(self, indent=""): def intarr2str(x): return " ".join(map(str, x)) numbers = ("FSType", "TypoAscender", "TypoDescender", "TypoLineGap", "winAscent", "winDescent", "XHeight", "CapHeight", "WeightClass", "WidthClass", "LowerOpSize", "UpperOpSize") ranges = ("UnicodeRange", "CodePageRange") keywords = dict([(x.lower(), [x, str]) for x in numbers]) keywords.update([(x.lower(), [x, intarr2str]) for x in ranges]) keywords["panose"] = ["Panose", intarr2str] keywords["vendor"] = ["Vendor", lambda y: '"{}"'.format(y)] if self.key in keywords: return "{} {};".format(keywords[self.key][0], keywords[self.key][1](self.value)) return "" # should raise exception class HheaField(Statement): def __init__(self, location, key, value): Statement.__init__(self, location) self.key = key self.value = value def build(self, builder): builder.add_hhea_field(self.key, self.value) def asFea(self, indent=""): fields = ("CaretOffset", "Ascender", "Descender", "LineGap") keywords = dict([(x.lower(), x) for x in fields]) return "{} {};".format(keywords[self.key], self.value) class VheaField(Statement): def __init__(self, location, key, value): Statement.__init__(self, location) self.key = key self.value = value def build(self, builder): builder.add_vhea_field(self.key, self.value) def asFea(self, indent=""): fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap") keywords = dict([(x.lower(), x) for x in fields]) return "{} {};".format(keywords[self.key], self.value)