diff --git a/Doc/source/index.rst b/Doc/source/index.rst index 7698ed823..94aae7c85 100644 --- a/Doc/source/index.rst +++ b/Doc/source/index.rst @@ -56,6 +56,7 @@ This last utility takes a subcommand, which could be one of: - ``varLib.models``: Normalize locations on a given designspace - ``varLib.mutator``: Instantiate a variation font - ``varLib.varStore``: Optimize a font's GDEF variation store +- ``volto``: Convert MS VOLT to AFDKO feature files. Libraries --------- @@ -87,6 +88,7 @@ libraries in the fontTools suite: - :py:mod:`fontTools.unicodedata`: Convert between Unicode and OpenType script information - :py:mod:`fontTools.varLib`: Module for dealing with 'gvar'-style font variations - :py:mod:`fontTools.voltLib`: Module for dealing with Visual OpenType Layout Tool (VOLT) files +- :py:mod:`fontTools.volto`: Convert MS VOLT to AFDKO feature files. A selection of sample Python programs using these libaries can be found in the `Snippets directory `_ of the fontTools repository. @@ -142,6 +144,7 @@ Table of Contents unicodedata/index varLib/index voltLib + volto .. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg diff --git a/Doc/source/volto.rst b/Doc/source/volto.rst new file mode 100644 index 000000000..19698c1ac --- /dev/null +++ b/Doc/source/volto.rst @@ -0,0 +1,8 @@ +############################################# +volto: Convert MS VOLT to AFDKO feature files +############################################# + +.. automodule:: fontTools.volto + :inherited-members: + :members: + :undoc-members: diff --git a/Lib/fontTools/volto.py b/Lib/fontTools/volto.py new file mode 100644 index 000000000..43b9ed214 --- /dev/null +++ b/Lib/fontTools/volto.py @@ -0,0 +1,724 @@ +"""\ +MS VOLT ``.vtp`` to AFDKO ``.fea`` OpenType Layout converter. + +Usage +----- + +To convert a VTP project file: + + + $ fonttools volto input.vtp output.fea + +It is also possible convert font files with `TSIV` table (as saved from Volt), +in this case the glyph names used in the Volt project will be mapped to the +actual glyph names in the font files when written to the feature file: + + $ fonttools volto input.ttf output.fea + +The ``--quiet`` option can be used to suppress warnings. + +The ``--traceback`` can be used to get Python traceback in case of exceptions, +instead of suppressing the traceback. + + +Limitations +----------- + +* Not all VOLT features are supported, the script will error if it it + encounters something it does not understand. Please report an issue if this + happens. +* AFDKO feature file syntax for mark positioning is awkward and does not allow + setting the mark coverage. It also defines mark anchors globally, as a result + some mark positioning lookups might cover many marks than what was in the VOLT + file. This should not be an issue in practice, but if it is then the only way + is to modify the VOLT file or the generated feature file manually to use unique + mark anchors for each lookup. +* VOLT allows subtable breaks in any lookup type, but AFDKO feature file + implementations vary in their support; currently AFDKO’s makeOTF supports + subtable breaks in pair positioning lookups only, while FontTools’ feaLib + support it for most substitution lookups and only some positioning lookups. +""" + +import logging +import re +from io import StringIO + +from fontTools.feaLib import ast +from fontTools.ttLib import TTFont, TTLibError +from fontTools.voltLib import ast as VAst +from fontTools.voltLib.parser import Parser as VoltParser + +log = logging.getLogger("fontTools.volto") + +TABLES = ["GDEF", "GSUB", "GPOS"] + + +class MarkClassDefinition(ast.MarkClassDefinition): + def asFea(self, indent=""): + res = "" + if not getattr(self, "used", False): + res += "#" + res += ast.MarkClassDefinition.asFea(self, indent) + return res + + +# For sorting voltLib.ast.GlyphDefinition, see its use below. +class Group: + def __init__(self, group): + self.name = group.name.lower() + self.groups = [ + x.group.lower() for x in group.enum.enum if isinstance(x, VAst.GroupName) + ] + + def __lt__(self, other): + if self.name in other.groups: + return True + if other.name in self.groups: + return False + if self.groups and not other.groups: + return False + if not self.groups and other.groups: + return True + + +class VoltToFea: + _NOT_LOOKUP_NAME_RE = re.compile(r"[^A-Za-z_0-9.]") + _NOT_CLASS_NAME_RE = re.compile(r"[^A-Za-z_0-9.\-]") + + def __init__(self, file_or_path, font=None): + self._file_or_path = file_or_path + self._font = font + + self._glyph_map = {} + self._glyph_order = None + + self._gdef = {} + self._glyphclasses = {} + self._features = {} + self._lookups = {} + + self._marks = set() + self._ligatures = {} + + self._markclasses = {} + self._anchors = {} + + self._settings = {} + + self._lookup_names = {} + self._class_names = {} + + def _lookupName(self, name): + if name not in self._lookup_names: + res = self._NOT_LOOKUP_NAME_RE.sub("_", name) + while res in self._lookup_names.values(): + res += "_" + self._lookup_names[name] = res + return self._lookup_names[name] + + def _className(self, name): + if name not in self._class_names: + res = self._NOT_CLASS_NAME_RE.sub("_", name) + while res in self._class_names.values(): + res += "_" + self._class_names[name] = res + return self._class_names[name] + + def _collectStatements(self, doc, tables): + # Collect and sort group definitions first, to make sure a group + # definition that references other groups comes after them since VOLT + # does not enforce such ordering, and feature file require it. + groups = [s for s in doc.statements if isinstance(s, VAst.GroupDefinition)] + for statement in sorted(groups, key=lambda x: Group(x)): + self._groupDefinition(statement) + + for statement in doc.statements: + if isinstance(statement, VAst.GlyphDefinition): + self._glyphDefinition(statement) + elif isinstance(statement, VAst.AnchorDefinition): + if "GPOS" in tables: + self._anchorDefinition(statement) + elif isinstance(statement, VAst.SettingDefinition): + self._settingDefinition(statement) + elif isinstance(statement, VAst.GroupDefinition): + pass # Handled above + elif isinstance(statement, VAst.ScriptDefinition): + self._scriptDefinition(statement) + elif not isinstance(statement, VAst.LookupDefinition): + raise NotImplementedError(statement) + + # Lookup definitions need to be handled last as they reference glyph + # and mark classes that might be defined after them. + for statement in doc.statements: + if isinstance(statement, VAst.LookupDefinition): + if statement.pos and "GPOS" not in tables: + continue + if statement.sub and "GSUB" not in tables: + continue + self._lookupDefinition(statement) + + def _buildFeatureFile(self, tables): + doc = ast.FeatureFile() + statements = doc.statements + + if self._glyphclasses: + statements.append(ast.Comment("# Glyph classes")) + statements.extend(self._glyphclasses.values()) + + if self._markclasses: + statements.append(ast.Comment("\n# Mark classes")) + statements.extend(c[1] for c in sorted(self._markclasses.items())) + + if self._lookups: + statements.append(ast.Comment("\n# Lookups")) + for lookup in self._lookups.values(): + statements.extend(getattr(lookup, "targets", [])) + statements.append(lookup) + + # Prune features + features = self._features.copy() + for ftag in features: + scripts = features[ftag] + for stag in scripts: + langs = scripts[stag] + for ltag in langs: + langs[ltag] = [l for l in langs[ltag] if l.lower() in self._lookups] + scripts[stag] = {t: l for t, l in langs.items() if l} + features[ftag] = {t: s for t, s in scripts.items() if s} + features = {t: f for t, f in features.items() if f} + + if features: + statements.append(ast.Comment("# Features")) + for ftag, scripts in features.items(): + feature = ast.FeatureBlock(ftag) + stags = sorted(scripts, key=lambda k: 0 if k == "DFLT" else 1) + for stag in stags: + feature.statements.append(ast.ScriptStatement(stag)) + ltags = sorted(scripts[stag], key=lambda k: 0 if k == "dflt" else 1) + for ltag in ltags: + include_default = True if ltag == "dflt" else False + feature.statements.append( + ast.LanguageStatement(ltag, include_default=include_default) + ) + for name in scripts[stag][ltag]: + lookup = self._lookups[name.lower()] + lookupref = ast.LookupReferenceStatement(lookup) + feature.statements.append(lookupref) + statements.append(feature) + + if self._gdef and "GDEF" in tables: + classes = [] + for name in ("BASE", "MARK", "LIGATURE", "COMPONENT"): + if name in self._gdef: + classname = "GDEF_" + name.lower() + glyphclass = ast.GlyphClassDefinition(classname, self._gdef[name]) + statements.append(glyphclass) + classes.append(ast.GlyphClassName(glyphclass)) + else: + classes.append(None) + + gdef = ast.TableBlock("GDEF") + gdef.statements.append(ast.GlyphClassDefStatement(*classes)) + statements.append(gdef) + + return doc + + def convert(self, tables=None): + doc = VoltParser(self._file_or_path).parse() + + if tables is None: + tables = TABLES + if self._font is not None: + self._glyph_order = self._font.getGlyphOrder() + + self._collectStatements(doc, tables) + fea = self._buildFeatureFile(tables) + return fea.asFea() + + def _glyphName(self, glyph): + try: + name = glyph.glyph + except AttributeError: + name = glyph + return ast.GlyphName(self._glyph_map.get(name, name)) + + def _groupName(self, group): + try: + name = group.group + except AttributeError: + name = group + return ast.GlyphClassName(self._glyphclasses[name.lower()]) + + def _coverage(self, coverage): + items = [] + for item in coverage: + if isinstance(item, VAst.GlyphName): + items.append(self._glyphName(item)) + elif isinstance(item, VAst.GroupName): + items.append(self._groupName(item)) + elif isinstance(item, VAst.Enum): + items.append(self._enum(item)) + elif isinstance(item, VAst.Range): + items.append((item.start, item.end)) + else: + raise NotImplementedError(item) + return items + + def _enum(self, enum): + return ast.GlyphClass(self._coverage(enum.enum)) + + def _context(self, context): + out = [] + for item in context: + coverage = self._coverage(item) + if not isinstance(coverage, (tuple, list)): + coverage = [coverage] + out.extend(coverage) + return out + + def _groupDefinition(self, group): + name = self._className(group.name) + glyphs = self._enum(group.enum) + glyphclass = ast.GlyphClassDefinition(name, glyphs) + + self._glyphclasses[group.name.lower()] = glyphclass + + def _glyphDefinition(self, glyph): + try: + self._glyph_map[glyph.name] = self._glyph_order[glyph.id] + except TypeError: + pass + + if glyph.type in ("BASE", "MARK", "LIGATURE", "COMPONENT"): + if glyph.type not in self._gdef: + self._gdef[glyph.type] = ast.GlyphClass() + self._gdef[glyph.type].glyphs.append(self._glyphName(glyph.name)) + + if glyph.type == "MARK": + self._marks.add(glyph.name) + elif glyph.type == "LIGATURE": + self._ligatures[glyph.name] = glyph.components + + def _scriptDefinition(self, script): + stag = script.tag + for lang in script.langs: + ltag = lang.tag + for feature in lang.features: + lookups = {l.split("\\")[0]: True for l in feature.lookups} + ftag = feature.tag + if ftag not in self._features: + self._features[ftag] = {} + if stag not in self._features[ftag]: + self._features[ftag][stag] = {} + assert ltag not in self._features[ftag][stag] + self._features[ftag][stag][ltag] = lookups.keys() + + def _settingDefinition(self, setting): + if setting.name.startswith("COMPILER_"): + self._settings[setting.name] = setting.value + else: + log.warning(f"Unsupported setting ignored: {setting.name}") + + def _adjustment(self, adjustment): + adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by = adjustment + + adv_device = adv_adjust_by and adv_adjust_by.items() or None + dx_device = dx_adjust_by and dx_adjust_by.items() or None + dy_device = dy_adjust_by and dy_adjust_by.items() or None + + return ast.ValueRecord( + xPlacement=dx, + yPlacement=dy, + xAdvance=adv, + xPlaDevice=dx_device, + yPlaDevice=dy_device, + xAdvDevice=adv_device, + ) + + def _anchor(self, adjustment): + adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by = adjustment + + assert not adv_adjust_by + dx_device = dx_adjust_by and dx_adjust_by.items() or None + dy_device = dy_adjust_by and dy_adjust_by.items() or None + + return ast.Anchor( + dx or 0, + dy or 0, + xDeviceTable=dx_device or None, + yDeviceTable=dy_device or None, + ) + + def _anchorDefinition(self, anchordef): + anchorname = anchordef.name + glyphname = anchordef.glyph_name + anchor = self._anchor(anchordef.pos) + + if anchorname.startswith("MARK_"): + name = "_".join(anchorname.split("_")[1:]) + markclass = ast.MarkClass(self._className(name)) + glyph = self._glyphName(glyphname) + markdef = MarkClassDefinition(markclass, anchor, glyph) + self._markclasses[(glyphname, anchorname)] = markdef + else: + if glyphname not in self._anchors: + self._anchors[glyphname] = {} + if anchorname not in self._anchors[glyphname]: + self._anchors[glyphname][anchorname] = {} + self._anchors[glyphname][anchorname][anchordef.component] = anchor + + 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) + + def _gposContextLookup( + self, lookup, prefix, suffix, ignore, fealookup, targetlookup + ): + statements = fealookup.statements + + assert not lookup.reversal + + pos = lookup.pos + if isinstance(pos, VAst.PositionAdjustPairDefinition): + for (idx1, idx2), (pos1, pos2) in pos.adjust_pair.items(): + glyphs1 = self._coverage(pos.coverages_1[idx1 - 1]) + glyphs2 = self._coverage(pos.coverages_2[idx2 - 1]) + assert len(glyphs1) == 1 + assert len(glyphs2) == 1 + glyphs = (glyphs1[0], glyphs2[0]) + + if ignore: + statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)]) + else: + lookups = (targetlookup, targetlookup) + statement = ast.ChainContextPosStatement( + prefix, glyphs, suffix, lookups + ) + statements.append(statement) + elif isinstance(pos, VAst.PositionAdjustSingleDefinition): + glyphs = [ast.GlyphClass()] + for a, b in pos.adjust_single: + glyph = self._coverage(a) + glyphs[0].extend(glyph) + + if ignore: + statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)]) + else: + statement = ast.ChainContextPosStatement( + prefix, glyphs, suffix, [targetlookup] + ) + statements.append(statement) + elif isinstance(pos, VAst.PositionAttachDefinition): + glyphs = [ast.GlyphClass()] + for coverage, _ in pos.coverage_to: + glyphs[0].extend(self._coverage(coverage)) + + if ignore: + statement = ast.IgnorePosStatement([(prefix, glyphs, suffix)]) + else: + statement = ast.ChainContextPosStatement( + prefix, glyphs, suffix, [targetlookup] + ) + statements.append(statement) + else: + raise NotImplementedError(pos) + + def _gsubLookup(self, lookup, prefix, suffix, ignore, chain, fealookup): + statements = fealookup.statements + + sub = lookup.sub + for key, val in sub.mapping.items(): + if not key or not val: + path, line, column = sub.location + log.warning(f"{path}:{line}:{column}: Ignoring empty substitution") + continue + statement = None + glyphs = self._coverage(key) + replacements = self._coverage(val) + if ignore: + chain_context = (prefix, glyphs, suffix) + statement = ast.IgnoreSubstStatement([chain_context]) + elif isinstance(sub, VAst.SubstitutionSingleDefinition): + assert len(glyphs) == 1 + assert len(replacements) == 1 + statement = ast.SingleSubstStatement( + glyphs, replacements, prefix, suffix, chain + ) + elif isinstance(sub, VAst.SubstitutionReverseChainingSingleDefinition): + assert len(glyphs) == 1 + assert len(replacements) == 1 + statement = ast.ReverseChainSingleSubstStatement( + prefix, suffix, glyphs, replacements + ) + elif isinstance(sub, VAst.SubstitutionMultipleDefinition): + assert len(glyphs) == 1 + statement = ast.MultipleSubstStatement( + prefix, glyphs[0], suffix, replacements, chain + ) + elif isinstance(sub, VAst.SubstitutionLigatureDefinition): + assert len(replacements) == 1 + statement = ast.LigatureSubstStatement( + prefix, glyphs, suffix, replacements[0], chain + ) + else: + raise NotImplementedError(sub) + statements.append(statement) + + def _lookupDefinition(self, lookup): + mark_attachement = None + mark_filtering = None + + flags = 0 + if lookup.direction == "RTL": + flags |= 1 + if not lookup.process_base: + flags |= 2 + # FIXME: Does VOLT support this? + # if not lookup.process_ligatures: + # flags |= 4 + if not lookup.process_marks: + flags |= 8 + elif isinstance(lookup.process_marks, str): + mark_attachement = self._groupName(lookup.process_marks) + elif lookup.mark_glyph_set is not None: + mark_filtering = self._groupName(lookup.mark_glyph_set) + + lookupflags = None + if flags or mark_attachement is not None or mark_filtering is not None: + lookupflags = ast.LookupFlagStatement( + flags, mark_attachement, mark_filtering + ) + if "\\" in lookup.name: + # Merge sub lookups as subtables (lookups named “base\sub”), + # makeotf/feaLib will issue a warning and ignore the subtable + # statement if it is not a pairpos lookup, though. + name = lookup.name.split("\\")[0] + if name.lower() not in self._lookups: + fealookup = ast.LookupBlock(self._lookupName(name)) + if lookupflags is not None: + fealookup.statements.append(lookupflags) + fealookup.statements.append(ast.Comment("# " + lookup.name)) + else: + fealookup = self._lookups[name.lower()] + fealookup.statements.append(ast.SubtableStatement()) + fealookup.statements.append(ast.Comment("# " + lookup.name)) + self._lookups[name.lower()] = fealookup + else: + fealookup = ast.LookupBlock(self._lookupName(lookup.name)) + if lookupflags is not None: + fealookup.statements.append(lookupflags) + self._lookups[lookup.name.lower()] = fealookup + + if lookup.comments is not None: + fealookup.statements.append(ast.Comment("# " + lookup.comments)) + + contexts = [] + if lookup.context: + for context in lookup.context: + prefix = self._context(context.left) + suffix = self._context(context.right) + ignore = context.ex_or_in == "EXCEPT_CONTEXT" + contexts.append([prefix, suffix, ignore, False]) + # It seems that VOLT will create contextual substitution using + # only the input if there is no other contexts in this lookup. + if ignore and len(lookup.context) == 1: + contexts.append([[], [], False, True]) + else: + contexts.append([[], [], False, False]) + + targetlookup = None + for prefix, suffix, ignore, chain in contexts: + if lookup.sub is not None: + self._gsubLookup(lookup, prefix, suffix, ignore, chain, fealookup) + + if lookup.pos is not None: + if self._settings.get("COMPILER_USEEXTENSIONLOOKUPS"): + fealookup.use_extension = True + if prefix or suffix or chain or ignore: + if not ignore and targetlookup is None: + targetname = self._lookupName(lookup.name + " target") + targetlookup = ast.LookupBlock(targetname) + fealookup.targets = getattr(fealookup, "targets", []) + fealookup.targets.append(targetlookup) + self._gposLookup(lookup, targetlookup) + self._gposContextLookup( + lookup, prefix, suffix, ignore, fealookup, targetlookup + ) + else: + self._gposLookup(lookup, fealookup) + + +def main(args=None): + """Convert MS VOLT to AFDKO feature files.""" + + import argparse + from pathlib import Path + + from fontTools import configLogger + + parser = argparse.ArgumentParser("fonttools volto", description=main.__doc__) + parser.add_argument( + "input", metavar="INPUT", type=Path, help="input font/VTP file to process" + ) + parser.add_argument( + "featurefile", metavar="OUTPUT", type=Path, help="output feature file" + ) + parser.add_argument( + "-t", + "--table", + action="append", + choices=TABLES, + dest="tables", + help="List of tables to write, by default all tables are written", + ) + parser.add_argument( + "-q", "--quiet", action="store_true", help="Suppress non-error messages" + ) + parser.add_argument( + "--traceback", action="store_true", help="Don’t catch exceptions" + ) + + options = parser.parse_args(args) + + configLogger(level=("ERROR" if options.quiet else "INFO")) + + file_or_path = options.input + font = None + try: + font = TTFont(file_or_path) + if "TSIV" in font: + file_or_path = StringIO(font["TSIV"].data.decode("utf-8")) + else: + log.error('"TSIV" table is missing, font was not saved from VOLT?') + return 1 + except TTLibError: + pass + + converter = VoltToFea(file_or_path, font) + try: + fea = converter.convert(options.tables) + except NotImplementedError as e: + if options.traceback: + raise + location = getattr(e.args[0], "location", None) + message = f'"{e}" is not supported' + if location: + path, line, column = location + log.error(f"{path}:{line}:{column}: {message}") + else: + log.error(message) + return 1 + with open(options.featurefile, "w") as feafile: + feafile.write(fea) + + +if __name__ == "__main__": + import sys + + sys.exit(main()) diff --git a/Tests/volto/data/Empty.ttf b/Tests/volto/data/Empty.ttf new file mode 100644 index 000000000..4eb6d88a1 Binary files /dev/null and b/Tests/volto/data/Empty.ttf differ diff --git a/Tests/volto/data/NamdhinggoSIL1006.fea b/Tests/volto/data/NamdhinggoSIL1006.fea new file mode 100644 index 000000000..aa8ab1a5d --- /dev/null +++ b/Tests/volto/data/NamdhinggoSIL1006.fea @@ -0,0 +1,506 @@ +# Glyph classes +@Cons = [uni1901 uni1902 uni1903 uni1904 uni1905 uni1906 uni1907 uni1908 uni1909 uni190A uni190B uni190C uni190D uni190E uni190F uni1910 uni1911 uni1912 uni1913 uni1914 uni1915 uni1916 uni1917 uni1918 uni1919 uni191A uni191B uni191C uni1940]; +@ConsRaU = [uni1901192A1922 uni1902192A1922 uni1903192A1922 uni1904192A1922 uni1905192A1922 uni1906192A1922 uni1907192A1922 uni1908192A1922 uni1909192A1922 uni190A192A1922 uni190B192A1922 uni190C192A1922 uni190D192A1922 uni190192AE1922 uni190F192A1922 uni1910192A1922 uni1911192A1922 uni1912192A1922 uni1913192A1922 uni1914192A1922 uni1915192A1922 uni1916192A1922 uni1917192A1922 uni1918192A1922 uni1919192A1922 uni1919192A1922 uni191A192A1922 uni191B192A1922 uni191C192A1922 uni1940192A1922]; +@ConsU = [uni19011922 uni19021922 uni19031922 uni19041922 uni19051922 uni19061922 uni19071922 uni19081922 uni19091922 uni190A1922 uni190B1922 uni190C1922 uni190D1922 uni190E1922 uni190F1922 uni19101922 uni19111922 uni19121922 uni19131922 uni19141922 uni19151922 uni19161922 uni19171922 uni19181922 uni19191922 uni191A1922 uni191B1922 uni191C1922 uni19401922]; +@Ikar = [uni1921 uni1921193A]; +@Vowels = [uni1920 uni1927 uni1928]; +@YaWa = [uni1929 uni192B]; +@AllCons = [@Cons @ConsU @ConsRaU]; +@VowelsKem = [@Vowels uni193A]; + +# Mark classes +markClass uni1920 @Aabove; +markClass uni1922 @U; +markClass uni1927 @eo; +markClass uni1928 @eo; +markClass uni193A @K; +markClass uni193A @VK; + +# Lookups +lookup EEAIDecomp { + sub uni1925 by uni1920 uni1923; + sub uni1926 by uni1920 uni1924; +} EEAIDecomp; + +lookup OoAuKComp { + sub uni1923 uni193A by uni1923193A; + sub uni1924 uni193A by uni1924193A; +} OoAuKComp; + +lookup OoAuKDecomp { + # The OoAuDecomp substitution rule replaces the OO and AU vowels with their visually constitutent components A plus EE or AI respectively. This is so that the 'A' portion can be positioned independently over the consonant when a Glide occurs between the consonant and the vowel. + sub uni1923193A by uni193A uni1923; + sub uni1924193A by uni193A uni1924; +} OoAuKDecomp; + +lookup GlideVowelComp { + sub uni1929 uni1920 uni193A by uni19291920193A; + sub uni1929 uni1922 uni193A by uni19291922193A; + sub uni1929 uni1927 uni193A by uni19291927193A; + sub uni1929 uni1928 uni193A by uni19291928193A; + sub uni1929 uni193A by uni1929193A; + sub uni1929 uni1920 by uni19291920; + sub uni1929 uni1922 by uni19291922; + sub uni1929 uni1927 by uni19291927; + sub uni1929 uni1928 by uni19291928; + sub uni192B uni1920 uni193A by uni192B1920193A; + sub uni192B uni1922 uni193A by uni192B1922193A; + sub uni192B uni1927 uni193A by uni192B1927193A; + sub uni192B uni1928 uni193A by uni192B1928193A; + sub uni192B uni193A by uni192B193A; + sub uni192B uni1920 by uni192B1920; + sub uni192B uni1922 by uni192B1922; + sub uni192B uni1927 by uni192B1927; + sub uni192B uni1928 by uni192B1928; +} GlideVowelComp; + +lookup GlideVowelDecomp { + sub uni19291920193A by uni1920 uni193A uni1929; + sub uni19291922193A by uni1922 uni193A uni1929; + sub uni19291927193A by uni1927 uni193A uni1929; + sub uni19291928193A by uni1928 uni193A uni1929; + sub uni1929193A by uni193A uni1929; + sub uni19291920 by uni1920 uni1929; + sub uni19291922 by uni1922 uni1929; + sub uni19291927 by uni1927 uni1929; + sub uni19291928 by uni1928 uni1929; + sub uni192B1920193A by uni1920 uni193A uni192B; + sub uni192B1922193A by uni1922 uni193A uni192B; + sub uni192B1927193A by uni1927 uni193A uni192B; + sub uni192B1928193A by uni1928 uni193A uni192B; + sub uni192B193A by uni193A uni192B; + sub uni192B1920 by uni1920 uni192B; + sub uni192B1922 by uni1922 uni192B; + sub uni192B1927 by uni1927 uni192B; + sub uni192B1928 by uni1928 uni192B; +} GlideVowelDecomp; + +lookup RaUkar { + # The RaUkar substitution rule replaces Consonant, Ra, Ukar with a ligature. + sub @Cons uni192A uni1922 by @ConsRaU; +} RaUkar; + +lookup Ukar { + # The Ukar substitution rule replaces Consonant + Ukar with a ligature. It also applies to the Vowel-Carrier, which has its own ligature with ukar. + sub @Cons uni1922 by @ConsU; + sub uni1900 uni1922 by uni19001922; +} Ukar; + +lookup IkarK { + # The IkarK substitution rule replaces Ikar + Kemphreng with a ligature. The ligature is then positioned properly on the base consonant via the positioning rule IEO. + sub uni1921 uni193A by uni1921193A; +} IkarK; + +lookup GlideIkar_target { + pos @YaWa -475; +} GlideIkar_target; + +lookup GlideIkar { + pos [@YaWa]' lookup GlideIkar_target @Ikar; +} GlideIkar; + +lookup IkarKWid_target { + pos uni1921193A 110; +} IkarKWid_target; + +lookup IkarKWid { + # The IkarKWid lookup, applied to the Kern feature, adds 110 units of width to the IkarKemphreng ligature when followed by a consonant with akar on it. This prevents the akar from overprinting the rightmost dot of the kemphreng. (The dot overhangs to the right slightly, which is OK unless the following character has akar on it). + pos [uni1921193A]' lookup IkarKWid_target @Cons uni1920; +} IkarKWid; + +lookup Akar { + # The Akar positioning rule positions the Akar on all consonants. + pos base uni1901 + mark @Aabove; + pos base uni1902 + mark @Aabove; + pos base uni1903 + mark @Aabove; + pos base uni1904 + mark @Aabove; + pos base uni1905 + mark @Aabove; + pos base uni1906 + mark @Aabove; + pos base uni1907 + mark @Aabove; + pos base uni1908 + mark @Aabove; + pos base uni1909 + mark @Aabove; + pos base uni190A + mark @Aabove; + pos base uni190B + mark @Aabove; + pos base uni190C + mark @Aabove; + pos base uni190D + mark @Aabove; + pos base uni190E + mark @Aabove; + pos base uni190F + mark @Aabove; + pos base uni1910 + mark @Aabove; + pos base uni1911 + mark @Aabove; + pos base uni1912 + mark @Aabove; + pos base uni1913 + mark @Aabove; + pos base uni1914 + mark @Aabove; + pos base uni1915 + mark @Aabove; + pos base uni1916 + mark @Aabove; + pos base uni1917 + mark @Aabove; + pos base uni1918 + mark @Aabove; + pos base uni1919 + mark @Aabove; + pos base uni191A + mark @Aabove; + pos base uni191B + mark @Aabove; + pos base uni191C + mark @Aabove; + pos base uni1940 + mark @Aabove; +} Akar; + +lookup Kemphreng { + # The Kemphreng positioning rule positions the Kemphreng on all consonants, including the vowel carrier. + pos base uni1901 + mark @K; + pos base uni1902 + mark @K; + pos base uni1903 + mark @K; + pos base uni1904 + mark @K; + pos base uni1905 + mark @K; + pos base uni1906 + mark @K; + pos base uni1907 + mark @K; + pos base uni1908 + mark @K; + pos base uni1909 + mark @K; + pos base uni190A + mark @K; + pos base uni190B + mark @K; + pos base uni190C + mark @K; + pos base uni190D + mark @K; + pos base uni190E + mark @K; + pos base uni190F + mark @K; + pos base uni1910 + mark @K; + pos base uni1911 + mark @K; + pos base uni1912 + mark @K; + pos base uni1913 + mark @K; + pos base uni1914 + mark @K; + pos base uni1915 + mark @K; + pos base uni1916 + mark @K; + pos base uni1917 + mark @K; + pos base uni1918 + mark @K; + pos base uni1919 + mark @K; + pos base uni191A + mark @K; + pos base uni191B + mark @K; + pos base uni191C + mark @K; + pos base uni1940 + mark @K; + pos base uni19011922 + mark @K; + pos base uni19021922 + mark @K; + pos base uni19031922 + mark @K; + pos base uni19041922 + mark @K; + pos base uni19051922 + mark @K; + pos base uni19061922 + mark @K; + pos base uni19071922 + mark @K; + pos base uni19081922 + mark @K; + pos base uni19091922 + mark @K; + pos base uni190A1922 + mark @K; + pos base uni190B1922 + mark @K; + pos base uni190C1922 + mark @K; + pos base uni190D1922 + mark @K; + pos base uni190E1922 + mark @K; + pos base uni190F1922 + mark @K; + pos base uni19101922 + mark @K; + pos base uni19111922 + mark @K; + pos base uni19121922 + mark @K; + pos base uni19131922 + mark @K; + pos base uni19141922 + mark @K; + pos base uni19151922 + mark @K; + pos base uni19161922 + mark @K; + pos base uni19171922 + mark @K; + pos base uni19181922 + mark @K; + pos base uni19191922 + mark @K; + pos base uni191A1922 + mark @K; + pos base uni191B1922 + mark @K; + pos base uni191C1922 + mark @K; + pos base uni19401922 + mark @K; + pos base uni1901192A1922 + mark @K; + pos base uni1902192A1922 + mark @K; + pos base uni1903192A1922 + mark @K; + pos base uni1904192A1922 + mark @K; + pos base uni1905192A1922 + mark @K; + pos base uni1906192A1922 + mark @K; + pos base uni1907192A1922 + mark @K; + pos base uni1908192A1922 + mark @K; + pos base uni1909192A1922 + mark @K; + pos base uni190A192A1922 + mark @K; + pos base uni190B192A1922 + mark @K; + pos base uni190C192A1922 + mark @K; + pos base uni190D192A1922 + mark @K; + pos base uni190192AE1922 + mark @K; + pos base uni190F192A1922 + mark @K; + pos base uni1910192A1922 + mark @K; + pos base uni1911192A1922 + mark @K; + pos base uni1912192A1922 + mark @K; + pos base uni1913192A1922 + mark @K; + pos base uni1914192A1922 + mark @K; + pos base uni1915192A1922 + mark @K; + pos base uni1916192A1922 + mark @K; + pos base uni1917192A1922 + mark @K; + pos base uni1918192A1922 + mark @K; + pos base uni1919192A1922 + mark @K; + pos base uni191A192A1922 + mark @K; + pos base uni191B192A1922 + mark @K; + pos base uni191C192A1922 + mark @K; + pos base uni1940192A1922 + mark @K; + pos base uni1900 + mark @K; +} Kemphreng; + +lookup EO { + # The IEO positioning rule positions ikar (including the ligature with kemphreng), e and o on all consonants plus the vowel carrier. + pos base uni1901 + mark @eo; + pos base uni1902 + mark @eo; + pos base uni1903 + mark @eo; + pos base uni1904 + mark @eo; + pos base uni1905 + mark @eo; + pos base uni1906 + mark @eo; + pos base uni1907 + mark @eo; + pos base uni1908 + mark @eo; + pos base uni1909 + mark @eo; + pos base uni190A + mark @eo; + pos base uni190B + mark @eo; + pos base uni190C + mark @eo; + pos base uni190D + mark @eo; + pos base uni190E + mark @eo; + pos base uni190F + mark @eo; + pos base uni1910 + mark @eo; + pos base uni1911 + mark @eo; + pos base uni1912 + mark @eo; + pos base uni1913 + mark @eo; + pos base uni1914 + mark @eo; + pos base uni1915 + mark @eo; + pos base uni1916 + mark @eo; + pos base uni1917 + mark @eo; + pos base uni1918 + mark @eo; + pos base uni1919 + mark @eo; + pos base uni191A + mark @eo; + pos base uni191B + mark @eo; + pos base uni191C + mark @eo; + pos base uni1940 + mark @eo; + pos base uni1900 + mark @eo; +} EO; + +lookup VKem { + lookupflag MarkAttachmentType @VowelsKem; + # The VKem positioning rule positions the kemphreng on all upper vowels (except ikar, which has its own ligature). The vowel itself is positioned on the consonant with the Akar or IEO positioning rule. + pos mark uni1920 + mark @VK; + pos mark uni1927 + mark @VK; + pos mark uni1928 + mark @VK; +} VKem; + +lookup GlideU { + # The GlideU positioning rule positions the ukar on the glides Ya and Wa. (There is already a ligature for each consonant with the Ra+Ukar combination). + pos base uni1929 + mark @U; + pos base uni192B + mark @U; +} GlideU; + +# Features +feature ccmp { + script latn; + language dflt; + lookup EEAIDecomp; + lookup OoAuKComp; + lookup OoAuKDecomp; + lookup GlideVowelComp; + lookup GlideVowelDecomp; + script limb; + language dflt; + lookup EEAIDecomp; + lookup OoAuKComp; + lookup OoAuKDecomp; + lookup GlideVowelComp; + lookup GlideVowelDecomp; +} ccmp; + +feature kern { + script latn; + language dflt; + lookup GlideIkar; + lookup IkarKWid; + script limb; + language dflt; + lookup GlideIkar; + lookup IkarKWid; +} kern; + +feature mark { + script latn; + language dflt; + lookup Akar; + lookup Kemphreng; + lookup EO; + script limb; + language dflt; + lookup Akar; + lookup Kemphreng; + lookup EO; +} mark; + +feature mkmk { + script latn; + language dflt; + lookup VKem; + lookup GlideU; + script limb; + language dflt; + lookup VKem; + lookup GlideU; +} mkmk; + +feature liga { + script latn; + language dflt; + lookup RaUkar; + lookup Ukar; + lookup IkarK; + script limb; + language dflt; + lookup RaUkar; + lookup Ukar; + lookup IkarK; +} liga; + +@GDEF_base = [glyph0 .null CR space exclam quotedbl numbersign dollar percent quotesingle parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde uni0965 uni1900 uni19001922 uni1901 uni19011922 uni1901192A1922 uni1902 uni19021922 uni1902192A1922 uni1903 uni19031922 uni1903192A1922 uni1904 uni19041922 uni1904192A1922 uni1905 uni19051922 uni1905192A1922 uni1906 uni19061922 uni1906192A1922 uni1907 uni19071922 uni1907192A1922 uni1908 uni19081922 uni1908192A1922 uni1909 uni19091922 uni1909192A1922 uni190A uni190A1922 uni190A192A1922 uni190B uni190B1922 uni190B192A1922 uni190C uni190C1922 uni190C192A1922 uni190D uni190D1922 uni190D192A1922 uni190E uni190E1922 uni190192AE1922 uni190F uni190F1922 uni190F192A1922 uni1910 uni19101922 uni1910192A1922 uni1911 uni19111922 uni1911192A1922 uni1912 uni19121922 uni1912192A1922 uni1913 uni19131922 uni1913192A1922 uni1914 uni19141922 uni1914192A1922 uni1915 uni19151922 uni1915192A1922 uni1916 uni19161922 uni1916192A1922 uni1917 uni19171922 uni1917192A1922 uni1918 uni19181922 uni1918192A1922 uni1919 uni19191922 uni1919192A1922 uni191A uni191A1922 uni191A192A1922 uni191B uni191B1922 uni191B192A1922 uni191C uni191C1922 uni191C192A1922 uni1921 uni1923 uni1924 uni1929 uni192B uni1930 uni1931 uni1932 uni1933 uni1934 uni1935 uni1936 uni1937 uni1938 uni1939 uni1940 uni19401922 uni1940192A1922 uni1944 uni1945 uni1946 uni1947 uni1948 uni1949 uni194A uni194B uni194C uni194D uni194E uni194F quoteleft quoteright quotedblleft quotedblright uni1921193A ampersand uni2009 endash emdash uni202F uni1923193A uni1924193A uni19291920 uni19291922 uni19291927 uni19291928 uni1929193A uni19291920193A uni19291922193A uni19291927193A uni19291928193A uni192B1920 uni192B1922 uni192B1927 uni192B1928 uni192B193A uni192B1920193A uni192B1922193A uni192B1927193A uni192B1928193A uni25CC uni191E uni191E1922 uni191E192A1922 uni191D uni191D1922 uni191D192A1922]; +@GDEF_mark = [uni1920 uni1920.widC uni1920.widD uni1922 uni1922.altA uni1922.altB uni1922.altC uni1925 uni1926 uni1927 uni1928 uni192A uni193A uni193A.widC uni193B uni193B.widA uni193B.widB uni193B.widC uni192A1922]; +table GDEF { + GlyphClassDef @GDEF_base, , @GDEF_mark, ; +} GDEF; diff --git a/Tests/volto/data/NamdhinggoSIL1006.vtp b/Tests/volto/data/NamdhinggoSIL1006.vtp new file mode 100644 index 000000000..7f2072b0a --- /dev/null +++ b/Tests/volto/data/NamdhinggoSIL1006.vtp @@ -0,0 +1 @@ + DEF_GLYPH "glyph0" ID 0 TYPE BASE END_GLYPH DEF_GLYPH ".null" ID 1 TYPE BASE END_GLYPH DEF_GLYPH "CR" ID 2 TYPE BASE END_GLYPH DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH DEF_GLYPH "exclam" ID 4 UNICODE 33 TYPE BASE END_GLYPH DEF_GLYPH "quotedbl" ID 5 UNICODE 34 TYPE BASE END_GLYPH DEF_GLYPH "numbersign" ID 6 UNICODE 35 TYPE BASE END_GLYPH DEF_GLYPH "dollar" ID 7 UNICODE 36 TYPE BASE END_GLYPH DEF_GLYPH "percent" ID 8 UNICODE 37 TYPE BASE END_GLYPH DEF_GLYPH "quotesingle" ID 9 UNICODE 39 TYPE BASE END_GLYPH DEF_GLYPH "parenleft" ID 10 UNICODE 40 TYPE BASE END_GLYPH DEF_GLYPH "parenright" ID 11 UNICODE 41 TYPE BASE END_GLYPH DEF_GLYPH "asterisk" ID 12 UNICODE 42 TYPE BASE END_GLYPH DEF_GLYPH "plus" ID 13 UNICODE 43 TYPE BASE END_GLYPH DEF_GLYPH "comma" ID 14 UNICODE 44 TYPE BASE END_GLYPH DEF_GLYPH "hyphen" ID 15 UNICODE 45 TYPE BASE END_GLYPH DEF_GLYPH "period" ID 16 UNICODE 46 TYPE BASE END_GLYPH DEF_GLYPH "slash" ID 17 UNICODE 47 TYPE BASE END_GLYPH DEF_GLYPH "zero" ID 18 UNICODE 48 TYPE BASE END_GLYPH DEF_GLYPH "one" ID 19 UNICODE 49 TYPE BASE END_GLYPH DEF_GLYPH "two" ID 20 UNICODE 50 TYPE BASE END_GLYPH DEF_GLYPH "three" ID 21 UNICODE 51 TYPE BASE END_GLYPH DEF_GLYPH "four" ID 22 UNICODE 52 TYPE BASE END_GLYPH DEF_GLYPH "five" ID 23 UNICODE 53 TYPE BASE END_GLYPH DEF_GLYPH "six" ID 24 UNICODE 54 TYPE BASE END_GLYPH DEF_GLYPH "seven" ID 25 UNICODE 55 TYPE BASE END_GLYPH DEF_GLYPH "eight" ID 26 UNICODE 56 TYPE BASE END_GLYPH DEF_GLYPH "nine" ID 27 UNICODE 57 TYPE BASE END_GLYPH DEF_GLYPH "colon" ID 28 UNICODE 58 TYPE BASE END_GLYPH DEF_GLYPH "semicolon" ID 29 UNICODE 59 TYPE BASE END_GLYPH DEF_GLYPH "less" ID 30 UNICODE 60 TYPE BASE END_GLYPH DEF_GLYPH "equal" ID 31 UNICODE 61 TYPE BASE END_GLYPH DEF_GLYPH "greater" ID 32 UNICODE 62 TYPE BASE END_GLYPH DEF_GLYPH "question" ID 33 UNICODE 63 TYPE BASE END_GLYPH DEF_GLYPH "at" ID 34 UNICODE 64 TYPE BASE END_GLYPH DEF_GLYPH "A" ID 35 UNICODE 65 TYPE BASE END_GLYPH DEF_GLYPH "B" ID 36 UNICODE 66 TYPE BASE END_GLYPH DEF_GLYPH "C" ID 37 UNICODE 67 TYPE BASE END_GLYPH DEF_GLYPH "D" ID 38 UNICODE 68 TYPE BASE END_GLYPH DEF_GLYPH "E" ID 39 UNICODE 69 TYPE BASE END_GLYPH DEF_GLYPH "F" ID 40 UNICODE 70 TYPE BASE END_GLYPH DEF_GLYPH "G" ID 41 UNICODE 71 TYPE BASE END_GLYPH DEF_GLYPH "H" ID 42 UNICODE 72 TYPE BASE END_GLYPH DEF_GLYPH "I" ID 43 UNICODE 73 TYPE BASE END_GLYPH DEF_GLYPH "J" ID 44 UNICODE 74 TYPE BASE END_GLYPH DEF_GLYPH "K" ID 45 UNICODE 75 TYPE BASE END_GLYPH DEF_GLYPH "L" ID 46 UNICODE 76 TYPE BASE END_GLYPH DEF_GLYPH "M" ID 47 UNICODE 77 TYPE BASE END_GLYPH DEF_GLYPH "N" ID 48 UNICODE 78 TYPE BASE END_GLYPH DEF_GLYPH "O" ID 49 UNICODE 79 TYPE BASE END_GLYPH DEF_GLYPH "P" ID 50 UNICODE 80 TYPE BASE END_GLYPH DEF_GLYPH "Q" ID 51 UNICODE 81 TYPE BASE END_GLYPH DEF_GLYPH "R" ID 52 UNICODE 82 TYPE BASE END_GLYPH DEF_GLYPH "S" ID 53 UNICODE 83 TYPE BASE END_GLYPH DEF_GLYPH "T" ID 54 UNICODE 84 TYPE BASE END_GLYPH DEF_GLYPH "U" ID 55 UNICODE 85 TYPE BASE END_GLYPH DEF_GLYPH "V" ID 56 UNICODE 86 TYPE BASE END_GLYPH DEF_GLYPH "W" ID 57 UNICODE 87 TYPE BASE END_GLYPH DEF_GLYPH "X" ID 58 UNICODE 88 TYPE BASE END_GLYPH DEF_GLYPH "Y" ID 59 UNICODE 89 TYPE BASE END_GLYPH DEF_GLYPH "Z" ID 60 UNICODE 90 TYPE BASE END_GLYPH DEF_GLYPH "bracketleft" ID 61 UNICODE 91 TYPE BASE END_GLYPH DEF_GLYPH "backslash" ID 62 UNICODE 92 TYPE BASE END_GLYPH DEF_GLYPH "bracketright" ID 63 UNICODE 93 TYPE BASE END_GLYPH DEF_GLYPH "asciicircum" ID 64 UNICODE 94 TYPE BASE END_GLYPH DEF_GLYPH "underscore" ID 65 UNICODE 95 TYPE BASE END_GLYPH DEF_GLYPH "grave" ID 66 UNICODE 96 TYPE BASE END_GLYPH DEF_GLYPH "a" ID 67 UNICODE 97 TYPE BASE END_GLYPH DEF_GLYPH "b" ID 68 UNICODE 98 TYPE BASE END_GLYPH DEF_GLYPH "c" ID 69 UNICODE 99 TYPE BASE END_GLYPH DEF_GLYPH "d" ID 70 UNICODE 100 TYPE BASE END_GLYPH DEF_GLYPH "e" ID 71 UNICODE 101 TYPE BASE END_GLYPH DEF_GLYPH "f" ID 72 UNICODE 102 TYPE BASE END_GLYPH DEF_GLYPH "g" ID 73 UNICODE 103 TYPE BASE END_GLYPH DEF_GLYPH "h" ID 74 UNICODE 104 TYPE BASE END_GLYPH DEF_GLYPH "i" ID 75 UNICODE 105 TYPE BASE END_GLYPH DEF_GLYPH "j" ID 76 UNICODE 106 TYPE BASE END_GLYPH DEF_GLYPH "k" ID 77 UNICODE 107 TYPE BASE END_GLYPH DEF_GLYPH "l" ID 78 UNICODE 108 TYPE BASE END_GLYPH DEF_GLYPH "m" ID 79 UNICODE 109 TYPE BASE END_GLYPH DEF_GLYPH "n" ID 80 UNICODE 110 TYPE BASE END_GLYPH DEF_GLYPH "o" ID 81 UNICODE 111 TYPE BASE END_GLYPH DEF_GLYPH "p" ID 82 UNICODE 112 TYPE BASE END_GLYPH DEF_GLYPH "q" ID 83 UNICODE 113 TYPE BASE END_GLYPH DEF_GLYPH "r" ID 84 UNICODE 114 TYPE BASE END_GLYPH DEF_GLYPH "s" ID 85 UNICODE 115 TYPE BASE END_GLYPH DEF_GLYPH "t" ID 86 UNICODE 116 TYPE BASE END_GLYPH DEF_GLYPH "u" ID 87 UNICODE 117 TYPE BASE END_GLYPH DEF_GLYPH "v" ID 88 UNICODE 118 TYPE BASE END_GLYPH DEF_GLYPH "w" ID 89 UNICODE 119 TYPE BASE END_GLYPH DEF_GLYPH "x" ID 90 UNICODE 120 TYPE BASE END_GLYPH DEF_GLYPH "y" ID 91 UNICODE 121 TYPE BASE END_GLYPH DEF_GLYPH "z" ID 92 UNICODE 122 TYPE BASE END_GLYPH DEF_GLYPH "braceleft" ID 93 UNICODE 123 TYPE BASE END_GLYPH DEF_GLYPH "bar" ID 94 UNICODE 124 TYPE BASE END_GLYPH DEF_GLYPH "braceright" ID 95 UNICODE 125 TYPE BASE END_GLYPH DEF_GLYPH "asciitilde" ID 96 UNICODE 126 TYPE BASE END_GLYPH DEF_GLYPH "uni0965" ID 97 UNICODE 2405 TYPE BASE END_GLYPH DEF_GLYPH "uni1900" ID 98 UNICODE 6400 TYPE BASE END_GLYPH DEF_GLYPH "uni19001922" ID 99 TYPE BASE END_GLYPH DEF_GLYPH "uni1901" ID 100 UNICODE 6401 TYPE BASE END_GLYPH DEF_GLYPH "uni19011922" ID 101 TYPE BASE END_GLYPH DEF_GLYPH "uni1901192A1922" ID 102 TYPE BASE END_GLYPH DEF_GLYPH "uni1902" ID 103 UNICODE 6402 TYPE BASE END_GLYPH DEF_GLYPH "uni19021922" ID 104 TYPE BASE END_GLYPH DEF_GLYPH "uni1902192A1922" ID 105 TYPE BASE END_GLYPH DEF_GLYPH "uni1903" ID 106 UNICODE 6403 TYPE BASE END_GLYPH DEF_GLYPH "uni19031922" ID 107 TYPE BASE END_GLYPH DEF_GLYPH "uni1903192A1922" ID 108 TYPE BASE END_GLYPH DEF_GLYPH "uni1904" ID 109 UNICODE 6404 TYPE BASE END_GLYPH DEF_GLYPH "uni19041922" ID 110 TYPE BASE END_GLYPH DEF_GLYPH "uni1904192A1922" ID 111 TYPE BASE END_GLYPH DEF_GLYPH "uni1905" ID 112 UNICODE 6405 TYPE BASE END_GLYPH DEF_GLYPH "uni19051922" ID 113 TYPE BASE END_GLYPH DEF_GLYPH "uni1905192A1922" ID 114 TYPE BASE END_GLYPH DEF_GLYPH "uni1906" ID 115 UNICODE 6406 TYPE BASE END_GLYPH DEF_GLYPH "uni19061922" ID 116 TYPE BASE END_GLYPH DEF_GLYPH "uni1906192A1922" ID 117 TYPE BASE END_GLYPH DEF_GLYPH "uni1907" ID 118 UNICODE 6407 TYPE BASE END_GLYPH DEF_GLYPH "uni19071922" ID 119 TYPE BASE END_GLYPH DEF_GLYPH "uni1907192A1922" ID 120 TYPE BASE END_GLYPH DEF_GLYPH "uni1908" ID 121 UNICODE 6408 TYPE BASE END_GLYPH DEF_GLYPH "uni19081922" ID 122 TYPE BASE END_GLYPH DEF_GLYPH "uni1908192A1922" ID 123 TYPE BASE END_GLYPH DEF_GLYPH "uni1909" ID 124 UNICODE 6409 TYPE BASE END_GLYPH DEF_GLYPH "uni19091922" ID 125 TYPE BASE END_GLYPH DEF_GLYPH "uni1909192A1922" ID 126 TYPE BASE END_GLYPH DEF_GLYPH "uni190A" ID 127 UNICODE 6410 TYPE BASE END_GLYPH DEF_GLYPH "uni190A1922" ID 128 TYPE BASE END_GLYPH DEF_GLYPH "uni190A192A1922" ID 129 TYPE BASE END_GLYPH DEF_GLYPH "uni190B" ID 130 UNICODE 6411 TYPE BASE END_GLYPH DEF_GLYPH "uni190B1922" ID 131 TYPE BASE END_GLYPH DEF_GLYPH "uni190B192A1922" ID 132 TYPE BASE END_GLYPH DEF_GLYPH "uni190C" ID 133 UNICODE 6412 TYPE BASE END_GLYPH DEF_GLYPH "uni190C1922" ID 134 TYPE BASE END_GLYPH DEF_GLYPH "uni190C192A1922" ID 135 TYPE BASE END_GLYPH DEF_GLYPH "uni190D" ID 136 UNICODE 6413 TYPE BASE END_GLYPH DEF_GLYPH "uni190D1922" ID 137 TYPE BASE END_GLYPH DEF_GLYPH "uni190D192A1922" ID 138 TYPE BASE END_GLYPH DEF_GLYPH "uni190E" ID 139 UNICODE 6414 TYPE BASE END_GLYPH DEF_GLYPH "uni190E1922" ID 140 TYPE BASE END_GLYPH DEF_GLYPH "uni190192AE1922" ID 141 TYPE BASE END_GLYPH DEF_GLYPH "uni190F" ID 142 UNICODE 6415 TYPE BASE END_GLYPH DEF_GLYPH "uni190F1922" ID 143 TYPE BASE END_GLYPH DEF_GLYPH "uni190F192A1922" ID 144 TYPE BASE END_GLYPH DEF_GLYPH "uni1910" ID 145 UNICODE 6416 TYPE BASE END_GLYPH DEF_GLYPH "uni19101922" ID 146 TYPE BASE END_GLYPH DEF_GLYPH "uni1910192A1922" ID 147 TYPE BASE END_GLYPH DEF_GLYPH "uni1911" ID 148 UNICODE 6417 TYPE BASE END_GLYPH DEF_GLYPH "uni19111922" ID 149 TYPE BASE END_GLYPH DEF_GLYPH "uni1911192A1922" ID 150 TYPE BASE END_GLYPH DEF_GLYPH "uni1912" ID 151 UNICODE 6418 TYPE BASE END_GLYPH DEF_GLYPH "uni19121922" ID 152 TYPE BASE END_GLYPH DEF_GLYPH "uni1912192A1922" ID 153 TYPE BASE END_GLYPH DEF_GLYPH "uni1913" ID 154 UNICODE 6419 TYPE BASE END_GLYPH DEF_GLYPH "uni19131922" ID 155 TYPE BASE END_GLYPH DEF_GLYPH "uni1913192A1922" ID 156 TYPE BASE END_GLYPH DEF_GLYPH "uni1914" ID 157 UNICODE 6420 TYPE BASE END_GLYPH DEF_GLYPH "uni19141922" ID 158 TYPE BASE END_GLYPH DEF_GLYPH "uni1914192A1922" ID 159 TYPE BASE END_GLYPH DEF_GLYPH "uni1915" ID 160 UNICODE 6421 TYPE BASE END_GLYPH DEF_GLYPH "uni19151922" ID 161 TYPE BASE END_GLYPH DEF_GLYPH "uni1915192A1922" ID 162 TYPE BASE END_GLYPH DEF_GLYPH "uni1916" ID 163 UNICODE 6422 TYPE BASE END_GLYPH DEF_GLYPH "uni19161922" ID 164 TYPE BASE END_GLYPH DEF_GLYPH "uni1916192A1922" ID 165 TYPE BASE END_GLYPH DEF_GLYPH "uni1917" ID 166 UNICODE 6423 TYPE BASE END_GLYPH DEF_GLYPH "uni19171922" ID 167 TYPE BASE END_GLYPH DEF_GLYPH "uni1917192A1922" ID 168 TYPE BASE END_GLYPH DEF_GLYPH "uni1918" ID 169 UNICODE 6424 TYPE BASE END_GLYPH DEF_GLYPH "uni19181922" ID 170 TYPE BASE END_GLYPH DEF_GLYPH "uni1918192A1922" ID 171 TYPE BASE END_GLYPH DEF_GLYPH "uni1919" ID 172 UNICODE 6425 TYPE BASE END_GLYPH DEF_GLYPH "uni19191922" ID 173 TYPE BASE END_GLYPH DEF_GLYPH "uni1919192A1922" ID 174 TYPE BASE END_GLYPH DEF_GLYPH "uni191A" ID 175 UNICODE 6426 TYPE BASE END_GLYPH DEF_GLYPH "uni191A1922" ID 176 TYPE BASE END_GLYPH DEF_GLYPH "uni191A192A1922" ID 177 TYPE BASE END_GLYPH DEF_GLYPH "uni191B" ID 178 UNICODE 6427 TYPE BASE END_GLYPH DEF_GLYPH "uni191B1922" ID 179 TYPE BASE END_GLYPH DEF_GLYPH "uni191B192A1922" ID 180 TYPE BASE END_GLYPH DEF_GLYPH "uni191C" ID 181 UNICODE 6428 TYPE BASE END_GLYPH DEF_GLYPH "uni191C1922" ID 182 TYPE BASE END_GLYPH DEF_GLYPH "uni191C192A1922" ID 183 TYPE BASE END_GLYPH DEF_GLYPH "uni1920" ID 184 UNICODE 6432 TYPE MARK END_GLYPH DEF_GLYPH "uni1920.widC" ID 185 TYPE MARK END_GLYPH DEF_GLYPH "uni1920.widD" ID 186 TYPE MARK END_GLYPH DEF_GLYPH "uni1921" ID 187 UNICODE 6433 TYPE BASE END_GLYPH DEF_GLYPH "uni1922" ID 188 UNICODE 6434 TYPE MARK END_GLYPH DEF_GLYPH "uni1922.altA" ID 189 TYPE MARK END_GLYPH DEF_GLYPH "uni1922.altB" ID 190 TYPE MARK END_GLYPH DEF_GLYPH "uni1922.altC" ID 191 TYPE MARK END_GLYPH DEF_GLYPH "uni1923" ID 192 UNICODE 6435 TYPE BASE END_GLYPH DEF_GLYPH "uni1924" ID 193 UNICODE 6436 TYPE BASE END_GLYPH DEF_GLYPH "uni1925" ID 194 UNICODE 6437 TYPE MARK END_GLYPH DEF_GLYPH "uni1926" ID 195 UNICODE 6438 TYPE MARK END_GLYPH DEF_GLYPH "uni1927" ID 196 UNICODE 6439 TYPE MARK END_GLYPH DEF_GLYPH "uni1928" ID 197 UNICODE 6440 TYPE MARK END_GLYPH DEF_GLYPH "uni1929" ID 198 UNICODE 6441 TYPE BASE END_GLYPH DEF_GLYPH "uni192A" ID 199 UNICODE 6442 TYPE MARK END_GLYPH DEF_GLYPH "uni192B" ID 200 UNICODE 6443 TYPE BASE END_GLYPH DEF_GLYPH "uni1930" ID 201 UNICODE 6448 TYPE BASE END_GLYPH DEF_GLYPH "uni1931" ID 202 UNICODE 6449 TYPE BASE END_GLYPH DEF_GLYPH "uni1932" ID 203 UNICODE 6450 TYPE BASE END_GLYPH DEF_GLYPH "uni1933" ID 204 UNICODE 6451 TYPE BASE END_GLYPH DEF_GLYPH "uni1934" ID 205 UNICODE 6452 TYPE BASE END_GLYPH DEF_GLYPH "uni1935" ID 206 UNICODE 6453 TYPE BASE END_GLYPH DEF_GLYPH "uni1936" ID 207 UNICODE 6454 TYPE BASE END_GLYPH DEF_GLYPH "uni1937" ID 208 UNICODE 6455 TYPE BASE END_GLYPH DEF_GLYPH "uni1938" ID 209 UNICODE 6456 TYPE BASE END_GLYPH DEF_GLYPH "uni1939" ID 210 UNICODE 6457 TYPE BASE END_GLYPH DEF_GLYPH "uni193A" ID 211 UNICODE 6458 TYPE MARK END_GLYPH DEF_GLYPH "uni193A.widC" ID 212 TYPE MARK END_GLYPH DEF_GLYPH "uni193B" ID 213 UNICODE 6459 TYPE MARK END_GLYPH DEF_GLYPH "uni193B.widA" ID 214 TYPE MARK END_GLYPH DEF_GLYPH "uni193B.widB" ID 215 TYPE MARK END_GLYPH DEF_GLYPH "uni193B.widC" ID 216 TYPE MARK END_GLYPH DEF_GLYPH "uni1940" ID 217 UNICODE 6464 TYPE BASE END_GLYPH DEF_GLYPH "uni19401922" ID 218 TYPE BASE END_GLYPH DEF_GLYPH "uni1940192A1922" ID 219 TYPE BASE END_GLYPH DEF_GLYPH "uni1944" ID 220 UNICODE 6468 TYPE BASE END_GLYPH DEF_GLYPH "uni1945" ID 221 UNICODE 6469 TYPE BASE END_GLYPH DEF_GLYPH "uni1946" ID 222 UNICODE 6470 TYPE BASE END_GLYPH DEF_GLYPH "uni1947" ID 223 UNICODE 6471 TYPE BASE END_GLYPH DEF_GLYPH "uni1948" ID 224 UNICODE 6472 TYPE BASE END_GLYPH DEF_GLYPH "uni1949" ID 225 UNICODE 6473 TYPE BASE END_GLYPH DEF_GLYPH "uni194A" ID 226 UNICODE 6474 TYPE BASE END_GLYPH DEF_GLYPH "uni194B" ID 227 UNICODE 6475 TYPE BASE END_GLYPH DEF_GLYPH "uni194C" ID 228 UNICODE 6476 TYPE BASE END_GLYPH DEF_GLYPH "uni194D" ID 229 UNICODE 6477 TYPE BASE END_GLYPH DEF_GLYPH "uni194E" ID 230 UNICODE 6478 TYPE BASE END_GLYPH DEF_GLYPH "uni194F" ID 231 UNICODE 6479 TYPE BASE END_GLYPH DEF_GLYPH "quoteleft" ID 232 UNICODE 8216 TYPE BASE END_GLYPH DEF_GLYPH "quoteright" ID 233 UNICODE 8217 TYPE BASE END_GLYPH DEF_GLYPH "quotedblleft" ID 234 UNICODE 8220 TYPE BASE END_GLYPH DEF_GLYPH "quotedblright" ID 235 UNICODE 8221 TYPE BASE END_GLYPH DEF_GLYPH "uni1921193A" ID 236 TYPE BASE END_GLYPH DEF_GLYPH "uni192A1922" ID 237 TYPE MARK END_GLYPH DEF_GLYPH "ampersand" ID 238 UNICODE 38 TYPE BASE END_GLYPH DEF_GLYPH "uni2009" ID 239 UNICODE 8201 TYPE BASE END_GLYPH DEF_GLYPH "endash" ID 240 UNICODE 8211 TYPE BASE END_GLYPH DEF_GLYPH "emdash" ID 241 UNICODE 8212 TYPE BASE END_GLYPH DEF_GLYPH "uni202F" ID 242 UNICODE 8239 TYPE BASE END_GLYPH DEF_GLYPH "uni1923193A" ID 243 TYPE BASE END_GLYPH DEF_GLYPH "uni1924193A" ID 244 TYPE BASE END_GLYPH DEF_GLYPH "uni19291920" ID 245 TYPE BASE END_GLYPH DEF_GLYPH "uni19291922" ID 246 TYPE BASE END_GLYPH DEF_GLYPH "uni19291927" ID 247 TYPE BASE END_GLYPH DEF_GLYPH "uni19291928" ID 248 TYPE BASE END_GLYPH DEF_GLYPH "uni1929193A" ID 249 TYPE BASE END_GLYPH DEF_GLYPH "uni19291920193A" ID 250 TYPE BASE END_GLYPH DEF_GLYPH "uni19291922193A" ID 251 TYPE BASE END_GLYPH DEF_GLYPH "uni19291927193A" ID 252 TYPE BASE END_GLYPH DEF_GLYPH "uni19291928193A" ID 253 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1920" ID 254 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1922" ID 255 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1927" ID 256 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1928" ID 257 TYPE BASE END_GLYPH DEF_GLYPH "uni192B193A" ID 258 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1920193A" ID 259 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1922193A" ID 260 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1927193A" ID 261 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1928193A" ID 262 TYPE BASE END_GLYPH DEF_GLYPH "uni25CC" ID 263 UNICODE 9676 TYPE BASE END_GLYPH DEF_GLYPH "uni191E" ID 264 UNICODE 6430 TYPE BASE END_GLYPH DEF_GLYPH "uni191E1922" ID 265 TYPE BASE END_GLYPH DEF_GLYPH "uni191E192A1922" ID 266 TYPE BASE END_GLYPH DEF_GLYPH "uni191D" ID 267 UNICODE 6429 TYPE BASE END_GLYPH DEF_GLYPH "uni191D1922" ID 268 TYPE BASE END_GLYPH DEF_GLYPH "uni191D192A1922" ID 269 TYPE BASE END_GLYPH DEF_SCRIPT NAME "Latin" TAG "latn" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Glyph Composition/Decomposition" TAG "ccmp" LOOKUP "EEAIDecomp" LOOKUP "OoAuKComp" LOOKUP "OoAuKDecomp" LOOKUP "GlideVowelComp" LOOKUP "GlideVowelDecomp" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "GlideIkar" LOOKUP "IkarKWid" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "Akar" LOOKUP "Kemphreng" LOOKUP "EO" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "VKem" LOOKUP "GlideU" END_FEATURE DEF_FEATURE NAME "Standard Ligatures" TAG "liga" LOOKUP "RaUkar" LOOKUP "Ukar" LOOKUP "IkarK" END_FEATURE END_LANGSYS END_SCRIPT DEF_SCRIPT NAME "Limbu" TAG "limb" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Glyph Composition/Decomposition" TAG "ccmp" LOOKUP "EEAIDecomp" LOOKUP "OoAuKComp" LOOKUP "OoAuKDecomp" LOOKUP "GlideVowelComp" LOOKUP "GlideVowelDecomp" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "GlideIkar" LOOKUP "IkarKWid" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "Akar" LOOKUP "Kemphreng" LOOKUP "EO" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "VKem" LOOKUP "GlideU" END_FEATURE DEF_FEATURE NAME "Standard Ligatures" TAG "liga" LOOKUP "RaUkar" LOOKUP "Ukar" LOOKUP "IkarK" END_FEATURE END_LANGSYS END_SCRIPT DEF_GROUP "AllCons" ENUM GROUP "cons" GROUP "ConsU" GROUP "ConsRaU" END_ENUM END_GROUP DEF_GROUP "Cons" ENUM GLYPH "uni1901" GLYPH "uni1902" GLYPH "uni1903" GLYPH "uni1904" GLYPH "uni1905" GLYPH "uni1906" GLYPH "uni1907" GLYPH "uni1908" GLYPH "uni1909" GLYPH "uni190A" GLYPH "uni190B" GLYPH "uni190C" GLYPH "uni190D" GLYPH "uni190E" GLYPH "uni190F" GLYPH "uni1910" GLYPH "uni1911" GLYPH "uni1912" GLYPH "uni1913" GLYPH "uni1914" GLYPH "uni1915" GLYPH "uni1916" GLYPH "uni1917" GLYPH "uni1918" GLYPH "uni1919" GLYPH "uni191A" GLYPH "uni191B" GLYPH "uni191C" GLYPH "uni1940" END_ENUM END_GROUP DEF_GROUP "ConsRaU" ENUM GLYPH "uni1901192A1922" GLYPH "uni1902192A1922" GLYPH "uni1903192A1922" GLYPH "uni1904192A1922" GLYPH "uni1905192A1922" GLYPH "uni1906192A1922" GLYPH "uni1907192A1922" GLYPH "uni1908192A1922" GLYPH "uni1909192A1922" GLYPH "uni190A192A1922" GLYPH "uni190B192A1922" GLYPH "uni190C192A1922" GLYPH "uni190D192A1922" GLYPH "uni190192AE1922" GLYPH "uni190F192A1922" GLYPH "uni1910192A1922" GLYPH "uni1911192A1922" GLYPH "uni1912192A1922" GLYPH "uni1913192A1922" GLYPH "uni1914192A1922" GLYPH "uni1915192A1922" GLYPH "uni1916192A1922" GLYPH "uni1917192A1922" GLYPH "uni1918192A1922" GLYPH "uni1919192A1922" GLYPH "uni1919192A1922" GLYPH "uni191A192A1922" GLYPH "uni191B192A1922" GLYPH "uni191C192A1922" GLYPH "uni1940192A1922" END_ENUM END_GROUP DEF_GROUP "ConsU" ENUM GLYPH "uni19011922" GLYPH "uni19021922" GLYPH "uni19031922" GLYPH "uni19041922" GLYPH "uni19051922" GLYPH "uni19061922" GLYPH "uni19071922" GLYPH "uni19081922" GLYPH "uni19091922" GLYPH "uni190A1922" GLYPH "uni190B1922" GLYPH "uni190C1922" GLYPH "uni190D1922" GLYPH "uni190E1922" GLYPH "uni190F1922" GLYPH "uni19101922" GLYPH "uni19111922" GLYPH "uni19121922" GLYPH "uni19131922" GLYPH "uni19141922" GLYPH "uni19151922" GLYPH "uni19161922" GLYPH "uni19171922" GLYPH "uni19181922" GLYPH "uni19191922" GLYPH "uni191A1922" GLYPH "uni191B1922" GLYPH "uni191C1922" GLYPH "uni19401922" END_ENUM END_GROUP DEF_GROUP "Ikar" ENUM GLYPH "uni1921" GLYPH "uni1921193A" END_ENUM END_GROUP DEF_GROUP "Vowels" ENUM GLYPH "uni1920" GLYPH "uni1927" GLYPH "uni1928" END_ENUM END_GROUP DEF_GROUP "VowelsKem" ENUM GROUP "Vowels" GLYPH "uni193A" END_ENUM END_GROUP DEF_GROUP "YaWa" ENUM GLYPH "uni1929" GLYPH "uni192B" END_ENUM END_GROUP DEF_LOOKUP "EEAIDecomp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1925" WITH GLYPH "uni1920" GLYPH "uni1923" END_SUB SUB GLYPH "uni1926" WITH GLYPH "uni1920" GLYPH "uni1924" END_SUB END_SUBSTITUTION DEF_LOOKUP "OoAuKComp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1923" GLYPH "uni193A" WITH GLYPH "uni1923193A" END_SUB SUB GLYPH "uni1924" GLYPH "uni193A" WITH GLYPH "uni1924193A" END_SUB END_SUBSTITUTION DEF_LOOKUP "OoAuKDecomp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The OoAuDecomp substitution rule replaces the OO and AU vowels with their visually constitutent components A plus EE or AI respectively. This is so that the 'A' portion can be positioned independently over the consonant when a Glide occurs between the consonant and the vowel." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1923193A" WITH GLYPH "uni193A" GLYPH "uni1923" END_SUB SUB GLYPH "uni1924193A" WITH GLYPH "uni193A" GLYPH "uni1924" END_SUB END_SUBSTITUTION DEF_LOOKUP "GlideVowelComp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1929" GLYPH "uni1920" GLYPH "uni193A" WITH GLYPH "uni19291920193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1922" GLYPH "uni193A" WITH GLYPH "uni19291922193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1927" GLYPH "uni193A" WITH GLYPH "uni19291927193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1928" GLYPH "uni193A" WITH GLYPH "uni19291928193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni193A" WITH GLYPH "uni1929193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1920" WITH GLYPH "uni19291920" END_SUB SUB GLYPH "uni1929" GLYPH "uni1922" WITH GLYPH "uni19291922" END_SUB SUB GLYPH "uni1929" GLYPH "uni1927" WITH GLYPH "uni19291927" END_SUB SUB GLYPH "uni1929" GLYPH "uni1928" WITH GLYPH "uni19291928" END_SUB SUB GLYPH "uni192B" GLYPH "uni1920" GLYPH "uni193A" WITH GLYPH "uni192B1920193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1922" GLYPH "uni193A" WITH GLYPH "uni192B1922193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1927" GLYPH "uni193A" WITH GLYPH "uni192B1927193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1928" GLYPH "uni193A" WITH GLYPH "uni192B1928193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni193A" WITH GLYPH "uni192B193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1920" WITH GLYPH "uni192B1920" END_SUB SUB GLYPH "uni192B" GLYPH "uni1922" WITH GLYPH "uni192B1922" END_SUB SUB GLYPH "uni192B" GLYPH "uni1927" WITH GLYPH "uni192B1927" END_SUB SUB GLYPH "uni192B" GLYPH "uni1928" WITH GLYPH "uni192B1928" END_SUB END_SUBSTITUTION DEF_LOOKUP "GlideVowelDecomp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni19291920193A" WITH GLYPH "uni1920" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291922193A" WITH GLYPH "uni1922" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291927193A" WITH GLYPH "uni1927" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291928193A" WITH GLYPH "uni1928" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni1929193A" WITH GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291920" WITH GLYPH "uni1920" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291922" WITH GLYPH "uni1922" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291927" WITH GLYPH "uni1927" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291928" WITH GLYPH "uni1928" GLYPH "uni1929" END_SUB SUB GLYPH "uni192B1920193A" WITH GLYPH "uni1920" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1922193A" WITH GLYPH "uni1922" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1927193A" WITH GLYPH "uni1927" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1928193A" WITH GLYPH "uni1928" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B193A" WITH GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1920" WITH GLYPH "uni1920" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1922" WITH GLYPH "uni1922" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1927" WITH GLYPH "uni1927" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1928" WITH GLYPH "uni1928" GLYPH "uni192B" END_SUB END_SUBSTITUTION DEF_LOOKUP "RaUkar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The RaUkar substitution rule replaces Consonant, Ra, Ukar with a ligature." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "Cons" GLYPH "uni192A" GLYPH "uni1922" WITH GROUP "ConsRaU" END_SUB SUB WITH END_SUB END_SUBSTITUTION DEF_LOOKUP "Ukar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The Ukar substitution rule replaces Consonant + Ukar with a ligature. It also applies to the Vowel-Carrier, which has its own ligature with ukar." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "Cons" GLYPH "uni1922" WITH GROUP "ConsU" END_SUB SUB GLYPH "uni1900" GLYPH "uni1922" WITH GLYPH "uni19001922" END_SUB END_SUBSTITUTION DEF_LOOKUP "IkarK" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The IkarK substitution rule replaces Ikar + Kemphreng with a ligature. The ligature is then positioned properly on the base consonant via the positioning rule IEO." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1921" GLYPH "uni193A" WITH GLYPH "uni1921193A" END_SUB END_SUBSTITUTION DEF_LOOKUP "GlideIkar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "Ikar" END_CONTEXT AS_POSITION ADJUST_SINGLE GROUP "YaWa" BY POS ADV -475 END_POS END_ADJUST END_POSITION DEF_LOOKUP "IkarKWid" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The IkarKWid lookup, applied to the Kern feature, adds 110 units of width to the IkarKemphreng ligature when followed by a consonant with akar on it. This prevents the akar from overprinting the rightmost dot of the kemphreng. (The dot overhangs to the right slightly, which is OK unless the following character has akar on it)." IN_CONTEXT RIGHT GROUP "cons" RIGHT GLYPH "uni1920" END_CONTEXT AS_POSITION ADJUST_SINGLE GLYPH "uni1921193A" BY POS ADV 110 END_POS END_ADJUST END_POSITION DEF_LOOKUP "Akar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The Akar positioning rule positions the Akar on all consonants." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "cons" TO GLYPH "uni1920" AT ANCHOR "Aabove" END_ATTACH END_POSITION DEF_LOOKUP "Kemphreng" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The Kemphreng positioning rule positions the Kemphreng on all consonants, including the vowel carrier." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "AllCons" GLYPH "uni1900" TO GLYPH "uni193A" AT ANCHOR "K" GLYPH "uni193A" AT ANCHOR "K" END_ATTACH END_POSITION DEF_LOOKUP "EO" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The IEO positioning rule positions ikar (including the ligature with kemphreng), e and o on all consonants plus the vowel carrier." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "cons" GLYPH "uni1900" TO GLYPH "uni1927" AT ANCHOR "eo" GLYPH "uni1928" AT ANCHOR "eo" END_ATTACH END_POSITION DEF_LOOKUP "VKem" PROCESS_BASE PROCESS_MARKS "VowelsKem" DIRECTION LTR COMMENTS "The VKem positioning rule positions the kemphreng on all upper vowels (except ikar, which has its own ligature). The vowel itself is positioned on the consonant with the Akar or IEO positioning rule." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "Vowels" TO GLYPH "uni193A" AT ANCHOR "VK" END_ATTACH END_POSITION DEF_LOOKUP "GlideU" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The GlideU positioning rule positions the ukar on the glides Ya and Wa. (There is already a ligature for each consonant with the Ra+Ukar combination)." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "YaWa" TO GLYPH "uni1922" AT ANCHOR "U" END_ATTACH END_POSITION DEF_ANCHOR "MARK_Aabove" ON 184 GLYPH uni1920 COMPONENT 1 LOCKED AT POS DX -500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 100 GLYPH uni1901 COMPONENT 1 AT POS DX 487 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 103 GLYPH uni1902 COMPONENT 1 AT POS DX 622 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 106 GLYPH uni1903 COMPONENT 1 AT POS DX 475 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 109 GLYPH uni1904 COMPONENT 1 AT POS DX 460 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 112 GLYPH uni1905 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 115 GLYPH uni1906 COMPONENT 1 AT POS DX 519 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 118 GLYPH uni1907 COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 121 GLYPH uni1908 COMPONENT 1 AT POS DX 564 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 124 GLYPH uni1909 COMPONENT 1 AT POS DX 430 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 127 GLYPH uni190A COMPONENT 1 AT POS DX 575 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 130 GLYPH uni190B COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 133 GLYPH uni190C COMPONENT 1 AT POS DX 556 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 136 GLYPH uni190D COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 139 GLYPH uni190E COMPONENT 1 AT POS DX 510 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 142 GLYPH uni190F COMPONENT 1 AT POS DX 497 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 145 GLYPH uni1910 COMPONENT 1 AT POS DX 657 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 148 GLYPH uni1911 COMPONENT 1 AT POS DX 690 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 151 GLYPH uni1912 COMPONENT 1 AT POS DX 538 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 154 GLYPH uni1913 COMPONENT 1 AT POS DX 571 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 157 GLYPH uni1914 COMPONENT 1 AT POS DX 538 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 160 GLYPH uni1915 COMPONENT 1 AT POS DX 470 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 163 GLYPH uni1916 COMPONENT 1 AT POS DX 503 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 166 GLYPH uni1917 COMPONENT 1 AT POS DX 548 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 169 GLYPH uni1918 COMPONENT 1 AT POS DX 511 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 172 GLYPH uni1919 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 175 GLYPH uni191A COMPONENT 1 AT POS DX 420 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 178 GLYPH uni191B COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 181 GLYPH uni191C COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 217 GLYPH uni1940 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_K" ON 211 GLYPH uni193A COMPONENT 1 LOCKED AT POS DX -260 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "K" ON 100 GLYPH uni1901 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 101 GLYPH uni19011922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 102 GLYPH uni1901192A1922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 103 GLYPH uni1902 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 104 GLYPH uni19021922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 105 GLYPH uni1902192A1922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 106 GLYPH uni1903 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 107 GLYPH uni19031922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 108 GLYPH uni1903192A1922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 109 GLYPH uni1904 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 110 GLYPH uni19041922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 111 GLYPH uni1904192A1922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 112 GLYPH uni1905 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 113 GLYPH uni19051922 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 114 GLYPH uni1905192A1922 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 115 GLYPH uni1906 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 116 GLYPH uni19061922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 117 GLYPH uni1906192A1922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 118 GLYPH uni1907 COMPONENT 1 AT POS DX 620 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 119 GLYPH uni19071922 COMPONENT 1 AT POS DX 620 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 120 GLYPH uni1907192A1922 COMPONENT 1 AT POS DX 620 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 121 GLYPH uni1908 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 122 GLYPH uni19081922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 123 GLYPH uni1908192A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 124 GLYPH uni1909 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 125 GLYPH uni19091922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 126 GLYPH uni1909192A1922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 127 GLYPH uni190A COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 128 GLYPH uni190A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 129 GLYPH uni190A192A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 130 GLYPH uni190B COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 131 GLYPH uni190B1922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 132 GLYPH uni190B192A1922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 133 GLYPH uni190C COMPONENT 1 AT POS DX 656 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 134 GLYPH uni190C1922 COMPONENT 1 AT POS DX 656 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 135 GLYPH uni190C192A1922 COMPONENT 1 AT POS DX 656 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 136 GLYPH uni190D COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 137 GLYPH uni190D1922 COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 138 GLYPH uni190D192A1922 COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 139 GLYPH uni190E COMPONENT 1 AT POS DX 530 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 140 GLYPH uni190E1922 COMPONENT 1 AT POS DX 530 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 141 GLYPH uni190192AE1922 COMPONENT 1 AT POS DX 530 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 142 GLYPH uni190F COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 143 GLYPH uni190F1922 COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 144 GLYPH uni190F192A1922 COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 145 GLYPH uni1910 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 146 GLYPH uni19101922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 147 GLYPH uni1910192A1922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 148 GLYPH uni1911 COMPONENT 1 AT POS DX 720 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 149 GLYPH uni19111922 COMPONENT 1 AT POS DX 720 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 150 GLYPH uni1911192A1922 COMPONENT 1 AT POS DX 720 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 151 GLYPH uni1912 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 152 GLYPH uni19121922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 153 GLYPH uni1912192A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 154 GLYPH uni1913 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 155 GLYPH uni19131922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 156 GLYPH uni1913192A1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 157 GLYPH uni1914 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 158 GLYPH uni19141922 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 159 GLYPH uni1914192A1922 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 160 GLYPH uni1915 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 161 GLYPH uni19151922 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 162 GLYPH uni1915192A1922 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 163 GLYPH uni1916 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 164 GLYPH uni19161922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 165 GLYPH uni1916192A1922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 166 GLYPH uni1917 COMPONENT 1 AT POS DX 585 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 167 GLYPH uni19171922 COMPONENT 1 AT POS DX 585 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 168 GLYPH uni1917192A1922 COMPONENT 1 AT POS DX 585 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 169 GLYPH uni1918 COMPONENT 1 AT POS DX 610 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 170 GLYPH uni19181922 COMPONENT 1 AT POS DX 610 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 171 GLYPH uni1918192A1922 COMPONENT 1 AT POS DX 610 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 172 GLYPH uni1919 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 173 GLYPH uni19191922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 174 GLYPH uni1919192A1922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 175 GLYPH uni191A COMPONENT 1 AT POS DX 440 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 176 GLYPH uni191A1922 COMPONENT 1 AT POS DX 440 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 177 GLYPH uni191A192A1922 COMPONENT 1 AT POS DX 440 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 178 GLYPH uni191B COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 179 GLYPH uni191B1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 180 GLYPH uni191B192A1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 181 GLYPH uni191C COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 182 GLYPH uni191C1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 183 GLYPH uni191C192A1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 217 GLYPH uni1940 COMPONENT 1 AT POS DX 490 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 218 GLYPH uni19401922 COMPONENT 1 AT POS DX 490 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 219 GLYPH uni1940192A1922 COMPONENT 1 AT POS DX 490 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 98 GLYPH uni1900 COMPONENT 1 AT POS DX 525 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_eo" ON 196 GLYPH uni1927 COMPONENT 1 AT POS DX -300 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 100 GLYPH uni1901 COMPONENT 1 AT POS DX 755 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 103 GLYPH uni1902 COMPONENT 1 AT POS DX 943 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 106 GLYPH uni1903 COMPONENT 1 AT POS DX 790 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 109 GLYPH uni1904 COMPONENT 1 AT POS DX 780 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 112 GLYPH uni1905 COMPONENT 1 AT POS DX 790 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 115 GLYPH uni1906 COMPONENT 1 AT POS DX 878 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 118 GLYPH uni1907 COMPONENT 1 AT POS DX 825 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 121 GLYPH uni1908 COMPONENT 1 AT POS DX 968 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 124 GLYPH uni1909 COMPONENT 1 AT POS DX 660 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 127 GLYPH uni190A COMPONENT 1 AT POS DX 569 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 130 GLYPH uni190B COMPONENT 1 AT POS DX 690 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 133 GLYPH uni190C COMPONENT 1 AT POS DX 649 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 136 GLYPH uni190D COMPONENT 1 AT POS DX 682 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 139 GLYPH uni190E COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 142 GLYPH uni190F COMPONENT 1 AT POS DX 778 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 145 GLYPH uni1910 COMPONENT 1 AT POS DX 920 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 148 GLYPH uni1911 COMPONENT 1 AT POS DX 894 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 151 GLYPH uni1912 COMPONENT 1 AT POS DX 782 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 154 GLYPH uni1913 COMPONENT 1 AT POS DX 982 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 157 GLYPH uni1914 COMPONENT 1 AT POS DX 917 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 160 GLYPH uni1915 COMPONENT 1 AT POS DX 730 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 163 GLYPH uni1916 COMPONENT 1 AT POS DX 767 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 166 GLYPH uni1917 COMPONENT 1 AT POS DX 937 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 169 GLYPH uni1918 COMPONENT 1 AT POS DX 862 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 172 GLYPH uni1919 COMPONENT 1 AT POS DX 670 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 175 GLYPH uni191A COMPONENT 1 AT POS DX 682 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 178 GLYPH uni191B COMPONENT 1 AT POS DX 921 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 181 GLYPH uni191C COMPONENT 1 AT POS DX 870 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 217 GLYPH uni1940 COMPONENT 1 AT POS DX 650 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 98 GLYPH uni1900 COMPONENT 1 AT POS DX 810 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_eo" ON 197 GLYPH uni1928 COMPONENT 1 AT POS DX -190 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_VK" ON 211 GLYPH uni193A COMPONENT 1 LOCKED AT POS DX -260 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "VK" ON 184 GLYPH uni1920 COMPONENT 1 AT POS DX -260 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "VK" ON 196 GLYPH uni1927 COMPONENT 1 AT POS DX -300 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "VK" ON 197 GLYPH uni1928 COMPONENT 1 AT POS DX -150 DY 1455 END_POS END_ANCHOR DEF_ANCHOR "MARK_U" ON 188 GLYPH uni1922 COMPONENT 1 LOCKED AT POS DX -150 DY -15 END_POS END_ANCHOR DEF_ANCHOR "U" ON 198 GLYPH uni1929 COMPONENT 1 AT POS DX -135 DY -40 END_POS END_ANCHOR DEF_ANCHOR "U" ON 200 GLYPH uni192B COMPONENT 1 AT POS DX -135 DY -40 END_POS END_ANCHOR GRID_PPEM 20 PRESENTATION_PPEM 72 PPOSITIONING_PPEM 144 CMAP_FORMAT 0 3 4 CMAP_FORMAT 1 0 6 CMAP_FORMAT 3 1 4 END \ No newline at end of file diff --git a/Tests/volto/data/Nutso.fea b/Tests/volto/data/Nutso.fea new file mode 100644 index 000000000..7a2c44bbf --- /dev/null +++ b/Tests/volto/data/Nutso.fea @@ -0,0 +1,328 @@ +# Glyph classes +@dnom = [zero.dnom one.dnom two.dnom three.dnom four.dnom five.dnom six.dnom seven.dnom eight.dnom nine.dnom]; +@numerals = [zero one two three four five six seven eight nine]; +@numr = [zero.numr one.numr two.numr three.numr four.numr five.numr six.numr seven.numr eight.numr nine.numr]; +@slash = [slash fraction]; + +# Mark classes +markClass eight.numr @INIT.1.10; +markClass eight.numr @INIT.2.10; +markClass eight.numr @INIT.3.10; +markClass eight.numr @INIT.4.10; +markClass eight.numr @INIT.5.10; +markClass eight.numr @INIT.6.10; +markClass eight.numr @INIT.7.10; +markClass eight.numr @INIT.8.10; +markClass eight.numr @INIT.9.10; +markClass eight.numr @NUMRNUMR; +markClass five.numr @INIT.1.10; +markClass five.numr @INIT.2.10; +markClass five.numr @INIT.3.10; +markClass five.numr @INIT.4.10; +markClass five.numr @INIT.5.10; +markClass five.numr @INIT.6.10; +markClass five.numr @INIT.7.10; +markClass five.numr @INIT.8.10; +markClass five.numr @INIT.9.10; +markClass five.numr @NUMRNUMR; +markClass four.numr @INIT.1.10; +markClass four.numr @INIT.2.10; +markClass four.numr @INIT.3.10; +markClass four.numr @INIT.4.10; +markClass four.numr @INIT.5.10; +markClass four.numr @INIT.6.10; +markClass four.numr @INIT.7.10; +markClass four.numr @INIT.8.10; +markClass four.numr @INIT.9.10; +markClass four.numr @NUMRNUMR; +markClass nine.numr @INIT.1.10; +markClass nine.numr @INIT.2.10; +markClass nine.numr @INIT.3.10; +markClass nine.numr @INIT.4.10; +markClass nine.numr @INIT.5.10; +markClass nine.numr @INIT.6.10; +markClass nine.numr @INIT.7.10; +markClass nine.numr @INIT.8.10; +markClass nine.numr @INIT.9.10; +markClass nine.numr @NUMRNUMR; +markClass one.numr @INIT.1.10; +markClass one.numr @INIT.2.10; +markClass one.numr @INIT.3.10; +markClass one.numr @INIT.4.10; +markClass one.numr @INIT.5.10; +markClass one.numr @INIT.6.10; +markClass one.numr @INIT.7.10; +markClass one.numr @INIT.8.10; +markClass one.numr @INIT.9.10; +markClass one.numr @NUMRNUMR; +markClass seven.numr @INIT.1.10; +markClass seven.numr @INIT.2.10; +markClass seven.numr @INIT.3.10; +markClass seven.numr @INIT.4.10; +markClass seven.numr @INIT.5.10; +markClass seven.numr @INIT.6.10; +markClass seven.numr @INIT.7.10; +markClass seven.numr @INIT.8.10; +markClass seven.numr @INIT.9.10; +markClass seven.numr @NUMRNUMR; +markClass six.numr @INIT.1.10; +markClass six.numr @INIT.2.10; +markClass six.numr @INIT.3.10; +markClass six.numr @INIT.4.10; +markClass six.numr @INIT.5.10; +markClass six.numr @INIT.6.10; +markClass six.numr @INIT.7.10; +markClass six.numr @INIT.8.10; +markClass six.numr @INIT.9.10; +markClass six.numr @NUMRNUMR; +markClass three.numr @INIT.1.10; +markClass three.numr @INIT.2.10; +markClass three.numr @INIT.3.10; +markClass three.numr @INIT.4.10; +markClass three.numr @INIT.5.10; +markClass three.numr @INIT.6.10; +markClass three.numr @INIT.7.10; +markClass three.numr @INIT.8.10; +markClass three.numr @INIT.9.10; +markClass three.numr @NUMRNUMR; +markClass two.numr @INIT.1.10; +markClass two.numr @INIT.2.10; +markClass two.numr @INIT.3.10; +markClass two.numr @INIT.4.10; +markClass two.numr @INIT.5.10; +markClass two.numr @INIT.6.10; +markClass two.numr @INIT.7.10; +markClass two.numr @INIT.8.10; +markClass two.numr @INIT.9.10; +markClass two.numr @NUMRNUMR; +markClass zero.numr @INIT.1.10; +markClass zero.numr @INIT.2.10; +markClass zero.numr @INIT.3.10; +markClass zero.numr @INIT.4.10; +markClass zero.numr @INIT.5.10; +markClass zero.numr @INIT.6.10; +markClass zero.numr @INIT.7.10; +markClass zero.numr @INIT.8.10; +markClass zero.numr @INIT.9.10; +markClass zero.numr @NUMRNUMR; + +# Lookups +lookup frac.numr { + sub @numerals by @numr; +} frac.numr; + +lookup frac.dnom { + sub [@slash @dnom] @numr' by @dnom; +} frac.dnom; + +lookup frac.noslash { + sub @numr slash by @numr; + sub @numr fraction by @numr; +} frac.noslash; + +lookup frac.fracinit { + ignore sub @numr @numr'; + sub @numr' by fracinit @numr; +} frac.fracinit; + +lookup kern.numeral_to_fraction { + enum pos @numerals fracinit 140; + pos @dnom @numerals 140; +} kern.numeral_to_fraction; + +lookup fracmark.init_1.10_target { + pos base fracinit + mark @INIT.1.10; +} fracmark.init_1.10_target; + +lookup fracmark.init_2.10_target { + pos base fracinit + mark @INIT.2.10; +} fracmark.init_2.10_target; + +lookup fracmark.init_3.10_target { + pos base fracinit + mark @INIT.3.10; +} fracmark.init_3.10_target; + +lookup fracmark.init_4.10_target { + pos base fracinit + mark @INIT.4.10; +} fracmark.init_4.10_target; + +lookup fracmark.init_5.10_target { + pos base fracinit + mark @INIT.5.10; +} fracmark.init_5.10_target; + +lookup fracmark.init_6.10_target { + pos base fracinit + mark @INIT.6.10; +} fracmark.init_6.10_target; + +lookup fracmark.init_7.10_target { + pos base fracinit + mark @INIT.7.10; +} fracmark.init_7.10_target; + +lookup fracmark.init_8.10_target { + pos base fracinit + mark @INIT.8.10; +} fracmark.init_8.10_target; + +lookup fracmark.init_9.10_target { + pos base fracinit + mark @INIT.9.10; +} fracmark.init_9.10_target; + +lookup fracmark.init { + # fracmark.init\1.10 + pos [@numr]' lookup fracmark.init_1.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\2.10 + pos [@numr]' lookup fracmark.init_2.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_2.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\3.10 + pos [@numr]' lookup fracmark.init_3.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_3.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_3.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\4.10 + pos [@numr]' lookup fracmark.init_4.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_4.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_4.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_4.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\5.10 + pos [@numr]' lookup fracmark.init_5.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_5.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_5.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_5.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_5.10_target @dnom @dnom @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\6.10 + pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_6.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_6.10_target @dnom @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\7.10 + pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_7.10_target @numr @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_7.10_target @dnom @dnom @dnom @dnom; + subtable; + # fracmark.init\8.10 + pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @numr @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_8.10_target @dnom @dnom @dnom; + subtable; + # fracmark.init\9.10 + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @dnom @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @numr @dnom @dnom @dnom; + pos [@numr]' lookup fracmark.init_9.10_target @dnom @dnom; +} fracmark.init; + +lookup fracmkmk.numrspacing { + pos mark zero.numr + mark @NUMRNUMR; + pos mark one.numr + mark @NUMRNUMR; + pos mark two.numr + mark @NUMRNUMR; + pos mark three.numr + mark @NUMRNUMR; + pos mark four.numr + mark @NUMRNUMR; + pos mark five.numr + mark @NUMRNUMR; + pos mark six.numr + mark @NUMRNUMR; + pos mark seven.numr + mark @NUMRNUMR; + pos mark eight.numr + mark @NUMRNUMR; + pos mark nine.numr + mark @NUMRNUMR; +} fracmkmk.numrspacing; + +# Features +feature afrc { + script DFLT; + language dflt; + lookup frac.numr; + lookup frac.dnom; + lookup frac.noslash; + lookup frac.fracinit; + script latn; + language dflt; + lookup frac.numr; + lookup frac.dnom; + lookup frac.noslash; + lookup frac.fracinit; +} afrc; + +feature frac { + script DFLT; + language dflt; + lookup frac.numr; + lookup frac.dnom; + lookup frac.noslash; + lookup frac.fracinit; + script latn; + language dflt; + lookup frac.numr; + lookup frac.dnom; + lookup frac.noslash; + lookup frac.fracinit; +} frac; + +feature kern { + script DFLT; + language dflt; + lookup kern.numeral_to_fraction; + script latn; + language dflt; + lookup kern.numeral_to_fraction; +} kern; + +feature mark { + script DFLT; + language dflt; + lookup fracmark.init; + script latn; + language dflt; + lookup fracmark.init; +} mark; + +feature mkmk { + script DFLT; + language dflt; + lookup fracmkmk.numrspacing; + script latn; + language dflt; + lookup fracmkmk.numrspacing; +} mkmk; + +@GDEF_base = [glyph0 \NULL CR space zero one two three four five six seven eight nine slash fraction fracinit zero.dnom one.dnom two.dnom three.dnom four.dnom five.dnom six.dnom seven.dnom eight.dnom nine.dnom]; +@GDEF_mark = [zero.numr one.numr two.numr three.numr four.numr five.numr six.numr seven.numr eight.numr nine.numr]; +table GDEF { + GlyphClassDef @GDEF_base, , @GDEF_mark, ; +} GDEF; diff --git a/Tests/volto/data/Nutso.ttf b/Tests/volto/data/Nutso.ttf new file mode 100644 index 000000000..5efec5683 Binary files /dev/null and b/Tests/volto/data/Nutso.ttf differ diff --git a/Tests/volto/data/Nutso.vtp b/Tests/volto/data/Nutso.vtp new file mode 100644 index 000000000..9572a0026 --- /dev/null +++ b/Tests/volto/data/Nutso.vtp @@ -0,0 +1 @@ + DEF_GLYPH "glyph0" ID 0 TYPE BASE END_GLYPH DEF_GLYPH "NULL" ID 1 UNICODE 0 TYPE BASE END_GLYPH DEF_GLYPH "CR" ID 2 UNICODE 13 TYPE BASE END_GLYPH DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH DEF_GLYPH "zero" ID 4 UNICODE 48 TYPE BASE END_GLYPH DEF_GLYPH "one" ID 5 UNICODE 49 TYPE BASE END_GLYPH DEF_GLYPH "two" ID 6 UNICODE 50 TYPE BASE END_GLYPH DEF_GLYPH "three" ID 7 UNICODE 51 TYPE BASE END_GLYPH DEF_GLYPH "four" ID 8 UNICODE 52 TYPE BASE END_GLYPH DEF_GLYPH "five" ID 9 UNICODE 53 TYPE BASE END_GLYPH DEF_GLYPH "six" ID 10 UNICODE 54 TYPE BASE END_GLYPH DEF_GLYPH "seven" ID 11 UNICODE 55 TYPE BASE END_GLYPH DEF_GLYPH "eight" ID 12 UNICODE 56 TYPE BASE END_GLYPH DEF_GLYPH "nine" ID 13 UNICODE 57 TYPE BASE END_GLYPH DEF_GLYPH "slash" ID 14 UNICODE 47 TYPE BASE END_GLYPH DEF_GLYPH "fraction" ID 15 UNICODE 8260 TYPE BASE END_GLYPH DEF_GLYPH "fracinit" ID 16 TYPE BASE END_GLYPH DEF_GLYPH "zero.numr" ID 17 TYPE MARK END_GLYPH DEF_GLYPH "one.numr" ID 18 TYPE MARK END_GLYPH DEF_GLYPH "two.numr" ID 19 TYPE MARK END_GLYPH DEF_GLYPH "three.numr" ID 20 TYPE MARK END_GLYPH DEF_GLYPH "four.numr" ID 21 TYPE MARK END_GLYPH DEF_GLYPH "five.numr" ID 22 TYPE MARK END_GLYPH DEF_GLYPH "six.numr" ID 23 TYPE MARK END_GLYPH DEF_GLYPH "seven.numr" ID 24 TYPE MARK END_GLYPH DEF_GLYPH "eight.numr" ID 25 TYPE MARK END_GLYPH DEF_GLYPH "nine.numr" ID 26 TYPE MARK END_GLYPH DEF_GLYPH "zero.dnom" ID 27 TYPE BASE END_GLYPH DEF_GLYPH "one.dnom" ID 28 TYPE BASE END_GLYPH DEF_GLYPH "two.dnom" ID 29 TYPE BASE END_GLYPH DEF_GLYPH "three.dnom" ID 30 TYPE BASE END_GLYPH DEF_GLYPH "four.dnom" ID 31 TYPE BASE END_GLYPH DEF_GLYPH "five.dnom" ID 32 TYPE BASE END_GLYPH DEF_GLYPH "six.dnom" ID 33 TYPE BASE END_GLYPH DEF_GLYPH "seven.dnom" ID 34 TYPE BASE END_GLYPH DEF_GLYPH "eight.dnom" ID 35 TYPE BASE END_GLYPH DEF_GLYPH "nine.dnom" ID 36 TYPE BASE END_GLYPH DEF_SCRIPT NAME "Default" TAG "DFLT" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Alternative Fractions" TAG "afrc" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Fractions" TAG "frac" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "kern.numeral-to-fraction" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "fracmark.init\1.10" LOOKUP "fracmark.init\2.10" LOOKUP "fracmark.init\3.10" LOOKUP "fracmark.init\4.10" LOOKUP "fracmark.init\5.10" LOOKUP "fracmark.init\6.10" LOOKUP "fracmark.init\7.10" LOOKUP "fracmark.init\8.10" LOOKUP "fracmark.init\9.10" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "fracmkmk.numrspacing" END_FEATURE END_LANGSYS END_SCRIPT DEF_SCRIPT NAME "Latin" TAG "latn" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Alternative Fractions" TAG "afrc" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Fractions" TAG "frac" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "kern.numeral-to-fraction" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "fracmark.init\1.10" LOOKUP "fracmark.init\2.10" LOOKUP "fracmark.init\3.10" LOOKUP "fracmark.init\4.10" LOOKUP "fracmark.init\5.10" LOOKUP "fracmark.init\6.10" LOOKUP "fracmark.init\7.10" LOOKUP "fracmark.init\8.10" LOOKUP "fracmark.init\9.10" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "fracmkmk.numrspacing" END_FEATURE END_LANGSYS END_SCRIPT DEF_GROUP "dnom" ENUM GLYPH "zero.dnom" GLYPH "one.dnom" GLYPH "two.dnom" GLYPH "three.dnom" GLYPH "four.dnom" GLYPH "five.dnom" GLYPH "six.dnom" GLYPH "seven.dnom" GLYPH "eight.dnom" GLYPH "nine.dnom" END_ENUM END_GROUP DEF_GROUP "numerals" ENUM GLYPH "zero" GLYPH "one" GLYPH "two" GLYPH "three" GLYPH "four" GLYPH "five" GLYPH "six" GLYPH "seven" GLYPH "eight" GLYPH "nine" END_ENUM END_GROUP DEF_GROUP "numr" ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" GLYPH "three.numr" GLYPH "four.numr" GLYPH "five.numr" GLYPH "six.numr" GLYPH "seven.numr" GLYPH "eight.numr" GLYPH "nine.numr" END_ENUM END_GROUP DEF_GROUP "slash" ENUM GLYPH "slash" GLYPH "fraction" END_ENUM END_GROUP DEF_LOOKUP "frac.numr" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "numerals" WITH GROUP "numr" END_SUB END_SUBSTITUTION DEF_LOOKUP "frac.dnom" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT LEFT ENUM GROUP "slash" GROUP "dnom" END_ENUM END_CONTEXT AS_SUBSTITUTION SUB GROUP "numr" WITH GROUP "dnom" END_SUB END_SUBSTITUTION DEF_LOOKUP "frac.noslash" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "numr" GLYPH "slash" WITH GROUP "numr" END_SUB SUB GROUP "numr" GLYPH "fraction" WITH GROUP "numr" END_SUB END_SUBSTITUTION DEF_LOOKUP "frac.fracinit" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR EXCEPT_CONTEXT LEFT GROUP "numr" END_CONTEXT AS_SUBSTITUTION SUB GROUP "numr" WITH GLYPH "fracinit" GROUP "numr" END_SUB END_SUBSTITUTION DEF_LOOKUP "kern.numeral-to-fraction" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_POSITION ADJUST_PAIR FIRST GROUP "numerals" FIRST GROUP "dnom" SECOND GROUP "numerals" SECOND GLYPH "fracinit" 1 2 BY POS ADV 140 END_POS POS END_POS 2 1 BY POS ADV 140 END_POS POS END_POS END_ADJUST END_POSITION DEF_LOOKUP "fracmark.init\1.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.1.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\2.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.2.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\3.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.3.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\4.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.4.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\5.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.5.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\6.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.6.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\7.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.7.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\8.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.8.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\9.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.9.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmkmk.numrspacing" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "numr" TO GROUP "numr" AT ANCHOR "NUMRNUMR" END_ATTACH END_POSITION DEF_ANCHOR "NUMRNUMR" ON 17 GLYPH zero.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 18 GLYPH one.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 19 GLYPH two.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 20 GLYPH three.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 21 GLYPH four.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 22 GLYPH five.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 23 GLYPH six.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 24 GLYPH seven.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 25 GLYPH eight.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 26 GLYPH nine.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "INIT.1.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 3150 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.2.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 2800 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.3.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 2450 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.4.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 2100 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.5.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 1750 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.6.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 1400 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.7.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.8.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.9.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 350 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR GRID_PPEM 20 PRESENTATION_PPEM 144 PPOSITIONING_PPEM 205 CMAP_FORMAT 0 3 4 CMAP_FORMAT 1 0 6 CMAP_FORMAT 3 1 4 END \ No newline at end of file diff --git a/Tests/volto/volto_test.py b/Tests/volto/volto_test.py new file mode 100644 index 000000000..255da21ad --- /dev/null +++ b/Tests/volto/volto_test.py @@ -0,0 +1,1251 @@ +import pathlib +import shutil +import tempfile +import unittest +from io import StringIO + +from fontTools.volto import VoltToFea + +DATADIR = pathlib.Path(__file__).parent / "data" + + +class ToFeaTest(unittest.TestCase): + @classmethod + def setup_class(cls): + cls.tempdir = None + cls.num_tempfiles = 0 + + @classmethod + def teardown_class(cls): + if cls.tempdir: + shutil.rmtree(cls.tempdir, ignore_errors=True) + + @classmethod + def temp_path(cls): + if not cls.tempdir: + cls.tempdir = pathlib.Path(tempfile.mkdtemp()) + cls.num_tempfiles += 1 + return cls.tempdir / f"tmp{cls.num_tempfiles}" + + def test_def_glyph_base(self): + fea = self.parse('DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH') + self.assertEqual( + fea, + "@GDEF_base = [.notdef];\n" + "table GDEF {\n" + " GlyphClassDef @GDEF_base, , , ;\n" + "} GDEF;\n", + ) + + def test_def_glyph_base_2_components(self): + fea = self.parse( + 'DEF_GLYPH "glyphBase" ID 320 TYPE BASE COMPONENTS 2 END_GLYPH' + ) + self.assertEqual( + fea, + "@GDEF_base = [glyphBase];\n" + "table GDEF {\n" + " GlyphClassDef @GDEF_base, , , ;\n" + "} GDEF;\n", + ) + + def test_def_glyph_ligature_2_components(self): + fea = self.parse('DEF_GLYPH "f_f" ID 320 TYPE LIGATURE COMPONENTS 2 END_GLYPH') + self.assertEqual( + fea, + "@GDEF_ligature = [f_f];\n" + "table GDEF {\n" + " GlyphClassDef , @GDEF_ligature, , ;\n" + "} GDEF;\n", + ) + + def test_def_glyph_mark(self): + fea = self.parse('DEF_GLYPH "brevecomb" ID 320 TYPE MARK END_GLYPH') + self.assertEqual( + fea, + "@GDEF_mark = [brevecomb];\n" + "table GDEF {\n" + " GlyphClassDef , , @GDEF_mark, ;\n" + "} GDEF;\n", + ) + + def test_def_glyph_component(self): + fea = self.parse('DEF_GLYPH "f.f_f" ID 320 TYPE COMPONENT END_GLYPH') + self.assertEqual( + fea, + "@GDEF_component = [f.f_f];\n" + "table GDEF {\n" + " GlyphClassDef , , , @GDEF_component;\n" + "} GDEF;\n", + ) + + def test_def_glyph_no_type(self): + fea = self.parse('DEF_GLYPH "glyph20" ID 20 END_GLYPH') + self.assertEqual(fea, "") + + def test_def_glyph_case_sensitive(self): + fea = self.parse( + 'DEF_GLYPH "A" ID 3 UNICODE 65 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "a" ID 4 UNICODE 97 TYPE BASE END_GLYPH\n' + ) + self.assertEqual( + fea, + "@GDEF_base = [A a];\n" + "table GDEF {\n" + " GlyphClassDef @GDEF_base, , , ;\n" + "} GDEF;\n", + ) + + def test_def_group_glyphs(self): + fea = self.parse( + 'DEF_GROUP "aaccented"\n' + 'ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" ' + 'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" ' + 'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n' + "END_GROUP\n" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@aaccented = [aacute abreve acircumflex adieresis ae" + " agrave amacron aogonek aring atilde];", + ) + + def test_def_group_groups(self): + fea = self.parse( + 'DEF_GROUP "Group1"\n' + 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "Group2"\n' + 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "TestGroup"\n' + 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' + "END_GROUP\n" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@Group1 = [a b c d];\n" + "@Group2 = [e f g h];\n" + "@TestGroup = [@Group1 @Group2];", + ) + + def test_def_group_groups_not_yet_defined(self): + fea = self.parse( + 'DEF_GROUP "Group1"\n' + 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "TestGroup1"\n' + 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "TestGroup2"\n' + 'ENUM GROUP "Group2" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "TestGroup3"\n' + 'ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "Group2"\n' + 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n' + "END_GROUP\n" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@Group1 = [a b c d];\n" + "@Group2 = [e f g h];\n" + "@TestGroup1 = [@Group1 @Group2];\n" + "@TestGroup2 = [@Group2];\n" + "@TestGroup3 = [@Group2 @Group1];", + ) + + def test_def_group_glyphs_and_group(self): + fea = self.parse( + 'DEF_GROUP "aaccented"\n' + 'ENUM GLYPH "aacute" GLYPH "abreve" GLYPH "acircumflex" ' + 'GLYPH "adieresis" GLYPH "ae" GLYPH "agrave" GLYPH "amacron" ' + 'GLYPH "aogonek" GLYPH "aring" GLYPH "atilde" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "KERN_lc_a_2ND"\n' + 'ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n' + "END_GROUP" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@aaccented = [aacute abreve acircumflex adieresis ae" + " agrave amacron aogonek aring atilde];\n" + "@KERN_lc_a_2ND = [a @aaccented];", + ) + + def test_def_group_range(self): + fea = self.parse( + 'DEF_GLYPH "a" ID 163 UNICODE 97 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "agrave" ID 194 UNICODE 224 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "aacute" ID 195 UNICODE 225 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "acircumflex" ID 196 UNICODE 226 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "atilde" ID 197 UNICODE 227 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "c" ID 165 UNICODE 99 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "ccaron" ID 209 UNICODE 269 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "ccedilla" ID 210 UNICODE 231 TYPE BASE END_GLYPH\n' + 'DEF_GLYPH "cdotaccent" ID 210 UNICODE 267 TYPE BASE END_GLYPH\n' + 'DEF_GROUP "KERN_lc_a_2ND"\n' + 'ENUM RANGE "a" TO "atilde" GLYPH "b" RANGE "c" TO "cdotaccent" ' + "END_ENUM\n" + "END_GROUP" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@KERN_lc_a_2ND = [a - atilde b c - cdotaccent];\n" + "@GDEF_base = [a agrave aacute acircumflex atilde c" + " ccaron ccedilla cdotaccent];\n" + "table GDEF {\n" + " GlyphClassDef @GDEF_base, , , ;\n" + "} GDEF;\n", + ) + + def test_script_without_langsys(self): + fea = self.parse('DEF_SCRIPT NAME "Latin" TAG "latn"\n' "END_SCRIPT") + self.assertEqual(fea, "") + + def test_langsys_normal(self): + fea = self.parse( + 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' + 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n' + "END_LANGSYS\n" + 'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n' + "END_LANGSYS\n" + "END_SCRIPT" + ) + self.assertEqual(fea, "") + + def test_langsys_no_script_name(self): + fea = self.parse( + 'DEF_SCRIPT TAG "latn"\n' + 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' + "END_LANGSYS\n" + "END_SCRIPT" + ) + self.assertEqual(fea, "") + + def test_langsys_lang_in_separate_scripts(self): + fea = self.parse( + 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n' + 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' + "END_LANGSYS\n" + 'DEF_LANGSYS NAME "Default" TAG "ROM "\n' + "END_LANGSYS\n" + "END_SCRIPT\n" + 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' + 'DEF_LANGSYS NAME "Default" TAG "dflt"\n' + "END_LANGSYS\n" + 'DEF_LANGSYS NAME "Default" TAG "ROM "\n' + "END_LANGSYS\n" + "END_SCRIPT" + ) + self.assertEqual(fea, "") + + def test_langsys_no_lang_name(self): + fea = self.parse( + 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' + 'DEF_LANGSYS TAG "dflt"\n' + "END_LANGSYS\n" + "END_SCRIPT" + ) + self.assertEqual(fea, "") + + def test_feature(self): + fea = self.parse( + 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' + 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n' + 'DEF_FEATURE NAME "Fractions" TAG "frac"\n' + 'LOOKUP "fraclookup"\n' + "END_FEATURE\n" + "END_LANGSYS\n" + "END_SCRIPT\n" + 'DEF_LOOKUP "fraclookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "one" GLYPH "slash" GLYPH "two"\n' + 'WITH GLYPH "one_slash_two.frac"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup fraclookup {\n" + " sub one slash two by one_slash_two.frac;\n" + "} fraclookup;\n" + "\n" + "# Features\n" + "feature frac {\n" + " script latn;\n" + " language ROM exclude_dflt;\n" + " lookup fraclookup;\n" + "} frac;\n", + ) + + def test_feature_sub_lookups(self): + fea = self.parse( + 'DEF_SCRIPT NAME "Latin" TAG "latn"\n' + 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n' + 'DEF_FEATURE NAME "Fractions" TAG "frac"\n' + 'LOOKUP "fraclookup\\1"\n' + 'LOOKUP "fraclookup\\1"\n' + "END_FEATURE\n" + "END_LANGSYS\n" + "END_SCRIPT\n" + 'DEF_LOOKUP "fraclookup\\1" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "one" GLYPH "slash" GLYPH "two"\n' + 'WITH GLYPH "one_slash_two.frac"\n' + "END_SUB\n" + "END_SUBSTITUTION\n" + 'DEF_LOOKUP "fraclookup\\2" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "one" GLYPH "slash" GLYPH "three"\n' + 'WITH GLYPH "one_slash_three.frac"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup fraclookup {\n" + " lookupflag RightToLeft;\n" + " # fraclookup\\1\n" + " sub one slash two by one_slash_two.frac;\n" + " subtable;\n" + " # fraclookup\\2\n" + " sub one slash three by one_slash_three.frac;\n" + "} fraclookup;\n" + "\n" + "# Features\n" + "feature frac {\n" + " script latn;\n" + " language ROM exclude_dflt;\n" + " lookup fraclookup;\n" + "} frac;\n", + ) + + def test_lookup_comment(self): + fea = self.parse( + 'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + 'COMMENTS "Smallcaps lookup for testing"\n' + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "a"\n' + 'WITH GLYPH "a.sc"\n' + "END_SUB\n" + 'SUB GLYPH "b"\n' + 'WITH GLYPH "b.sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup smcp {\n" + " # Smallcaps lookup for testing\n" + " sub a by a.sc;\n" + " sub b by b.sc;\n" + "} smcp;\n", + ) + + def test_substitution_single(self): + fea = self.parse( + 'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "a"\n' + 'WITH GLYPH "a.sc"\n' + "END_SUB\n" + 'SUB GLYPH "b"\n' + 'WITH GLYPH "b.sc"\n' + "END_SUB\n" + "SUB WITH\n" # Empty substitution, will be ignored + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup smcp {\n" + " sub a by a.sc;\n" + " sub b by b.sc;\n" + "} smcp;\n", + ) + + def test_substitution_single_in_context(self): + fea = self.parse( + 'DEF_GROUP "Denominators" ENUM GLYPH "one.dnom" GLYPH "two.dnom" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + 'IN_CONTEXT LEFT ENUM GROUP "Denominators" GLYPH "fraction" ' + "END_ENUM\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "one"\n' + 'WITH GLYPH "one.dnom"\n' + "END_SUB\n" + 'SUB GLYPH "two"\n' + 'WITH GLYPH "two.dnom"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@Denominators = [one.dnom two.dnom];\n" + "\n" + "# Lookups\n" + "lookup fracdnom {\n" + " sub [@Denominators fraction] one' by one.dnom;\n" + " sub [@Denominators fraction] two' by two.dnom;\n" + "} fracdnom;\n", + ) + + def test_substitution_single_in_contexts(self): + fea = self.parse( + 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + 'RIGHT GROUP "Hebrew"\n' + 'RIGHT GLYPH "one.Hebr"\n' + "END_CONTEXT\n" + "IN_CONTEXT\n" + 'LEFT GROUP "Hebrew"\n' + 'LEFT GLYPH "one.Hebr"\n' + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "dollar"\n' + 'WITH GLYPH "dollar.Hebr"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@Hebrew = [uni05D0 uni05D1];\n" + "\n" + "# Lookups\n" + "lookup HebrewCurrency {\n" + " sub dollar' @Hebrew one.Hebr by dollar.Hebr;\n" + " sub @Hebrew one.Hebr dollar' by dollar.Hebr;\n" + "} HebrewCurrency;\n", + ) + + def test_substitution_single_except_context(self): + fea = self.parse( + 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "EXCEPT_CONTEXT\n" + 'RIGHT GROUP "Hebrew"\n' + 'RIGHT GLYPH "one.Hebr"\n' + "END_CONTEXT\n" + "IN_CONTEXT\n" + 'LEFT GROUP "Hebrew"\n' + 'LEFT GLYPH "one.Hebr"\n' + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "dollar"\n' + 'WITH GLYPH "dollar.Hebr"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@Hebrew = [uni05D0 uni05D1];\n" + "\n" + "# Lookups\n" + "lookup HebrewCurrency {\n" + " ignore sub dollar' @Hebrew one.Hebr;\n" + " sub @Hebrew one.Hebr dollar' by dollar.Hebr;\n" + "} HebrewCurrency;\n", + ) + + def test_substitution_skip_base(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [marka markb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " lookupflag IgnoreBaseGlyphs;\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_process_base(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [marka markb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_process_marks_all(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "ALL"' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [marka markb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_process_marks_none(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "NONE"' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [marka markb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " lookupflag IgnoreMarks;\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_skip_marks(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [marka markb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " lookupflag IgnoreMarks;\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_mark_attachment(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE ' + 'PROCESS_MARKS "SomeMarks" \n' + "DIRECTION RTL\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [acutecmb gravecmb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " lookupflag RightToLeft MarkAttachmentType" + " @SomeMarks;\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_mark_glyph_set(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE ' + 'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" \n' + "DIRECTION RTL\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [acutecmb gravecmb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " lookupflag RightToLeft UseMarkFilteringSet" + " @SomeMarks;\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_process_all_marks(self): + fea = self.parse( + 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" ' + "END_ENUM END_GROUP\n" + 'DEF_LOOKUP "SomeSub" PROCESS_BASE ' + "PROCESS_MARKS ALL \n" + "DIRECTION RTL\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "A"\n' + 'WITH GLYPH "A.c2sc"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@SomeMarks = [acutecmb gravecmb];\n" + "\n" + "# Lookups\n" + "lookup SomeSub {\n" + " lookupflag RightToLeft;\n" + " sub A by A.c2sc;\n" + "} SomeSub;\n", + ) + + def test_substitution_no_reversal(self): + # TODO: check right context with no reversal + fea = self.parse( + 'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n' + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "a"\n' + 'WITH GLYPH "a.alt"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup Lookup {\n" + " sub a' [a b] by a.alt;\n" + "} Lookup;\n", + ) + + def test_substitution_reversal(self): + fea = self.parse( + 'DEF_GROUP "DFLT_Num_standardFigures"\n' + 'ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "DFLT_Num_numerators"\n' + 'ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n' + "END_GROUP\n" + 'DEF_LOOKUP "RevLookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR REVERSAL\n" + "IN_CONTEXT\n" + 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n' + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GROUP "DFLT_Num_standardFigures"\n' + 'WITH GROUP "DFLT_Num_numerators"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@DFLT_Num_standardFigures = [zero one two];\n" + "@DFLT_Num_numerators = [zero.numr one.numr two.numr];\n" + "\n" + "# Lookups\n" + "lookup RevLookup {\n" + " rsub @DFLT_Num_standardFigures' [a b] by @DFLT_Num_numerators;\n" + "} RevLookup;\n", + ) + + def test_substitution_single_to_multiple(self): + fea = self.parse( + 'DEF_LOOKUP "ccmp" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "aacute"\n' + 'WITH GLYPH "a" GLYPH "acutecomb"\n' + "END_SUB\n" + 'SUB GLYPH "agrave"\n' + 'WITH GLYPH "a" GLYPH "gravecomb"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup ccmp {\n" + " sub aacute by a acutecomb;\n" + " sub agrave by a gravecomb;\n" + "} ccmp;\n", + ) + + def test_substitution_multiple_to_single(self): + fea = self.parse( + 'DEF_LOOKUP "liga" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB GLYPH "f" GLYPH "i"\n' + 'WITH GLYPH "f_i"\n' + "END_SUB\n" + 'SUB GLYPH "f" GLYPH "t"\n' + 'WITH GLYPH "f_t"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup liga {\n" + " sub f i by f_i;\n" + " sub f t by f_t;\n" + "} liga;\n", + ) + + def test_substitution_reverse_chaining_single(self): + fea = self.parse( + 'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR REVERSAL\n" + "IN_CONTEXT\n" + "RIGHT ENUM " + 'GLYPH "fraction" ' + 'RANGE "zero.numr" TO "nine.numr" ' + "END_ENUM\n" + "END_CONTEXT\n" + "AS_SUBSTITUTION\n" + 'SUB RANGE "zero" TO "nine"\n' + 'WITH RANGE "zero.numr" TO "nine.numr"\n' + "END_SUB\n" + "END_SUBSTITUTION" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup numr {\n" + " rsub zero - nine' [fraction zero.numr - nine.numr] by zero.numr - nine.numr;\n" + "} numr;\n", + ) + + # GPOS + # ATTACH_CURSIVE + # ATTACH + # ADJUST_PAIR + # ADJUST_SINGLE + def test_position_attach(self): + fea = self.parse( + 'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + 'ATTACH GLYPH "a" GLYPH "e"\n' + 'TO GLYPH "acutecomb" AT ANCHOR "top" ' + 'GLYPH "gravecomb" AT ANCHOR "top"\n' + "END_ATTACH\n" + "END_POSITION\n" + 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb COMPONENT 1 ' + "AT POS DX 0 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 ' + "AT POS DX 0 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "top" ON 31 GLYPH a COMPONENT 1 ' + "AT POS DX 210 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 ' + "AT POS DX 215 DY 450 END_POS END_ANCHOR\n" + ) + self.assertEqual( + fea, + "\n# Mark classes\n" + "markClass acutecomb @top;\n" + "markClass gravecomb @top;\n" + "\n" + "# Lookups\n" + "lookup anchor_top {\n" + " lookupflag RightToLeft;\n" + " pos base a\n" + " mark @top;\n" + " pos base e\n" + " mark @top;\n" + "} anchor_top;\n", + ) + + def test_position_attach_mkmk(self): + fea = self.parse( + 'DEF_GLYPH "brevecomb" ID 1 TYPE MARK END_GLYPH\n' + 'DEF_GLYPH "gravecomb" ID 2 TYPE MARK END_GLYPH\n' + 'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + 'ATTACH GLYPH "gravecomb"\n' + 'TO GLYPH "acutecomb" AT ANCHOR "top"\n' + "END_ATTACH\n" + "END_POSITION\n" + 'DEF_ANCHOR "MARK_top" ON 1 GLYPH acutecomb COMPONENT 1 ' + "AT POS DX 0 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "top" ON 2 GLYPH gravecomb COMPONENT 1 ' + "AT POS DX 210 DY 450 END_POS END_ANCHOR\n" + ) + self.assertEqual( + fea, + "\n# Mark classes\n" + "markClass acutecomb @top;\n" + "\n" + "# Lookups\n" + "lookup anchor_top {\n" + " lookupflag RightToLeft;\n" + " pos mark gravecomb\n" + " mark @top;\n" + "} anchor_top;\n" + "\n" + "@GDEF_mark = [brevecomb gravecomb];\n" + "table GDEF {\n" + " GlyphClassDef , , @GDEF_mark, ;\n" + "} GDEF;\n", + ) + + def test_position_attach_in_context(self): + fea = self.parse( + 'DEF_LOOKUP "test" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + 'EXCEPT_CONTEXT LEFT GLYPH "a" END_CONTEXT\n' + "AS_POSITION\n" + 'ATTACH GLYPH "a"\n' + 'TO GLYPH "acutecomb" AT ANCHOR "top" ' + 'GLYPH "gravecomb" AT ANCHOR "top"\n' + "END_ATTACH\n" + "END_POSITION\n" + 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb COMPONENT 1 ' + "AT POS DX 0 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 ' + "AT POS DX 0 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "top" ON 31 GLYPH a COMPONENT 1 ' + "AT POS DX 210 DY 450 END_POS END_ANCHOR\n" + ) + self.assertEqual( + fea, + "\n# Mark classes\n" + "markClass acutecomb @top;\n" + "markClass gravecomb @top;\n" + "\n" + "# Lookups\n" + "lookup test_target {\n" + " pos base a\n" + " mark @top;\n" + "} test_target;\n" + "\n" + "lookup test {\n" + " lookupflag RightToLeft;\n" + " ignore pos a [acutecomb gravecomb]';\n" + " pos [acutecomb gravecomb]' lookup test_target;\n" + "} test;\n", + ) + + def test_position_attach_cursive(self): + fea = self.parse( + 'DEF_LOOKUP "SomeLookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + 'ATTACH_CURSIVE EXIT GLYPH "a" GLYPH "b" ' + 'ENTER GLYPH "a" GLYPH "c"\n' + "END_ATTACH\n" + "END_POSITION\n" + 'DEF_ANCHOR "exit" ON 1 GLYPH a COMPONENT 1 AT POS END_POS END_ANCHOR\n' + 'DEF_ANCHOR "entry" ON 1 GLYPH a COMPONENT 1 AT POS END_POS END_ANCHOR\n' + 'DEF_ANCHOR "exit" ON 2 GLYPH b COMPONENT 1 AT POS END_POS END_ANCHOR\n' + 'DEF_ANCHOR "entry" ON 3 GLYPH c COMPONENT 1 AT POS END_POS END_ANCHOR\n' + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup SomeLookup {\n" + " lookupflag RightToLeft;\n" + " pos cursive a ;\n" + " pos cursive c ;\n" + " pos cursive b ;\n" + "} SomeLookup;\n", + ) + + def test_position_adjust_pair(self): + fea = self.parse( + 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION RTL\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + "ADJUST_PAIR\n" + ' FIRST GLYPH "A" FIRST GLYPH "V"\n' + ' SECOND GLYPH "A" SECOND GLYPH "V"\n' + " 1 2 BY POS ADV -30 END_POS POS END_POS\n" + " 2 1 BY POS ADV -25 END_POS POS END_POS\n" + "END_ADJUST\n" + "END_POSITION\n" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup kern1 {\n" + " lookupflag RightToLeft;\n" + " enum pos A V -30;\n" + " enum pos V A -25;\n" + "} kern1;\n", + ) + + def test_position_adjust_pair_in_context(self): + fea = self.parse( + 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + 'EXCEPT_CONTEXT LEFT GLYPH "A" END_CONTEXT\n' + "AS_POSITION\n" + "ADJUST_PAIR\n" + ' FIRST GLYPH "A" FIRST GLYPH "V"\n' + ' SECOND GLYPH "A" SECOND GLYPH "V"\n' + " 2 1 BY POS ADV -25 END_POS POS END_POS\n" + "END_ADJUST\n" + "END_POSITION\n" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup kern1_target {\n" + " enum pos V A -25;\n" + "} kern1_target;\n" + "\n" + "lookup kern1 {\n" + " ignore pos A V' A';\n" + " pos V' lookup kern1_target A' lookup kern1_target;\n" + "} kern1;\n", + ) + + def test_position_adjust_single(self): + fea = self.parse( + 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + "ADJUST_SINGLE" + ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS\n' + ' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n' + "END_ADJUST\n" + "END_POSITION\n" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup TestLookup {\n" + " pos glyph1 <123 0 0 0>;\n" + " pos glyph2 <456 0 0 0>;\n" + "} TestLookup;\n", + ) + + def test_position_adjust_single_in_context(self): + fea = self.parse( + 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "EXCEPT_CONTEXT\n" + 'LEFT GLYPH "leftGlyph"\n' + 'RIGHT GLYPH "rightGlyph"\n' + "END_CONTEXT\n" + "AS_POSITION\n" + "ADJUST_SINGLE" + ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS\n' + ' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n' + "END_ADJUST\n" + "END_POSITION\n" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup TestLookup_target {\n" + " pos glyph1 <123 0 0 0>;\n" + " pos glyph2 <456 0 0 0>;\n" + "} TestLookup_target;\n" + "\n" + "lookup TestLookup {\n" + " ignore pos leftGlyph [glyph1 glyph2]' rightGlyph;\n" + " pos [glyph1 glyph2]' lookup TestLookup_target;\n" + "} TestLookup;\n", + ) + + def test_def_anchor(self): + fea = self.parse( + 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + 'ATTACH GLYPH "a"\n' + 'TO GLYPH "acutecomb" AT ANCHOR "top"\n' + "END_ATTACH\n" + "END_POSITION\n" + 'DEF_ANCHOR "top" ON 120 GLYPH a ' + "COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb ' + "COMPONENT 1 AT POS DX 0 DY 450 END_POS END_ANCHOR" + ) + self.assertEqual( + fea, + "\n# Mark classes\n" + "markClass acutecomb @top;\n" + "\n" + "# Lookups\n" + "lookup TestLookup {\n" + " pos base a\n" + " mark @top;\n" + "} TestLookup;\n", + ) + + def test_def_anchor_multi_component(self): + fea = self.parse( + 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + 'ATTACH GLYPH "f_f"\n' + 'TO GLYPH "acutecomb" AT ANCHOR "top"\n' + "END_ATTACH\n" + "END_POSITION\n" + 'DEF_GLYPH "f_f" ID 120 TYPE LIGATURE COMPONENTS 2 END_GLYPH\n' + 'DEF_ANCHOR "top" ON 120 GLYPH f_f ' + "COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "top" ON 120 GLYPH f_f ' + "COMPONENT 2 AT POS DX 450 DY 450 END_POS END_ANCHOR\n" + 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb ' + "COMPONENT 1 AT POS END_POS END_ANCHOR" + ) + self.assertEqual( + fea, + "\n# Mark classes\n" + "markClass acutecomb @top;\n" + "\n" + "# Lookups\n" + "lookup TestLookup {\n" + " pos ligature f_f\n" + " mark @top\n" + " ligComponent\n" + " mark @top;\n" + "} TestLookup;\n" + "\n" + "@GDEF_ligature = [f_f];\n" + "table GDEF {\n" + " GlyphClassDef , @GDEF_ligature, , ;\n" + "} GDEF;\n", + ) + + def test_anchor_adjust_device(self): + fea = self.parse( + 'DEF_ANCHOR "MARK_top" ON 123 GLYPH diacglyph ' + "COMPONENT 1 AT POS DX 0 DY 456 ADJUST_BY 12 AT 34 " + "ADJUST_BY 56 AT 78 END_POS END_ANCHOR" + ) + self.assertEqual( + fea, + "\n# Mark classes\n" + "#markClass diacglyph " + " > @top;", + ) + + def test_use_extension(self): + fea = self.parse( + 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR\n" + "IN_CONTEXT\n" + "END_CONTEXT\n" + "AS_POSITION\n" + "ADJUST_PAIR\n" + ' FIRST GLYPH "A" FIRST GLYPH "V"\n' + ' SECOND GLYPH "A" SECOND GLYPH "V"\n' + " 1 2 BY POS ADV -30 END_POS POS END_POS\n" + " 2 1 BY POS ADV -25 END_POS POS END_POS\n" + "END_ADJUST\n" + "END_POSITION\n" + "COMPILER_USEEXTENSIONLOOKUPS\n" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup kern1 useExtension {\n" + " enum pos A V -30;\n" + " enum pos V A -25;\n" + "} kern1;\n", + ) + + def test_unsupported_compiler_flags(self): + with self.assertLogs(level="WARNING") as logs: + fea = self.parse("CMAP_FORMAT 0 3 4") + self.assertEqual(fea, "") + self.assertEqual( + logs.output, + ["WARNING:fontTools.volto:Unsupported setting ignored: CMAP_FORMAT"], + ) + + def test_sanitize_lookup_name(self): + fea = self.parse( + 'DEF_LOOKUP "Test Lookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR IN_CONTEXT END_CONTEXT\n" + "AS_POSITION ADJUST_PAIR END_ADJUST END_POSITION\n" + 'DEF_LOOKUP "Test-Lookup" PROCESS_BASE PROCESS_MARKS ALL ' + "DIRECTION LTR IN_CONTEXT END_CONTEXT\n" + "AS_POSITION ADJUST_PAIR END_ADJUST END_POSITION\n" + ) + self.assertEqual( + fea, + "\n# Lookups\n" + "lookup Test_Lookup {\n" + " \n" + "} Test_Lookup;\n" + "\n" + "lookup Test_Lookup_ {\n" + " \n" + "} Test_Lookup_;\n", + ) + + def test_sanitize_group_name(self): + fea = self.parse( + 'DEF_GROUP "aaccented glyphs"\n' + 'ENUM GLYPH "aacute" GLYPH "abreve" END_ENUM\n' + "END_GROUP\n" + 'DEF_GROUP "aaccented+glyphs"\n' + 'ENUM GLYPH "aacute" GLYPH "abreve" END_ENUM\n' + "END_GROUP\n" + ) + self.assertEqual( + fea, + "# Glyph classes\n" + "@aaccented_glyphs = [aacute abreve];\n" + "@aaccented_glyphs_ = [aacute abreve];", + ) + + def test_cli_vtp(self): + vtp = DATADIR / "Nutso.vtp" + fea = DATADIR / "Nutso.fea" + self.volto(vtp, fea) + + def test_group_order(self): + vtp = DATADIR / "NamdhinggoSIL1006.vtp" + fea = DATADIR / "NamdhinggoSIL1006.fea" + self.volto(vtp, fea) + + def test_cli_ttf(self): + ttf = DATADIR / "Nutso.ttf" + fea = DATADIR / "Nutso.fea" + self.volto(ttf, fea) + + def test_cli_ttf_no_TSIV(self): + from fontTools.volto import main as volto + + ttf = DATADIR / "Empty.ttf" + temp = self.temp_path() + self.assertEqual(1, volto([str(ttf), str(temp)])) + + def volto(self, source, fea): + from fontTools.volto import main as volto + + temp = self.temp_path() + volto([str(source), str(temp)]) + with temp.open() as f: + res = f.read() + with fea.open() as f: + ref = f.read() + self.assertEqual(ref, res) + + def parse(self, text): + return VoltToFea(StringIO(text)).convert() + + +if __name__ == "__main__": + import sys + + sys.exit(unittest.main())