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