from io import BytesIO import pytest import logging from matplotlib import _afm from matplotlib import font_manager as fm # See note in afm.py re: use of comma as decimal separator in the # UnderlineThickness field and re: use of non-ASCII characters in the Notice # field. AFM_TEST_DATA = b"""StartFontMetrics 2.0 Comment Comments are ignored. Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017 FontName MyFont-Bold EncodingScheme FontSpecific FullName My Font Bold FamilyName Test Fonts Weight Bold ItalicAngle 0.0 IsFixedPitch false UnderlinePosition -100 UnderlineThickness 56,789 Version 001.000 Notice Copyright \xa9 2017 No one. FontBBox 0 -321 1234 369 StartCharMetrics 3 C 0 ; WX 250 ; N space ; B 0 0 0 0 ; C 42 ; WX 1141 ; N foo ; B 40 60 800 360 ; C 99 ; WX 583 ; N bar ; B 40 -10 543 210 ; EndCharMetrics EndFontMetrics """ def test_nonascii_str(): # This tests that we also decode bytes as utf-8 properly. # Else, font files with non ascii characters fail to load. inp_str = "привет" byte_str = inp_str.encode("utf8") ret = _afm._to_str(byte_str) assert ret == inp_str def test_parse_header(): fh = BytesIO(AFM_TEST_DATA) header = _afm._parse_header(fh) assert header == { 'StartFontMetrics': 2.0, 'FontName': 'MyFont-Bold', 'EncodingScheme': 'FontSpecific', 'FullName': 'My Font Bold', 'FamilyName': 'Test Fonts', 'Weight': 'Bold', 'ItalicAngle': 0.0, 'IsFixedPitch': False, 'UnderlinePosition': -100, 'UnderlineThickness': 56.789, 'Version': '001.000', 'Notice': b'Copyright \xa9 2017 No one.', 'FontBBox': [0, -321, 1234, 369], 'StartCharMetrics': 3, } def test_parse_char_metrics(): fh = BytesIO(AFM_TEST_DATA) _afm._parse_header(fh) # position metrics = _afm._parse_char_metrics(fh) assert metrics == ( { 0: _afm.CharMetrics(250.0, 'space', (0, 0, 0, 0)), 42: _afm.CharMetrics(1141.0, 'foo', (40, 60, 800, 360)), 99: _afm.CharMetrics(583.0, 'bar', (40, -10, 543, 210)), }, { 'space': _afm.CharMetrics(250.0, 'space', (0, 0, 0, 0)), 'foo': _afm.CharMetrics(1141.0, 'foo', (40, 60, 800, 360)), 'bar': _afm.CharMetrics(583.0, 'bar', (40, -10, 543, 210)), } ) def test_get_familyname_guessed(): fh = BytesIO(AFM_TEST_DATA) font = _afm.AFM(fh) del font._header['FamilyName'] # remove FamilyName, so we have to guess assert font.get_familyname() == 'My Font' def test_font_manager_weight_normalization(): font = _afm.AFM(BytesIO( AFM_TEST_DATA.replace(b"Weight Bold\n", b"Weight Custom\n"))) assert fm.afmFontProperty("", font).weight == "normal" @pytest.mark.parametrize( "afm_data", [ b"""nope really nope""", b"""StartFontMetrics 2.0 Comment Comments are ignored. Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017 FontName MyFont-Bold EncodingScheme FontSpecific""", ], ) def test_bad_afm(afm_data): fh = BytesIO(afm_data) with pytest.raises(RuntimeError): _afm._parse_header(fh) @pytest.mark.parametrize( "afm_data", [ b"""StartFontMetrics 2.0 Comment Comments are ignored. Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017 Aardvark bob FontName MyFont-Bold EncodingScheme FontSpecific StartCharMetrics 3""", b"""StartFontMetrics 2.0 Comment Comments are ignored. Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017 ItalicAngle zero degrees FontName MyFont-Bold EncodingScheme FontSpecific StartCharMetrics 3""", ], ) def test_malformed_header(afm_data, caplog): fh = BytesIO(afm_data) with caplog.at_level(logging.ERROR): _afm._parse_header(fh) assert len(caplog.records) == 1 def test_afm_kerning(): fn = fm.findfont("Helvetica", fontext="afm") with open(fn, 'rb') as fh: afm = _afm.AFM(fh) assert afm.get_kern_dist_from_name('A', 'V') == -70.0 assert afm.get_kern_dist_from_name('V', 'A') == -80.0