vf2s.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # ========================================================
  4. # vf2s.py
  5. # Copyright 2019 Google, LLC
  6. # Apache License, v2.0
  7. #
  8. # A variable font to static font instance generator
  9. # + unique name table writer
  10. # =======================================================
  11. # PyInstaller build for macOS architecture
  12. #
  13. # pyinstaller -c --onefile --hidden-import=fontTools --clean --distpath="dist/macos64" -n vf2s vf2s.py
  14. import os
  15. import sys
  16. import argparse
  17. from fontTools.ttLib import TTFont
  18. from fontTools.varLib.mutator import instantiateVariableFont
  19. SCRIPT_VERSION = "v0.6.0"
  20. # Define font name for font path and name table re-writes
  21. FONTNAME = "Open Sans"
  22. # Default axis values (when not explicitly included on the command line)
  23. DEFAULT_WEIGHT = 400
  24. DEFAULT_WIDTH = 100
  25. # Min/Max of design axis range values for validity checking command line entries
  26. WEIGHT_MIN = 300
  27. WEIGHT_MAX = 800
  28. WIDTH_MIN = 60
  29. WIDTH_MAX = 100
  30. # macOS rendering bit
  31. # used for workaround fix for fontTools varLib.mutator bug
  32. MAC_OVERLAP_RENDERING_BIT = 1 << 6
  33. def set_mac_overlap_rendering_bit(font):
  34. """Sets the bit6 macOS overlap rendering bit."""
  35. glyf = font["glyf"]
  36. for glyph_name in glyf.keys():
  37. glyph = glyf[glyph_name]
  38. # Only needs to be set for glyphs with contours
  39. if glyph.numberOfContours > 0:
  40. glyph.flags[0] |= MAC_OVERLAP_RENDERING_BIT
  41. return font
  42. def main():
  43. parser = argparse.ArgumentParser(
  44. description="A variable font to static instance generator."
  45. )
  46. parser.add_argument(
  47. "--weight",
  48. default=DEFAULT_WEIGHT,
  49. type=int,
  50. help="Weight axis value ({}-{})".format(WEIGHT_MIN, WEIGHT_MAX),
  51. ) # wght
  52. parser.add_argument(
  53. "--width",
  54. default=DEFAULT_WIDTH,
  55. type=int,
  56. help="Width axis value ({}-{})".format(WIDTH_MIN, WIDTH_MAX),
  57. ) # opsz
  58. parser.add_argument(
  59. "-v",
  60. "--version",
  61. action="version",
  62. version="vf2s {}".format(SCRIPT_VERSION),
  63. help="Display application version",
  64. )
  65. parser.add_argument("path", help="Variable font path")
  66. args = parser.parse_args()
  67. instance_location = {}
  68. # axis value validity testing and location definitions
  69. if args.weight is not None:
  70. if args.weight < WEIGHT_MIN or args.weight > WEIGHT_MAX:
  71. sys.stderr.write(
  72. "Weight axis value must be in the range {} - {}{}".format(
  73. WEIGHT_MIN, WEIGHT_MAX, os.linesep
  74. )
  75. )
  76. sys.exit(1)
  77. else:
  78. instance_location["wght"] = args.weight
  79. if args.width is not None:
  80. if args.width < WIDTH_MIN or args.width > WIDTH_MAX:
  81. sys.stderr.write(
  82. "Width axis value must be in the range {} - {}{}".format(
  83. WIDTH_MIN, WIDTH_MAX, os.linesep
  84. )
  85. )
  86. sys.exit(1)
  87. else:
  88. instance_location["wdth"] = args.width
  89. # variable font path check
  90. if not os.path.exists(args.path):
  91. sys.stderr.write(
  92. "{} does not appear to be a valid path to a variable font{}".format(
  93. args.path, os.linesep
  94. )
  95. )
  96. sys.exit(1)
  97. # instantiate the variable font with the requested values
  98. font = TTFont(args.path)
  99. instantiateVariableFont(font, instance_location, inplace=True)
  100. # ---------------------------------------------------------------
  101. # rewrite name table records with new name values for A/B testing
  102. # ---------------------------------------------------------------
  103. namerecord_list = font["name"].names
  104. # create a name string from the axis location parameters
  105. axis_param_string = ""
  106. for axis_value in instance_location:
  107. axis_param_string += "{}{}".format(axis_value, instance_location[axis_value])
  108. # map axis name to an abbreviation in font path and name table record string values
  109. axis_param_string = axis_param_string.replace("wght", "wg")
  110. axis_param_string = axis_param_string.replace("wdth", "wd")
  111. # name definitions (NEEDS TO BE MODIFIED TO SUPPORT STYLES OTHER THAN REGULAR)
  112. nameID1_name = "{} {}".format(FONTNAME, axis_param_string)
  113. nameID4_name = "{} {} Regular".format(FONTNAME, axis_param_string)
  114. nameID6_name = "{}-{}-Regular".format(FONTNAME, axis_param_string)
  115. outfont_name = "{}-{}-Regular.ttf".format(FONTNAME, axis_param_string)
  116. outfont_path = os.path.join(
  117. os.path.dirname(os.path.abspath(args.path)), outfont_name
  118. )
  119. for record in namerecord_list:
  120. if record.nameID == 1:
  121. record.string = nameID1_name
  122. elif record.nameID == 4:
  123. record.string = nameID4_name
  124. elif record.nameID == 6:
  125. record.string = nameID6_name
  126. # Set the macOS overlap rendering bit
  127. # addresses bug in overlap path rendering on macOS web browsers
  128. font = set_mac_overlap_rendering_bit(font)
  129. # write the instance font to disk
  130. try:
  131. font.save(outfont_path)
  132. print("[New instance]: {}".format(outfont_path))
  133. except Exception as e:
  134. sys.stderr.write(
  135. "Failed to write font file {} with error: {}{}".format(
  136. outfont_name, str(e), os.linesep
  137. )
  138. )
  139. sys.exit(1)
  140. if __name__ == "__main__":
  141. main()