def asFeaAST(self): lut = lookup_type(self) glyphs = [glyphref(x) for x in self.glyphs] positionings = list(zip(glyphs, self.valuerecords)) if len(self.glyphs) == 1 or lut == 8: return feaast.SinglePosStatement( positionings, [glyphref(x) for x in self.precontext], [glyphref(x) for x in self.postcontext], (lut == 8), ) if lut == 2: return feaast.PairPosStatement(glyphs[0], self.valuerecords[0], glyphs[1], self.valuerecords[1]) raise ValueError()
def parse_position_(self, enumerated, vertical): assert self.cur_token_ in {"position", "pos"} if self.next_token_ == "cursive": # GPOS type 3 return self.parse_position_cursive_(enumerated, vertical) elif self.next_token_ == "base": # GPOS type 4 return self.parse_position_base_(enumerated, vertical) elif self.next_token_ == "ligature": # GPOS type 5 return self.parse_position_ligature_(enumerated, vertical) elif self.next_token_ == "mark": # GPOS type 6 return self.parse_position_mark_(enumerated, vertical) location = self.cur_token_location_ prefix, glyphs, lookups, suffix = self.parse_glyph_pattern_() gc2, value2 = None, None if not prefix and len(glyphs) == 2 and not suffix and not any(lookups): # Pair positioning, format B: 'pos' glyphs gc2 value1 gc2 = glyphs[1] glyphs = [glyphs[0]] if prefix or len(glyphs) > 1 or suffix or any(lookups): # GPOS type 8: Chaining contextual positioning self.expect_symbol_(";") return ast.ChainContextPosStatement(location, prefix, glyphs, suffix, lookups) value1 = self.parse_valuerecord_(vertical) if self.next_token_ != ";" and gc2 is None: # Pair positioning, format A: 'pos' gc1 value1 gc2 value2 gc2 = self.parse_glyphclass_(accept_glyphname=True).glyphSet() value2 = self.parse_valuerecord_(vertical) self.expect_symbol_(";") if gc2 is None: if enumerated: raise FeatureLibError( '"enumerate" is only allowed with pair positionings', self.cur_token_location_) return ast.SinglePosStatement(location, glyphs[0], value1) else: return ast.PairPosStatement(location, enumerated, glyphs[0], value1, gc2, value2)
def parse_position_(self, enumerated, vertical): assert self.cur_token_ in {"position", "pos"} if self.next_token_ == "cursive": # GPOS type 3 return self.parse_position_cursive_(enumerated, vertical) elif self.next_token_ == "base": # GPOS type 4 return self.parse_position_base_(enumerated, vertical) elif self.next_token_ == "ligature": # GPOS type 5 return self.parse_position_ligature_(enumerated, vertical) elif self.next_token_ == "mark": # GPOS type 6 return self.parse_position_mark_(enumerated, vertical) location = self.cur_token_location_ prefix, glyphs, lookups, values, suffix, hasMarks = \ self.parse_glyph_pattern_(vertical) self.expect_symbol_(";") if any(lookups): # GPOS type 8: Chaining contextual positioning; explicit lookups if any(values): raise FeatureLibError( "If \"lookup\" is present, no values must be specified", location) return ast.ChainContextPosStatement( location, prefix, glyphs, suffix, lookups) # Pair positioning, format A: "pos V 10 A -10;" # Pair positioning, format B: "pos V A -20;" if not prefix and not suffix and len(glyphs) == 2 and not hasMarks: if values[0] is None: # Format B: "pos V A -20;" values.reverse() return ast.PairPosStatement( location, enumerated, glyphs[0], values[0], glyphs[1], values[1]) if enumerated: raise FeatureLibError( '"enumerate" is only allowed with pair positionings', location) return ast.SinglePosStatement(location, list(zip(glyphs, values)), prefix, suffix, forceChain=hasMarks)
def _gposLookup(self, lookup, fealookup): statements = fealookup.statements pos = lookup.pos if isinstance(pos, VAst.PositionAdjustPairDefinition): for (idx1, idx2), (pos1, pos2) in pos.adjust_pair.items(): coverage_1 = pos.coverages_1[idx1 - 1] coverage_2 = pos.coverages_2[idx2 - 1] # If not both are groups, use “enum pos” otherwise makeotf will # fail. enumerated = False for item in coverage_1 + coverage_2: if not isinstance(item, VAst.GroupName): enumerated = True glyphs1 = self._coverage(coverage_1) glyphs2 = self._coverage(coverage_2) record1 = self._adjustment(pos1) record2 = self._adjustment(pos2) assert len(glyphs1) == 1 assert len(glyphs2) == 1 statements.append( ast.PairPosStatement(glyphs1[0], record1, glyphs2[0], record2, enumerated=enumerated)) elif isinstance(pos, VAst.PositionAdjustSingleDefinition): for a, b in pos.adjust_single: glyphs = self._coverage(a) record = self._adjustment(b) assert len(glyphs) == 1 statements.append( ast.SinglePosStatement([(glyphs[0], record)], [], [], False)) elif isinstance(pos, VAst.PositionAttachDefinition): anchors = {} for marks, classname in pos.coverage_to: for mark in marks: # Set actually used mark classes. Basically a hack to get # around the feature file syntax limitation of making mark # classes global and not allowing mark positioning to # specify mark coverage. for name in mark.glyphSet(): key = (name, "MARK_" + classname) self._markclasses[key].used = True markclass = ast.MarkClass(self._className(classname)) for base in pos.coverage: for name in base.glyphSet(): if name not in anchors: anchors[name] = [] if classname not in anchors[name]: anchors[name].append(classname) for name in anchors: components = 1 if name in self._ligatures: components = self._ligatures[name] marks = [] for mark in anchors[name]: markclass = ast.MarkClass(self._className(mark)) for component in range(1, components + 1): if len(marks) < component: marks.append([]) anchor = None if component in self._anchors[name][mark]: anchor = self._anchors[name][mark][component] marks[component - 1].append((anchor, markclass)) base = self._glyphName(name) if name in self._marks: mark = ast.MarkMarkPosStatement(base, marks[0]) elif name in self._ligatures: mark = ast.MarkLigPosStatement(base, marks) else: mark = ast.MarkBasePosStatement(base, marks[0]) statements.append(mark) elif isinstance(pos, VAst.PositionAttachCursiveDefinition): # Collect enter and exit glyphs enter_coverage = [] for coverage in pos.coverages_enter: for base in coverage: for name in base.glyphSet(): enter_coverage.append(name) exit_coverage = [] for coverage in pos.coverages_exit: for base in coverage: for name in base.glyphSet(): exit_coverage.append(name) # Write enter anchors, also check if the glyph has exit anchor and # write it, too. for name in enter_coverage: glyph = self._glyphName(name) entry = self._anchors[name]["entry"][1] exit = None if name in exit_coverage: exit = self._anchors[name]["exit"][1] exit_coverage.pop(exit_coverage.index(name)) statements.append(ast.CursivePosStatement(glyph, entry, exit)) # Write any remaining exit anchors. for name in exit_coverage: glyph = self._glyphName(name) exit = self._anchors[name]["exit"][1] statements.append(ast.CursivePosStatement(glyph, None, exit)) else: raise NotImplementedError(pos)