Skip to content

Commit

Permalink
[designspace/varLib] Hack to read avar2 mapping and build avar2 table
Browse files Browse the repository at this point in the history
This just uses list of list of dictionaries. Should use proper objects.

#3049

https://github.com/harfbuzz/boring-expansion-spec/blob/main/avar2-in-designspace.md
  • Loading branch information
behdad committed May 25, 2023
1 parent 77a35fe commit c1a881b
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 13 deletions.
21 changes: 21 additions & 0 deletions Lib/fontTools/designspaceLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2005,6 +2005,24 @@ def readAxes(self):
self.documentObject.axes.append(axisObject)
self.axisDefaults[axisObject.name] = axisObject.default

mappingElement = self.root.find(".axes/mapping")
self.documentObject.mapping = []
if mappingElement is not None:
for regionElement in mappingElement.findall("region"):
inputElement = regionElement.find("input")
outputElement = regionElement.find("output")
inputLoc = {}
outputLoc = {}

Check warning on line 2015 in Lib/fontTools/designspaceLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/designspaceLib/__init__.py#L2012-L2015

Added lines #L2012 - L2015 were not covered by tests
for dimElement in inputElement.findall(".dimension"):
tag = dimElement.attrib["tag"]
value = float(dimElement.attrib["xvalue"])
inputLoc[tag] = value

Check warning on line 2019 in Lib/fontTools/designspaceLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/designspaceLib/__init__.py#L2017-L2019

Added lines #L2017 - L2019 were not covered by tests
for dimElement in outputElement.findall(".dimension"):
tag = dimElement.attrib["tag"]
value = float(dimElement.attrib["xvalue"])
outputLoc[tag] = value
self.documentObject.mapping.append([inputLoc, outputLoc])

Check warning on line 2024 in Lib/fontTools/designspaceLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/designspaceLib/__init__.py#L2021-L2024

Added lines #L2021 - L2024 were not covered by tests

def readAxisLabel(self, element: ET.Element):
xml_attrs = {
"userminimum",
Expand Down Expand Up @@ -2547,6 +2565,9 @@ def __init__(self, readerClass=None, writerClass=None):

self.axes: List[Union[AxisDescriptor, DiscreteAxisDescriptor]] = []
"""List of this document's axes."""

self.mapping = []

self.locationLabels: List[LocationLabelDescriptor] = []
"""List of this document's STAT format 4 labels.
Expand Down
1 change: 1 addition & 0 deletions Lib/fontTools/designspaceLib/split.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def maybeExpandDesignLocation(object):
)

subDoc.lib = doc.lib
subDoc.mapping = doc.mapping # TODO

return subDoc

Expand Down
66 changes: 53 additions & 13 deletions Lib/fontTools/varLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import List
from fontTools.misc.vector import Vector
from fontTools.misc.roundTools import noRound, otRound
from fontTools.misc.fixedTools import floatToFixed as fl2fi
from fontTools.misc.textTools import Tag, tostr
from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
Expand Down Expand Up @@ -130,7 +131,7 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
return fvar


def _add_avar(font, axes):
def _add_avar(font, axes, mapping, axisTags):
"""
Add 'avar' table to font.
Expand All @@ -145,6 +146,7 @@ def _add_avar(font, axes):
avar = newTable("avar")

interesting = False
vals_triples = {}
for axis in axes.values():
# Currently, some rasterizers require that the default value maps
# (-1 to -1, 0 to 0, and 1 to 1) be present for all the segment
Expand All @@ -154,6 +156,11 @@ def _add_avar(font, axes):
# https://github.com/fonttools/fonttools/issues/1011
# TODO(anthrotype) revert this (and 19c4b37) when issue is fixed
curve = avar.segments[axis.tag] = {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}

keys_triple = (axis.minimum, axis.default, axis.maximum)
vals_triple = tuple(axis.map_forward(v) for v in keys_triple)
vals_triples[axis.tag] = vals_triple

if not axis.map:
continue

Expand Down Expand Up @@ -191,9 +198,6 @@ def _add_avar(font, axes):
f"Axis '{axis.name}': mapping output values must be in ascending order."
)

keys_triple = (axis.minimum, axis.default, axis.maximum)
vals_triple = tuple(axis.map_forward(v) for v in keys_triple)

keys = [models.normalizeValue(v, keys_triple) for v in keys]
vals = [models.normalizeValue(v, vals_triple) for v in vals]

Expand All @@ -208,6 +212,44 @@ def _add_avar(font, axes):
assert +1.0 not in curve or curve[+1.0] == +1.0
# curve.update({-1.0: -1.0, 0.0: 0.0, 1.0: 1.0})

if mapping:
interesting = True

Check warning on line 216 in Lib/fontTools/varLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/__init__.py#L216

Added line #L216 was not covered by tests

derived = [
{
tag: models.normalizeValue(v, vals_triples[tag])
for tag, v in inputLoc.items()
}
for inputLoc, outputLoc in mapping
]
source = [
{
tag: models.normalizeValue(v, vals_triples[tag])
for tag, v in outputLoc.items()
}
for inputLoc, outputLoc in mapping
]

model = models.VariationModel(derived, axisTags)
builder = varStore.OnlineVarStoreBuilder(axisTags)
builder.setModel(model)

Check warning on line 235 in Lib/fontTools/varLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/__init__.py#L233-L235

Added lines #L233 - L235 were not covered by tests
varIdxes = {
t: builder.storeMasters([fl2fi(m.get(t, 0), 14) for m in source])[1]
for t in axisTags
}
store = builder.finish()
optimized = store.optimize()

Check warning on line 241 in Lib/fontTools/varLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/__init__.py#L240-L241

Added lines #L240 - L241 were not covered by tests
varIdxes = {axis: optimized[value] for axis, value in varIdxes.items()}

varIdxMap = ot.DeltaSetIndexMap()
varIdxMap.Format = 1

Check warning on line 245 in Lib/fontTools/varLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/__init__.py#L244-L245

Added lines #L244 - L245 were not covered by tests
varIdxMap.mapping = [varIdxes[t] for t in axisTags]

avar.majorVersion = 2
avar.table = ot.avar()
avar.table.VarIdxMap = varIdxMap
avar.table.VarStore = store

Check warning on line 251 in Lib/fontTools/varLib/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/__init__.py#L248-L251

Added lines #L248 - L251 were not covered by tests

assert "avar" not in font
if not interesting:
log.info("No need for avar")
Expand Down Expand Up @@ -349,7 +391,6 @@ def _remove_TTHinting(font):


def _merge_TTHinting(font, masterModel, master_ttfs):

log.info("Merging TT hinting")
assert "cvar" not in font

Expand Down Expand Up @@ -452,7 +493,6 @@ def _add_VVAR(font, masterModel, master_ttfs, axisTags):


def _add_VHVAR(font, masterModel, master_ttfs, axisTags, tableFields):

tableTag = tableFields.tableTag
assert tableTag not in font
log.info("Generating " + tableTag)
Expand Down Expand Up @@ -508,7 +548,6 @@ def _get_advance_metrics(
advMetricses,
vOrigMetricses=None,
):

vhAdvanceDeltasAndSupports = {}
vOrigDeltasAndSupports = {}
for glyph in glyphOrder:
Expand Down Expand Up @@ -598,7 +637,6 @@ def _get_advance_metrics(


def _add_MVAR(font, masterModel, master_ttfs, axisTags):

log.info("Generating MVAR")

store_builder = varStore.OnlineVarStoreBuilder(axisTags)
Expand Down Expand Up @@ -677,7 +715,6 @@ def _add_MVAR(font, masterModel, master_ttfs, axisTags):


def _add_BASE(font, masterModel, master_ttfs, axisTags):

log.info("Generating BASE")

merger = VariationMerger(masterModel, axisTags, font)
Expand All @@ -693,7 +730,6 @@ def _add_BASE(font, masterModel, master_ttfs, axisTags):


def _merge_OTL(font, model, master_fonts, axisTags):

log.info("Merging OpenType Layout tables")
merger = VariationMerger(model, axisTags, font)

Expand Down Expand Up @@ -734,7 +770,6 @@ def normalize(name, value):

conditional_subs = []
for rule in rules:

region = []
for conditions in rule.conditionSets:
space = {}
Expand Down Expand Up @@ -763,6 +798,7 @@ def normalize(name, value):
"_DesignSpaceData",
[
"axes",
"mapping",
"internal_axis_supports",
"base_idx",
"normalized_master_locs",
Expand Down Expand Up @@ -856,6 +892,9 @@ def load_designspace(designspace):
axes[axis_name] = axis
log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()]))

mapping = ds.mapping
log.info("Mapping:\n%s", pformat(mapping))

# Check all master and instance locations are valid and fill in defaults
for obj in masters + instances:
obj_name = obj.name or obj.styleName or ""
Expand Down Expand Up @@ -915,6 +954,7 @@ def load_designspace(designspace):

return _DesignSpaceData(
axes,
mapping,
internal_axis_supports,
base_idx,
normalized_master_locs,
Expand Down Expand Up @@ -1065,8 +1105,6 @@ def build(
fvar = _add_fvar(vf, ds.axes, ds.instances)
if "STAT" not in exclude:
_add_stat(vf)
if "avar" not in exclude:
_add_avar(vf, ds.axes)

# Map from axis names to axis tags...
normalized_master_locs = [
Expand All @@ -1080,6 +1118,8 @@ def build(
assert 0 == model.mapping[ds.base_idx]

log.info("Building variations tables")
if "avar" not in exclude:
_add_avar(vf, ds.axes, ds.mapping, axisTags)
if "BASE" not in exclude and "BASE" in vf:
_add_BASE(vf, model, master_fonts, axisTags)
if "MVAR" not in exclude:
Expand Down

0 comments on commit c1a881b

Please sign in to comment.