Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

192 lines
6.9 KiB
Python
Raw Normal View History

2015-06-20 00:13:01 +02:00
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
floatToFixed as fl2fi,
floatToFixedToStr as fl2str,
strToFixedToFloat as str2fl,
)
2023-03-08 09:38:07 -07:00
from fontTools.misc.textTools import bytesjoin, safeEval
from fontTools.misc.roundTools import otRound
from fontTools.varLib.models import piecewiseLinearMap
from fontTools.varLib.varStore import VarStoreInstancer, NO_VARIATION_INDEX
2015-06-20 00:13:01 +02:00
from fontTools.ttLib import TTLibError
from . import DefaultTable
from . import otTables
2015-06-20 00:13:01 +02:00
import struct
import logging
2015-06-20 00:13:01 +02:00
log = logging.getLogger(__name__)
2022-06-17 12:09:13 -06:00
from .otBase import BaseTTXConverter
2015-06-20 00:13:01 +02:00
2023-03-07 11:19:22 -07:00
2022-06-17 12:09:13 -06:00
class table__a_v_a_r(BaseTTXConverter):
"""Axis Variations table
This class represents the ``avar`` table of a variable font. The object has one
substantive attribute, ``segments``, which maps axis tags to a segments dictionary::
>>> font["avar"].segments # doctest: +SKIP
{'wght': {-1.0: -1.0,
0.0: 0.0,
0.125: 0.11444091796875,
0.25: 0.23492431640625,
0.5: 0.35540771484375,
0.625: 0.5,
0.75: 0.6566162109375,
0.875: 0.81927490234375,
1.0: 1.0},
'ital': {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}}
Notice that the segments dictionary is made up of normalized values. A valid
``avar`` segment mapping must contain the entries ``-1.0: -1.0, 0.0: 0.0, 1.0: 1.0``.
fontTools does not enforce this, so it is your responsibility to ensure that
mappings are valid.
See also https://learn.microsoft.com/en-us/typography/opentype/spec/avar
"""
2015-06-20 00:13:01 +02:00
dependencies = ["fvar"]
def __init__(self, tag=None):
super().__init__(tag)
self.segments = {}
2015-06-20 00:13:01 +02:00
def compile(self, ttFont):
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
if not hasattr(self, "table"):
self.table = otTables.avar()
2023-03-14 13:46:39 -06:00
if not hasattr(self.table, "Reserved"):
self.table.Reserved = 0
self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
self, "minorVersion", 0
)
self.table.AxisCount = len(axisTags)
self.table.AxisSegmentMap = []
2015-06-20 00:13:01 +02:00
for axis in axisTags:
mappings = self.segments[axis]
segmentMap = otTables.AxisSegmentMap()
segmentMap.PositionMapCount = len(mappings)
segmentMap.AxisValueMap = []
for key, value in sorted(mappings.items()):
valueMap = otTables.AxisValueMap()
valueMap.FromCoordinate = key
valueMap.ToCoordinate = value
segmentMap.AxisValueMap.append(valueMap)
self.table.AxisSegmentMap.append(segmentMap)
return super().compile(ttFont)
2015-06-20 00:13:01 +02:00
def decompile(self, data, ttFont):
super().decompile(data, ttFont)
self.majorVersion = self.table.Version >> 16
2023-03-08 09:38:07 -07:00
self.minorVersion = self.table.Version & 0xFFFF
if self.majorVersion not in (1, 2):
raise NotImplementedError("Unknown avar table version")
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
2015-06-20 00:13:01 +02:00
for axis in axisTags:
self.segments[axis] = {}
for axis, segmentMap in zip(axisTags, self.table.AxisSegmentMap):
2015-06-20 00:13:01 +02:00
segments = self.segments[axis] = {}
for segment in segmentMap.AxisValueMap:
segments[segment.FromCoordinate] = segment.ToCoordinate
2015-06-20 00:13:01 +02:00
def toXML(self, writer, ttFont):
2023-03-08 11:07:06 -07:00
writer.simpletag(
"version",
major=getattr(self, "majorVersion", 1),
minor=getattr(self, "minorVersion", 0),
)
2023-03-08 09:38:07 -07:00
writer.newline()
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
2015-06-20 00:13:01 +02:00
for axis in axisTags:
writer.begintag("segment", axis=axis)
writer.newline()
for key, value in sorted(self.segments[axis].items()):
key = fl2str(key, 14)
value = fl2str(value, 14)
2015-06-20 00:13:01 +02:00
writer.simpletag("mapping", **{"from": key, "to": value})
writer.newline()
writer.endtag("segment")
writer.newline()
2023-03-08 11:02:52 -07:00
if getattr(self, "majorVersion", 1) >= 2:
2023-03-08 09:38:07 -07:00
if self.table.VarIdxMap:
2023-03-08 11:44:23 -07:00
self.table.VarIdxMap.toXML(writer, ttFont, name="VarIdxMap")
2023-03-08 09:38:07 -07:00
if self.table.VarStore:
self.table.VarStore.toXML(writer, ttFont)
2015-06-20 00:13:01 +02:00
def fromXML(self, name, attrs, content, ttFont):
2023-03-08 11:02:52 -07:00
if not hasattr(self, "table"):
self.table = otTables.avar()
2023-03-14 13:46:39 -06:00
if not hasattr(self.table, "Reserved"):
2023-03-08 11:02:52 -07:00
self.table.Reserved = 0
2023-03-08 09:38:07 -07:00
if name == "version":
self.majorVersion = safeEval(attrs["major"])
self.minorVersion = safeEval(attrs["minor"])
2023-03-08 10:56:45 -07:00
self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
self, "minorVersion", 0
)
2023-03-08 09:38:07 -07:00
elif name == "segment":
2015-06-20 00:13:01 +02:00
axis = attrs["axis"]
segment = self.segments[axis] = {}
for element in content:
if isinstance(element, tuple):
elementName, elementAttrs, _ = element
2015-06-20 00:13:01 +02:00
if elementName == "mapping":
fromValue = str2fl(elementAttrs["from"], 14)
toValue = str2fl(elementAttrs["to"], 14)
2015-06-20 00:13:01 +02:00
if fromValue in segment:
log.warning(
"duplicate entry for %s in axis '%s'", fromValue, axis
)
2015-06-20 00:13:01 +02:00
segment[fromValue] = toValue
2023-03-08 11:44:23 -07:00
else:
super().fromXML(name, attrs, content, ttFont)
def renormalizeLocation(self, location, font):
majorVersion = getattr(self, "majorVersion", 1)
if majorVersion not in (1, 2):
raise NotImplementedError("Unknown avar table version")
avarSegments = self.segments
mappedLocation = {}
for axisTag, value in location.items():
avarMapping = avarSegments.get(axisTag, None)
if avarMapping is not None:
value = piecewiseLinearMap(value, avarMapping)
mappedLocation[axisTag] = value
if majorVersion < 2:
return mappedLocation
# Version 2
varIdxMap = self.table.VarIdxMap
varStore = self.table.VarStore
axes = font["fvar"].axes
if varStore is not None:
instancer = VarStoreInstancer(varStore, axes, mappedLocation)
coords = list(fl2fi(mappedLocation.get(axis.axisTag, 0), 14) for axis in axes)
out = []
for varIdx, v in enumerate(coords):
if varIdxMap is not None:
2024-04-02 10:38:36 -06:00
varIdx = varIdxMap[varIdx]
if varStore is not None:
delta = instancer[varIdx]
v += otRound(delta)
v = min(max(v, -(1 << 14)), +(1 << 14))
out.append(v)
mappedLocation = {
axis.axisTag: fi2fl(v, 14) for v, axis in zip(out, axes) if v != 0
}
return mappedLocation