diff --git a/MANIFEST.in b/MANIFEST.in index addf07e4b..0f7ee62a0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,4 +10,4 @@ include docs/kiva/agg/notes recursive-include docs *.py *.rst *.txt *.css *.png *.ico *.doc recursive-include examples *.py *.txt *.gif *.jpg *.enaml recursive-include kiva/quartz *.pyx *.pxi *.pxd mac_context*.* -recursive-include kiva/fonttools/tests/data *.txt *.ttc +recursive-include kiva/fonttools/tests/data *.txt *.ttc *.ttf diff --git a/kiva/fonttools/font_manager.py b/kiva/fonttools/font_manager.py index c5832dedc..e62e85f5b 100644 --- a/kiva/fonttools/font_manager.py +++ b/kiva/fonttools/font_manager.py @@ -220,17 +220,42 @@ def is_string_like(obj): return False try: obj + '' - except: + except Exception: return False return True +def decode_prop(prop): + """ Decode a prop string. + + Parameters + ---------- + prop : bytestring + + Returns + ------- + string + """ + # Adapted from: https://gist.github.com/pklaus/dce37521579513c574d0 + encoding = "utf-16-be" if b"\x00" in prop else "utf-8" + + return prop.decode(encoding) + + def getPropDict(font): n = font['name'] propdict = {} for prop in n.names: - prop_key = (prop.platformID, prop.platEncID, prop.langID, prop.nameID) - propdict[prop_key] = prop.string + try: + if 'name' in propdict and 'sfnt4' in propdict: + break + elif prop.nameID == 1 and 'name' not in propdict: + propdict['name'] = decode_prop(prop.string) + elif prop.nameID == 4 and 'sfnt4' not in propdict: + propdict['sfnt4'] = decode_prop(prop.string) + except UnicodeDecodeError: + continue + return propdict @@ -522,32 +547,17 @@ def ttfFontProperty(fpath, font): *font* is a :class:`FT2Font` instance. """ props = getPropDict(font) - name = props[(1, 0, 0, 1)].decode() + name = props.get('name') + if name is None: + raise KeyError("No name could be found for: {}".format(fpath)) # Styles are: italic, oblique, and normal (default) + sfnt4 = props.get('sfnt4', '') - try: - sfnt2 = props[(1, 0, 0, 2)] - except: - sfnt2 = None - try: - sfnt4 = props[(1, 0, 0, 4)] - except: - sfnt4 = None - if sfnt2: - sfnt2 = sfnt2.lower().decode() - else: - sfnt2 = '' - if sfnt4: - sfnt4 = sfnt4.lower().decode() - else: - sfnt4 = '' if sfnt4.find('oblique') >= 0: style = 'oblique' elif sfnt4.find('italic') >= 0: style = 'italic' - elif sfnt2.find('regular') >= 0: - style = 'normal' else: style = 'normal' @@ -665,6 +675,7 @@ def afmFontProperty(fontpath, font): return FontEntry(fontpath, name, style, variant, weight, stretch, size) + def createFontList(fontfiles, fontext='ttf'): """ A function to create a font lookup list. The default is to create @@ -685,7 +696,7 @@ def createFontList(fontfiles, fontext='ttf'): if fontext == 'afm': try: fh = open(fpath, 'r') - except: + except Exception: logger.error( "Could not open font file %s", fpath, exc_info=True) continue @@ -710,7 +721,7 @@ def createFontList(fontfiles, fontext='ttf'): _, ext = os.path.splitext(fpath) try: if ext.lower() == ".ttc": - collection = TTCollection(str(fpath)) + collection = TTCollection(six.text_type(fpath)) try: props = [] for font in collection.fonts: @@ -724,7 +735,7 @@ def createFontList(fontfiles, fontext='ttf'): ) continue else: - font = TTFont(str(fpath)) + font = TTFont(six.text_type(fpath)) except (RuntimeError, TTLibError): logger.error( "Could not open font file %s", fpath, exc_info=True) @@ -841,8 +852,8 @@ def __init__(self, family=None, style=None, variant=None, weight=None, self.set_size(size) def __hash__(self): - l = [(k, getattr(self, "get" + k)()) for k in sorted(self.__dict__)] - return hash(repr(l)) + lst = [(k, getattr(self, "get" + k)()) for k in sorted(self.__dict__)] + return hash(repr(lst)) def __str__(self): return str((self._family, self._slant, self._variant, @@ -859,12 +870,13 @@ def get_name(self): Return the name of the font that best matches the font properties. """ - filename = str(fontManager.findfont(self)) + filename = six.text_type(fontManager.findfont(self)) if filename.endswith('.afm'): return afm.AFM(open(filename)).get_familyname() font = fontManager.findfont(self) - return getPropDict(TTFont(str(font)))[(1, 0, 0, 1)] + prop_dict = getPropDict(TTFont(six.text_type(font))) + return prop_dict['name'] def get_style(self): """ @@ -1088,7 +1100,7 @@ def __init__(self, size=None, weight='normal'): else: paths.append(ttfpath) - logger.debug("font search path %s", str(paths)) + logger.debug("font search path %s", six.text_type(paths)) # Load TrueType fonts and create font dictionary. self.ttffiles = findSystemFonts(paths) + findSystemFonts() @@ -1444,7 +1456,7 @@ def findfont(prop, fontext='ttf'): else: fontManager.default_size = None logger.debug("Using fontManager instance from %s", _fmcache) - except: + except Exception: _rebuild() def findfont(prop, **kw): diff --git a/kiva/fonttools/tests/data/TestTTF.ttf b/kiva/fonttools/tests/data/TestTTF.ttf new file mode 100644 index 000000000..e906d012d Binary files /dev/null and b/kiva/fonttools/tests/data/TestTTF.ttf differ diff --git a/kiva/fonttools/tests/data/source.txt b/kiva/fonttools/tests/data/source.txt index c6eb3f304..fd25162a4 100644 --- a/kiva/fonttools/tests/data/source.txt +++ b/kiva/fonttools/tests/data/source.txt @@ -6,3 +6,4 @@ Files and orginal authors: ---------------------------------------------------------------------------- kiva/fonttools/tests/data: TestTTC.ttc | fonttools + TestTTF.ttf | fonttools diff --git a/kiva/fonttools/tests/test_font_manager.py b/kiva/fonttools/tests/test_font_manager.py index c180e51c9..25d984916 100644 --- a/kiva/fonttools/tests/test_font_manager.py +++ b/kiva/fonttools/tests/test_font_manager.py @@ -6,11 +6,13 @@ import mock from pkg_resources import resource_filename +from fontTools.ttLib import TTFont -from ..font_manager import FontEntry, createFontList +from ..font_manager import FontEntry, createFontList, ttfFontProperty data_dir = resource_filename('kiva.fonttools.tests', 'data') + class TestCreateFontList(unittest.TestCase): def setUp(self): @@ -44,3 +46,27 @@ def test_ttc_exception_on_TTCollection(self, m_TTCollection): # Then self.assertEqual(len(fontlist), 0) self.assertEqual(m_TTCollection.call_count, 1) + + +class TestTTFFontProperty(unittest.TestCase): + + def test_font(self): + # Given + test_font = os.path.join(data_dir, "TestTTF.ttf") + exp_name = "Test TTF" + exp_style = "normal" + exp_variant = "normal" + exp_weight = 400 + exp_stretch = "normal" + exp_size = "scalable" + + # When + entry = ttfFontProperty(test_font, TTFont(test_font)) + + # Then + self.assertEqual(entry.name, exp_name) + self.assertEqual(entry.style, exp_style) + self.assertEqual(entry.variant, exp_variant) + self.assertEqual(entry.weight, exp_weight) + self.assertEqual(entry.stretch, exp_stretch) + self.assertEqual(entry.size, exp_size) diff --git a/setup.py b/setup.py index 3a4a9af97..c7c032a29 100644 --- a/setup.py +++ b/setup.py @@ -288,6 +288,7 @@ def run(self): 'enable.savage.trait_defs.ui.wx': ['data/*.svg'], 'kiva': ['tests/agg/doubleprom_soho_full.jpg', 'fonttools/tests/data/*.ttc', + 'fonttools/tests/data/*.ttf', 'fonttools/tests/data/*.txt'], }, platforms=["Windows", "Linux", "Mac OS-X", "Unix", "Solaris"],