1import os 2 3if os.environ.get("SDL_VIDEODRIVER") == "dummy": 4 __tags__ = ("ignore", "subprocess_ignore") 5 6import unittest 7import sys 8import ctypes 9import weakref 10import gc 11import platform 12 13try: 14 import pathlib 15except ImportError: 16 pathlib = None 17 18IS_PYPY = "PyPy" == platform.python_implementation() 19 20 21try: 22 from pygame.tests.test_utils import arrinter 23except NameError: 24 pass 25 26import pygame 27 28try: 29 import pygame.freetype as ft 30except ImportError: 31 ft = None 32from pygame.compat import as_unicode, bytes_, unichr_, unicode_ 33 34 35FONTDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures", "fonts") 36 37 38def nullfont(): 39 """return an uninitialized font instance""" 40 return ft.Font.__new__(ft.Font) 41 42 43max_point_size_FX6 = 0x7FFFFFFF 44max_point_size = max_point_size_FX6 >> 6 45max_point_size_f = max_point_size_FX6 * 0.015625 46 47 48def surf_same_image(a, b): 49 """Return True if a's pixel buffer is identical to b's""" 50 51 a_sz = a.get_height() * a.get_pitch() 52 b_sz = b.get_height() * b.get_pitch() 53 if a_sz != b_sz: 54 return False 55 a_bytes = ctypes.string_at(a._pixels_address, a_sz) 56 b_bytes = ctypes.string_at(b._pixels_address, b_sz) 57 return a_bytes == b_bytes 58 59 60class FreeTypeFontTest(unittest.TestCase): 61 62 _fixed_path = os.path.join(FONTDIR, "test_fixed.otf") 63 _sans_path = os.path.join(FONTDIR, "test_sans.ttf") 64 _mono_path = os.path.join(FONTDIR, "PyGameMono.otf") 65 _bmp_8_75dpi_path = os.path.join(FONTDIR, "PyGameMono-8.bdf") 66 _bmp_18_75dpi_path = os.path.join(FONTDIR, "PyGameMono-18-75dpi.bdf") 67 _bmp_18_100dpi_path = os.path.join(FONTDIR, "PyGameMono-18-100dpi.bdf") 68 _TEST_FONTS = {} 69 70 @classmethod 71 def setUpClass(cls): 72 ft.init() 73 74 # Setup the test fonts. 75 76 # Inconsolata is an open-source font designed by Raph Levien. 77 # Licensed under the Open Font License. 78 # http://www.levien.com/type/myfonts/inconsolata.html 79 cls._TEST_FONTS["fixed"] = ft.Font(cls._fixed_path) 80 81 # Liberation Sans is an open-source font designed by Steve Matteson. 82 # Licensed under the GNU GPL. 83 # https://fedorahosted.org/liberation-fonts/ 84 cls._TEST_FONTS["sans"] = ft.Font(cls._sans_path) 85 86 # A scalable mono test font made for pygame. It contains only 87 # a few glyphs: '\0', 'A', 'B', 'C', and U+13079. 88 # It also contains two bitmap sizes: 8.0 X 8.0 and 19.0 X 19.0. 89 cls._TEST_FONTS["mono"] = ft.Font(cls._mono_path) 90 91 # A fixed size bitmap mono test font made for pygame. 92 # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079. 93 # The size is 8.0 X 8.0. 94 cls._TEST_FONTS["bmp-8-75dpi"] = ft.Font(cls._bmp_8_75dpi_path) 95 96 # A fixed size bitmap mono test font made for pygame. 97 # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079. 98 # The size is 8.0 X 8.0. 99 cls._TEST_FONTS["bmp-18-75dpi"] = ft.Font(cls._bmp_18_75dpi_path) 100 101 # A fixed size bitmap mono test font made for pygame. 102 # It contains only a few glyphs: '\0', 'A', 'B', 'C', and U+13079. 103 # The size is 8.0 X 8.0. 104 cls._TEST_FONTS["bmp-18-100dpi"] = ft.Font(cls._bmp_18_100dpi_path) 105 106 @classmethod 107 def tearDownClass(cls): 108 ft.quit() 109 110 def test_freetype_defaultfont(self): 111 font = ft.Font(None) 112 self.assertEqual(font.name, "FreeSans") 113 114 def test_freetype_Font_init(self): 115 116 self.assertRaises(IOError, ft.Font, os.path.join(FONTDIR, "nonexistant.ttf")) 117 118 f = self._TEST_FONTS["sans"] 119 self.assertIsInstance(f, ft.Font) 120 121 f = self._TEST_FONTS["fixed"] 122 self.assertIsInstance(f, ft.Font) 123 124 # Test keyword arguments 125 f = ft.Font(size=22, file=None) 126 self.assertEqual(f.size, 22) 127 f = ft.Font(font_index=0, file=None) 128 self.assertNotEqual(ft.get_default_resolution(), 100) 129 f = ft.Font(resolution=100, file=None) 130 self.assertEqual(f.resolution, 100) 131 f = ft.Font(ucs4=True, file=None) 132 self.assertTrue(f.ucs4) 133 self.assertRaises(OverflowError, ft.Font, file=None, size=(max_point_size + 1)) 134 self.assertRaises(OverflowError, ft.Font, file=None, size=-1) 135 136 f = ft.Font(None, size=24) 137 self.assertTrue(f.height > 0) 138 self.assertRaises(IOError, f.__init__, os.path.join(FONTDIR, "nonexistant.ttf")) 139 140 # Test attribute preservation during reinitalization 141 f = ft.Font(self._sans_path, size=24, ucs4=True) 142 self.assertEqual(f.name, "Liberation Sans") 143 self.assertTrue(f.scalable) 144 self.assertFalse(f.fixed_width) 145 self.assertTrue(f.antialiased) 146 self.assertFalse(f.oblique) 147 self.assertTrue(f.ucs4) 148 f.antialiased = False 149 f.oblique = True 150 f.__init__(self._mono_path) 151 self.assertEqual(f.name, "PyGameMono") 152 self.assertTrue(f.scalable) 153 self.assertTrue(f.fixed_width) 154 self.assertFalse(f.antialiased) 155 self.assertTrue(f.oblique) 156 self.assertTrue(f.ucs4) 157 158 # For a bitmap font, the size is automatically set to the first 159 # size in the available sizes list. 160 f = ft.Font(self._bmp_8_75dpi_path) 161 sizes = f.get_sizes() 162 self.assertEqual(len(sizes), 1) 163 size_pt, width_px, height_px, x_ppem, y_ppem = sizes[0] 164 self.assertEqual(f.size, (x_ppem, y_ppem)) 165 f.__init__(self._bmp_8_75dpi_path, size=12) 166 self.assertEqual(f.size, 12.0) 167 168 @unittest.skipIf(IS_PYPY, "PyPy doesn't use refcounting") 169 def test_freetype_Font_dealloc(self): 170 import sys 171 172 handle = open(self._sans_path, "rb") 173 174 def load_font(): 175 tempFont = ft.Font(handle) 176 177 try: 178 load_font() 179 180 self.assertEqual(sys.getrefcount(handle), 2) 181 finally: 182 # Ensures file is closed even if test fails. 183 handle.close() 184 185 def test_freetype_Font_kerning(self): 186 """Ensures get/set works with the kerning property.""" 187 ft_font = self._TEST_FONTS["sans"] 188 189 # Test default is disabled. 190 self.assertFalse(ft_font.kerning) 191 192 # Test setting to True. 193 ft_font.kerning = True 194 195 self.assertTrue(ft_font.kerning) 196 197 # Test setting to False. 198 ft_font.kerning = False 199 200 self.assertFalse(ft_font.kerning) 201 202 def test_freetype_Font_kerning__enabled(self): 203 """Ensures exceptions are not raised when calling freetype methods 204 while kerning is enabled. 205 206 Note: This does not test what changes occur to a rendered font by 207 having kerning enabled. 208 209 Related to issue #367. 210 """ 211 surface = pygame.Surface((10, 10), 0, 32) 212 TEST_TEXT = "Freetype Font" 213 ft_font = self._TEST_FONTS["bmp-8-75dpi"] 214 215 ft_font.kerning = True 216 217 # Call different methods to ensure they don't raise an exception. 218 metrics = ft_font.get_metrics(TEST_TEXT) 219 self.assertIsInstance(metrics, list) 220 221 rect = ft_font.get_rect(TEST_TEXT) 222 self.assertIsInstance(rect, pygame.Rect) 223 224 font_surf, rect = ft_font.render(TEST_TEXT) 225 self.assertIsInstance(font_surf, pygame.Surface) 226 self.assertIsInstance(rect, pygame.Rect) 227 228 rect = ft_font.render_to(surface, (0, 0), TEST_TEXT) 229 self.assertIsInstance(rect, pygame.Rect) 230 231 buf, size = ft_font.render_raw(TEST_TEXT) 232 self.assertIsInstance(buf, bytes_) 233 self.assertIsInstance(size, tuple) 234 235 rect = ft_font.render_raw_to(surface.get_view("2"), TEST_TEXT) 236 self.assertIsInstance(rect, pygame.Rect) 237 238 def test_freetype_Font_scalable(self): 239 240 f = self._TEST_FONTS["sans"] 241 self.assertTrue(f.scalable) 242 243 self.assertRaises(RuntimeError, lambda: nullfont().scalable) 244 245 def test_freetype_Font_fixed_width(self): 246 247 f = self._TEST_FONTS["sans"] 248 self.assertFalse(f.fixed_width) 249 250 f = self._TEST_FONTS["mono"] 251 self.assertTrue(f.fixed_width) 252 253 self.assertRaises(RuntimeError, lambda: nullfont().fixed_width) 254 255 def test_freetype_Font_fixed_sizes(self): 256 257 f = self._TEST_FONTS["sans"] 258 self.assertEqual(f.fixed_sizes, 0) 259 f = self._TEST_FONTS["bmp-8-75dpi"] 260 self.assertEqual(f.fixed_sizes, 1) 261 f = self._TEST_FONTS["mono"] 262 self.assertEqual(f.fixed_sizes, 2) 263 264 def test_freetype_Font_get_sizes(self): 265 f = self._TEST_FONTS["sans"] 266 szlist = f.get_sizes() 267 self.assertIsInstance(szlist, list) 268 self.assertEqual(len(szlist), 0) 269 270 f = self._TEST_FONTS["bmp-8-75dpi"] 271 szlist = f.get_sizes() 272 self.assertIsInstance(szlist, list) 273 self.assertEqual(len(szlist), 1) 274 275 size8 = szlist[0] 276 self.assertIsInstance(size8[0], int) 277 self.assertEqual(size8[0], 8) 278 self.assertIsInstance(size8[1], int) 279 self.assertIsInstance(size8[2], int) 280 self.assertIsInstance(size8[3], float) 281 self.assertEqual(int(size8[3] * 64.0 + 0.5), 8 * 64) 282 self.assertIsInstance(size8[4], float) 283 self.assertEqual(int(size8[4] * 64.0 + 0.5), 8 * 64) 284 285 f = self._TEST_FONTS["mono"] 286 szlist = f.get_sizes() 287 self.assertIsInstance(szlist, list) 288 self.assertEqual(len(szlist), 2) 289 290 size8 = szlist[0] 291 self.assertEqual(size8[3], 8) 292 self.assertEqual(int(size8[3] * 64.0 + 0.5), 8 * 64) 293 self.assertEqual(int(size8[4] * 64.0 + 0.5), 8 * 64) 294 295 size19 = szlist[1] 296 self.assertEqual(size19[3], 19) 297 self.assertEqual(int(size19[3] * 64.0 + 0.5), 19 * 64) 298 self.assertEqual(int(size19[4] * 64.0 + 0.5), 19 * 64) 299 300 def test_freetype_Font_use_bitmap_strikes(self): 301 f = self._TEST_FONTS["mono"] 302 try: 303 # use_bitmap_strikes == True 304 # 305 self.assertTrue(f.use_bitmap_strikes) 306 307 # bitmap compatible properties 308 s_strike, sz = f.render_raw("A", size=19) 309 try: 310 f.vertical = True 311 s_strike_vert, sz = f.render_raw("A", size=19) 312 finally: 313 f.vertical = False 314 try: 315 f.wide = True 316 s_strike_wide, sz = f.render_raw("A", size=19) 317 finally: 318 f.wide = False 319 try: 320 f.underline = True 321 s_strike_underline, sz = f.render_raw("A", size=19) 322 finally: 323 f.underline = False 324 325 # bitmap incompatible properties 326 s_strike_rot45, sz = f.render_raw("A", size=19, rotation=45) 327 try: 328 f.strong = True 329 s_strike_strong, sz = f.render_raw("A", size=19) 330 finally: 331 f.strong = False 332 try: 333 f.oblique = True 334 s_strike_oblique, sz = f.render_raw("A", size=19) 335 finally: 336 f.oblique = False 337 338 # compare with use_bitmap_strikes == False 339 # 340 f.use_bitmap_strikes = False 341 self.assertFalse(f.use_bitmap_strikes) 342 343 # bitmap compatible properties 344 s_outline, sz = f.render_raw("A", size=19) 345 self.assertNotEqual(s_outline, s_strike) 346 try: 347 f.vertical = True 348 s_outline, sz = f.render_raw("A", size=19) 349 self.assertNotEqual(s_outline, s_strike_vert) 350 finally: 351 f.vertical = False 352 try: 353 f.wide = True 354 s_outline, sz = f.render_raw("A", size=19) 355 self.assertNotEqual(s_outline, s_strike_wide) 356 finally: 357 f.wide = False 358 try: 359 f.underline = True 360 s_outline, sz = f.render_raw("A", size=19) 361 self.assertNotEqual(s_outline, s_strike_underline) 362 finally: 363 f.underline = False 364 365 # bitmap incompatible properties 366 s_outline, sz = f.render_raw("A", size=19, rotation=45) 367 self.assertEqual(s_outline, s_strike_rot45) 368 try: 369 f.strong = True 370 s_outline, sz = f.render_raw("A", size=19) 371 self.assertEqual(s_outline, s_strike_strong) 372 finally: 373 f.strong = False 374 try: 375 f.oblique = True 376 s_outline, sz = f.render_raw("A", size=19) 377 self.assertEqual(s_outline, s_strike_oblique) 378 finally: 379 f.oblique = False 380 finally: 381 f.use_bitmap_strikes = True 382 383 def test_freetype_Font_bitmap_files(self): 384 """Ensure bitmap file restrictions are caught""" 385 f = self._TEST_FONTS["bmp-8-75dpi"] 386 f_null = nullfont() 387 s = pygame.Surface((10, 10), 0, 32) 388 a = s.get_view("3") 389 390 exception = AttributeError 391 self.assertRaises(exception, setattr, f, "strong", True) 392 self.assertRaises(exception, setattr, f, "oblique", True) 393 self.assertRaises(exception, setattr, f, "style", ft.STYLE_STRONG) 394 self.assertRaises(exception, setattr, f, "style", ft.STYLE_OBLIQUE) 395 exception = RuntimeError 396 self.assertRaises(exception, setattr, f_null, "strong", True) 397 self.assertRaises(exception, setattr, f_null, "oblique", True) 398 self.assertRaises(exception, setattr, f_null, "style", ft.STYLE_STRONG) 399 self.assertRaises(exception, setattr, f_null, "style", ft.STYLE_OBLIQUE) 400 exception = ValueError 401 self.assertRaises(exception, f.render, "A", (0, 0, 0), size=8, rotation=1) 402 self.assertRaises( 403 exception, f.render, "A", (0, 0, 0), size=8, style=ft.STYLE_OBLIQUE 404 ) 405 self.assertRaises( 406 exception, f.render, "A", (0, 0, 0), size=8, style=ft.STYLE_STRONG 407 ) 408 self.assertRaises(exception, f.render_raw, "A", size=8, rotation=1) 409 self.assertRaises(exception, f.render_raw, "A", size=8, style=ft.STYLE_OBLIQUE) 410 self.assertRaises(exception, f.render_raw, "A", size=8, style=ft.STYLE_STRONG) 411 self.assertRaises( 412 exception, f.render_to, s, (0, 0), "A", (0, 0, 0), size=8, rotation=1 413 ) 414 self.assertRaises( 415 exception, 416 f.render_to, 417 s, 418 (0, 0), 419 "A", 420 (0, 0, 0), 421 size=8, 422 style=ft.STYLE_OBLIQUE, 423 ) 424 self.assertRaises( 425 exception, 426 f.render_to, 427 s, 428 (0, 0), 429 "A", 430 (0, 0, 0), 431 size=8, 432 style=ft.STYLE_STRONG, 433 ) 434 self.assertRaises(exception, f.render_raw_to, a, "A", size=8, rotation=1) 435 self.assertRaises( 436 exception, f.render_raw_to, a, "A", size=8, style=ft.STYLE_OBLIQUE 437 ) 438 self.assertRaises( 439 exception, f.render_raw_to, a, "A", size=8, style=ft.STYLE_STRONG 440 ) 441 self.assertRaises(exception, f.get_rect, "A", size=8, rotation=1) 442 self.assertRaises(exception, f.get_rect, "A", size=8, style=ft.STYLE_OBLIQUE) 443 self.assertRaises(exception, f.get_rect, "A", size=8, style=ft.STYLE_STRONG) 444 445 # Unsupported point size 446 exception = pygame.error 447 self.assertRaises(exception, f.get_rect, "A", size=42) 448 self.assertRaises(exception, f.get_metrics, "A", size=42) 449 self.assertRaises(exception, f.get_sized_ascender, 42) 450 self.assertRaises(exception, f.get_sized_descender, 42) 451 self.assertRaises(exception, f.get_sized_height, 42) 452 self.assertRaises(exception, f.get_sized_glyph_height, 42) 453 454 def test_freetype_Font_get_metrics(self): 455 456 font = self._TEST_FONTS["sans"] 457 458 metrics = font.get_metrics("ABCD", size=24) 459 self.assertEqual(len(metrics), len("ABCD")) 460 self.assertIsInstance(metrics, list) 461 462 for metrics_tuple in metrics: 463 self.assertIsInstance(metrics_tuple, tuple, metrics_tuple) 464 self.assertEqual(len(metrics_tuple), 6) 465 466 for m in metrics_tuple[:4]: 467 self.assertIsInstance(m, int) 468 469 for m in metrics_tuple[4:]: 470 self.assertIsInstance(m, float) 471 472 # test for empty string 473 metrics = font.get_metrics("", size=24) 474 self.assertEqual(metrics, []) 475 476 # test for invalid string 477 self.assertRaises(TypeError, font.get_metrics, 24, 24) 478 479 # raises exception when uninitalized 480 self.assertRaises(RuntimeError, nullfont().get_metrics, "a", size=24) 481 482 def test_freetype_Font_get_rect(self): 483 484 font = self._TEST_FONTS["sans"] 485 486 def test_rect(r): 487 self.assertIsInstance(r, pygame.Rect) 488 489 rect_default = font.get_rect("ABCDabcd", size=24) 490 test_rect(rect_default) 491 self.assertTrue(rect_default.size > (0, 0)) 492 self.assertTrue(rect_default.width > rect_default.height) 493 494 rect_bigger = font.get_rect("ABCDabcd", size=32) 495 test_rect(rect_bigger) 496 self.assertTrue(rect_bigger.size > rect_default.size) 497 498 rect_strong = font.get_rect("ABCDabcd", size=24, style=ft.STYLE_STRONG) 499 test_rect(rect_strong) 500 self.assertTrue(rect_strong.size > rect_default.size) 501 502 font.vertical = True 503 rect_vert = font.get_rect("ABCDabcd", size=24) 504 test_rect(rect_vert) 505 self.assertTrue(rect_vert.width < rect_vert.height) 506 font.vertical = False 507 508 rect_oblique = font.get_rect("ABCDabcd", size=24, style=ft.STYLE_OBLIQUE) 509 test_rect(rect_oblique) 510 self.assertTrue(rect_oblique.width > rect_default.width) 511 self.assertTrue(rect_oblique.height == rect_default.height) 512 513 rect_under = font.get_rect("ABCDabcd", size=24, style=ft.STYLE_UNDERLINE) 514 test_rect(rect_under) 515 self.assertTrue(rect_under.width == rect_default.width) 516 self.assertTrue(rect_under.height > rect_default.height) 517 518 # Rect size should change if UTF surrogate pairs are treated as 519 # one code point or two. 520 ufont = self._TEST_FONTS["mono"] 521 rect_utf32 = ufont.get_rect(as_unicode(r"\U00013079"), size=24) 522 rect_utf16 = ufont.get_rect(as_unicode(r"\uD80C\uDC79"), size=24) 523 self.assertEqual(rect_utf16, rect_utf32) 524 ufont.ucs4 = True 525 try: 526 rect_utf16 = ufont.get_rect(as_unicode(r"\uD80C\uDC79"), size=24) 527 finally: 528 ufont.ucs4 = False 529 self.assertNotEqual(rect_utf16, rect_utf32) 530 531 self.assertRaises(RuntimeError, nullfont().get_rect, "a", size=24) 532 533 # text stretching 534 rect12 = font.get_rect("A", size=12.0) 535 rect24 = font.get_rect("A", size=24.0) 536 rect_x = font.get_rect("A", size=(24.0, 12.0)) 537 self.assertEqual(rect_x.width, rect24.width) 538 self.assertEqual(rect_x.height, rect12.height) 539 rect_y = font.get_rect("A", size=(12.0, 24.0)) 540 self.assertEqual(rect_y.width, rect12.width) 541 self.assertEqual(rect_y.height, rect24.height) 542 543 def test_freetype_Font_height(self): 544 545 f = self._TEST_FONTS["sans"] 546 self.assertEqual(f.height, 2355) 547 548 f = self._TEST_FONTS["fixed"] 549 self.assertEqual(f.height, 1100) 550 551 self.assertRaises(RuntimeError, lambda: nullfont().height) 552 553 def test_freetype_Font_name(self): 554 555 f = self._TEST_FONTS["sans"] 556 self.assertEqual(f.name, "Liberation Sans") 557 558 f = self._TEST_FONTS["fixed"] 559 self.assertEqual(f.name, "Inconsolata") 560 561 nf = nullfont() 562 self.assertEqual(nf.name, repr(nf)) 563 564 def test_freetype_Font_size(self): 565 566 f = ft.Font(None, size=12) 567 self.assertEqual(f.size, 12) 568 f.size = 22 569 self.assertEqual(f.size, 22) 570 f.size = 0 571 self.assertEqual(f.size, 0) 572 f.size = max_point_size 573 self.assertEqual(f.size, max_point_size) 574 f.size = 6.5 575 self.assertEqual(f.size, 6.5) 576 f.size = max_point_size_f 577 self.assertEqual(f.size, max_point_size_f) 578 self.assertRaises(OverflowError, setattr, f, "size", -1) 579 self.assertRaises(OverflowError, setattr, f, "size", (max_point_size + 1)) 580 581 f.size = 24.0, 0 582 size = f.size 583 self.assertIsInstance(size, float) 584 self.assertEqual(size, 24.0) 585 586 f.size = 16, 16 587 size = f.size 588 self.assertIsInstance(size, tuple) 589 self.assertEqual(len(size), 2) 590 591 x, y = size 592 self.assertIsInstance(x, float) 593 self.assertEqual(x, 16.0) 594 self.assertIsInstance(y, float) 595 self.assertEqual(y, 16.0) 596 597 f.size = 20.5, 22.25 598 x, y = f.size 599 self.assertEqual(x, 20.5) 600 self.assertEqual(y, 22.25) 601 602 f.size = 0, 0 603 size = f.size 604 self.assertIsInstance(size, float) 605 self.assertEqual(size, 0.0) 606 self.assertRaises(ValueError, setattr, f, "size", (0, 24.0)) 607 self.assertRaises(TypeError, setattr, f, "size", (24.0,)) 608 self.assertRaises(TypeError, setattr, f, "size", (24.0, 0, 0)) 609 self.assertRaises(TypeError, setattr, f, "size", (24.0j, 24.0)) 610 self.assertRaises(TypeError, setattr, f, "size", (24.0, 24.0j)) 611 self.assertRaises(OverflowError, setattr, f, "size", (-1, 16)) 612 self.assertRaises(OverflowError, setattr, f, "size", (max_point_size + 1, 16)) 613 self.assertRaises(OverflowError, setattr, f, "size", (16, -1)) 614 self.assertRaises(OverflowError, setattr, f, "size", (16, max_point_size + 1)) 615 616 # bitmap files with identical point size but differing ppems. 617 f75 = self._TEST_FONTS["bmp-18-75dpi"] 618 sizes = f75.get_sizes() 619 self.assertEqual(len(sizes), 1) 620 size_pt, width_px, height_px, x_ppem, y_ppem = sizes[0] 621 self.assertEqual(size_pt, 18) 622 self.assertEqual(x_ppem, 19.0) 623 self.assertEqual(y_ppem, 19.0) 624 rect = f75.get_rect("A", size=18) 625 rect = f75.get_rect("A", size=19) 626 rect = f75.get_rect("A", size=(19.0, 19.0)) 627 self.assertRaises(pygame.error, f75.get_rect, "A", size=17) 628 f100 = self._TEST_FONTS["bmp-18-100dpi"] 629 sizes = f100.get_sizes() 630 self.assertEqual(len(sizes), 1) 631 size_pt, width_px, height_px, x_ppem, y_ppem = sizes[0] 632 self.assertEqual(size_pt, 18) 633 self.assertEqual(x_ppem, 25.0) 634 self.assertEqual(y_ppem, 25.0) 635 rect = f100.get_rect("A", size=18) 636 rect = f100.get_rect("A", size=25) 637 rect = f100.get_rect("A", size=(25.0, 25.0)) 638 self.assertRaises(pygame.error, f100.get_rect, "A", size=17) 639 640 def test_freetype_Font_rotation(self): 641 642 test_angles = [ 643 (30, 30), 644 (360, 0), 645 (390, 30), 646 (720, 0), 647 (764, 44), 648 (-30, 330), 649 (-360, 0), 650 (-390, 330), 651 (-720, 0), 652 (-764, 316), 653 ] 654 655 f = ft.Font(None) 656 self.assertEqual(f.rotation, 0) 657 for r, r_reduced in test_angles: 658 f.rotation = r 659 self.assertEqual( 660 f.rotation, 661 r_reduced, 662 "for angle %d: %d != %d" % (r, f.rotation, r_reduced), 663 ) 664 self.assertRaises(TypeError, setattr, f, "rotation", "12") 665 666 def test_freetype_Font_render_to(self): 667 # Rendering to an existing target surface is equivalent to 668 # blitting a surface returned by Font.render with the target. 669 font = self._TEST_FONTS["sans"] 670 671 surf = pygame.Surface((800, 600)) 672 color = pygame.Color(0, 0, 0) 673 674 rrect = font.render_to(surf, (32, 32), "FoobarBaz", color, None, size=24) 675 self.assertIsInstance(rrect, pygame.Rect) 676 self.assertEqual(rrect.topleft, (32, 32)) 677 self.assertNotEqual(rrect.bottomright, (32, 32)) 678 679 rcopy = rrect.copy() 680 rcopy.topleft = (32, 32) 681 self.assertTrue(surf.get_rect().contains(rcopy)) 682 683 rect = pygame.Rect(20, 20, 2, 2) 684 rrect = font.render_to(surf, rect, "FoobarBax", color, None, size=24) 685 self.assertEqual(rect.topleft, rrect.topleft) 686 self.assertNotEqual(rrect.size, rect.size) 687 rrect = font.render_to(surf, (20.1, 18.9), "FoobarBax", color, None, size=24) 688 689 rrect = font.render_to(surf, rect, "", color, None, size=24) 690 self.assertFalse(rrect) 691 self.assertEqual(rrect.height, font.get_sized_height(24)) 692 693 # invalid surf test 694 self.assertRaises(TypeError, font.render_to, "not a surface", "text", color) 695 self.assertRaises(TypeError, font.render_to, pygame.Surface, "text", color) 696 697 # invalid dest test 698 for dest in [ 699 None, 700 0, 701 "a", 702 "ab", 703 (), 704 (1,), 705 ("a", 2), 706 (1, "a"), 707 (1 + 2j, 2), 708 (1, 1 + 2j), 709 (1, int), 710 (int, 1), 711 ]: 712 self.assertRaises( 713 TypeError, font.render_to, surf, dest, "foobar", color, size=24 714 ) 715 716 # misc parameter test 717 self.assertRaises(ValueError, font.render_to, surf, (0, 0), "foobar", color) 718 self.assertRaises( 719 TypeError, font.render_to, surf, (0, 0), "foobar", color, 2.3, size=24 720 ) 721 self.assertRaises( 722 ValueError, 723 font.render_to, 724 surf, 725 (0, 0), 726 "foobar", 727 color, 728 None, 729 style=42, 730 size=24, 731 ) 732 self.assertRaises( 733 TypeError, 734 font.render_to, 735 surf, 736 (0, 0), 737 "foobar", 738 color, 739 None, 740 style=None, 741 size=24, 742 ) 743 self.assertRaises( 744 ValueError, 745 font.render_to, 746 surf, 747 (0, 0), 748 "foobar", 749 color, 750 None, 751 style=97, 752 size=24, 753 ) 754 755 def test_freetype_Font_render(self): 756 757 font = self._TEST_FONTS["sans"] 758 759 surf = pygame.Surface((800, 600)) 760 color = pygame.Color(0, 0, 0) 761 762 rend = font.render("FoobarBaz", pygame.Color(0, 0, 0), None, size=24) 763 self.assertIsInstance(rend, tuple) 764 self.assertEqual(len(rend), 2) 765 self.assertIsInstance(rend[0], pygame.Surface) 766 self.assertIsInstance(rend[1], pygame.Rect) 767 self.assertEqual(rend[0].get_rect().size, rend[1].size) 768 769 s, r = font.render("", pygame.Color(0, 0, 0), None, size=24) 770 self.assertEqual(r.width, 0) 771 self.assertEqual(r.height, font.get_sized_height(24)) 772 self.assertEqual(s.get_size(), r.size) 773 self.assertEqual(s.get_bitsize(), 32) 774 775 # misc parameter test 776 self.assertRaises(ValueError, font.render, "foobar", color) 777 self.assertRaises(TypeError, font.render, "foobar", color, 2.3, size=24) 778 self.assertRaises( 779 ValueError, font.render, "foobar", color, None, style=42, size=24 780 ) 781 self.assertRaises( 782 TypeError, font.render, "foobar", color, None, style=None, size=24 783 ) 784 self.assertRaises( 785 ValueError, font.render, "foobar", color, None, style=97, size=24 786 ) 787 788 # valid surrogate pairs 789 font2 = self._TEST_FONTS["mono"] 790 ucs4 = font2.ucs4 791 try: 792 font2.ucs4 = False 793 rend1 = font2.render(as_unicode(r"\uD80C\uDC79"), color, size=24) 794 rend2 = font2.render(as_unicode(r"\U00013079"), color, size=24) 795 self.assertEqual(rend1[1], rend2[1]) 796 font2.ucs4 = True 797 rend1 = font2.render(as_unicode(r"\uD80C\uDC79"), color, size=24) 798 self.assertNotEqual(rend1[1], rend2[1]) 799 finally: 800 font2.ucs4 = ucs4 801 802 # malformed surrogate pairs 803 self.assertRaises( 804 UnicodeEncodeError, font.render, as_unicode(r"\uD80C"), color, size=24 805 ) 806 self.assertRaises( 807 UnicodeEncodeError, font.render, as_unicode(r"\uDCA7"), color, size=24 808 ) 809 self.assertRaises( 810 UnicodeEncodeError, font.render, as_unicode(r"\uD7FF\uDCA7"), color, size=24 811 ) 812 self.assertRaises( 813 UnicodeEncodeError, font.render, as_unicode(r"\uDC00\uDCA7"), color, size=24 814 ) 815 self.assertRaises( 816 UnicodeEncodeError, font.render, as_unicode(r"\uD80C\uDBFF"), color, size=24 817 ) 818 self.assertRaises( 819 UnicodeEncodeError, font.render, as_unicode(r"\uD80C\uE000"), color, size=24 820 ) 821 822 # raises exception when uninitalized 823 self.assertRaises(RuntimeError, nullfont().render, "a", (0, 0, 0), size=24) 824 825 # Confirm the correct glpyhs are returned for a couple of 826 # unicode code points, 'A' and '\U00023079'. For each code point 827 # the rendered glyph is compared with an image of glyph bitmap 828 # as exported by FontForge. 829 path = os.path.join(FONTDIR, "A_PyGameMono-8.png") 830 A = pygame.image.load(path) 831 path = os.path.join(FONTDIR, "u13079_PyGameMono-8.png") 832 u13079 = pygame.image.load(path) 833 834 font = self._TEST_FONTS["mono"] 835 font.ucs4 = False 836 A_rendered, r = font.render("A", bgcolor=pygame.Color("white"), size=8) 837 u13079_rendered, r = font.render( 838 as_unicode(r"\U00013079"), bgcolor=pygame.Color("white"), size=8 839 ) 840 841 ## before comparing the surfaces, make sure they are the same 842 ## pixel format. Use 32-bit SRCALPHA to avoid row padding and 843 ## undefined bytes (the alpha byte will be set to 255.) 844 bitmap = pygame.Surface(A.get_size(), pygame.SRCALPHA, 32) 845 bitmap.blit(A, (0, 0)) 846 rendering = pygame.Surface(A_rendered.get_size(), pygame.SRCALPHA, 32) 847 rendering.blit(A_rendered, (0, 0)) 848 self.assertTrue(surf_same_image(rendering, bitmap)) 849 bitmap = pygame.Surface(u13079.get_size(), pygame.SRCALPHA, 32) 850 bitmap.blit(u13079, (0, 0)) 851 rendering = pygame.Surface(u13079_rendered.get_size(), pygame.SRCALPHA, 32) 852 rendering.blit(u13079_rendered, (0, 0)) 853 self.assertTrue(surf_same_image(rendering, bitmap)) 854 855 def test_freetype_Font_render_mono(self): 856 font = self._TEST_FONTS["sans"] 857 color = pygame.Color("black") 858 colorkey = pygame.Color("white") 859 text = "." 860 861 save_antialiased = font.antialiased 862 font.antialiased = False 863 try: 864 surf, r = font.render(text, color, size=24) 865 self.assertEqual(surf.get_bitsize(), 8) 866 flags = surf.get_flags() 867 self.assertTrue(flags & pygame.SRCCOLORKEY) 868 self.assertFalse(flags & (pygame.SRCALPHA | pygame.HWSURFACE)) 869 self.assertEqual(surf.get_colorkey(), colorkey) 870 self.assertIsNone(surf.get_alpha()) 871 872 translucent_color = pygame.Color(*color) 873 translucent_color.a = 55 874 surf, r = font.render(text, translucent_color, size=24) 875 self.assertEqual(surf.get_bitsize(), 8) 876 flags = surf.get_flags() 877 self.assertTrue(flags & (pygame.SRCCOLORKEY | pygame.SRCALPHA)) 878 self.assertFalse(flags & pygame.HWSURFACE) 879 self.assertEqual(surf.get_colorkey(), colorkey) 880 self.assertEqual(surf.get_alpha(), translucent_color.a) 881 882 surf, r = font.render(text, color, colorkey, size=24) 883 self.assertEqual(surf.get_bitsize(), 32) 884 finally: 885 font.antialiased = save_antialiased 886 887 def test_freetype_Font_render_to_mono(self): 888 # Blitting is done in two stages. First the target is alpha filled 889 # with the background color, if any. Second, the foreground 890 # color is alpha blitted to the background. 891 font = self._TEST_FONTS["sans"] 892 text = " ." 893 rect = font.get_rect(text, size=24) 894 size = rect.size 895 fg = pygame.Surface((1, 1), pygame.SRCALPHA, 32) 896 bg = pygame.Surface((1, 1), pygame.SRCALPHA, 32) 897 surrogate = pygame.Surface((1, 1), pygame.SRCALPHA, 32) 898 surfaces = [ 899 pygame.Surface(size, 0, 8), 900 pygame.Surface(size, 0, 16), 901 pygame.Surface(size, pygame.SRCALPHA, 16), 902 pygame.Surface(size, 0, 24), 903 pygame.Surface(size, 0, 32), 904 pygame.Surface(size, pygame.SRCALPHA, 32), 905 ] 906 fg_colors = [ 907 surfaces[0].get_palette_at(2), 908 surfaces[1].unmap_rgb(surfaces[1].map_rgb((128, 64, 200))), 909 surfaces[2].unmap_rgb(surfaces[2].map_rgb((99, 0, 100, 64))), 910 (128, 97, 213), 911 (128, 97, 213), 912 (128, 97, 213, 60), 913 ] 914 fg_colors = [pygame.Color(*c) for c in fg_colors] 915 self.assertEqual(len(surfaces), len(fg_colors)) # integrity check 916 bg_colors = [ 917 surfaces[0].get_palette_at(4), 918 surfaces[1].unmap_rgb(surfaces[1].map_rgb((220, 20, 99))), 919 surfaces[2].unmap_rgb(surfaces[2].map_rgb((55, 200, 0, 86))), 920 (255, 120, 13), 921 (255, 120, 13), 922 (255, 120, 13, 180), 923 ] 924 bg_colors = [pygame.Color(*c) for c in bg_colors] 925 self.assertEqual(len(surfaces), len(bg_colors)) # integrity check 926 927 save_antialiased = font.antialiased 928 font.antialiased = False 929 try: 930 fill_color = pygame.Color("black") 931 for i, surf in enumerate(surfaces): 932 surf.fill(fill_color) 933 fg_color = fg_colors[i] 934 fg.set_at((0, 0), fg_color) 935 surf.blit(fg, (0, 0)) 936 r_fg_color = surf.get_at((0, 0)) 937 surf.set_at((0, 0), fill_color) 938 rrect = font.render_to(surf, (0, 0), text, fg_color, size=24) 939 bottomleft = 0, rrect.height - 1 940 self.assertEqual( 941 surf.get_at(bottomleft), 942 fill_color, 943 "Position: {}. Depth: {}." 944 " fg_color: {}.".format(bottomleft, surf.get_bitsize(), fg_color), 945 ) 946 bottomright = rrect.width - 1, rrect.height - 1 947 self.assertEqual( 948 surf.get_at(bottomright), 949 r_fg_color, 950 "Position: {}. Depth: {}." 951 " fg_color: {}.".format(bottomright, surf.get_bitsize(), fg_color), 952 ) 953 for i, surf in enumerate(surfaces): 954 surf.fill(fill_color) 955 fg_color = fg_colors[i] 956 bg_color = bg_colors[i] 957 bg.set_at((0, 0), bg_color) 958 fg.set_at((0, 0), fg_color) 959 if surf.get_bitsize() == 24: 960 # For a 24 bit target surface test against Pygame's alpha 961 # blit as there appears to be a problem with SDL's alpha 962 # blit: 963 # 964 # self.assertEqual(surf.get_at(bottomright), r_fg_color) 965 # 966 # raises 967 # 968 # AssertionError: (128, 97, 213, 255) != (129, 98, 213, 255) 969 # 970 surrogate.set_at((0, 0), fill_color) 971 surrogate.blit(bg, (0, 0)) 972 r_bg_color = surrogate.get_at((0, 0)) 973 surrogate.blit(fg, (0, 0)) 974 r_fg_color = surrogate.get_at((0, 0)) 975 else: 976 # Surface blit values for comparison. 977 surf.blit(bg, (0, 0)) 978 r_bg_color = surf.get_at((0, 0)) 979 surf.blit(fg, (0, 0)) 980 r_fg_color = surf.get_at((0, 0)) 981 surf.set_at((0, 0), fill_color) 982 rrect = font.render_to(surf, (0, 0), text, fg_color, bg_color, size=24) 983 bottomleft = 0, rrect.height - 1 984 self.assertEqual(surf.get_at(bottomleft), r_bg_color) 985 bottomright = rrect.width - 1, rrect.height - 1 986 self.assertEqual(surf.get_at(bottomright), r_fg_color) 987 finally: 988 font.antialiased = save_antialiased 989 990 def test_freetype_Font_render_raw(self): 991 992 font = self._TEST_FONTS["sans"] 993 994 text = "abc" 995 size = font.get_rect(text, size=24).size 996 rend = font.render_raw(text, size=24) 997 self.assertIsInstance(rend, tuple) 998 self.assertEqual(len(rend), 2) 999 1000 r, s = rend 1001 self.assertIsInstance(r, bytes_) 1002 self.assertIsInstance(s, tuple) 1003 self.assertTrue(len(s), 2) 1004 1005 w, h = s 1006 self.assertIsInstance(w, int) 1007 self.assertIsInstance(h, int) 1008 self.assertEqual(s, size) 1009 self.assertEqual(len(r), w * h) 1010 1011 r, (w, h) = font.render_raw("", size=24) 1012 self.assertEqual(w, 0) 1013 self.assertEqual(h, font.height) 1014 self.assertEqual(len(r), 0) 1015 1016 # bug with decenders: this would crash 1017 rend = font.render_raw("render_raw", size=24) 1018 1019 # bug with non-printable characters: this would cause a crash 1020 # because the text length was not adjusted for skipped characters. 1021 text = unicode_("").join([unichr_(i) for i in range(31, 64)]) 1022 rend = font.render_raw(text, size=10) 1023 1024 def test_freetype_Font_render_raw_to(self): 1025 1026 # This only checks that blits do not crash. It needs to check: 1027 # - int values 1028 # - invert option 1029 # 1030 1031 font = self._TEST_FONTS["sans"] 1032 text = "abc" 1033 1034 # No frills antialiased render to int1 (__render_glyph_INT) 1035 srect = font.get_rect(text, size=24) 1036 surf = pygame.Surface(srect.size, 0, 8) 1037 rrect = font.render_raw_to(surf.get_view("2"), text, size=24) 1038 self.assertEqual(rrect, srect) 1039 1040 for bpp in [24, 32]: 1041 surf = pygame.Surface(srect.size, 0, bpp) 1042 rrect = font.render_raw_to(surf.get_view("r"), text, size=24) 1043 self.assertEqual(rrect, srect) 1044 1045 # Underlining to int1 (__fill_glyph_INT) 1046 srect = font.get_rect(text, size=24, style=ft.STYLE_UNDERLINE) 1047 surf = pygame.Surface(srect.size, 0, 8) 1048 rrect = font.render_raw_to( 1049 surf.get_view("2"), text, size=24, style=ft.STYLE_UNDERLINE 1050 ) 1051 self.assertEqual(rrect, srect) 1052 1053 for bpp in [24, 32]: 1054 surf = pygame.Surface(srect.size, 0, bpp) 1055 rrect = font.render_raw_to( 1056 surf.get_view("r"), text, size=24, style=ft.STYLE_UNDERLINE 1057 ) 1058 self.assertEqual(rrect, srect) 1059 1060 # Unaliased (mono) rendering to int1 (__render_glyph_MONO_as_INT) 1061 font.antialiased = False 1062 try: 1063 srect = font.get_rect(text, size=24) 1064 surf = pygame.Surface(srect.size, 0, 8) 1065 rrect = font.render_raw_to(surf.get_view("2"), text, size=24) 1066 self.assertEqual(rrect, srect) 1067 1068 for bpp in [24, 32]: 1069 surf = pygame.Surface(srect.size, 0, bpp) 1070 rrect = font.render_raw_to(surf.get_view("r"), text, size=24) 1071 self.assertEqual(rrect, srect) 1072 finally: 1073 font.antialiased = True 1074 1075 # Antialiased render to ints sized greater than 1 byte 1076 # (__render_glyph_INT) 1077 srect = font.get_rect(text, size=24) 1078 1079 for bpp in [16, 24, 32]: 1080 surf = pygame.Surface(srect.size, 0, bpp) 1081 rrect = font.render_raw_to(surf.get_view("2"), text, size=24) 1082 self.assertEqual(rrect, srect) 1083 1084 # Underline render to ints sized greater than 1 byte 1085 # (__fill_glyph_INT) 1086 srect = font.get_rect(text, size=24, style=ft.STYLE_UNDERLINE) 1087 1088 for bpp in [16, 24, 32]: 1089 surf = pygame.Surface(srect.size, 0, bpp) 1090 rrect = font.render_raw_to( 1091 surf.get_view("2"), text, size=24, style=ft.STYLE_UNDERLINE 1092 ) 1093 self.assertEqual(rrect, srect) 1094 1095 # Unaliased (mono) rendering to ints greater than 1 byte 1096 # (__render_glyph_MONO_as_INT) 1097 font.antialiased = False 1098 try: 1099 srect = font.get_rect(text, size=24) 1100 1101 for bpp in [16, 24, 32]: 1102 surf = pygame.Surface(srect.size, 0, bpp) 1103 rrect = font.render_raw_to(surf.get_view("2"), text, size=24) 1104 self.assertEqual(rrect, srect) 1105 finally: 1106 font.antialiased = True 1107 1108 # Invalid dest parameter test. 1109 srect = font.get_rect(text, size=24) 1110 surf_buf = pygame.Surface(srect.size, 0, 32).get_view("2") 1111 1112 for dest in [ 1113 0, 1114 "a", 1115 "ab", 1116 (), 1117 (1,), 1118 ("a", 2), 1119 (1, "a"), 1120 (1 + 2j, 2), 1121 (1, 1 + 2j), 1122 (1, int), 1123 (int, 1), 1124 ]: 1125 self.assertRaises( 1126 TypeError, font.render_raw_to, surf_buf, text, dest, size=24 1127 ) 1128 1129 def test_freetype_Font_text_is_None(self): 1130 f = ft.Font(self._sans_path, 36) 1131 f.style = ft.STYLE_NORMAL 1132 f.rotation = 0 1133 text = "ABCD" 1134 1135 # reference values 1136 get_rect = f.get_rect(text) 1137 f.vertical = True 1138 get_rect_vert = f.get_rect(text) 1139 1140 self.assertTrue(get_rect_vert.width < get_rect.width) 1141 self.assertTrue(get_rect_vert.height > get_rect.height) 1142 f.vertical = False 1143 render_to_surf = pygame.Surface(get_rect.size, pygame.SRCALPHA, 32) 1144 1145 if IS_PYPY: 1146 return 1147 1148 arr = arrinter.Array(get_rect.size, "u", 1) 1149 render = f.render(text, (0, 0, 0)) 1150 render_to = f.render_to(render_to_surf, (0, 0), text, (0, 0, 0)) 1151 render_raw = f.render_raw(text) 1152 render_raw_to = f.render_raw_to(arr, text) 1153 1154 # comparisons 1155 surf = pygame.Surface(get_rect.size, pygame.SRCALPHA, 32) 1156 self.assertEqual(f.get_rect(None), get_rect) 1157 s, r = f.render(None, (0, 0, 0)) 1158 self.assertEqual(r, render[1]) 1159 self.assertTrue(surf_same_image(s, render[0])) 1160 r = f.render_to(surf, (0, 0), None, (0, 0, 0)) 1161 self.assertEqual(r, render_to) 1162 self.assertTrue(surf_same_image(surf, render_to_surf)) 1163 px, sz = f.render_raw(None) 1164 self.assertEqual(sz, render_raw[1]) 1165 self.assertEqual(px, render_raw[0]) 1166 sz = f.render_raw_to(arr, None) 1167 self.assertEqual(sz, render_raw_to) 1168 1169 def test_freetype_Font_text_is_None(self): 1170 f = ft.Font(self._sans_path, 36) 1171 f.style = ft.STYLE_NORMAL 1172 f.rotation = 0 1173 text = "ABCD" 1174 1175 # reference values 1176 get_rect = f.get_rect(text) 1177 f.vertical = True 1178 get_rect_vert = f.get_rect(text) 1179 1180 # vertical: trigger glyph positioning. 1181 f.vertical = True 1182 r = f.get_rect(None) 1183 self.assertEqual(r, get_rect_vert) 1184 f.vertical = False 1185 1186 # wide style: trigger glyph reload 1187 r = f.get_rect(None, style=ft.STYLE_WIDE) 1188 self.assertEqual(r.height, get_rect.height) 1189 self.assertTrue(r.width > get_rect.width) 1190 r = f.get_rect(None) 1191 self.assertEqual(r, get_rect) 1192 1193 # rotated: trigger glyph reload 1194 r = f.get_rect(None, rotation=90) 1195 self.assertEqual(r.width, get_rect.height) 1196 self.assertEqual(r.height, get_rect.width) 1197 1198 # this method will not support None text 1199 self.assertRaises(TypeError, f.get_metrics, None) 1200 1201 def test_freetype_Font_fgcolor(self): 1202 f = ft.Font(self._bmp_8_75dpi_path) 1203 notdef = "\0" # the PyGameMono .notdef glyph has a pixel at (0, 0) 1204 f.origin = False 1205 f.pad = False 1206 black = pygame.Color("black") # initial color 1207 green = pygame.Color("green") 1208 alpha128 = pygame.Color(10, 20, 30, 128) 1209 1210 c = f.fgcolor 1211 self.assertIsInstance(c, pygame.Color) 1212 self.assertEqual(c, black) 1213 1214 s, r = f.render(notdef) 1215 self.assertEqual(s.get_at((0, 0)), black) 1216 1217 f.fgcolor = green 1218 self.assertEqual(f.fgcolor, green) 1219 1220 s, r = f.render(notdef) 1221 self.assertEqual(s.get_at((0, 0)), green) 1222 1223 f.fgcolor = alpha128 1224 s, r = f.render(notdef) 1225 self.assertEqual(s.get_at((0, 0)), alpha128) 1226 1227 surf = pygame.Surface(f.get_rect(notdef).size, pygame.SRCALPHA, 32) 1228 f.render_to(surf, (0, 0), None) 1229 self.assertEqual(surf.get_at((0, 0)), alpha128) 1230 1231 self.assertRaises(AttributeError, setattr, f, "fgcolor", None) 1232 1233 def test_freetype_Font_bgcolor(self): 1234 f = ft.Font(None, 32) 1235 zero = "0" # the default font 0 glyph does not have a pixel at (0, 0) 1236 f.origin = False 1237 f.pad = False 1238 1239 transparent_black = pygame.Color(0, 0, 0, 0) # initial color 1240 green = pygame.Color("green") 1241 alpha128 = pygame.Color(10, 20, 30, 128) 1242 1243 c = f.bgcolor 1244 self.assertIsInstance(c, pygame.Color) 1245 self.assertEqual(c, transparent_black) 1246 1247 s, r = f.render(zero, pygame.Color(255, 255, 255)) 1248 self.assertEqual(s.get_at((0, 0)), transparent_black) 1249 1250 f.bgcolor = green 1251 self.assertEqual(f.bgcolor, green) 1252 1253 s, r = f.render(zero) 1254 self.assertEqual(s.get_at((0, 0)), green) 1255 1256 f.bgcolor = alpha128 1257 s, r = f.render(zero) 1258 self.assertEqual(s.get_at((0, 0)), alpha128) 1259 1260 surf = pygame.Surface(f.get_rect(zero).size, pygame.SRCALPHA, 32) 1261 f.render_to(surf, (0, 0), None) 1262 self.assertEqual(surf.get_at((0, 0)), alpha128) 1263 1264 self.assertRaises(AttributeError, setattr, f, "bgcolor", None) 1265 1266 @unittest.skipIf(not pygame.HAVE_NEWBUF, "newbuf not implemented") 1267 @unittest.skipIf(IS_PYPY, "pypy2 no likey") 1268 def test_newbuf(self): 1269 from pygame.tests.test_utils import buftools 1270 1271 Exporter = buftools.Exporter 1272 font = self._TEST_FONTS["sans"] 1273 srect = font.get_rect("Hi", size=12) 1274 for format in [ 1275 "b", 1276 "B", 1277 "h", 1278 "H", 1279 "i", 1280 "I", 1281 "l", 1282 "L", 1283 "q", 1284 "Q", 1285 "x", 1286 "1x", 1287 "2x", 1288 "3x", 1289 "4x", 1290 "5x", 1291 "6x", 1292 "7x", 1293 "8x", 1294 "9x", 1295 "<h", 1296 ">h", 1297 "=h", 1298 "@h", 1299 "!h", 1300 "1h", 1301 "=1h", 1302 ]: 1303 newbuf = Exporter(srect.size, format=format) 1304 rrect = font.render_raw_to(newbuf, "Hi", size=12) 1305 self.assertEqual(rrect, srect) 1306 # Some unsupported formats 1307 for format in ["f", "d", "2h", "?", "hh"]: 1308 newbuf = Exporter(srect.size, format=format, itemsize=4) 1309 self.assertRaises(ValueError, font.render_raw_to, newbuf, "Hi", size=12) 1310 1311 def test_freetype_Font_style(self): 1312 1313 font = self._TEST_FONTS["sans"] 1314 1315 # make sure STYLE_NORMAL is the default value 1316 self.assertEqual(ft.STYLE_NORMAL, font.style) 1317 1318 # make sure we check for style type 1319 with self.assertRaises(TypeError): 1320 font.style = "None" 1321 with self.assertRaises(TypeError): 1322 font.style = None 1323 1324 # make sure we only accept valid constants 1325 with self.assertRaises(ValueError): 1326 font.style = 112 1327 1328 # make assure no assignments happened 1329 self.assertEqual(ft.STYLE_NORMAL, font.style) 1330 1331 # test assignement 1332 font.style = ft.STYLE_UNDERLINE 1333 self.assertEqual(ft.STYLE_UNDERLINE, font.style) 1334 1335 # test complex styles 1336 st = ft.STYLE_STRONG | ft.STYLE_UNDERLINE | ft.STYLE_OBLIQUE 1337 1338 font.style = st 1339 self.assertEqual(st, font.style) 1340 1341 # and that STYLE_DEFAULT has no effect (continued from above) 1342 self.assertNotEqual(st, ft.STYLE_DEFAULT) 1343 font.style = ft.STYLE_DEFAULT 1344 self.assertEqual(st, font.style) 1345 1346 # revert changes 1347 font.style = ft.STYLE_NORMAL 1348 self.assertEqual(ft.STYLE_NORMAL, font.style) 1349 1350 def test_freetype_Font_resolution(self): 1351 text = "|" # Differs in width and height 1352 resolution = ft.get_default_resolution() 1353 new_font = ft.Font(self._sans_path, resolution=2 * resolution) 1354 self.assertEqual(new_font.resolution, 2 * resolution) 1355 size_normal = self._TEST_FONTS["sans"].get_rect(text, size=24).size 1356 size_scaled = new_font.get_rect(text, size=24).size 1357 size_by_2 = size_normal[0] * 2 1358 self.assertTrue( 1359 size_by_2 + 2 >= size_scaled[0] >= size_by_2 - 2, 1360 "%i not equal %i" % (size_scaled[1], size_by_2), 1361 ) 1362 size_by_2 = size_normal[1] * 2 1363 self.assertTrue( 1364 size_by_2 + 2 >= size_scaled[1] >= size_by_2 - 2, 1365 "%i not equal %i" % (size_scaled[1], size_by_2), 1366 ) 1367 new_resolution = resolution + 10 1368 ft.set_default_resolution(new_resolution) 1369 try: 1370 new_font = ft.Font(self._sans_path, resolution=0) 1371 self.assertEqual(new_font.resolution, new_resolution) 1372 finally: 1373 ft.set_default_resolution() 1374 1375 def test_freetype_Font_path(self): 1376 self.assertEqual(self._TEST_FONTS["sans"].path, self._sans_path) 1377 self.assertRaises(AttributeError, getattr, nullfont(), "path") 1378 1379 # This Font cache test is conditional on freetype being built by a debug 1380 # version of Python or with the C macro PGFT_DEBUG_CACHE defined. 1381 def test_freetype_Font_cache(self): 1382 glyphs = "abcde" 1383 glen = len(glyphs) 1384 other_glyphs = "123" 1385 oglen = len(other_glyphs) 1386 uempty = unicode_("") 1387 ## many_glyphs = (uempty.join([unichr_(i) for i in range(32,127)] + 1388 ## [unichr_(i) for i in range(161,172)] + 1389 ## [unichr_(i) for i in range(174,239)])) 1390 many_glyphs = uempty.join([unichr_(i) for i in range(32, 127)]) 1391 mglen = len(many_glyphs) 1392 1393 count = 0 1394 access = 0 1395 hit = 0 1396 miss = 0 1397 1398 f = ft.Font(None, size=24, font_index=0, resolution=72, ucs4=False) 1399 f.style = ft.STYLE_NORMAL 1400 f.antialiased = True 1401 1402 # Ensure debug counters are zero 1403 self.assertEqual(f._debug_cache_stats, (0, 0, 0, 0, 0)) 1404 # Load some basic glyphs 1405 count = access = miss = glen 1406 f.render_raw(glyphs) 1407 self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) 1408 # Vertical should not affect the cache 1409 access += glen 1410 hit += glen 1411 f.vertical = True 1412 f.render_raw(glyphs) 1413 f.vertical = False 1414 self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) 1415 # New glyphs will 1416 count += oglen 1417 access += oglen 1418 miss += oglen 1419 f.render_raw(other_glyphs) 1420 self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) 1421 # Point size does 1422 count += glen 1423 access += glen 1424 miss += glen 1425 f.render_raw(glyphs, size=12) 1426 self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) 1427 # Underline style does not 1428 access += oglen 1429 hit += oglen 1430 f.underline = True 1431 f.render_raw(other_glyphs) 1432 f.underline = False 1433 self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) 1434 # Oblique style does 1435 count += glen 1436 access += glen 1437 miss += glen 1438 f.oblique = True 1439 f.render_raw(glyphs) 1440 f.oblique = False 1441 self.assertEqual(f._debug_cache_stats, (count, 0, access, hit, miss)) 1442 # Strong style does; by this point cache clears can happen 1443 count += glen 1444 access += glen 1445 miss += glen 1446 f.strong = True 1447 f.render_raw(glyphs) 1448 f.strong = False 1449 ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats 1450 self.assertEqual( 1451 (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) 1452 ) 1453 # Rotation does 1454 count += glen 1455 access += glen 1456 miss += glen 1457 f.render_raw(glyphs, rotation=10) 1458 ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats 1459 self.assertEqual( 1460 (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) 1461 ) 1462 # aliased (mono) glyphs do 1463 count += oglen 1464 access += oglen 1465 miss += oglen 1466 f.antialiased = False 1467 f.render_raw(other_glyphs) 1468 f.antialiased = True 1469 ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats 1470 self.assertEqual( 1471 (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) 1472 ) 1473 # Trigger a cleanup for sure. 1474 count += 2 * mglen 1475 access += 2 * mglen 1476 miss += 2 * mglen 1477 f.get_metrics(many_glyphs, size=8) 1478 f.get_metrics(many_glyphs, size=10) 1479 ccount, cdelete_count, caccess, chit, cmiss = f._debug_cache_stats 1480 self.assertTrue(ccount < count) 1481 self.assertEqual( 1482 (ccount + cdelete_count, caccess, chit, cmiss), (count, access, hit, miss) 1483 ) 1484 1485 try: 1486 ft.Font._debug_cache_stats 1487 except AttributeError: 1488 del test_freetype_Font_cache 1489 1490 def test_undefined_character_code(self): 1491 # To be consistent with pygame.font.Font, undefined codes 1492 # are rendered as the undefined character, and has metrics 1493 # of None. 1494 font = self._TEST_FONTS["sans"] 1495 1496 img, size1 = font.render(unichr_(1), (0, 0, 0), size=24) 1497 img, size0 = font.render("", (0, 0, 0), size=24) 1498 self.assertTrue(size1.width > size0.width) 1499 1500 metrics = font.get_metrics(unichr_(1) + unichr_(48), size=24) 1501 self.assertEqual(len(metrics), 2) 1502 self.assertIsNone(metrics[0]) 1503 self.assertIsInstance(metrics[1], tuple) 1504 1505 @unittest.skipIf( 1506 pygame.get_sdl_version()[0] == 2, "SDL2 surfaces are only limited by memory" 1507 ) 1508 def test_issue_144(self): 1509 """Issue #144: unable to render text""" 1510 1511 # The bug came in two parts. The first was a convertion bug from 1512 # FT_Fixed to integer in for an Intel x86_64 Pygame build. The second 1513 # was to have the raised exception disappear before Font.render 1514 # returned to Python level. 1515 # 1516 font = ft.Font(None, size=64) 1517 s = "M" * 100000 # Way too long for an SDL surface 1518 self.assertRaises(pygame.error, font.render, s, (0, 0, 0)) 1519 1520 def test_issue_242(self): 1521 """Issue #242: get_rect() uses 0 as default style""" 1522 1523 # Issue #242: freetype.Font.get_rect() ignores style defaults when 1524 # the style argument is not given 1525 # 1526 # The text boundary rectangle returned by freetype.Font.get_rect() 1527 # should match the boundary of the same text rendered directly to a 1528 # surface. This permits accurate text positioning. To work properly, 1529 # get_rect() should calculate the text boundary to reflect text style, 1530 # such as underline. Instead, it ignores the style settings for the 1531 # Font object when the style argument is omitted. 1532 # 1533 # When the style argument is not given, freetype.get_rect() uses 1534 # unstyled text when calculating the boundary rectangle. This is 1535 # because _ftfont_getrect(), in _freetype.c, set the default 1536 # style to 0 rather than FT_STYLE_DEFAULT. 1537 # 1538 font = self._TEST_FONTS["sans"] 1539 1540 # Try wide style on a wide character. 1541 prev_style = font.wide 1542 font.wide = True 1543 try: 1544 rect = font.get_rect("M", size=64) 1545 surf, rrect = font.render(None, size=64) 1546 self.assertEqual(rect, rrect) 1547 finally: 1548 font.wide = prev_style 1549 1550 # Try strong style on several wide characters. 1551 prev_style = font.strong 1552 font.strong = True 1553 try: 1554 rect = font.get_rect("Mm_", size=64) 1555 surf, rrect = font.render(None, size=64) 1556 self.assertEqual(rect, rrect) 1557 finally: 1558 font.strong = prev_style 1559 1560 # Try oblique style on a tall, narrow character. 1561 prev_style = font.oblique 1562 font.oblique = True 1563 try: 1564 rect = font.get_rect("|", size=64) 1565 surf, rrect = font.render(None, size=64) 1566 self.assertEqual(rect, rrect) 1567 finally: 1568 font.oblique = prev_style 1569 1570 # Try underline style on a glyphless character. 1571 prev_style = font.underline 1572 font.underline = True 1573 try: 1574 rect = font.get_rect(" ", size=64) 1575 surf, rrect = font.render(None, size=64) 1576 self.assertEqual(rect, rrect) 1577 finally: 1578 font.underline = prev_style 1579 1580 def test_issue_237(self): 1581 """Issue #237: Memory overrun when rendered with underlining""" 1582 1583 # Issue #237: Memory overrun when text without descenders is rendered 1584 # with underlining 1585 # 1586 # The bug crashes the Python interpreter. The bug is caught with C 1587 # assertions in ft_render_cb.c when the Pygame module is compiled 1588 # for debugging. So far it is only known to affect Times New Roman. 1589 # 1590 name = "Times New Roman" 1591 font = ft.SysFont(name, 19) 1592 if font.name != name: 1593 # The font is unavailable, so skip the test. 1594 return 1595 font.underline = True 1596 s, r = font.render("Amazon", size=19) 1597 1598 # Some other checks to make sure nothing else broke. 1599 for adj in [-2, -1.9, -1, 0, 1.9, 2]: 1600 font.underline_adjustment = adj 1601 s, r = font.render("Amazon", size=19) 1602 1603 def test_issue_243(self): 1604 """Issue Y: trailing space ignored in boundary calculation""" 1605 1606 # Issue #243: For a string with trailing spaces, freetype ignores the 1607 # last space in boundary calculations 1608 # 1609 font = self._TEST_FONTS["fixed"] 1610 r1 = font.get_rect(" ", size=64) 1611 self.assertTrue(r1.width > 1) 1612 r2 = font.get_rect(" ", size=64) 1613 self.assertEqual(r2.width, 2 * r1.width) 1614 1615 def test_garbage_collection(self): 1616 """Check reference counting on returned new references""" 1617 1618 def ref_items(seq): 1619 return [weakref.ref(o) for o in seq] 1620 1621 font = self._TEST_FONTS["bmp-8-75dpi"] 1622 font.size = font.get_sizes()[0][0] 1623 text = "A" 1624 rect = font.get_rect(text) 1625 surf = pygame.Surface(rect.size, pygame.SRCALPHA, 32) 1626 refs = [] 1627 refs.extend(ref_items(font.render(text, (0, 0, 0)))) 1628 refs.append(weakref.ref(font.render_to(surf, (0, 0), text, (0, 0, 0)))) 1629 refs.append(weakref.ref(font.get_rect(text))) 1630 1631 n = len(refs) 1632 self.assertTrue(n > 0) 1633 1634 # for pypy we garbage collection twice. 1635 for i in range(2): 1636 gc.collect() 1637 1638 for i in range(n): 1639 self.assertIsNone(refs[i](), "ref %d not collected" % i) 1640 1641 try: 1642 from sys import getrefcount 1643 except ImportError: 1644 pass 1645 else: 1646 array = arrinter.Array(rect.size, "u", 1) 1647 o = font.render_raw(text) 1648 self.assertEqual(getrefcount(o), 2) 1649 self.assertEqual(getrefcount(o[0]), 2) 1650 self.assertEqual(getrefcount(o[1]), 2) 1651 self.assertEqual(getrefcount(font.render_raw_to(array, text)), 1) 1652 o = font.get_metrics("AB") 1653 self.assertEqual(getrefcount(o), 2) 1654 for i in range(len(o)): 1655 self.assertEqual(getrefcount(o[i]), 2, "refcount fail for item %d" % i) 1656 o = font.get_sizes() 1657 self.assertEqual(getrefcount(o), 2) 1658 for i in range(len(o)): 1659 self.assertEqual(getrefcount(o[i]), 2, "refcount fail for item %d" % i) 1660 1661 def test_display_surface_quit(self): 1662 """Font.render_to() on a closed display surface""" 1663 1664 # The Font.render_to() method checks that PySurfaceObject.surf is NULL 1665 # and raise a exception if it is. This fixes a bug in Pygame revision 1666 # 0600ea4f1cfb and earlier where Pygame segfaults instead. 1667 null_surface = pygame.Surface.__new__(pygame.Surface) 1668 f = self._TEST_FONTS["sans"] 1669 self.assertRaises( 1670 pygame.error, f.render_to, null_surface, (0, 0), "Crash!", size=12 1671 ) 1672 1673 def test_issue_565(self): 1674 """get_metrics supporting rotation/styles/size""" 1675 1676 tests = [ 1677 {"method": "size", "value": 36, "msg": "metrics same for size"}, 1678 {"method": "rotation", "value": 90, "msg": "metrics same for rotation"}, 1679 {"method": "oblique", "value": True, "msg": "metrics same for oblique"}, 1680 ] 1681 text = "|" 1682 1683 def run_test(method, value, msg): 1684 font = ft.Font(self._sans_path, size=24) 1685 before = font.get_metrics(text) 1686 font.__setattr__(method, value) 1687 after = font.get_metrics(text) 1688 self.assertNotEqual(before, after, msg) 1689 1690 for test in tests: 1691 run_test(test["method"], test["value"], test["msg"]) 1692 1693 def test_freetype_SysFont_name(self): 1694 """that SysFont accepts names of various types""" 1695 fonts = pygame.font.get_fonts() 1696 size = 12 1697 1698 # Check single name string: 1699 font_name = ft.SysFont(fonts[0], size).name 1700 self.assertFalse(font_name is None) 1701 1702 # Check string of comma-separated names. 1703 names = ",".join(fonts) 1704 font_name_2 = ft.SysFont(names, size).name 1705 self.assertEqual(font_name_2, font_name) 1706 1707 # Check list of names. 1708 font_name_2 = ft.SysFont(fonts, size).name 1709 self.assertEqual(font_name_2, font_name) 1710 1711 # Check generator: 1712 names = (name for name in fonts) 1713 font_name_2 = ft.SysFont(names, size).name 1714 self.assertEqual(font_name_2, font_name) 1715 1716 fonts_b = [f.encode() for f in fonts] 1717 1718 # Check single name bytes. 1719 font_name_2 = ft.SysFont(fonts_b[0], size).name 1720 self.assertEqual(font_name_2, font_name) 1721 1722 # Check comma-separated bytes. 1723 names = b",".join(fonts_b) 1724 font_name_2 = ft.SysFont(names, size).name 1725 self.assertEqual(font_name_2, font_name) 1726 1727 # Check list of bytes. 1728 font_name_2 = ft.SysFont(fonts_b, size).name 1729 self.assertEqual(font_name_2, font_name) 1730 1731 # Check mixed list of bytes and string. 1732 names = [fonts[0], fonts_b[1], fonts[2], fonts_b[3]] 1733 font_name_2 = ft.SysFont(names, size).name 1734 self.assertEqual(font_name_2, font_name) 1735 1736 @unittest.skipIf(pathlib is None, "no pathlib") 1737 def test_pathlib(self): 1738 f = ft.Font(pathlib.Path(self._fixed_path), 20) 1739 1740 1741class FreeTypeTest(unittest.TestCase): 1742 def setUp(self): 1743 ft.init() 1744 1745 def tearDown(self): 1746 ft.quit() 1747 1748 def test_resolution(self): 1749 try: 1750 ft.set_default_resolution() 1751 resolution = ft.get_default_resolution() 1752 self.assertEqual(resolution, 72) 1753 new_resolution = resolution + 10 1754 ft.set_default_resolution(new_resolution) 1755 self.assertEqual(ft.get_default_resolution(), new_resolution) 1756 ft.init(resolution=resolution + 20) 1757 self.assertEqual(ft.get_default_resolution(), new_resolution) 1758 finally: 1759 ft.set_default_resolution() 1760 1761 def test_autoinit_and_autoquit(self): 1762 pygame.init() 1763 self.assertTrue(ft.get_init()) 1764 pygame.quit() 1765 self.assertFalse(ft.get_init()) 1766 1767 # Ensure autoquit is replaced at init time 1768 pygame.init() 1769 self.assertTrue(ft.get_init()) 1770 pygame.quit() 1771 self.assertFalse(ft.get_init()) 1772 1773 def test_init(self): 1774 # Test if module initialized after calling init(). 1775 ft.quit() 1776 ft.init() 1777 1778 self.assertTrue(ft.get_init()) 1779 1780 def test_init__multiple(self): 1781 # Test if module initialized after multiple init() calls. 1782 ft.init() 1783 ft.init() 1784 1785 self.assertTrue(ft.get_init()) 1786 1787 def test_quit(self): 1788 # Test if module uninitialized after calling quit(). 1789 ft.quit() 1790 1791 self.assertFalse(ft.get_init()) 1792 1793 def test_quit__multiple(self): 1794 # Test if module initialized after multiple quit() calls. 1795 ft.quit() 1796 ft.quit() 1797 1798 self.assertFalse(ft.get_init()) 1799 1800 def test_get_init(self): 1801 # Test if get_init() gets the init state. 1802 self.assertTrue(ft.get_init()) 1803 1804 def test_cache_size(self): 1805 DEFAULT_CACHE_SIZE = 64 1806 self.assertEqual(ft.get_cache_size(), DEFAULT_CACHE_SIZE) 1807 ft.quit() 1808 self.assertEqual(ft.get_cache_size(), 0) 1809 new_cache_size = DEFAULT_CACHE_SIZE * 2 1810 ft.init(cache_size=new_cache_size) 1811 self.assertEqual(ft.get_cache_size(), new_cache_size) 1812 1813 def test_get_error(self): 1814 """Ensures get_error() is initially empty (None).""" 1815 error_msg = ft.get_error() 1816 1817 self.assertIsNone(error_msg) 1818 1819 1820if __name__ == "__main__": 1821 unittest.main() 1822