diff options
author | Roozbeh Pournader <roozbeh@google.com> | 2014-07-31 20:09:19 -0700 |
---|---|---|
committer | James Godfrey-Kittle <jamesgk@google.com> | 2015-04-16 12:16:04 -0700 |
commit | 985c16569167e1cc7d6d1dcf15df77135e67913f (patch) | |
tree | f12335c6e41e83360acd1eca05a3f12691f0ed44 /scripts/coverage_test.py | |
parent | 8f7caa4ca489caca851ec0d8426d7857b6d7a051 (diff) |
Add scripts for testing minimal character coverage.
Diffstat (limited to 'scripts/coverage_test.py')
-rwxr-xr-x | scripts/coverage_test.py | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/scripts/coverage_test.py b/scripts/coverage_test.py new file mode 100755 index 0000000..bb25041 --- /dev/null +++ b/scripts/coverage_test.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +"""Routines for checking character coverage of Roboto fonts. + +This scripts takes the name of the directory where the fonts are and checks +that they cover all characters required in the Roboto extension contract. + +The data is in res/char_requirements.tsv. +""" + +__author__ = ( + "roozbeh@google.com (Roozbeh Pournader) and " + "cibu@google.com (Cibu Johny)") + +import sys + +import glob + +from fontTools import ttLib +from nototools import coverage +from nototools import font_data +from nototools import unicode_data + + +def load_fonts(): + """Load all fonts built for Android.""" + all_fonts = (glob.glob('out/RobotoTTF/*.ttf') + + glob.glob('out/RobotoCondensedTTF/*.ttf')) + all_fonts = [ttLib.TTFont(font) for font in all_fonts] + return all_fonts + + +def _character_name(code): + """Returns the printable name of a character.""" + return unicode_data.name(unichr(code), '<Unassigned>') + + +def _print_char(code, additional_info=None): + """Print a Unicode character as code and name and perhaps extra info.""" + sys.stdout.write('U+%04X %s' % (code, _character_name(code))) + + if additional_info is not None: + sys.stdout.write('\t' + additional_info) + + sys.stdout.write('\n') + + +def _range_string_to_set(range_str): + """Convert a range encoding in a string to a set.""" + if '..' in range_str: + range_start, range_end = range_str.split('..') + range_start = int(range_start, 16) + range_end = int(range_end, 16) + return set(range(range_start, range_end+1)) + else: + return {int(range_str, 16)} + + +def _multiple_range_string_to_set(ranges_str): + """Convert a string of multiple ranges to a set.""" + char_set = set() + for range_str in ranges_str.split(', '): + if range_str.startswith('and '): + range_str = range_str[4:] # drop the 'and ' + char_set.update(_range_string_to_set(range_str)) + return char_set + + +def _defined_characters_in_range(range_str): + """Given a range string, returns defined Unicode characters in the range.""" + characters = set() + for code in _range_string_to_set(range_str): + if unicode_data.is_defined(code): + characters.add(code) + return characters + + +_EXCEPTION_STARTER = 'Everything except ' + + +def _find_required_chars(block_range, full_coverage_required, exceptions): + """Finds required coverage based on a row of the spreadsheet.""" + chars_defined_in_block = _defined_characters_in_range(block_range) + if full_coverage_required: + return chars_defined_in_block + else: + if not exceptions: + return set() + if exceptions.startswith(_EXCEPTION_STARTER): + exceptions = exceptions[len(_EXCEPTION_STARTER):] + chars_to_exclude = _multiple_range_string_to_set(exceptions) + return chars_defined_in_block - chars_to_exclude + else: + chars_to_limit_to = _multiple_range_string_to_set(exceptions) + return chars_defined_in_block & chars_to_limit_to + + +def main(): + """Checkes the coverage of all Roboto fonts.""" + with open('res/char_requirements.tsv') as char_reqs_file: + char_reqs_data = char_reqs_file.read() + + # The format of the data to be parsed is like the following: + # General Punctuation\t2000..206F\t111\t35\t54\t0\tEverything except 2028..202E, 2060..2064, and 2066..206F + # Currency Symbols\t20A0..20CF\t29\t5\t24\t1\t + required_set = set() + for line in char_reqs_data.split('\n'): + if line.startswith('#'): # Skip comment lines + continue + line = line.split('\t') + if not line[0]: + continue # Skip the first line and empty lines + block_range = line[1] + full_coverage_required = (line[5] == '1') + exceptions = line[6] + required_set.update( + _find_required_chars(block_range, + full_coverage_required, + exceptions)) + + # Skip ASCII and C1 controls + required_set -= set(range(0, 0x20) + range(0x7F, 0xA0)) + + missing_char_found = False + for font in load_fonts(): + font_coverage = coverage.character_set(font) + missing_chars = required_set - font_coverage + if missing_chars: + missing_char_found = True + font_name = font_data.font_name(font) + print 'Characters missing from %s:' % font_name + for char in sorted(missing_chars): + _print_char(char) + print + + if missing_char_found: + sys.exit(1) + +if __name__ == '__main__': + main() |