• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.fixedTools import (
3    fixedToFloat as fi2fl,
4    floatToFixed as fl2fi,
5    floatToFixedToStr as fl2str,
6    strToFixedToFloat as str2fl,
7)
8from fontTools.misc.textTools import bytesjoin, safeEval
9from fontTools.ttLib import TTLibError
10from . import DefaultTable
11from . import otTables
12import struct
13import logging
14
15
16log = logging.getLogger(__name__)
17
18from .otBase import BaseTTXConverter
19
20
21class table__a_v_a_r(BaseTTXConverter):
22    """Axis Variations Table
23
24    This class represents the ``avar`` table of a variable font. The object has one
25    substantive attribute, ``segments``, which maps axis tags to a segments dictionary::
26
27        >>> font["avar"].segments   # doctest: +SKIP
28        {'wght': {-1.0: -1.0,
29          0.0: 0.0,
30          0.125: 0.11444091796875,
31          0.25: 0.23492431640625,
32          0.5: 0.35540771484375,
33          0.625: 0.5,
34          0.75: 0.6566162109375,
35          0.875: 0.81927490234375,
36          1.0: 1.0},
37         'ital': {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}}
38
39    Notice that the segments dictionary is made up of normalized values. A valid
40    ``avar`` segment mapping must contain the entries ``-1.0: -1.0, 0.0: 0.0, 1.0: 1.0``.
41    fontTools does not enforce this, so it is your responsibility to ensure that
42    mappings are valid.
43    """
44
45    dependencies = ["fvar"]
46
47    def __init__(self, tag=None):
48        super().__init__(tag)
49        self.segments = {}
50
51    def compile(self, ttFont):
52        axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
53        if not hasattr(self, "table"):
54            self.table = otTables.avar()
55        if not hasattr(self.table, "Reserved"):
56            self.table.Reserved = 0
57        self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
58            self, "minorVersion", 0
59        )
60        self.table.AxisCount = len(axisTags)
61        self.table.AxisSegmentMap = []
62        for axis in axisTags:
63            mappings = self.segments[axis]
64            segmentMap = otTables.AxisSegmentMap()
65            segmentMap.PositionMapCount = len(mappings)
66            segmentMap.AxisValueMap = []
67            for key, value in sorted(mappings.items()):
68                valueMap = otTables.AxisValueMap()
69                valueMap.FromCoordinate = key
70                valueMap.ToCoordinate = value
71                segmentMap.AxisValueMap.append(valueMap)
72            self.table.AxisSegmentMap.append(segmentMap)
73        return super().compile(ttFont)
74
75    def decompile(self, data, ttFont):
76        super().decompile(data, ttFont)
77        assert self.table.Version >= 0x00010000
78        self.majorVersion = self.table.Version >> 16
79        self.minorVersion = self.table.Version & 0xFFFF
80        axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
81        for axis in axisTags:
82            self.segments[axis] = {}
83        for axis, segmentMap in zip(axisTags, self.table.AxisSegmentMap):
84            segments = self.segments[axis] = {}
85            for segment in segmentMap.AxisValueMap:
86                segments[segment.FromCoordinate] = segment.ToCoordinate
87
88    def toXML(self, writer, ttFont):
89        writer.simpletag(
90            "version",
91            major=getattr(self, "majorVersion", 1),
92            minor=getattr(self, "minorVersion", 0),
93        )
94        writer.newline()
95        axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
96        for axis in axisTags:
97            writer.begintag("segment", axis=axis)
98            writer.newline()
99            for key, value in sorted(self.segments[axis].items()):
100                key = fl2str(key, 14)
101                value = fl2str(value, 14)
102                writer.simpletag("mapping", **{"from": key, "to": value})
103                writer.newline()
104            writer.endtag("segment")
105            writer.newline()
106        if getattr(self, "majorVersion", 1) >= 2:
107            if self.table.VarIdxMap:
108                self.table.VarIdxMap.toXML(writer, ttFont, name="VarIdxMap")
109            if self.table.VarStore:
110                self.table.VarStore.toXML(writer, ttFont)
111
112    def fromXML(self, name, attrs, content, ttFont):
113        if not hasattr(self, "table"):
114            self.table = otTables.avar()
115        if not hasattr(self.table, "Reserved"):
116            self.table.Reserved = 0
117        if name == "version":
118            self.majorVersion = safeEval(attrs["major"])
119            self.minorVersion = safeEval(attrs["minor"])
120            self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
121                self, "minorVersion", 0
122            )
123        elif name == "segment":
124            axis = attrs["axis"]
125            segment = self.segments[axis] = {}
126            for element in content:
127                if isinstance(element, tuple):
128                    elementName, elementAttrs, _ = element
129                    if elementName == "mapping":
130                        fromValue = str2fl(elementAttrs["from"], 14)
131                        toValue = str2fl(elementAttrs["to"], 14)
132                        if fromValue in segment:
133                            log.warning(
134                                "duplicate entry for %s in axis '%s'", fromValue, axis
135                            )
136                        segment[fromValue] = toValue
137        else:
138            super().fromXML(name, attrs, content, ttFont)
139