_a_v_a_r.py 5.2 KB

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