map.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from fontTools.varLib.models import normalizeValue
  2. def _denormalize(v, triplet):
  3. if v >= 0:
  4. return triplet[1] + v * (triplet[2] - triplet[1])
  5. else:
  6. return triplet[1] + v * (triplet[1] - triplet[0])
  7. def map(
  8. font, location, *, inputNormalized=False, outputNormalized=False, dropZeroes=False
  9. ):
  10. if "fvar" not in font:
  11. return None
  12. fvar = font["fvar"]
  13. axes = {a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in fvar.axes}
  14. unknownAxes = sorted(tag for tag in location if tag not in axes)
  15. if unknownAxes:
  16. raise ValueError(f"Unknown axis tag(s): {', '.join(unknownAxes)}")
  17. if not inputNormalized:
  18. location = {
  19. tag: normalizeValue(value, axes[tag]) for tag, value in location.items()
  20. }
  21. if "avar" in font:
  22. location = font["avar"].renormalizeLocation(location, font, dropZeroes)
  23. if not outputNormalized:
  24. location = {
  25. tag: _denormalize(value, axes[tag]) for tag, value in location.items()
  26. }
  27. return location
  28. def main(args=None):
  29. """Map variation coordinates through the `avar` table."""
  30. from fontTools.ttLib import TTFont
  31. import argparse
  32. if args is None:
  33. import sys
  34. args = sys.argv[1:]
  35. parser = argparse.ArgumentParser(
  36. "fonttools varLib.avar.map",
  37. description="Map variation coordinates through the `avar` table.",
  38. )
  39. parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
  40. parser.add_argument(
  41. "coords",
  42. metavar="[AXIS=value...]",
  43. help="Coordinates to map, e.g. 'wght=700 wdth=75'.",
  44. nargs="*",
  45. default=None,
  46. )
  47. parser.add_argument(
  48. "-f", action="store_true", help="Do not omit axes at default location."
  49. )
  50. parser.add_argument(
  51. "-i", action="store_true", help="Input coordinates are normalized (-1..1)."
  52. )
  53. parser.add_argument(
  54. "-o", action="store_true", help="Output coordinates as normalized (-1..1)."
  55. )
  56. options = parser.parse_args(args)
  57. if not options.coords:
  58. parser.error(
  59. "No coordinates provided. Please specify at least one axis coordinate (e.g., wght=500)"
  60. )
  61. if options.font.endswith(".designspace"):
  62. from .build import build
  63. font = TTFont()
  64. build(font, options.font)
  65. else:
  66. font = TTFont(options.font)
  67. if "fvar" not in font:
  68. parser.error(f"Font '{options.font}' does not contain an 'fvar' table.")
  69. location = {}
  70. for item in options.coords:
  71. tag, sep, value = item.partition("=")
  72. if not sep or not tag or not value:
  73. parser.error(
  74. f"Invalid coordinate {item!r}. Expected AXIS=value, e.g. wght=500"
  75. )
  76. try:
  77. location[tag] = float(value)
  78. except ValueError:
  79. parser.error(
  80. f"Invalid coordinate value in {item!r}. Expected a number after '='"
  81. )
  82. try:
  83. mapped = map(
  84. font,
  85. location,
  86. inputNormalized=options.i,
  87. outputNormalized=options.o,
  88. dropZeroes=not options.f,
  89. )
  90. except ValueError as e:
  91. parser.error(str(e))
  92. assert mapped is not None
  93. for tag in mapped:
  94. v = mapped[tag]
  95. v = int(v) if v == int(v) else v
  96. print(f"{tag}={v:g}")
  97. if __name__ == "__main__":
  98. import sys
  99. sys.exit(main())