Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix exception when guessing the AFM familyname #9760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
fix exception when guessing the AFM familyname
  • Loading branch information
timhoffm committed Nov 13, 2017
commit 712c393475acdb57886ff2d54eaee32cc587e460
16 changes: 9 additions & 7 deletions lib/matplotlib/afm.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,13 @@ def _parse_optional(fh):
def parse_afm(fh):
"""
Parse the Adobe Font Metics file in file handle *fh*. Return value
is a (*dhead*, *dcmetrics*, *dkernpairs*, *dcomposite*) tuple where
*dhead* is a :func:`_parse_header` dict, *dcmetrics* is a
:func:`_parse_composites` dict, *dkernpairs* is a
:func:`_parse_kern_pairs` dict (possibly {}), and *dcomposite* is a
:func:`_parse_composites` dict (possibly {})
is a (*dhead*, *dcmetrics_ascii*, *dmetrics_name*, *dkernpairs*,
*dcomposite*) tuple where
*dhead* is a :func:`_parse_header` dict,
*dcmetrics_ascii* and *dcmetrics_name* are the two resulting dicts
from :func:`_parse_char_metrics`,
*dkernpairs* is a :func:`_parse_kern_pairs` dict (possibly {}) and
*dcomposite* is a :func:`_parse_composites` dict (possibly {})
"""
_sanity_check(fh)
dhead = _parse_header(fh)
Expand Down Expand Up @@ -503,8 +505,8 @@ def get_familyname(self):

# FamilyName not specified so we'll make a guess
name = self.get_fullname()
extras = (br'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
br'light|ultralight|extra|condensed))+$')
extras = (r'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that what ever font was being used in the OP did not have a b'FamilyName' entry, hence hit this code block, where as most afm do have it and return from this function early?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. the re.sub would always fail. But it's not reached if the entry has a b'FamilyName'.

r'light|ultralight|extra|condensed))+$')
return re.sub(extras, '', name)

@property
Expand Down
68 changes: 68 additions & 0 deletions lib/matplotlib/tests/test_afm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,35 @@

from __future__ import (absolute_import, division, print_function,
unicode_literals)
from six import BytesIO

import matplotlib.afm as afm


AFM_TEST_DATA = b"""StartFontMetrics 2.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use one of the afm files in https://github.com/matplotlib/matplotlib/tree/master/lib/matplotlib/mpl-data/fonts ? I am (with out thinking about it too much) concerned about copyright on including this in the source.

Is there something about this afm file that triggers the bug that does not trigger it otherwise?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These files are a bit lengthy in their CharMetrics block, which would make it a bit cumbersome to test their char metrics. I've changed the AFM_TEST_DATA to a shorter one with invented data. That should prevent any copyright issues.

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 50
Version 001.000
Notice Copyright (c) 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.
Expand All @@ -14,3 +39,46 @@ def test_nonascii_str():

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 == {
b'StartFontMetrics': 2.0,
b'FontName': 'MyFont-Bold',
b'EncodingScheme': 'FontSpecific',
b'FullName': 'My Font Bold',
b'FamilyName': 'Test Fonts',
b'Weight': 'Bold',
b'ItalicAngle': 0.0,
b'IsFixedPitch': False,
b'UnderlinePosition': -100,
b'UnderlineThickness': 50,
b'Version': '001.000',
b'Notice': 'Copyright (c) 2017 No one.',
b'FontBBox': [0, -321, 1234, 369],
b'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: (250.0, 'space', [0, 0, 0, 0]),
42: (1141.0, 'foo', [40, 60, 800, 360]),
99: (583.0, 'bar', [40, -10, 543, 210]),
},
{'space': (250.0, [0, 0, 0, 0]),
'foo': (1141.0, [40, 60, 800, 360]),
'bar': (583.0, [40, -10, 543, 210]),
})


def test_get_familyname_guessed():
fh = BytesIO(AFM_TEST_DATA)
fm = afm.AFM(fh)
del fm._header[b'FamilyName'] # remove FamilyName, so we have to guess
assert fm.get_familyname() == 'My Font'