Merge branch 'fonttools:main' into ttf2otf
This commit is contained in:
commit
9f7025af8f
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@ -118,7 +118,7 @@ jobs:
|
||||
# so that all artifacts are downloaded in the same directory specified by 'path'
|
||||
merge-multiple: true
|
||||
path: dist
|
||||
- uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||
- uses: pypa/gh-action-pypi-publish@v1.10.1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_PASSWORD }}
|
||||
|
@ -3,3 +3,4 @@ colorLib.builder: Build COLR/CPAL tables from scratch
|
||||
#####################################################
|
||||
|
||||
.. automodule:: fontTools.colorLib.builder
|
||||
:no-inherited-members:
|
||||
|
@ -31,16 +31,20 @@ needs_sphinx = "1.3"
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.autosectionlabel",
|
||||
]
|
||||
|
||||
autodoc_mock_imports = ["gtk", "reportlab"]
|
||||
|
||||
autodoc_default_options = {"members": True, "inherited-members": True}
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"inherited-members": True,
|
||||
"show-inheritance": True,
|
||||
}
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
@ -78,7 +82,7 @@ release = "4.0"
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = "en"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
|
@ -297,7 +297,7 @@ Example of all axis elements together
|
||||
|
||||
|
||||
``<output>`` element
|
||||
...................
|
||||
....................
|
||||
|
||||
- Defines the output location of an axis mapping.
|
||||
- Child element of ``<mapping>``
|
||||
|
@ -1,4 +1,5 @@
|
||||
:orphan:
|
||||
|
||||
.. _developerinfo:
|
||||
.. image:: ../../Icons/FontToolsIconGreenCircle.png
|
||||
:width: 200px
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
---fontTools Documentation---
|
||||
=======
|
||||
=============================
|
||||
About
|
||||
-----
|
||||
|
||||
|
@ -14,4 +14,4 @@ ttFont: Read/write OpenType and TrueType fonts
|
||||
|
||||
.. automodule:: fontTools.ttLib.ttFont
|
||||
:members: getTableModule, registerCustomTableClass, unregisterCustomTableClass, getCustomTableClass, getClassTag, newTable, tagToIdentifier, identifierToTag, tagToXML, xmlToTag, sortedTagList, reorderFontTables
|
||||
|
||||
:exclude-members: TTFont, GlyphOrder
|
||||
|
@ -1,3 +1,9 @@
|
||||
"""
|
||||
designSpaceDocument
|
||||
|
||||
- Read and write designspace files
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
@ -15,11 +21,6 @@ from fontTools.misc import plistlib
|
||||
from fontTools.misc.loggingTools import LogMixin
|
||||
from fontTools.misc.textTools import tobytes, tostr
|
||||
|
||||
"""
|
||||
designSpaceDocument
|
||||
|
||||
- read and write designspace files
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"AxisDescriptor",
|
||||
|
@ -122,15 +122,16 @@ Other options
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
For the other options listed below, to see the current value of the option,
|
||||
pass a value of '?' to it, with or without a '='.
|
||||
pass a value of '?' to it, with or without a '='. In some environments,
|
||||
you might need to escape the question mark, like this: '--glyph-names\?'.
|
||||
|
||||
Examples::
|
||||
|
||||
$ pyftsubset --glyph-names?
|
||||
Current setting for 'glyph-names' is: False
|
||||
$ ./pyftsubset --name-IDs=?
|
||||
$ pyftsubset --name-IDs=?
|
||||
Current setting for 'name-IDs' is: [0, 1, 2, 3, 4, 5, 6]
|
||||
$ ./pyftsubset --hinting? --no-hinting --hinting?
|
||||
$ pyftsubset --hinting? --no-hinting --hinting?
|
||||
Current setting for 'hinting' is: True
|
||||
Current setting for 'hinting' is: False
|
||||
|
||||
|
@ -1175,17 +1175,8 @@ class NameRecordVisitor(TTVisitor):
|
||||
|
||||
@NameRecordVisitor.register_attrs(
|
||||
(
|
||||
(otTables.FeatureParamsSize, ("SubfamilyID", "SubfamilyNameID")),
|
||||
(otTables.FeatureParamsSize, ("SubfamilyNameID",)),
|
||||
(otTables.FeatureParamsStylisticSet, ("UINameID",)),
|
||||
(
|
||||
otTables.FeatureParamsCharacterVariants,
|
||||
(
|
||||
"FeatUILabelNameID",
|
||||
"FeatUITooltipTextNameID",
|
||||
"SampleTextNameID",
|
||||
"FirstParamUILabelNameID",
|
||||
),
|
||||
),
|
||||
(otTables.STAT, ("ElidedFallbackNameID",)),
|
||||
(otTables.AxisRecord, ("AxisNameID",)),
|
||||
(otTables.AxisValue, ("ValueNameID",)),
|
||||
@ -1197,6 +1188,22 @@ def visit(visitor, obj, attr, value):
|
||||
visitor.seen.add(value)
|
||||
|
||||
|
||||
@NameRecordVisitor.register(otTables.FeatureParamsCharacterVariants)
|
||||
def visit(visitor, obj):
|
||||
for attr in ("FeatUILabelNameID", "FeatUITooltipTextNameID", "SampleTextNameID"):
|
||||
value = getattr(obj, attr)
|
||||
visitor.seen.add(value)
|
||||
# also include the sequence of UI strings for individual variants, if any
|
||||
if obj.FirstParamUILabelNameID == 0 or obj.NumNamedParameters == 0:
|
||||
return
|
||||
visitor.seen.update(
|
||||
range(
|
||||
obj.FirstParamUILabelNameID,
|
||||
obj.FirstParamUILabelNameID + obj.NumNamedParameters,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@NameRecordVisitor.register(ttLib.getTableClass("fvar"))
|
||||
def visit(visitor, obj):
|
||||
for inst in obj.instances:
|
||||
|
@ -1,26 +1,3 @@
|
||||
import os
|
||||
from copy import deepcopy
|
||||
from os import fsdecode
|
||||
import logging
|
||||
import zipfile
|
||||
import enum
|
||||
from collections import OrderedDict
|
||||
import fs
|
||||
import fs.base
|
||||
import fs.subfs
|
||||
import fs.errors
|
||||
import fs.copy
|
||||
import fs.osfs
|
||||
import fs.zipfs
|
||||
import fs.tempfs
|
||||
import fs.tools
|
||||
from fontTools.misc import plistlib
|
||||
from fontTools.ufoLib.validators import *
|
||||
from fontTools.ufoLib.filenames import userNameToFileName
|
||||
from fontTools.ufoLib.converters import convertUFO1OrUFO2KerningToUFO3Kerning
|
||||
from fontTools.ufoLib.errors import UFOLibError
|
||||
from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
|
||||
|
||||
"""
|
||||
A library for importing .ufo files and their descendants.
|
||||
Refer to http://unifiedfontobject.com for the UFO specification.
|
||||
@ -51,6 +28,29 @@ fontinfo.plist values between the possible format versions.
|
||||
convertFontInfoValueForAttributeFromVersion3ToVersion2
|
||||
"""
|
||||
|
||||
import os
|
||||
from copy import deepcopy
|
||||
from os import fsdecode
|
||||
import logging
|
||||
import zipfile
|
||||
import enum
|
||||
from collections import OrderedDict
|
||||
import fs
|
||||
import fs.base
|
||||
import fs.subfs
|
||||
import fs.errors
|
||||
import fs.copy
|
||||
import fs.osfs
|
||||
import fs.zipfs
|
||||
import fs.tempfs
|
||||
import fs.tools
|
||||
from fontTools.misc import plistlib
|
||||
from fontTools.ufoLib.validators import *
|
||||
from fontTools.ufoLib.filenames import userNameToFileName
|
||||
from fontTools.ufoLib.converters import convertUFO1OrUFO2KerningToUFO3Kerning
|
||||
from fontTools.ufoLib.errors import UFOLibError
|
||||
from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
|
||||
|
||||
__all__ = [
|
||||
"makeUFOPath",
|
||||
"UFOLibError",
|
||||
|
@ -869,7 +869,7 @@ def _add_COLR(font, model, master_fonts, axisTags, colr_layer_reuse=True):
|
||||
colr.VarIndexMap = builder.buildDeltaSetIndexMap(varIdxes)
|
||||
|
||||
|
||||
def load_designspace(designspace, log_enabled=True):
|
||||
def load_designspace(designspace, log_enabled=True, *, require_sources=True):
|
||||
# TODO: remove this and always assume 'designspace' is a DesignSpaceDocument,
|
||||
# never a file path, as that's already handled by caller
|
||||
if hasattr(designspace, "sources"): # Assume a DesignspaceDocument
|
||||
@ -878,7 +878,7 @@ def load_designspace(designspace, log_enabled=True):
|
||||
ds = DesignSpaceDocument.fromfile(designspace)
|
||||
|
||||
masters = ds.sources
|
||||
if not masters:
|
||||
if require_sources and not masters:
|
||||
raise VarLibValidationError("Designspace must have at least one source.")
|
||||
instances = ds.instances
|
||||
|
||||
@ -978,7 +978,7 @@ def load_designspace(designspace, log_enabled=True):
|
||||
"More than one base master found in Designspace."
|
||||
)
|
||||
base_idx = i
|
||||
if base_idx is None:
|
||||
if require_sources and base_idx is None:
|
||||
raise VarLibValidationError(
|
||||
"Base master not found; no master at default location?"
|
||||
)
|
||||
|
@ -1,10 +1,187 @@
|
||||
from fontTools.varLib import _add_avar, load_designspace
|
||||
from fontTools.varLib.models import VariationModel
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
from fontTools.misc.fixedTools import fixedToFloat as fi2fl
|
||||
from fontTools.misc.cliTools import makeOutputFileName
|
||||
from itertools import product
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("fontTools.varLib.avar")
|
||||
|
||||
|
||||
def _denormalize(v, axis):
|
||||
if v >= 0:
|
||||
return axis.defaultValue + v * (axis.maxValue - axis.defaultValue)
|
||||
else:
|
||||
return axis.defaultValue + v * (axis.defaultValue - axis.minValue)
|
||||
|
||||
|
||||
def _pruneLocations(locations, poles, axisTags):
|
||||
# Now we have all the input locations, find which ones are
|
||||
# not needed and remove them.
|
||||
|
||||
# Note: This algorithm is heavily tied to how VariationModel
|
||||
# is implemented. It assumes that input was extracted from
|
||||
# VariationModel-generated object, like an ItemVariationStore
|
||||
# created by fontmake using varLib.models.VariationModel.
|
||||
# Some CoPilot blabbering:
|
||||
# I *think* I can prove that this algorithm is correct, but
|
||||
# I'm not 100% sure. It's possible that there are edge cases
|
||||
# where this algorithm will fail. I'm not sure how to prove
|
||||
# that it's correct, but I'm also not sure how to prove that
|
||||
# it's incorrect. I'm not sure how to write a test case that
|
||||
# would prove that it's incorrect. I'm not sure how to write
|
||||
# a test case that would prove that it's correct.
|
||||
|
||||
model = VariationModel(locations, axisTags)
|
||||
modelMapping = model.mapping
|
||||
modelSupports = model.supports
|
||||
pins = {tuple(k.items()): None for k in poles}
|
||||
for location in poles:
|
||||
i = locations.index(location)
|
||||
i = modelMapping[i]
|
||||
support = modelSupports[i]
|
||||
supportAxes = set(support.keys())
|
||||
for axisTag, (minV, _, maxV) in support.items():
|
||||
for v in (minV, maxV):
|
||||
if v in (-1, 0, 1):
|
||||
continue
|
||||
for pin in pins.keys():
|
||||
pinLocation = dict(pin)
|
||||
pinAxes = set(pinLocation.keys())
|
||||
if pinAxes != supportAxes:
|
||||
continue
|
||||
if axisTag not in pinAxes:
|
||||
continue
|
||||
if pinLocation[axisTag] == v:
|
||||
break
|
||||
else:
|
||||
# No pin found. Go through the previous masters
|
||||
# and find a suitable pin. Going backwards is
|
||||
# better because it can find a pin that is close
|
||||
# to the pole in more dimensions, and reducing
|
||||
# the total number of pins needed.
|
||||
for candidateIdx in range(i - 1, -1, -1):
|
||||
candidate = modelSupports[candidateIdx]
|
||||
candidateAxes = set(candidate.keys())
|
||||
if candidateAxes != supportAxes:
|
||||
continue
|
||||
if axisTag not in candidateAxes:
|
||||
continue
|
||||
candidate = {
|
||||
k: defaultV for k, (_, defaultV, _) in candidate.items()
|
||||
}
|
||||
if candidate[axisTag] == v:
|
||||
pins[tuple(candidate.items())] = None
|
||||
break
|
||||
else:
|
||||
assert False, "No pin found"
|
||||
return [dict(t) for t in pins.keys()]
|
||||
|
||||
|
||||
def mappings_from_avar(font, denormalize=True):
|
||||
fvarAxes = font["fvar"].axes
|
||||
axisMap = {a.axisTag: a for a in fvarAxes}
|
||||
axisTags = [a.axisTag for a in fvarAxes]
|
||||
axisIndexes = {a.axisTag: i for i, a in enumerate(fvarAxes)}
|
||||
if "avar" not in font:
|
||||
return {}, {}
|
||||
avar = font["avar"]
|
||||
axisMaps = {
|
||||
tag: seg
|
||||
for tag, seg in avar.segments.items()
|
||||
if seg and seg != {-1: -1, 0: 0, 1: 1}
|
||||
}
|
||||
mappings = []
|
||||
|
||||
if getattr(avar, "majorVersion", 1) == 2:
|
||||
varStore = avar.table.VarStore
|
||||
regions = varStore.VarRegionList.Region
|
||||
|
||||
# Find all the input locations; this finds "poles", that are
|
||||
# locations of the peaks, and "corners", that are locations
|
||||
# of the corners of the regions. These two sets of locations
|
||||
# together constitute inputLocations to consider.
|
||||
|
||||
poles = {(): None} # Just using it as an ordered set
|
||||
inputLocations = set({()})
|
||||
for varData in varStore.VarData:
|
||||
regionIndices = varData.VarRegionIndex
|
||||
for regionIndex in regionIndices:
|
||||
peakLocation = []
|
||||
corners = []
|
||||
region = regions[regionIndex]
|
||||
for axisIndex, axis in enumerate(region.VarRegionAxis):
|
||||
if axis.PeakCoord == 0:
|
||||
continue
|
||||
axisTag = axisTags[axisIndex]
|
||||
peakLocation.append((axisTag, axis.PeakCoord))
|
||||
corner = []
|
||||
if axis.StartCoord != 0:
|
||||
corner.append((axisTag, axis.StartCoord))
|
||||
if axis.EndCoord != 0:
|
||||
corner.append((axisTag, axis.EndCoord))
|
||||
corners.append(corner)
|
||||
corners = set(product(*corners))
|
||||
peakLocation = tuple(peakLocation)
|
||||
poles[peakLocation] = None
|
||||
inputLocations.add(peakLocation)
|
||||
inputLocations.update(corners)
|
||||
|
||||
# Sort them by number of axes, then by axis order
|
||||
inputLocations = [
|
||||
dict(t)
|
||||
for t in sorted(
|
||||
inputLocations,
|
||||
key=lambda t: (len(t), tuple(axisIndexes[tag] for tag, _ in t)),
|
||||
)
|
||||
]
|
||||
poles = [dict(t) for t in poles.keys()]
|
||||
inputLocations = _pruneLocations(inputLocations, list(poles), axisTags)
|
||||
|
||||
# Find the output locations, at input locations
|
||||
varIdxMap = avar.table.VarIdxMap
|
||||
instancer = VarStoreInstancer(varStore, fvarAxes)
|
||||
for location in inputLocations:
|
||||
instancer.setLocation(location)
|
||||
outputLocation = {}
|
||||
for axisIndex, axisTag in enumerate(axisTags):
|
||||
varIdx = axisIndex
|
||||
if varIdxMap is not None:
|
||||
varIdx = varIdxMap[varIdx]
|
||||
delta = instancer[varIdx]
|
||||
if delta != 0:
|
||||
v = location.get(axisTag, 0)
|
||||
v = v + fi2fl(delta, 14)
|
||||
# See https://github.com/fonttools/fonttools/pull/3598#issuecomment-2266082009
|
||||
# v = max(-1, min(1, v))
|
||||
outputLocation[axisTag] = v
|
||||
mappings.append((location, outputLocation))
|
||||
|
||||
# Remove base master we added, if it maps to the default location
|
||||
assert mappings[0][0] == {}
|
||||
if mappings[0][1] == {}:
|
||||
mappings.pop(0)
|
||||
|
||||
if denormalize:
|
||||
for tag, seg in axisMaps.items():
|
||||
if tag not in axisMap:
|
||||
raise ValueError(f"Unknown axis tag {tag}")
|
||||
denorm = lambda v: _denormalize(v, axisMap[tag])
|
||||
axisMaps[tag] = {denorm(k): denorm(v) for k, v in seg.items()}
|
||||
|
||||
for i, (inputLoc, outputLoc) in enumerate(mappings):
|
||||
inputLoc = {
|
||||
tag: _denormalize(val, axisMap[tag]) for tag, val in inputLoc.items()
|
||||
}
|
||||
outputLoc = {
|
||||
tag: _denormalize(val, axisMap[tag]) for tag, val in outputLoc.items()
|
||||
}
|
||||
mappings[i] = (inputLoc, outputLoc)
|
||||
|
||||
return axisMaps, mappings
|
||||
|
||||
|
||||
def main(args=None):
|
||||
"""Add `avar` table from designspace file to variable font."""
|
||||
|
||||
@ -24,7 +201,11 @@ def main(args=None):
|
||||
)
|
||||
parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
|
||||
parser.add_argument(
|
||||
"designspace", metavar="family.designspace", help="Designspace file."
|
||||
"designspace",
|
||||
metavar="family.designspace",
|
||||
help="Designspace file.",
|
||||
nargs="?",
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
@ -45,9 +226,18 @@ def main(args=None):
|
||||
log.error("Not a variable font.")
|
||||
return 1
|
||||
|
||||
if options.designspace is None:
|
||||
from pprint import pprint
|
||||
|
||||
segments, mappings = mappings_from_avar(font)
|
||||
pprint(segments)
|
||||
pprint(mappings)
|
||||
print(len(mappings), "mappings")
|
||||
return
|
||||
|
||||
axisTags = [a.axisTag for a in font["fvar"].axes]
|
||||
|
||||
ds = load_designspace(options.designspace)
|
||||
ds = load_designspace(options.designspace, require_sources=False)
|
||||
|
||||
if "avar" in font:
|
||||
log.warning("avar table already present, overwriting.")
|
||||
|
@ -135,6 +135,7 @@ def test_gen(
|
||||
kinkiness=DEFAULT_KINKINESS,
|
||||
upem=DEFAULT_UPEM,
|
||||
show_all=False,
|
||||
discrete_axes=[],
|
||||
):
|
||||
if tolerance >= 10:
|
||||
tolerance *= 0.01
|
||||
@ -150,7 +151,9 @@ def test_gen(
|
||||
# ... risks the sparse master being the first one, and only processing a subset of the glyphs
|
||||
glyphs = {g for glyphset in glyphsets for g in glyphset.keys()}
|
||||
|
||||
parents, order = find_parents_and_order(glyphsets, locations)
|
||||
parents, order = find_parents_and_order(
|
||||
glyphsets, locations, discrete_axes=discrete_axes
|
||||
)
|
||||
|
||||
def grand_parent(i, glyphname):
|
||||
if i is None:
|
||||
@ -701,6 +704,7 @@ def main(args=None):
|
||||
fonts = []
|
||||
names = []
|
||||
locations = []
|
||||
discrete_axes = set()
|
||||
upem = DEFAULT_UPEM
|
||||
|
||||
original_args_inputs = tuple(args.inputs)
|
||||
@ -713,8 +717,13 @@ def main(args=None):
|
||||
designspace = DesignSpaceDocument.fromfile(args.inputs[0])
|
||||
args.inputs = [master.path for master in designspace.sources]
|
||||
locations = [master.location for master in designspace.sources]
|
||||
discrete_axes = {
|
||||
a.name for a in designspace.axes if not hasattr(a, "minimum")
|
||||
}
|
||||
axis_triples = {
|
||||
a.name: (a.minimum, a.default, a.maximum) for a in designspace.axes
|
||||
a.name: (a.minimum, a.default, a.maximum)
|
||||
for a in designspace.axes
|
||||
if a.name not in discrete_axes
|
||||
}
|
||||
axis_mappings = {a.name: a.map for a in designspace.axes}
|
||||
axis_triples = {
|
||||
@ -879,7 +888,13 @@ def main(args=None):
|
||||
glyphset[gn] = None
|
||||
|
||||
# Normalize locations
|
||||
locations = [normalizeLocation(loc, axis_triples) for loc in locations]
|
||||
locations = [
|
||||
{
|
||||
**normalizeLocation(loc, axis_triples),
|
||||
**{k: v for k, v in loc.items() if k in discrete_axes},
|
||||
}
|
||||
for loc in locations
|
||||
]
|
||||
tolerance = args.tolerance or DEFAULT_TOLERANCE
|
||||
kinkiness = args.kinkiness if args.kinkiness is not None else DEFAULT_KINKINESS
|
||||
|
||||
@ -896,6 +911,7 @@ def main(args=None):
|
||||
tolerance=tolerance,
|
||||
kinkiness=kinkiness,
|
||||
show_all=args.show_all,
|
||||
discrete_axes=discrete_axes,
|
||||
)
|
||||
problems = defaultdict(list)
|
||||
|
||||
|
@ -293,17 +293,19 @@ def add_isomorphisms(points, isomorphisms, reverse):
|
||||
)
|
||||
|
||||
|
||||
def find_parents_and_order(glyphsets, locations):
|
||||
def find_parents_and_order(glyphsets, locations, *, discrete_axes=set()):
|
||||
parents = [None] + list(range(len(glyphsets) - 1))
|
||||
order = list(range(len(glyphsets)))
|
||||
if locations:
|
||||
# Order base master first
|
||||
bases = (i for i, l in enumerate(locations) if all(v == 0 for v in l.values()))
|
||||
bases = [
|
||||
i
|
||||
for i, l in enumerate(locations)
|
||||
if all(v == 0 for k, v in l.items() if k not in discrete_axes)
|
||||
]
|
||||
if bases:
|
||||
base = next(bases)
|
||||
logging.info("Base master index %s, location %s", base, locations[base])
|
||||
logging.info("Found %s base masters: %s", len(bases), bases)
|
||||
else:
|
||||
base = 0
|
||||
logging.warning("No base master location found")
|
||||
|
||||
# Form a minimum spanning tree of the locations
|
||||
@ -317,9 +319,17 @@ def find_parents_and_order(glyphsets, locations):
|
||||
axes = sorted(axes)
|
||||
vectors = [tuple(l.get(k, 0) for k in axes) for l in locations]
|
||||
for i, j in itertools.combinations(range(len(locations)), 2):
|
||||
i_discrete_location = {
|
||||
k: v for k, v in zip(axes, vectors[i]) if k in discrete_axes
|
||||
}
|
||||
j_discrete_location = {
|
||||
k: v for k, v in zip(axes, vectors[j]) if k in discrete_axes
|
||||
}
|
||||
if i_discrete_location != j_discrete_location:
|
||||
continue
|
||||
graph[i][j] = vdiff_hypot2(vectors[i], vectors[j])
|
||||
|
||||
tree = minimum_spanning_tree(graph)
|
||||
tree = minimum_spanning_tree(graph, overwrite=True)
|
||||
rows, cols = tree.nonzero()
|
||||
graph = defaultdict(set)
|
||||
for row, col in zip(rows, cols):
|
||||
@ -330,7 +340,7 @@ def find_parents_and_order(glyphsets, locations):
|
||||
parents = [None] * len(locations)
|
||||
order = []
|
||||
visited = set()
|
||||
queue = deque([base])
|
||||
queue = deque(bases)
|
||||
while queue:
|
||||
i = queue.popleft()
|
||||
visited.add(i)
|
||||
@ -339,6 +349,9 @@ def find_parents_and_order(glyphsets, locations):
|
||||
if j not in visited:
|
||||
parents[j] = i
|
||||
queue.append(j)
|
||||
assert len(order) == len(
|
||||
parents
|
||||
), "Not all masters are reachable; report an issue"
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
@ -209,10 +209,14 @@ def supportScalar(location, support, ot=True, extrapolate=False, axisRanges=None
|
||||
class VariationModel(object):
|
||||
"""Locations must have the base master at the origin (ie. 0).
|
||||
|
||||
If axis-ranges are not provided, values are assumed to be normalized to
|
||||
the range [-1, 1].
|
||||
|
||||
If the extrapolate argument is set to True, then values are extrapolated
|
||||
outside the axis range.
|
||||
|
||||
>>> from pprint import pprint
|
||||
>>> axisRanges = {'wght': (-180, +180), 'wdth': (-1, +1)}
|
||||
>>> locations = [ \
|
||||
{'wght':100}, \
|
||||
{'wght':-100}, \
|
||||
@ -224,7 +228,7 @@ class VariationModel(object):
|
||||
{'wght':+180,'wdth':.3}, \
|
||||
{'wght':+180}, \
|
||||
]
|
||||
>>> model = VariationModel(locations, axisOrder=['wght'])
|
||||
>>> model = VariationModel(locations, axisOrder=['wght'], axisRanges=axisRanges)
|
||||
>>> pprint(model.locations)
|
||||
[{},
|
||||
{'wght': -100},
|
||||
@ -252,14 +256,22 @@ class VariationModel(object):
|
||||
7: 0.6666666666666667}]
|
||||
"""
|
||||
|
||||
def __init__(self, locations, axisOrder=None, extrapolate=False):
|
||||
def __init__(
|
||||
self, locations, axisOrder=None, extrapolate=False, *, axisRanges=None
|
||||
):
|
||||
if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations):
|
||||
raise VariationModelError("Locations must be unique.")
|
||||
|
||||
self.origLocations = locations
|
||||
self.axisOrder = axisOrder if axisOrder is not None else []
|
||||
self.extrapolate = extrapolate
|
||||
self.axisRanges = self.computeAxisRanges(locations) if extrapolate else None
|
||||
if axisRanges is None:
|
||||
if extrapolate:
|
||||
axisRanges = self.computeAxisRanges(locations)
|
||||
else:
|
||||
allAxes = {axis for loc in locations for axis in loc.keys()}
|
||||
axisRanges = {axis: (-1, 1) for axis in allAxes}
|
||||
self.axisRanges = axisRanges
|
||||
|
||||
locations = [{k: v for k, v in loc.items() if v != 0.0} for loc in locations]
|
||||
keyFunc = self.getMasterLocationsSortKeyFunc(
|
||||
@ -374,7 +386,7 @@ class VariationModel(object):
|
||||
locAxes = set(region.keys())
|
||||
# Walk over previous masters now
|
||||
for prev_region in regions[:i]:
|
||||
# Master with extra axes do not participte
|
||||
# Master with different axes do not participte
|
||||
if set(prev_region.keys()) != locAxes:
|
||||
continue
|
||||
# If it's NOT in the current box, it does not participate
|
||||
@ -425,23 +437,16 @@ class VariationModel(object):
|
||||
|
||||
def _locationsToRegions(self):
|
||||
locations = self.locations
|
||||
# Compute min/max across each axis, use it as total range.
|
||||
# TODO Take this as input from outside?
|
||||
minV = {}
|
||||
maxV = {}
|
||||
for l in locations:
|
||||
for k, v in l.items():
|
||||
minV[k] = min(v, minV.get(k, v))
|
||||
maxV[k] = max(v, maxV.get(k, v))
|
||||
axisRanges = self.axisRanges
|
||||
|
||||
regions = []
|
||||
for loc in locations:
|
||||
region = {}
|
||||
for axis, locV in loc.items():
|
||||
if locV > 0:
|
||||
region[axis] = (0, locV, maxV[axis])
|
||||
region[axis] = (0, locV, axisRanges[axis][1])
|
||||
else:
|
||||
region[axis] = (minV[axis], locV, 0)
|
||||
region[axis] = (axisRanges[axis][0], locV, 0)
|
||||
regions.append(region)
|
||||
return regions
|
||||
|
||||
|
@ -1112,12 +1112,12 @@ class BuilderTest(unittest.TestCase):
|
||||
var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
|
||||
var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
|
||||
var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 1.0)
|
||||
assert self.get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0)
|
||||
var_region_axis_wght = var_region_list.Region[1].VarRegionAxis[0]
|
||||
var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
|
||||
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 1.0)
|
||||
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 1.0)
|
||||
|
||||
# With `avar`, shifting the wght axis' positive midpoint 0.5 a bit to
|
||||
# the right, but leaving the wdth axis alone:
|
||||
@ -1129,12 +1129,12 @@ class BuilderTest(unittest.TestCase):
|
||||
var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
|
||||
var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
|
||||
var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 1.0)
|
||||
assert self.get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0)
|
||||
var_region_axis_wght = var_region_list.Region[1].VarRegionAxis[0]
|
||||
var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
|
||||
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)
|
||||
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 1.0)
|
||||
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 1.0)
|
||||
|
||||
def test_ligatureCaretByPos_variable_scalar(self):
|
||||
"""Test that the `avar` table is consulted when normalizing user-space
|
||||
@ -1158,7 +1158,7 @@ class BuilderTest(unittest.TestCase):
|
||||
|
||||
var_region_list = table.VarStore.VarRegionList
|
||||
var_region_axis = var_region_list.Region[0].VarRegionAxis[0]
|
||||
assert self.get_region(var_region_axis) == (0.0, 0.875, 0.875)
|
||||
assert self.get_region(var_region_axis) == (0.0, 0.875, 1.0)
|
||||
|
||||
|
||||
def generate_feature_file_test(name):
|
||||
|
@ -16,7 +16,7 @@
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.875"/>
|
||||
<EndCoord value="0.875"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
<VarRegionAxis index="1">
|
||||
<StartCoord value="0.0"/>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.875"/>
|
||||
<EndCoord value="0.875"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
<VarRegionAxis index="1">
|
||||
<StartCoord value="0.0"/>
|
||||
@ -24,12 +24,12 @@
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.875"/>
|
||||
<EndCoord value="0.875"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
<VarRegionAxis index="1">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.5"/>
|
||||
<EndCoord value="0.5"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.875"/>
|
||||
<EndCoord value="0.875"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
<VarRegionAxis index="1">
|
||||
<StartCoord value="0.0"/>
|
||||
@ -24,12 +24,12 @@
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.875"/>
|
||||
<EndCoord value="0.875"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
<VarRegionAxis index="1">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.5"/>
|
||||
<EndCoord value="0.5"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
|
@ -2,7 +2,6 @@ import contextlib
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from subprocess import run
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import pytest
|
||||
@ -28,18 +27,17 @@ def test_main(tmpdir: Path):
|
||||
input = tmpdir / "in.ttf"
|
||||
fb.save(str(input))
|
||||
output = tmpdir / "out.ttf"
|
||||
run(
|
||||
[
|
||||
"fonttools",
|
||||
"otlLib.optimize",
|
||||
"--gpos-compression-level",
|
||||
"5",
|
||||
str(input),
|
||||
"-o",
|
||||
str(output),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
args = [
|
||||
"--gpos-compression-level",
|
||||
"5",
|
||||
str(input),
|
||||
"-o",
|
||||
str(output),
|
||||
]
|
||||
from fontTools.otlLib.optimize import main
|
||||
|
||||
ret = main(args)
|
||||
assert ret in (0, None)
|
||||
assert output.exists()
|
||||
|
||||
|
||||
|
733
Tests/subset/data/Andika-Regular.subset.ttx
Normal file
733
Tests/subset/data/Andika-Regular.subset.ttx
Normal file
@ -0,0 +1,733 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.53">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="space"/>
|
||||
<GlyphID id="2" name="eng"/>
|
||||
<GlyphID id="3" name="Eng.UCStyle"/>
|
||||
<GlyphID id="4" name="Eng.BaselineHook"/>
|
||||
<GlyphID id="5" name="Eng"/>
|
||||
<GlyphID id="6" name="Eng.Kom"/>
|
||||
<GlyphID id="7" name="eng.BaselineHook.sc"/>
|
||||
<GlyphID id="8" name="eng.UCStyle.sc"/>
|
||||
<GlyphID id="9" name="eng.Kom.sc"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<head>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="1.0"/>
|
||||
<fontRevision value="6.101"/>
|
||||
<checkSumAdjustment value="0x60016654"/>
|
||||
<magicNumber value="0x5f0f3cf5"/>
|
||||
<flags value="00000000 00000111"/>
|
||||
<unitsPerEm value="2048"/>
|
||||
<created value="Wed Feb 2 15:47:19 2022"/>
|
||||
<modified value="Wed Feb 9 14:12:57 2022"/>
|
||||
<xMin value="-1387"/>
|
||||
<yMin value="-1148"/>
|
||||
<xMax value="4805"/>
|
||||
<yMax value="2620"/>
|
||||
<macStyle value="00000000 00000000"/>
|
||||
<lowestRecPPEM value="6"/>
|
||||
<fontDirectionHint value="2"/>
|
||||
<indexToLocFormat value="0"/>
|
||||
<glyphDataFormat value="0"/>
|
||||
</head>
|
||||
|
||||
<hhea>
|
||||
<tableVersion value="0x00010000"/>
|
||||
<ascent value="2500"/>
|
||||
<descent value="-800"/>
|
||||
<lineGap value="0"/>
|
||||
<advanceWidthMax value="4885"/>
|
||||
<minLeftSideBearing value="-1387"/>
|
||||
<minRightSideBearing value="-1060"/>
|
||||
<xMaxExtent value="4805"/>
|
||||
<caretSlopeRise value="1"/>
|
||||
<caretSlopeRun value="0"/>
|
||||
<caretOffset value="0"/>
|
||||
<reserved0 value="0"/>
|
||||
<reserved1 value="0"/>
|
||||
<reserved2 value="0"/>
|
||||
<reserved3 value="0"/>
|
||||
<metricDataFormat value="0"/>
|
||||
<numberOfHMetrics value="10"/>
|
||||
</hhea>
|
||||
|
||||
<maxp>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="0x10000"/>
|
||||
<numGlyphs value="10"/>
|
||||
<maxPoints value="191"/>
|
||||
<maxContours value="24"/>
|
||||
<maxCompositePoints value="116"/>
|
||||
<maxCompositeContours value="7"/>
|
||||
<maxZones value="1"/>
|
||||
<maxTwilightPoints value="0"/>
|
||||
<maxStorage value="0"/>
|
||||
<maxFunctionDefs value="0"/>
|
||||
<maxInstructionDefs value="0"/>
|
||||
<maxStackElements value="0"/>
|
||||
<maxSizeOfInstructions value="0"/>
|
||||
<maxComponentElements value="5"/>
|
||||
<maxComponentDepth value="1"/>
|
||||
</maxp>
|
||||
|
||||
<OS_2>
|
||||
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
|
||||
will be recalculated by the compiler -->
|
||||
<version value="4"/>
|
||||
<xAvgCharWidth value="1146"/>
|
||||
<usWeightClass value="400"/>
|
||||
<usWidthClass value="5"/>
|
||||
<fsType value="00000000 00000000"/>
|
||||
<ySubscriptXSize value="1433"/>
|
||||
<ySubscriptYSize value="1331"/>
|
||||
<ySubscriptXOffset value="0"/>
|
||||
<ySubscriptYOffset value="286"/>
|
||||
<ySuperscriptXSize value="1433"/>
|
||||
<ySuperscriptYSize value="1331"/>
|
||||
<ySuperscriptXOffset value="0"/>
|
||||
<ySuperscriptYOffset value="976"/>
|
||||
<yStrikeoutSize value="100"/>
|
||||
<yStrikeoutPosition value="700"/>
|
||||
<sFamilyClass value="0"/>
|
||||
<panose>
|
||||
<bFamilyType value="2"/>
|
||||
<bSerifStyle value="0"/>
|
||||
<bWeight value="0"/>
|
||||
<bProportion value="0"/>
|
||||
<bContrast value="0"/>
|
||||
<bStrokeVariation value="0"/>
|
||||
<bArmStyle value="0"/>
|
||||
<bLetterForm value="0"/>
|
||||
<bMidline value="0"/>
|
||||
<bXHeight value="0"/>
|
||||
</panose>
|
||||
<ulUnicodeRange1 value="00000000 00000000 00000000 00000101"/>
|
||||
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
|
||||
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
|
||||
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
|
||||
<achVendID value="SIL "/>
|
||||
<fsSelection value="00000000 11000000"/>
|
||||
<usFirstCharIndex value="32"/>
|
||||
<usLastCharIndex value="331"/>
|
||||
<sTypoAscender value="2500"/>
|
||||
<sTypoDescender value="-800"/>
|
||||
<sTypoLineGap value="0"/>
|
||||
<usWinAscent value="2500"/>
|
||||
<usWinDescent value="800"/>
|
||||
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
|
||||
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
|
||||
<sxHeight value="1040"/>
|
||||
<sCapHeight value="1485"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="7"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
<mtx name=".notdef" width="1400" lsb="100"/>
|
||||
<mtx name="Eng" width="1460" lsb="135"/>
|
||||
<mtx name="Eng.BaselineHook" width="1496" lsb="135"/>
|
||||
<mtx name="Eng.Kom" width="1496" lsb="135"/>
|
||||
<mtx name="Eng.UCStyle" width="1500" lsb="160"/>
|
||||
<mtx name="eng" width="1185" lsb="105"/>
|
||||
<mtx name="eng.BaselineHook.sc" width="1396" lsb="129"/>
|
||||
<mtx name="eng.Kom.sc" width="1396" lsb="129"/>
|
||||
<mtx name="eng.UCStyle.sc" width="1400" lsb="153"/>
|
||||
<mtx name="space" width="550" lsb="0"/>
|
||||
</hmtx>
|
||||
|
||||
<cmap>
|
||||
<tableVersion version="0"/>
|
||||
<cmap_format_4 platformID="0" platEncID="3" language="0">
|
||||
<map code="0x20" name="space"/><!-- SPACE -->
|
||||
<map code="0x14a" name="Eng"/><!-- LATIN CAPITAL LETTER ENG -->
|
||||
<map code="0x14b" name="eng"/><!-- LATIN SMALL LETTER ENG -->
|
||||
</cmap_format_4>
|
||||
<cmap_format_4 platformID="3" platEncID="1" language="0">
|
||||
<map code="0x20" name="space"/><!-- SPACE -->
|
||||
<map code="0x14a" name="Eng"/><!-- LATIN CAPITAL LETTER ENG -->
|
||||
<map code="0x14b" name="eng"/><!-- LATIN SMALL LETTER ENG -->
|
||||
</cmap_format_4>
|
||||
</cmap>
|
||||
|
||||
<loca>
|
||||
<!-- The 'loca' table will be calculated by the compiler -->
|
||||
</loca>
|
||||
|
||||
<glyf>
|
||||
|
||||
<!-- The xMin, yMin, xMax and yMax values
|
||||
will be recalculated by the compiler. -->
|
||||
|
||||
<TTGlyph name=".notdef"/><!-- contains no outline data -->
|
||||
|
||||
<TTGlyph name="Eng" xMin="135" yMin="-470" xMax="1275" yMax="1485">
|
||||
<contour>
|
||||
<pt x="135" y="1455" on="1"/>
|
||||
<pt x="320" y="1455" on="1"/>
|
||||
<pt x="330" y="1425" on="0"/>
|
||||
<pt x="349" y="1321" on="0"/>
|
||||
<pt x="366" y="1215" on="0"/>
|
||||
<pt x="370" y="1180" on="1"/>
|
||||
<pt x="490" y="1343" on="0"/>
|
||||
<pt x="747" y="1485" on="0"/>
|
||||
<pt x="880" y="1485" on="1"/>
|
||||
<pt x="1066" y="1485" on="0"/>
|
||||
<pt x="1275" y="1186" on="0"/>
|
||||
<pt x="1275" y="892" on="1"/>
|
||||
<pt x="1275" y="145" on="1"/>
|
||||
<pt x="1275" y="-174" on="0"/>
|
||||
<pt x="1044" y="-470" on="0"/>
|
||||
<pt x="845" y="-470" on="1"/>
|
||||
<pt x="784" y="-470" on="0"/>
|
||||
<pt x="674" y="-436" on="0"/>
|
||||
<pt x="650" y="-420" on="1"/>
|
||||
<pt x="715" y="-265" on="1"/>
|
||||
<pt x="740" y="-284" on="0"/>
|
||||
<pt x="824" y="-300" on="0"/>
|
||||
<pt x="870" y="-300" on="1"/>
|
||||
<pt x="924" y="-300" on="0"/>
|
||||
<pt x="1022" y="-223" on="0"/>
|
||||
<pt x="1085" y="-55" on="0"/>
|
||||
<pt x="1085" y="80" on="1"/>
|
||||
<pt x="1085" y="785" on="1"/>
|
||||
<pt x="1085" y="964" on="0"/>
|
||||
<pt x="1040" y="1184" on="0"/>
|
||||
<pt x="923" y="1285" on="0"/>
|
||||
<pt x="815" y="1285" on="1"/>
|
||||
<pt x="743" y="1285" on="0"/>
|
||||
<pt x="609" y="1202" on="0"/>
|
||||
<pt x="494" y="1068" on="0"/>
|
||||
<pt x="411" y="913" on="0"/>
|
||||
<pt x="390" y="840" on="1"/>
|
||||
<pt x="390" y="0" on="1"/>
|
||||
<pt x="185" y="0" on="1"/>
|
||||
<pt x="196" y="60" on="0"/>
|
||||
<pt x="200" y="282" on="0"/>
|
||||
<pt x="200" y="430" on="1"/>
|
||||
<pt x="200" y="880" on="1"/>
|
||||
<pt x="200" y="1089" on="0"/>
|
||||
<pt x="156" y="1381" on="0"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="Eng.BaselineHook" xMin="135" yMin="-25" xMax="1305" yMax="1485">
|
||||
<contour>
|
||||
<pt x="135" y="1455" on="1"/>
|
||||
<pt x="320" y="1455" on="1"/>
|
||||
<pt x="330" y="1425" on="0"/>
|
||||
<pt x="349" y="1321" on="0"/>
|
||||
<pt x="366" y="1215" on="0"/>
|
||||
<pt x="370" y="1180" on="1"/>
|
||||
<pt x="490" y="1343" on="0"/>
|
||||
<pt x="770" y="1485" on="0"/>
|
||||
<pt x="910" y="1485" on="1"/>
|
||||
<pt x="1096" y="1485" on="0"/>
|
||||
<pt x="1305" y="1186" on="0"/>
|
||||
<pt x="1305" y="892" on="1"/>
|
||||
<pt x="1305" y="714" on="0"/>
|
||||
<pt x="1305" y="590" on="0"/>
|
||||
<pt x="1305" y="590" on="1"/>
|
||||
<pt x="1305" y="271" on="0"/>
|
||||
<pt x="1089" y="-25" on="0"/>
|
||||
<pt x="905" y="-25" on="1"/>
|
||||
<pt x="844" y="-25" on="0"/>
|
||||
<pt x="734" y="9" on="0"/>
|
||||
<pt x="710" y="25" on="1"/>
|
||||
<pt x="775" y="180" on="1"/>
|
||||
<pt x="800" y="161" on="0"/>
|
||||
<pt x="884" y="145" on="0"/>
|
||||
<pt x="930" y="145" on="1"/>
|
||||
<pt x="974" y="145" on="0"/>
|
||||
<pt x="1059" y="222" on="0"/>
|
||||
<pt x="1115" y="390" on="0"/>
|
||||
<pt x="1115" y="525" on="1"/>
|
||||
<pt x="1115" y="785" on="1"/>
|
||||
<pt x="1115" y="964" on="0"/>
|
||||
<pt x="1070" y="1184" on="0"/>
|
||||
<pt x="953" y="1285" on="0"/>
|
||||
<pt x="845" y="1285" on="1"/>
|
||||
<pt x="770" y="1285" on="0"/>
|
||||
<pt x="625" y="1202" on="0"/>
|
||||
<pt x="501" y="1068" on="0"/>
|
||||
<pt x="411" y="913" on="0"/>
|
||||
<pt x="390" y="840" on="1"/>
|
||||
<pt x="390" y="0" on="1"/>
|
||||
<pt x="185" y="0" on="1"/>
|
||||
<pt x="196" y="60" on="0"/>
|
||||
<pt x="200" y="282" on="0"/>
|
||||
<pt x="200" y="430" on="1"/>
|
||||
<pt x="200" y="880" on="1"/>
|
||||
<pt x="200" y="1089" on="0"/>
|
||||
<pt x="156" y="1381" on="0"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="Eng.Kom" xMin="135" yMin="-25" xMax="1305" yMax="1485">
|
||||
<contour>
|
||||
<pt x="135" y="1455" on="1"/>
|
||||
<pt x="320" y="1455" on="1"/>
|
||||
<pt x="330" y="1425" on="0"/>
|
||||
<pt x="347" y="1331" on="0"/>
|
||||
<pt x="362" y="1235" on="0"/>
|
||||
<pt x="366" y="1200" on="1"/>
|
||||
<pt x="482" y="1350" on="0"/>
|
||||
<pt x="770" y="1485" on="0"/>
|
||||
<pt x="910" y="1485" on="1"/>
|
||||
<pt x="1096" y="1485" on="0"/>
|
||||
<pt x="1305" y="1186" on="0"/>
|
||||
<pt x="1305" y="892" on="1"/>
|
||||
<pt x="1305" y="752" on="0"/>
|
||||
<pt x="1305" y="640" on="0"/>
|
||||
<pt x="1305" y="640" on="1"/>
|
||||
<pt x="1305" y="403" on="0"/>
|
||||
<pt x="1175" y="110" on="0"/>
|
||||
<pt x="958" y="-25" on="0"/>
|
||||
<pt x="825" y="-25" on="1"/>
|
||||
<pt x="756" y="-25" on="0"/>
|
||||
<pt x="634" y="9" on="0"/>
|
||||
<pt x="610" y="25" on="1"/>
|
||||
<pt x="675" y="180" on="1"/>
|
||||
<pt x="700" y="161" on="0"/>
|
||||
<pt x="796" y="145" on="0"/>
|
||||
<pt x="850" y="145" on="1"/>
|
||||
<pt x="904" y="145" on="0"/>
|
||||
<pt x="1027" y="226" on="0"/>
|
||||
<pt x="1115" y="415" on="0"/>
|
||||
<pt x="1115" y="575" on="1"/>
|
||||
<pt x="1115" y="785" on="1"/>
|
||||
<pt x="1115" y="964" on="0"/>
|
||||
<pt x="1070" y="1184" on="0"/>
|
||||
<pt x="953" y="1285" on="0"/>
|
||||
<pt x="845" y="1285" on="1"/>
|
||||
<pt x="770" y="1285" on="0"/>
|
||||
<pt x="626" y="1209" on="0"/>
|
||||
<pt x="504" y="1085" on="0"/>
|
||||
<pt x="413" y="940" on="0"/>
|
||||
<pt x="390" y="870" on="1"/>
|
||||
<pt x="390" y="480" on="1"/>
|
||||
<pt x="185" y="480" on="1"/>
|
||||
<pt x="196" y="540" on="0"/>
|
||||
<pt x="200" y="750" on="0"/>
|
||||
<pt x="200" y="860" on="1"/>
|
||||
<pt x="200" y="880" on="1"/>
|
||||
<pt x="200" y="1089" on="0"/>
|
||||
<pt x="156" y="1381" on="0"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="Eng.UCStyle" xMin="160" yMin="-470" xMax="1315" yMax="1460">
|
||||
<contour>
|
||||
<pt x="200" y="1355" on="1"/>
|
||||
<pt x="340" y="1460" on="1"/>
|
||||
<pt x="1275" y="100" on="1"/>
|
||||
<pt x="1135" y="0" on="1"/>
|
||||
</contour>
|
||||
<contour>
|
||||
<pt x="340" y="1460" on="1"/>
|
||||
<pt x="340" y="0" on="1"/>
|
||||
<pt x="160" y="0" on="1"/>
|
||||
<pt x="171" y="60" on="0"/>
|
||||
<pt x="175" y="287" on="0"/>
|
||||
<pt x="175" y="435" on="1"/>
|
||||
<pt x="175" y="1025" on="1"/>
|
||||
<pt x="175" y="1173" on="0"/>
|
||||
<pt x="171" y="1400" on="0"/>
|
||||
<pt x="160" y="1460" on="1"/>
|
||||
</contour>
|
||||
<contour>
|
||||
<pt x="1135" y="1460" on="1"/>
|
||||
<pt x="1315" y="1460" on="1"/>
|
||||
<pt x="1305" y="1400" on="0"/>
|
||||
<pt x="1300" y="1173" on="0"/>
|
||||
<pt x="1300" y="1025" on="1"/>
|
||||
<pt x="1300" y="25" on="1"/>
|
||||
<pt x="1300" y="-161" on="0"/>
|
||||
<pt x="1208" y="-378" on="0"/>
|
||||
<pt x="1040" y="-470" on="0"/>
|
||||
<pt x="926" y="-470" on="1"/>
|
||||
<pt x="869" y="-470" on="0"/>
|
||||
<pt x="734" y="-431" on="0"/>
|
||||
<pt x="687" y="-395" on="1"/>
|
||||
<pt x="722" y="-230" on="1"/>
|
||||
<pt x="758" y="-257" on="0"/>
|
||||
<pt x="875" y="-306" on="0"/>
|
||||
<pt x="936" y="-306" on="1"/>
|
||||
<pt x="1020" y="-306" on="0"/>
|
||||
<pt x="1135" y="-165" on="0"/>
|
||||
<pt x="1135" y="-25" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="eng" xMin="105" yMin="-470" xMax="1050" yMax="1040">
|
||||
<contour>
|
||||
<pt x="105" y="1020" on="1"/>
|
||||
<pt x="285" y="1020" on="1"/>
|
||||
<pt x="293" y="999" on="0"/>
|
||||
<pt x="308" y="925" on="0"/>
|
||||
<pt x="322" y="849" on="0"/>
|
||||
<pt x="325" y="825" on="1"/>
|
||||
<pt x="421" y="939" on="0"/>
|
||||
<pt x="615" y="1040" on="0"/>
|
||||
<pt x="720" y="1040" on="1"/>
|
||||
<pt x="880" y="1040" on="0"/>
|
||||
<pt x="1050" y="819" on="0"/>
|
||||
<pt x="1050" y="560" on="1"/>
|
||||
<pt x="1050" y="110" on="1"/>
|
||||
<pt x="1050" y="-115" on="0"/>
|
||||
<pt x="948" y="-367" on="0"/>
|
||||
<pt x="766" y="-470" on="0"/>
|
||||
<pt x="645" y="-470" on="1"/>
|
||||
<pt x="600" y="-470" on="0"/>
|
||||
<pt x="496" y="-440" on="0"/>
|
||||
<pt x="455" y="-415" on="1"/>
|
||||
<pt x="520" y="-265" on="1"/>
|
||||
<pt x="572" y="-305" on="0"/>
|
||||
<pt x="660" y="-305" on="1"/>
|
||||
<pt x="724" y="-305" on="0"/>
|
||||
<pt x="815" y="-247" on="0"/>
|
||||
<pt x="865" y="-95" on="0"/>
|
||||
<pt x="865" y="45" on="1"/>
|
||||
<pt x="865" y="485" on="1"/>
|
||||
<pt x="865" y="646" on="0"/>
|
||||
<pt x="821" y="809" on="0"/>
|
||||
<pt x="732" y="865" on="0"/>
|
||||
<pt x="665" y="865" on="1"/>
|
||||
<pt x="593" y="865" on="0"/>
|
||||
<pt x="461" y="779" on="0"/>
|
||||
<pt x="362" y="648" on="0"/>
|
||||
<pt x="340" y="580" on="1"/>
|
||||
<pt x="340" y="0" on="1"/>
|
||||
<pt x="155" y="0" on="1"/>
|
||||
<pt x="155" y="615" on="1"/>
|
||||
<pt x="155" y="762" on="0"/>
|
||||
<pt x="121" y="968" on="0"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="eng.BaselineHook.sc" xMin="129" yMin="-21" xMax="1202" yMax="1241">
|
||||
<contour>
|
||||
<pt x="129" y="1216" on="1"/>
|
||||
<pt x="310" y="1216" on="1"/>
|
||||
<pt x="322" y="1180" on="0"/>
|
||||
<pt x="348" y="1044" on="0"/>
|
||||
<pt x="355" y="997" on="1"/>
|
||||
<pt x="463" y="1127" on="0"/>
|
||||
<pt x="713" y="1241" on="0"/>
|
||||
<pt x="839" y="1241" on="1"/>
|
||||
<pt x="1009" y="1241" on="0"/>
|
||||
<pt x="1202" y="991" on="0"/>
|
||||
<pt x="1202" y="744" on="1"/>
|
||||
<pt x="1202" y="495" on="1"/>
|
||||
<pt x="1202" y="228" on="0"/>
|
||||
<pt x="1003" y="-21" on="0"/>
|
||||
<pt x="834" y="-21" on="1"/>
|
||||
<pt x="777" y="-21" on="0"/>
|
||||
<pt x="674" y="10" on="0"/>
|
||||
<pt x="649" y="24" on="1"/>
|
||||
<pt x="715" y="166" on="1"/>
|
||||
<pt x="739" y="150" on="0"/>
|
||||
<pt x="815" y="134" on="0"/>
|
||||
<pt x="856" y="134" on="1"/>
|
||||
<pt x="914" y="134" on="0"/>
|
||||
<pt x="1017" y="277" on="0"/>
|
||||
<pt x="1017" y="441" on="1"/>
|
||||
<pt x="1017" y="655" on="1"/>
|
||||
<pt x="1017" y="801" on="0"/>
|
||||
<pt x="978" y="979" on="0"/>
|
||||
<pt x="875" y="1061" on="0"/>
|
||||
<pt x="780" y="1061" on="1"/>
|
||||
<pt x="713" y="1061" on="0"/>
|
||||
<pt x="585" y="993" on="0"/>
|
||||
<pt x="475" y="885" on="0"/>
|
||||
<pt x="394" y="759" on="0"/>
|
||||
<pt x="374" y="700" on="1"/>
|
||||
<pt x="374" y="0" on="1"/>
|
||||
<pt x="175" y="0" on="1"/>
|
||||
<pt x="186" y="56" on="0"/>
|
||||
<pt x="190" y="242" on="0"/>
|
||||
<pt x="190" y="363" on="1"/>
|
||||
<pt x="190" y="734" on="1"/>
|
||||
<pt x="190" y="906" on="0"/>
|
||||
<pt x="148" y="1152" on="0"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="eng.Kom.sc" xMin="129" yMin="-21" xMax="1202" yMax="1241">
|
||||
<contour>
|
||||
<pt x="129" y="1216" on="1"/>
|
||||
<pt x="310" y="1216" on="1"/>
|
||||
<pt x="322" y="1179" on="0"/>
|
||||
<pt x="344" y="1054" on="0"/>
|
||||
<pt x="351" y="1009" on="1"/>
|
||||
<pt x="457" y="1132" on="0"/>
|
||||
<pt x="713" y="1241" on="0"/>
|
||||
<pt x="839" y="1241" on="1"/>
|
||||
<pt x="1009" y="1241" on="0"/>
|
||||
<pt x="1202" y="991" on="0"/>
|
||||
<pt x="1202" y="744" on="1"/>
|
||||
<pt x="1202" y="536" on="1"/>
|
||||
<pt x="1202" y="338" on="0"/>
|
||||
<pt x="1083" y="92" on="0"/>
|
||||
<pt x="884" y="-21" on="0"/>
|
||||
<pt x="762" y="-21" on="1"/>
|
||||
<pt x="698" y="-21" on="0"/>
|
||||
<pt x="583" y="10" on="0"/>
|
||||
<pt x="559" y="24" on="1"/>
|
||||
<pt x="625" y="166" on="1"/>
|
||||
<pt x="649" y="150" on="0"/>
|
||||
<pt x="736" y="134" on="0"/>
|
||||
<pt x="785" y="134" on="1"/>
|
||||
<pt x="832" y="134" on="0"/>
|
||||
<pt x="940" y="200" on="0"/>
|
||||
<pt x="1017" y="353" on="0"/>
|
||||
<pt x="1017" y="482" on="1"/>
|
||||
<pt x="1017" y="655" on="1"/>
|
||||
<pt x="1017" y="801" on="0"/>
|
||||
<pt x="978" y="979" on="0"/>
|
||||
<pt x="875" y="1061" on="0"/>
|
||||
<pt x="780" y="1061" on="1"/>
|
||||
<pt x="693" y="1061" on="0"/>
|
||||
<pt x="529" y="954" on="0"/>
|
||||
<pt x="404" y="798" on="0"/>
|
||||
<pt x="374" y="723" on="1"/>
|
||||
<pt x="374" y="396" on="1"/>
|
||||
<pt x="175" y="396" on="1"/>
|
||||
<pt x="186" y="452" on="0"/>
|
||||
<pt x="190" y="627" on="0"/>
|
||||
<pt x="190" y="717" on="1"/>
|
||||
<pt x="190" y="734" on="1"/>
|
||||
<pt x="190" y="906" on="0"/>
|
||||
<pt x="148" y="1152" on="0"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="eng.UCStyle.sc" xMin="153" yMin="-388" xMax="1213" yMax="1222">
|
||||
<contour>
|
||||
<pt x="187" y="1128" on="1"/>
|
||||
<pt x="325" y="1222" on="1"/>
|
||||
<pt x="1178" y="88" on="1"/>
|
||||
<pt x="1040" y="-2" on="1"/>
|
||||
</contour>
|
||||
<contour>
|
||||
<pt x="329" y="1221" on="1"/>
|
||||
<pt x="329" y="0" on="1"/>
|
||||
<pt x="153" y="0" on="1"/>
|
||||
<pt x="164" y="56" on="0"/>
|
||||
<pt x="167" y="245" on="0"/>
|
||||
<pt x="167" y="366" on="1"/>
|
||||
<pt x="167" y="853" on="1"/>
|
||||
<pt x="167" y="975" on="0"/>
|
||||
<pt x="164" y="1164" on="0"/>
|
||||
<pt x="153" y="1221" on="1"/>
|
||||
</contour>
|
||||
<contour>
|
||||
<pt x="1036" y="1221" on="1"/>
|
||||
<pt x="1213" y="1221" on="1"/>
|
||||
<pt x="1203" y="1165" on="0"/>
|
||||
<pt x="1198" y="975" on="0"/>
|
||||
<pt x="1198" y="853" on="1"/>
|
||||
<pt x="1198" y="28" on="1"/>
|
||||
<pt x="1198" y="-129" on="0"/>
|
||||
<pt x="1113" y="-311" on="0"/>
|
||||
<pt x="959" y="-388" on="0"/>
|
||||
<pt x="854" y="-388" on="1"/>
|
||||
<pt x="802" y="-388" on="0"/>
|
||||
<pt x="675" y="-353" on="0"/>
|
||||
<pt x="630" y="-322" on="1"/>
|
||||
<pt x="664" y="-171" on="1"/>
|
||||
<pt x="699" y="-194" on="0"/>
|
||||
<pt x="808" y="-238" on="0"/>
|
||||
<pt x="862" y="-238" on="1"/>
|
||||
<pt x="936" y="-238" on="0"/>
|
||||
<pt x="1036" y="-125" on="0"/>
|
||||
<pt x="1036" y="-13" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="space"/><!-- contains no outline data -->
|
||||
|
||||
</glyf>
|
||||
|
||||
<name>
|
||||
<namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
|
||||
Copyright (c) 2004-2022 SIL International
|
||||
</namerecord>
|
||||
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
|
||||
Andika
|
||||
</namerecord>
|
||||
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
|
||||
Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
|
||||
SIL International: Andika Regular: 2022
|
||||
</namerecord>
|
||||
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
|
||||
Andika
|
||||
</namerecord>
|
||||
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
|
||||
Version 6.101
|
||||
</namerecord>
|
||||
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
|
||||
Andika
|
||||
</namerecord>
|
||||
<namerecord nameID="321" platformID="3" platEncID="1" langID="0x409">
|
||||
Capital Eng
|
||||
</namerecord>
|
||||
<namerecord nameID="322" platformID="3" platEncID="1" langID="0x409">
|
||||
Alternate forms of capital Eng
|
||||
</namerecord>
|
||||
<namerecord nameID="323" platformID="3" platEncID="1" langID="0x409">
|
||||
Ŋ
|
||||
</namerecord>
|
||||
<namerecord nameID="324" platformID="3" platEncID="1" langID="0x409">
|
||||
Lowercase no descender
|
||||
</namerecord>
|
||||
<namerecord nameID="325" platformID="3" platEncID="1" langID="0x409">
|
||||
Capital form
|
||||
</namerecord>
|
||||
<namerecord nameID="326" platformID="3" platEncID="1" langID="0x409">
|
||||
Lowercase short stem
|
||||
</namerecord>
|
||||
</name>
|
||||
|
||||
<post>
|
||||
<formatType value="2.0"/>
|
||||
<italicAngle value="0.0"/>
|
||||
<underlinePosition value="-110"/>
|
||||
<underlineThickness value="80"/>
|
||||
<isFixedPitch value="0"/>
|
||||
<minMemType42 value="0"/>
|
||||
<maxMemType42 value="0"/>
|
||||
<minMemType1 value="0"/>
|
||||
<maxMemType1 value="0"/>
|
||||
<psNames>
|
||||
<!-- This file uses unique glyph names based on the information
|
||||
found in the 'post' table. Since these names might not be unique,
|
||||
we have to invent artificial names in case of clashes. In order to
|
||||
be able to retain the original information, we need a name to
|
||||
ps name mapping for those cases where they differ. That's what
|
||||
you see below.
|
||||
-->
|
||||
</psNames>
|
||||
<extraNames>
|
||||
<!-- following are the name that are not taken from the standard Mac glyph order -->
|
||||
<psName name="eng"/>
|
||||
<psName name="Eng.UCStyle"/>
|
||||
<psName name="Eng.BaselineHook"/>
|
||||
<psName name="Eng"/>
|
||||
<psName name="Eng.Kom"/>
|
||||
<psName name="eng.BaselineHook.sc"/>
|
||||
<psName name="eng.UCStyle.sc"/>
|
||||
<psName name="eng.Kom.sc"/>
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<gasp>
|
||||
<gaspRange rangeMaxPPEM="65535" rangeGaspBehavior="15"/>
|
||||
</gasp>
|
||||
|
||||
<GDEF>
|
||||
<Version value="0x00010000"/>
|
||||
<GlyphClassDef>
|
||||
<ClassDef glyph=".notdef" class="1"/>
|
||||
<ClassDef glyph="Eng" class="1"/>
|
||||
<ClassDef glyph="Eng.BaselineHook" class="1"/>
|
||||
<ClassDef glyph="Eng.Kom" class="1"/>
|
||||
<ClassDef glyph="Eng.UCStyle" class="1"/>
|
||||
<ClassDef glyph="eng" class="1"/>
|
||||
<ClassDef glyph="eng.BaselineHook.sc" class="1"/>
|
||||
<ClassDef glyph="eng.Kom.sc" class="1"/>
|
||||
<ClassDef glyph="eng.UCStyle.sc" class="1"/>
|
||||
<ClassDef glyph="space" class="1"/>
|
||||
</GlyphClassDef>
|
||||
</GDEF>
|
||||
|
||||
<GSUB>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=3 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="DFLT"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
<ScriptRecord index="1">
|
||||
<ScriptTag value="cyrl"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
<ScriptRecord index="2">
|
||||
<ScriptTag value="latn"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="cv43"/>
|
||||
<Feature>
|
||||
<FeatureParamsCharacterVariants Format="0">
|
||||
<Format value="0"/>
|
||||
<FeatUILabelNameID value="321"/> <!-- Capital Eng -->
|
||||
<FeatUITooltipTextNameID value="322"/> <!-- Alternate forms of capital Eng -->
|
||||
<SampleTextNameID value="323"/> <!-- Ŋ -->
|
||||
<NumNamedParameters value="3"/>
|
||||
<FirstParamUILabelNameID value="324"/> <!-- Lowercase no descender -->
|
||||
<!-- CharCount=0 -->
|
||||
</FeatureParamsCharacterVariants>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="3"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<AlternateSubst index="0">
|
||||
<AlternateSet glyph="Eng">
|
||||
<Alternate glyph="Eng.BaselineHook"/>
|
||||
<Alternate glyph="Eng.UCStyle"/>
|
||||
<Alternate glyph="Eng.Kom"/>
|
||||
</AlternateSet>
|
||||
</AlternateSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
@ -2071,5 +2071,28 @@ def test_prune_unused_user_name_IDs_with_keep_all(ttf_path):
|
||||
assert nameIDs == keepNameIDs
|
||||
|
||||
|
||||
def test_cvXX_feature_params_nameIDs_are_retained():
|
||||
# https://github.com/fonttools/fonttools/issues/3616
|
||||
font = TTFont()
|
||||
ttx = pathlib.Path(__file__).parent / "data" / "Andika-Regular.subset.ttx"
|
||||
font.importXML(ttx)
|
||||
|
||||
keepNameIDs = {n.nameID for n in font["name"].names}
|
||||
|
||||
options = subset.Options()
|
||||
options.glyph_names = True
|
||||
# that's where the FeatureParamsCharacteVariants are stored
|
||||
options.layout_features.append("cv43")
|
||||
|
||||
subsetter = subset.Subsetter(options)
|
||||
subsetter.populate(glyphs=font.getGlyphOrder())
|
||||
subsetter.subset(font)
|
||||
|
||||
# we expect that all nameIDs are retained, including all the nameIDs
|
||||
# used by the FeatureParamsCharacterVariants
|
||||
nameIDs = {n.nameID for n in font["name"].names}
|
||||
assert nameIDs == keepNameIDs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(unittest.main())
|
||||
|
94
Tests/varLib/avar_test.py
Normal file
94
Tests/varLib/avar_test.py
Normal file
@ -0,0 +1,94 @@
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.varLib.models import VariationModel
|
||||
from fontTools.varLib.avar import _pruneLocations, mappings_from_avar
|
||||
import os
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
TESTS = [
|
||||
(
|
||||
[
|
||||
{"wght": 1},
|
||||
{"wght": 0.5},
|
||||
],
|
||||
[
|
||||
{"wght": 0.5},
|
||||
],
|
||||
[
|
||||
{"wght": 0.5},
|
||||
],
|
||||
),
|
||||
(
|
||||
[
|
||||
{"wght": 1, "wdth": 1},
|
||||
{"wght": 0.5, "wdth": 1},
|
||||
],
|
||||
[
|
||||
{"wght": 1, "wdth": 1},
|
||||
],
|
||||
[
|
||||
{"wght": 1, "wdth": 1},
|
||||
{"wght": 0.5, "wdth": 1},
|
||||
],
|
||||
),
|
||||
(
|
||||
[
|
||||
{"wght": 1},
|
||||
{"wdth": 1},
|
||||
{"wght": 0.5, "wdth": 0.5},
|
||||
],
|
||||
[
|
||||
{"wght": 0.5, "wdth": 0.5},
|
||||
],
|
||||
[
|
||||
{"wght": 0.5, "wdth": 0.5},
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("locations, poles, expected", TESTS)
|
||||
def test_pruneLocations(locations, poles, expected):
|
||||
axisTags = set()
|
||||
for location in locations:
|
||||
axisTags.update(location.keys())
|
||||
axisTags = sorted(axisTags)
|
||||
|
||||
locations = [{}] + locations
|
||||
|
||||
pruned = _pruneLocations(locations, poles, axisTags)
|
||||
|
||||
assert pruned == expected, (pruned, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("locations, poles, expected", TESTS)
|
||||
def test_roundtrip(locations, poles, expected):
|
||||
axisTags = set()
|
||||
for location in locations:
|
||||
axisTags.update(location.keys())
|
||||
axisTags = sorted(axisTags)
|
||||
|
||||
locations = [{}] + locations
|
||||
expected = [{}] + expected
|
||||
|
||||
model1 = VariationModel(locations, axisTags)
|
||||
model2 = VariationModel(expected, axisTags)
|
||||
|
||||
for location in poles:
|
||||
i = model1.locations.index(location)
|
||||
support1 = model1.supports[i]
|
||||
|
||||
i = model2.locations.index(location)
|
||||
support2 = model2.supports[i]
|
||||
|
||||
assert support1 == support2, (support1, support2)
|
||||
|
||||
|
||||
def test_mappings_from_avar():
|
||||
CWD = os.path.abspath(os.path.dirname(__file__))
|
||||
DATADIR = os.path.join(CWD, "..", "ttLib", "tables", "data")
|
||||
varfont_path = os.path.join(DATADIR, "Amstelvar-avar2.subset.ttf")
|
||||
font = TTFont(varfont_path)
|
||||
mappings = mappings_from_avar(font)
|
||||
|
||||
assert len(mappings) == 2, mappings
|
@ -23,7 +23,7 @@
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.38"/>
|
||||
<EndCoord value="0.38"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
|
@ -6,4 +6,4 @@ mypy>=0.782
|
||||
readme_renderer[md]>=43.0
|
||||
|
||||
# Pin black as each version could change formatting, breaking CI randomly.
|
||||
black==24.4.2
|
||||
black==24.8.0
|
||||
|
@ -11,10 +11,10 @@ fs==2.4.16
|
||||
skia-pathops==0.8.0.post1; platform_python_implementation != "PyPy"
|
||||
# this is only required to run Tests/cu2qu/{ufo,cli}_test.py
|
||||
ufoLib2==0.16.0
|
||||
ufo2ft==3.2.5
|
||||
ufo2ft==3.2.7
|
||||
pyobjc==10.3.1; sys_platform == "darwin"
|
||||
freetype-py==2.4.0
|
||||
uharfbuzz==0.39.3
|
||||
glyphsLib==6.7.1 # this is only required to run Tests/varLib/interpolatable_test.py
|
||||
lxml==5.2.2
|
||||
sympy==1.13.0
|
||||
glyphsLib==6.8.0 # this is only required to run Tests/varLib/interpolatable_test.py
|
||||
lxml==5.3.0
|
||||
sympy==1.13.2
|
||||
|
Loading…
x
Reference in New Issue
Block a user