common_tests.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. # coding=UTF-8
  2. #
  3. # Copyright 2015 Google Inc. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Common tests for different targets."""
  17. import glob
  18. import unittest
  19. from fontTools import ttLib
  20. from nototools import coverage
  21. from nototools import font_data
  22. import freetype
  23. import layout
  24. import roboto_data
  25. def get_rendered_char_height(font_filename, font_size, char, target='mono'):
  26. if target == 'mono':
  27. render_params = freetype.FT_LOAD_TARGET_MONO
  28. elif target == 'lcd':
  29. render_params = freetype.FT_LOAD_TARGET_LCD
  30. render_params |= freetype.FT_LOAD_RENDER
  31. face = freetype.Face(font_filename)
  32. face.set_char_size(font_size*64)
  33. face.load_char(char, render_params)
  34. return face.glyph.bitmap.rows
  35. def load_fonts(patterns, expected_count=None):
  36. """Load all fonts specified in the patterns.
  37. Also assert that the number of the fonts found is exactly the same as
  38. expected_count."""
  39. all_font_files = []
  40. for pattern in patterns:
  41. all_font_files += glob.glob(pattern)
  42. all_fonts = [ttLib.TTFont(font) for font in all_font_files]
  43. if expected_count:
  44. assert len(all_font_files) == expected_count
  45. return all_font_files, all_fonts
  46. class FontTest(unittest.TestCase):
  47. """Parent class for all font tests."""
  48. loaded_fonts = None
  49. class TestItalicAngle(FontTest):
  50. """Test the italic angle of fonts."""
  51. def setUp(self):
  52. _, self.fonts = self.loaded_fonts
  53. def test_italic_angle(self):
  54. """Tests the italic angle of fonts to be correct."""
  55. for font in self.fonts:
  56. post_table = font['post']
  57. if 'Italic' in font_data.font_name(font):
  58. expected_angle = -12.0
  59. else:
  60. expected_angle = 0.0
  61. self.assertEqual(post_table.italicAngle, expected_angle)
  62. class TestMetaInfo(FontTest):
  63. """Test various meta information."""
  64. def setUp(self):
  65. _, self.fonts = self.loaded_fonts
  66. def test_mac_style(self):
  67. """Tests the macStyle of the fonts to be correct.
  68. Bug: https://code.google.com/a/google.com/p/roboto/issues/detail?id=8
  69. """
  70. for font in self.fonts:
  71. font_name = font_data.font_name(font)
  72. bold = ('Bold' in font_name) or ('Black' in font_name)
  73. italic = 'Italic' in font_name
  74. expected_mac_style = (italic << 1) | bold
  75. self.assertEqual(font['head'].macStyle, expected_mac_style)
  76. def test_fs_type(self):
  77. """Tests the fsType of the fonts to be 0.
  78. fsType of 0 marks the font free for installation, embedding, etc.
  79. Bug: https://code.google.com/a/google.com/p/roboto/issues/detail?id=29
  80. """
  81. for font in self.fonts:
  82. self.assertEqual(font['OS/2'].fsType, 0)
  83. def test_vendor_id(self):
  84. """Tests the vendor ID of the fonts to be 'GOOG'."""
  85. for font in self.fonts:
  86. self.assertEqual(font['OS/2'].achVendID, 'GOOG')
  87. def test_us_weight(self):
  88. "Tests the usWeight of the fonts to be correct."""
  89. for font in self.fonts:
  90. weight = roboto_data.extract_weight_name(font_data.font_name(font))
  91. expected_numeric_weight = roboto_data.WEIGHTS[weight]
  92. self.assertEqual(
  93. font['OS/2'].usWeightClass,
  94. expected_numeric_weight)
  95. def test_version_numbers(self):
  96. "Tests the two version numbers of the font to be correct."""
  97. for font in self.fonts:
  98. build_number = roboto_data.get_build_number()
  99. expected_version = '2.' + build_number
  100. version = font_data.font_version(font)
  101. usable_part_of_version = version.split(';')[0]
  102. self.assertEqual(usable_part_of_version,
  103. 'Version ' + expected_version)
  104. revision = font_data.printable_font_revision(font, accuracy=5)
  105. self.assertEqual(revision, expected_version)
  106. class TestNames(FontTest):
  107. """Tests various strings in the name table."""
  108. def setUp(self):
  109. _, self.fonts = self.loaded_fonts
  110. self.condensed_family_name = self.family_name + ' Condensed'
  111. self.names = []
  112. for font in self.fonts:
  113. self.names.append(font_data.get_name_records(font))
  114. def test_copyright(self):
  115. """Tests the copyright message."""
  116. for records in self.names:
  117. self.assertEqual(
  118. records[0],
  119. 'Copyright 2011 Google Inc. All Rights Reserved.')
  120. def test_family_name(self):
  121. """Tests the family name."""
  122. for records in self.names:
  123. self.assertIn(records[1],
  124. [self.family_name, self.condensed_family_name])
  125. if 16 in records:
  126. self.assertEqual(records[16], records[1])
  127. def test_postscript_name_for_spaces(self):
  128. """Tests that there are no spaces in PostScript names."""
  129. for records in self.names:
  130. self.assertFalse(' ' in records[6])
  131. class TestDigitWidths(FontTest):
  132. """Tests the width of digits."""
  133. def setUp(self):
  134. self.font_files, self.fonts = self.loaded_fonts
  135. self.digits = [
  136. 'zero', 'one', 'two', 'three', 'four',
  137. 'five', 'six', 'seven', 'eight', 'nine']
  138. def test_digit_widths(self):
  139. """Tests all decimal digits to make sure they have the same width."""
  140. for font in self.fonts:
  141. hmtx_table = font['hmtx']
  142. widths = [hmtx_table[digit][0] for digit in self.digits]
  143. self.assertEqual(len(set(widths)), 1)
  144. def test_superscript_digits(self):
  145. """Tests that 'numr' features maps digits to Unicode superscripts."""
  146. ascii_digits = '0123456789'
  147. superscript_digits = u'⁰¹²³⁴⁵⁶⁷⁸⁹'
  148. for font_file in self.font_files:
  149. numr_glyphs = layout.get_advances(
  150. ascii_digits, font_file, '--features=numr')
  151. superscript_glyphs = layout.get_advances(
  152. superscript_digits, font_file)
  153. self.assertEqual(superscript_glyphs, numr_glyphs)
  154. class TestCharacterCoverage(FontTest):
  155. """Tests character coverage."""
  156. def setUp(self):
  157. _, self.fonts = self.loaded_fonts
  158. self.LEGACY_PUA = frozenset({0xEE01, 0xEE02, 0xF6C3})
  159. def test_inclusion_of_legacy_pua(self):
  160. """Tests that legacy PUA characters remain in the fonts."""
  161. for font in self.fonts:
  162. charset = coverage.character_set(font)
  163. for char in self.LEGACY_PUA:
  164. self.assertIn(char, charset)
  165. def test_non_inclusion_of_other_pua(self):
  166. """Tests that there are not other PUA characters except legacy ones."""
  167. for font in self.fonts:
  168. charset = coverage.character_set(font)
  169. pua_chars = {
  170. char for char in charset
  171. if 0xE000 <= char <= 0xF8FF or 0xF0000 <= char <= 0x10FFFF}
  172. self.assertTrue(pua_chars <= self.LEGACY_PUA)
  173. def test_lack_of_unassigned_chars(self):
  174. """Tests that unassigned characters are not in the fonts."""
  175. for font in self.fonts:
  176. charset = coverage.character_set(font)
  177. self.assertNotIn(0x2072, charset)
  178. self.assertNotIn(0x2073, charset)
  179. self.assertNotIn(0x208F, charset)
  180. def test_inclusion_of_sound_recording_copyright(self):
  181. """Tests that sound recording copyright symbol is in the fonts."""
  182. for font in self.fonts:
  183. charset = coverage.character_set(font)
  184. self.assertIn(
  185. 0x2117, charset, # SOUND RECORDING COPYRIGHT
  186. 'U+2117 not found in %s.' % font_data.font_name(font))
  187. class TestLigatures(FontTest):
  188. """Tests formation or lack of formation of ligatures."""
  189. def setUp(self):
  190. self.fontfiles, _ = self.loaded_fonts
  191. def test_lack_of_ff_ligature(self):
  192. """Tests that the ff ligature is not formed by default."""
  193. for fontfile in self.fontfiles:
  194. advances = layout.get_advances('ff', fontfile)
  195. self.assertEqual(len(advances), 2)
  196. def test_st_ligatures(self):
  197. """Tests that st ligatures are formed by dlig."""
  198. for fontfile in self.fontfiles:
  199. for combination in [u'st', u'ſt']:
  200. normal = layout.get_glyphs(combination, fontfile)
  201. ligated = layout.get_glyphs(
  202. combination, fontfile, '--features=dlig')
  203. self.assertTrue(len(normal) == 2 and len(ligated) == 1)
  204. class TestFeatures(FontTest):
  205. """Tests typographic features."""
  206. def setUp(self):
  207. self.fontfiles, _ = self.loaded_fonts
  208. def test_smcp_coverage(self):
  209. """Tests that smcp is supported for our required set."""
  210. with open('res/smcp_requirements.txt') as smcp_reqs_file:
  211. smcp_reqs_lis = []
  212. for line in smcp_reqs_file.readlines():
  213. line = line[:line.index(' #')]
  214. smcp_reqs_list.append(unichr(int(line, 16)))
  215. for fontfile in self.fontfiles:
  216. chars_with_no_smcp = []
  217. for char in smcp_reqs_list:
  218. normal = layout.get_glyphs(char, fontfile)
  219. smcp = layout.get_glyphs(char, fontfile, '--features=smcp')
  220. if normal == smcp:
  221. chars_with_no_smcp.append(char)
  222. self.assertEqual(
  223. chars_with_no_smcp, [],
  224. ("smcp feature is not applied to '%s'" %
  225. u''.join(chars_with_no_smcp).encode('UTF-8')))
  226. class TestVerticalMetrics(FontTest):
  227. """Test the vertical metrics of fonts."""
  228. def setUp(self):
  229. _, self.fonts = self.loaded_fonts
  230. def test_ymin_ymax(self):
  231. """Tests yMin and yMax to be equal to Roboto v1 values.
  232. Android requires this, and web fonts expect this.
  233. """
  234. for font in self.fonts:
  235. head_table = font['head']
  236. self.assertEqual(head_table.yMin, -555)
  237. self.assertEqual(head_table.yMax, 2163)
  238. def test_hhea_table_metrics(self):
  239. """Tests ascent, descent, and lineGap to be equal to Roboto v1 values.
  240. """
  241. for font in self.fonts:
  242. hhea_table = font['hhea']
  243. self.assertEqual(hhea_table.descent, -500)
  244. self.assertEqual(hhea_table.ascent, 1900)
  245. self.assertEqual(hhea_table.lineGap, 0)