font-tables.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # ------------------------------------------------------------------------------
  4. # font-tables.py
  5. # Copyright 2015 Christopher Simpkins
  6. # MIT license
  7. # ------------------------------------------------------------------------------
  8. import sys
  9. import os
  10. import os.path
  11. import hashlib
  12. from fontTools import ttLib
  13. # TODO: expand Python objects within the table values
  14. # TODO: modify TTFA table read to create YAML format from the strings
  15. def main(fontpaths):
  16. """The main function creates a YAML formatted report on the OpenType tables in one
  17. or more fonts included in the fontpaths function parameter.
  18. :param fontpaths: """
  19. # create a report directory, gracefully fail if it already exists
  20. if not os.path.isdir("otreports"):
  21. os.mkdir("otreports")
  22. # iterate through fonts requested in the command
  23. for fontpath in fontpaths:
  24. if os.path.isfile(fontpath):
  25. # create a fonttools TTFont object using the fontpath
  26. tt = ttLib.TTFont(fontpath)
  27. print("Processing " + fontpath + "...")
  28. # define the outfile path
  29. basename = os.path.basename(fontpath)
  30. basefilename = basename + "-TABLES.yaml"
  31. outfilepath = os.path.join("otreports", basefilename)
  32. # read the font data and create a SHA1 hash digest for the report
  33. fontdata = read_bin(fontpath)
  34. hash_digest = hashlib.sha1(fontdata).hexdigest()
  35. # report strings for file name and SHA1 digest
  36. report_header_string = "FILE: " + fontpath + "\n"
  37. report_header_string += "SHA1: " + hash_digest + "\n\n"
  38. # open outfile write stream, create file, write name + SHA1 header
  39. with open(outfilepath, "w") as writer:
  40. writer.write(report_header_string)
  41. # iterate through the OpenType tables, write table fields in a newline delimited format with YAML syntax
  42. for table in tt.keys():
  43. table_dict = tt[table].__dict__
  44. if len(table_dict) > 0:
  45. table_string = yaml_formatter(table, table_dict)
  46. with open(outfilepath, 'a') as appender:
  47. appender.write(table_string)
  48. print("[✓] " + table)
  49. else:
  50. print("[E] " + table) # indicate missing table data in standard output, do not write to YAML file
  51. print(fontpath + " table report is available in " + outfilepath + "\n")
  52. else: # not a valid filepath
  53. sys.stderr.write("Error: '" + fontpath + "' was not found. Please check the filepath.\n\n")
  54. def yaml_formatter(table_name, table_dict):
  55. """Creates a YAML formatted string for OpenType table font reports"""
  56. # define the list of tables that require table-specific processing
  57. special_table_list = ['name', 'OS/2', 'TTFA']
  58. if table_name in special_table_list:
  59. if table_name == "name":
  60. return name_yaml_formatter(table_dict)
  61. elif table_name == "OS/2":
  62. return os2_yaml_formatter(table_dict)
  63. elif table_name == "TTFA":
  64. return ttfa_yaml_formatter(table_dict)
  65. else:
  66. table_string = table_name.strip() + ": {\n"
  67. for field in table_dict.keys():
  68. table_string = table_string + (" " * 4) + field + ": " + str(table_dict[field]) + ',\n'
  69. table_string += "}\n\n"
  70. return table_string
  71. def name_yaml_formatter(table_dict):
  72. """Formats the YAML table string for OpenType name tables"""
  73. table_string = "name: {\n"
  74. namerecord_list = table_dict['names']
  75. for record in namerecord_list:
  76. if record.__dict__['langID'] == 0:
  77. record_name = str(record.__dict__['nameID'])
  78. else:
  79. record_name = str(record.__dict__['nameID']) + "u"
  80. record_field = (" " * 4) + "nameID" + record_name
  81. table_string = table_string + record_field + ": " + str(record.__dict__) + ",\n"
  82. table_string = table_string + "}\n\n"
  83. return table_string
  84. def os2_yaml_formatter(table_dict):
  85. """Formats the YAML table string for OpenType OS/2 tables"""
  86. table_string = "OS/2: {\n"
  87. for field in table_dict.keys():
  88. if field == "panose":
  89. table_string = table_string + (" "*4) + field + ": {\n"
  90. panose_string = ""
  91. panose_dict = table_dict['panose'].__dict__
  92. for panose_field in panose_dict.keys():
  93. panose_string = panose_string + (" " * 8) + panose_field[1:] + ": " + str(panose_dict[panose_field]) + ",\n"
  94. table_string = table_string + panose_string + (" " * 4) + "}\n"
  95. else:
  96. table_string = table_string + (" "*4) + field + ": " + str(table_dict[field]) + ',\n'
  97. table_string = table_string + "}\n\n"
  98. return table_string
  99. def ttfa_yaml_formatter(table_dict):
  100. """Formats the YAML table string for the ttfautohint TTFA table"""
  101. data_string = table_dict['data'].strip()
  102. data_list = data_string.split('\n') # split on newlines in the string
  103. table_string = "TTFA: {\n"
  104. for definition_string in data_list:
  105. definition_list = definition_string.split("=")
  106. field = definition_list[0].strip()
  107. if len(definition_list) > 1:
  108. value = definition_list[1].strip()
  109. else:
  110. value = "''"
  111. table_string = table_string + (" " * 4) + field + ": " + value + ",\n"
  112. table_string = table_string + "}\n\n"
  113. return table_string
  114. def read_bin(filepath):
  115. """read_bin function reads filepath parameter as binary data and returns raw binary to calling code"""
  116. try:
  117. with open(filepath, 'rb') as bin_reader:
  118. data = bin_reader.read()
  119. return data
  120. except Exception as e:
  121. sys.stderr.write("Error: Unable to read file " + filepath + ". " + str(e))
  122. if __name__ == '__main__':
  123. main(sys.argv[1:])