Add Volto
A tool for converting VOLT VTP projects to feature files. Essentially https://github.com/TiroTypeworks/TiroTools/tree/master/Volto with minor modifications to fit into FontTools.
This commit is contained in:
parent
4923a353bb
commit
de76e71b7b
@ -56,6 +56,7 @@ This last utility takes a subcommand, which could be one of:
|
|||||||
- ``varLib.models``: Normalize locations on a given designspace
|
- ``varLib.models``: Normalize locations on a given designspace
|
||||||
- ``varLib.mutator``: Instantiate a variation font
|
- ``varLib.mutator``: Instantiate a variation font
|
||||||
- ``varLib.varStore``: Optimize a font's GDEF variation store
|
- ``varLib.varStore``: Optimize a font's GDEF variation store
|
||||||
|
- ``volto``: Convert MS VOLT to AFDKO feature files.
|
||||||
|
|
||||||
Libraries
|
Libraries
|
||||||
---------
|
---------
|
||||||
@ -87,6 +88,7 @@ libraries in the fontTools suite:
|
|||||||
- :py:mod:`fontTools.unicodedata`: Convert between Unicode and OpenType script information
|
- :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.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.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 <https://github.com/fonttools/fonttools/blob/main/Snippets/>`_ of the fontTools repository.
|
A selection of sample Python programs using these libaries can be found in the `Snippets directory <https://github.com/fonttools/fonttools/blob/main/Snippets/>`_ of the fontTools repository.
|
||||||
|
|
||||||
@ -142,6 +144,7 @@ Table of Contents
|
|||||||
unicodedata/index
|
unicodedata/index
|
||||||
varLib/index
|
varLib/index
|
||||||
voltLib
|
voltLib
|
||||||
|
volto
|
||||||
|
|
||||||
|
|
||||||
.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
|
.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
|
||||||
|
8
Doc/source/volto.rst
Normal file
8
Doc/source/volto.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#############################################
|
||||||
|
volto: Convert MS VOLT to AFDKO feature files
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
.. automodule:: fontTools.volto
|
||||||
|
:inherited-members:
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
724
Lib/fontTools/volto.py
Normal file
724
Lib/fontTools/volto.py
Normal file
@ -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())
|
BIN
Tests/volto/data/Empty.ttf
Normal file
BIN
Tests/volto/data/Empty.ttf
Normal file
Binary file not shown.
506
Tests/volto/data/NamdhinggoSIL1006.fea
Normal file
506
Tests/volto/data/NamdhinggoSIL1006.fea
Normal file
@ -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 <anchor -500 1050> @Aabove;
|
||||||
|
markClass uni1922 <anchor -150 -15> @U;
|
||||||
|
markClass uni1927 <anchor -300 1050> @eo;
|
||||||
|
markClass uni1928 <anchor -190 1050> @eo;
|
||||||
|
markClass uni193A <anchor -260 1250> @K;
|
||||||
|
markClass uni193A <anchor -260 1250> @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
|
||||||
|
<anchor 487 1050> mark @Aabove;
|
||||||
|
pos base uni1902
|
||||||
|
<anchor 622 1050> mark @Aabove;
|
||||||
|
pos base uni1903
|
||||||
|
<anchor 475 1050> mark @Aabove;
|
||||||
|
pos base uni1904
|
||||||
|
<anchor 460 1050> mark @Aabove;
|
||||||
|
pos base uni1905
|
||||||
|
<anchor 590 1050> mark @Aabove;
|
||||||
|
pos base uni1906
|
||||||
|
<anchor 519 1050> mark @Aabove;
|
||||||
|
pos base uni1907
|
||||||
|
<anchor 570 1050> mark @Aabove;
|
||||||
|
pos base uni1908
|
||||||
|
<anchor 564 1050> mark @Aabove;
|
||||||
|
pos base uni1909
|
||||||
|
<anchor 430 1050> mark @Aabove;
|
||||||
|
pos base uni190A
|
||||||
|
<anchor 575 1050> mark @Aabove;
|
||||||
|
pos base uni190B
|
||||||
|
<anchor 450 1050> mark @Aabove;
|
||||||
|
pos base uni190C
|
||||||
|
<anchor 556 1050> mark @Aabove;
|
||||||
|
pos base uni190D
|
||||||
|
<anchor 515 1050> mark @Aabove;
|
||||||
|
pos base uni190E
|
||||||
|
<anchor 510 1050> mark @Aabove;
|
||||||
|
pos base uni190F
|
||||||
|
<anchor 497 1050> mark @Aabove;
|
||||||
|
pos base uni1910
|
||||||
|
<anchor 657 1050> mark @Aabove;
|
||||||
|
pos base uni1911
|
||||||
|
<anchor 690 1050> mark @Aabove;
|
||||||
|
pos base uni1912
|
||||||
|
<anchor 538 1050> mark @Aabove;
|
||||||
|
pos base uni1913
|
||||||
|
<anchor 571 1050> mark @Aabove;
|
||||||
|
pos base uni1914
|
||||||
|
<anchor 538 1050> mark @Aabove;
|
||||||
|
pos base uni1915
|
||||||
|
<anchor 470 1050> mark @Aabove;
|
||||||
|
pos base uni1916
|
||||||
|
<anchor 503 1050> mark @Aabove;
|
||||||
|
pos base uni1917
|
||||||
|
<anchor 548 1050> mark @Aabove;
|
||||||
|
pos base uni1918
|
||||||
|
<anchor 511 1050> mark @Aabove;
|
||||||
|
pos base uni1919
|
||||||
|
<anchor 560 1050> mark @Aabove;
|
||||||
|
pos base uni191A
|
||||||
|
<anchor 420 1050> mark @Aabove;
|
||||||
|
pos base uni191B
|
||||||
|
<anchor 580 1050> mark @Aabove;
|
||||||
|
pos base uni191C
|
||||||
|
<anchor 540 1050> mark @Aabove;
|
||||||
|
pos base uni1940
|
||||||
|
<anchor 480 1050> mark @Aabove;
|
||||||
|
} Akar;
|
||||||
|
|
||||||
|
lookup Kemphreng {
|
||||||
|
# The Kemphreng positioning rule positions the Kemphreng on all consonants, including the vowel carrier.
|
||||||
|
pos base uni1901
|
||||||
|
<anchor 500 1050> mark @K;
|
||||||
|
pos base uni1902
|
||||||
|
<anchor 680 1050> mark @K;
|
||||||
|
pos base uni1903
|
||||||
|
<anchor 540 1050> mark @K;
|
||||||
|
pos base uni1904
|
||||||
|
<anchor 500 1050> mark @K;
|
||||||
|
pos base uni1905
|
||||||
|
<anchor 590 1050> mark @K;
|
||||||
|
pos base uni1906
|
||||||
|
<anchor 540 1050> mark @K;
|
||||||
|
pos base uni1907
|
||||||
|
<anchor 620 1050> mark @K;
|
||||||
|
pos base uni1908
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni1909
|
||||||
|
<anchor 450 1050> mark @K;
|
||||||
|
pos base uni190A
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni190B
|
||||||
|
<anchor 450 1050> mark @K;
|
||||||
|
pos base uni190C
|
||||||
|
<anchor 656 1050> mark @K;
|
||||||
|
pos base uni190D
|
||||||
|
<anchor 570 1050> mark @K;
|
||||||
|
pos base uni190E
|
||||||
|
<anchor 530 1050> mark @K;
|
||||||
|
pos base uni190F
|
||||||
|
<anchor 515 1050> mark @K;
|
||||||
|
pos base uni1910
|
||||||
|
<anchor 680 1050> mark @K;
|
||||||
|
pos base uni1911
|
||||||
|
<anchor 720 1050> mark @K;
|
||||||
|
pos base uni1912
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni1913
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni1914
|
||||||
|
<anchor 560 1050> mark @K;
|
||||||
|
pos base uni1915
|
||||||
|
<anchor 480 1050> mark @K;
|
||||||
|
pos base uni1916
|
||||||
|
<anchor 520 1050> mark @K;
|
||||||
|
pos base uni1917
|
||||||
|
<anchor 585 1050> mark @K;
|
||||||
|
pos base uni1918
|
||||||
|
<anchor 610 1050> mark @K;
|
||||||
|
pos base uni1919
|
||||||
|
<anchor 520 1050> mark @K;
|
||||||
|
pos base uni191A
|
||||||
|
<anchor 440 1050> mark @K;
|
||||||
|
pos base uni191B
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni191C
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni1940
|
||||||
|
<anchor 490 1050> mark @K;
|
||||||
|
pos base uni19011922
|
||||||
|
<anchor 500 1050> mark @K;
|
||||||
|
pos base uni19021922
|
||||||
|
<anchor 680 1050> mark @K;
|
||||||
|
pos base uni19031922
|
||||||
|
<anchor 540 1050> mark @K;
|
||||||
|
pos base uni19041922
|
||||||
|
<anchor 500 1050> mark @K;
|
||||||
|
pos base uni19051922
|
||||||
|
<anchor 590 1050> mark @K;
|
||||||
|
pos base uni19061922
|
||||||
|
<anchor 540 1050> mark @K;
|
||||||
|
pos base uni19071922
|
||||||
|
<anchor 620 1050> mark @K;
|
||||||
|
pos base uni19081922
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni19091922
|
||||||
|
<anchor 450 1050> mark @K;
|
||||||
|
pos base uni190A1922
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni190B1922
|
||||||
|
<anchor 450 1050> mark @K;
|
||||||
|
pos base uni190C1922
|
||||||
|
<anchor 656 1050> mark @K;
|
||||||
|
pos base uni190D1922
|
||||||
|
<anchor 570 1050> mark @K;
|
||||||
|
pos base uni190E1922
|
||||||
|
<anchor 530 1050> mark @K;
|
||||||
|
pos base uni190F1922
|
||||||
|
<anchor 515 1050> mark @K;
|
||||||
|
pos base uni19101922
|
||||||
|
<anchor 680 1050> mark @K;
|
||||||
|
pos base uni19111922
|
||||||
|
<anchor 720 1050> mark @K;
|
||||||
|
pos base uni19121922
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni19131922
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni19141922
|
||||||
|
<anchor 560 1050> mark @K;
|
||||||
|
pos base uni19151922
|
||||||
|
<anchor 480 1050> mark @K;
|
||||||
|
pos base uni19161922
|
||||||
|
<anchor 520 1050> mark @K;
|
||||||
|
pos base uni19171922
|
||||||
|
<anchor 585 1050> mark @K;
|
||||||
|
pos base uni19181922
|
||||||
|
<anchor 610 1050> mark @K;
|
||||||
|
pos base uni19191922
|
||||||
|
<anchor 520 1050> mark @K;
|
||||||
|
pos base uni191A1922
|
||||||
|
<anchor 440 1050> mark @K;
|
||||||
|
pos base uni191B1922
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni191C1922
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni19401922
|
||||||
|
<anchor 490 1050> mark @K;
|
||||||
|
pos base uni1901192A1922
|
||||||
|
<anchor 500 1050> mark @K;
|
||||||
|
pos base uni1902192A1922
|
||||||
|
<anchor 680 1050> mark @K;
|
||||||
|
pos base uni1903192A1922
|
||||||
|
<anchor 540 1050> mark @K;
|
||||||
|
pos base uni1904192A1922
|
||||||
|
<anchor 500 1050> mark @K;
|
||||||
|
pos base uni1905192A1922
|
||||||
|
<anchor 590 1050> mark @K;
|
||||||
|
pos base uni1906192A1922
|
||||||
|
<anchor 540 1050> mark @K;
|
||||||
|
pos base uni1907192A1922
|
||||||
|
<anchor 620 1050> mark @K;
|
||||||
|
pos base uni1908192A1922
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni1909192A1922
|
||||||
|
<anchor 450 1050> mark @K;
|
||||||
|
pos base uni190A192A1922
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni190B192A1922
|
||||||
|
<anchor 450 1050> mark @K;
|
||||||
|
pos base uni190C192A1922
|
||||||
|
<anchor 656 1050> mark @K;
|
||||||
|
pos base uni190D192A1922
|
||||||
|
<anchor 570 1050> mark @K;
|
||||||
|
pos base uni190192AE1922
|
||||||
|
<anchor 530 1050> mark @K;
|
||||||
|
pos base uni190F192A1922
|
||||||
|
<anchor 515 1050> mark @K;
|
||||||
|
pos base uni1910192A1922
|
||||||
|
<anchor 680 1050> mark @K;
|
||||||
|
pos base uni1911192A1922
|
||||||
|
<anchor 720 1050> mark @K;
|
||||||
|
pos base uni1912192A1922
|
||||||
|
<anchor 580 1050> mark @K;
|
||||||
|
pos base uni1913192A1922
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni1914192A1922
|
||||||
|
<anchor 560 1050> mark @K;
|
||||||
|
pos base uni1915192A1922
|
||||||
|
<anchor 480 1050> mark @K;
|
||||||
|
pos base uni1916192A1922
|
||||||
|
<anchor 520 1050> mark @K;
|
||||||
|
pos base uni1917192A1922
|
||||||
|
<anchor 585 1050> mark @K;
|
||||||
|
pos base uni1918192A1922
|
||||||
|
<anchor 610 1050> mark @K;
|
||||||
|
pos base uni1919192A1922
|
||||||
|
<anchor 520 1050> mark @K;
|
||||||
|
pos base uni191A192A1922
|
||||||
|
<anchor 440 1050> mark @K;
|
||||||
|
pos base uni191B192A1922
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni191C192A1922
|
||||||
|
<anchor 600 1050> mark @K;
|
||||||
|
pos base uni1940192A1922
|
||||||
|
<anchor 490 1050> mark @K;
|
||||||
|
pos base uni1900
|
||||||
|
<anchor 525 1050> 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
|
||||||
|
<anchor 755 1050> mark @eo;
|
||||||
|
pos base uni1902
|
||||||
|
<anchor 943 1050> mark @eo;
|
||||||
|
pos base uni1903
|
||||||
|
<anchor 790 1050> mark @eo;
|
||||||
|
pos base uni1904
|
||||||
|
<anchor 780 1050> mark @eo;
|
||||||
|
pos base uni1905
|
||||||
|
<anchor 790 1050> mark @eo;
|
||||||
|
pos base uni1906
|
||||||
|
<anchor 878 1050> mark @eo;
|
||||||
|
pos base uni1907
|
||||||
|
<anchor 825 1050> mark @eo;
|
||||||
|
pos base uni1908
|
||||||
|
<anchor 968 1050> mark @eo;
|
||||||
|
pos base uni1909
|
||||||
|
<anchor 660 1050> mark @eo;
|
||||||
|
pos base uni190A
|
||||||
|
<anchor 569 1050> mark @eo;
|
||||||
|
pos base uni190B
|
||||||
|
<anchor 690 1050> mark @eo;
|
||||||
|
pos base uni190C
|
||||||
|
<anchor 649 1050> mark @eo;
|
||||||
|
pos base uni190D
|
||||||
|
<anchor 682 1050> mark @eo;
|
||||||
|
pos base uni190E
|
||||||
|
<anchor 680 1050> mark @eo;
|
||||||
|
pos base uni190F
|
||||||
|
<anchor 778 1050> mark @eo;
|
||||||
|
pos base uni1910
|
||||||
|
<anchor 920 1050> mark @eo;
|
||||||
|
pos base uni1911
|
||||||
|
<anchor 894 1050> mark @eo;
|
||||||
|
pos base uni1912
|
||||||
|
<anchor 782 1050> mark @eo;
|
||||||
|
pos base uni1913
|
||||||
|
<anchor 982 1050> mark @eo;
|
||||||
|
pos base uni1914
|
||||||
|
<anchor 917 1050> mark @eo;
|
||||||
|
pos base uni1915
|
||||||
|
<anchor 730 1050> mark @eo;
|
||||||
|
pos base uni1916
|
||||||
|
<anchor 767 1050> mark @eo;
|
||||||
|
pos base uni1917
|
||||||
|
<anchor 937 1050> mark @eo;
|
||||||
|
pos base uni1918
|
||||||
|
<anchor 862 1050> mark @eo;
|
||||||
|
pos base uni1919
|
||||||
|
<anchor 670 1050> mark @eo;
|
||||||
|
pos base uni191A
|
||||||
|
<anchor 682 1050> mark @eo;
|
||||||
|
pos base uni191B
|
||||||
|
<anchor 921 1050> mark @eo;
|
||||||
|
pos base uni191C
|
||||||
|
<anchor 870 1050> mark @eo;
|
||||||
|
pos base uni1940
|
||||||
|
<anchor 650 1050> mark @eo;
|
||||||
|
pos base uni1900
|
||||||
|
<anchor 810 1050> 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
|
||||||
|
<anchor -260 1250> mark @VK;
|
||||||
|
pos mark uni1927
|
||||||
|
<anchor -300 1250> mark @VK;
|
||||||
|
pos mark uni1928
|
||||||
|
<anchor -150 1455> 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
|
||||||
|
<anchor -135 -40> mark @U;
|
||||||
|
pos base uni192B
|
||||||
|
<anchor -135 -40> 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;
|
1
Tests/volto/data/NamdhinggoSIL1006.vtp
Normal file
1
Tests/volto/data/NamdhinggoSIL1006.vtp
Normal file
File diff suppressed because one or more lines are too long
328
Tests/volto/data/Nutso.fea
Normal file
328
Tests/volto/data/Nutso.fea
Normal file
@ -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 <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass eight.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass five.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass five.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass four.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass four.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass nine.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass one.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass one.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass seven.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass six.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass six.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass three.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass three.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass two.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass two.numr <anchor 0 0> @NUMRNUMR;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.1.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.2.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.3.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.4.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.5.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.6.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.7.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.8.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @INIT.9.10;
|
||||||
|
markClass zero.numr <anchor 0 0> @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
|
||||||
|
<anchor 3150 0> mark @INIT.1.10;
|
||||||
|
} fracmark.init_1.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_2.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 2800 0> mark @INIT.2.10;
|
||||||
|
} fracmark.init_2.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_3.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 2450 0> mark @INIT.3.10;
|
||||||
|
} fracmark.init_3.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_4.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 2100 0> mark @INIT.4.10;
|
||||||
|
} fracmark.init_4.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_5.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 1750 0> mark @INIT.5.10;
|
||||||
|
} fracmark.init_5.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_6.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 1400 0> mark @INIT.6.10;
|
||||||
|
} fracmark.init_6.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_7.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 1050 0> mark @INIT.7.10;
|
||||||
|
} fracmark.init_7.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_8.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 700 0> mark @INIT.8.10;
|
||||||
|
} fracmark.init_8.10_target;
|
||||||
|
|
||||||
|
lookup fracmark.init_9.10_target {
|
||||||
|
pos base fracinit
|
||||||
|
<anchor 350 0> 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
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark one.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark two.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark three.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark four.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark five.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark six.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark seven.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark eight.numr
|
||||||
|
<anchor 700 0> mark @NUMRNUMR;
|
||||||
|
pos mark nine.numr
|
||||||
|
<anchor 700 0> 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;
|
BIN
Tests/volto/data/Nutso.ttf
Normal file
BIN
Tests/volto/data/Nutso.ttf
Normal file
Binary file not shown.
1
Tests/volto/data/Nutso.vtp
Normal file
1
Tests/volto/data/Nutso.vtp
Normal file
File diff suppressed because one or more lines are too long
1251
Tests/volto/volto_test.py
Normal file
1251
Tests/volto/volto_test.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user