diff --git a/.travis.yml b/.travis.yml index 32b32ed06..dd4b0b154 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,9 +29,9 @@ matrix: - env: RUNTIME=2.7 TOOLKITS="wx" PILLOW='pillow' - env: RUNTIME=3.5 TOOLKITS="null pyqt pyqt5" PILLOW='pillow' - env: RUNTIME=3.6 TOOLKITS="null pyqt pyqt5" PILLOW='pillow' - - env: RUNTIME=2.7 TOOLKIT=null PILLOW='pillow<3.0.0' - - env: RUNTIME=3.5 TOOLKIT=null PILLOW='pillow<3.0.0' - - env: RUNTIME=3.6 TOOLKIT=null PILLOW='pillow<3.0.0' + - env: RUNTIME=2.7 TOOLKITS=null PILLOW='pillow<3.0.0' + - env: RUNTIME=3.5 TOOLKITS=null PILLOW='pillow<3.0.0' + - env: RUNTIME=3.6 TOOLKITS=null PILLOW='pillow<3.0.0' allow_failures: - env: RUNTIME=2.7 TOOLKITS="wx" PILLOW='pillow' fast_finish: true diff --git a/CHANGES.txt b/CHANGES.txt index 2d2a148f1..23cf4a738 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ Enable CHANGELOG ================ -Changes since Enable 4.8.0 +Changes since Enable 4.8.1 ========================== Enhancements @@ -13,6 +13,21 @@ Fixes Maintenance ----------- +Enable 4.8.1 +============ + +Fixes +----- + +* PR #368: Deferral of kiva font_manager imports +* PR #365: Fix a KeyError while parsing ttfFontProperty + +Maintenance +----------- + +* PR #366: Fix CI config to avoid skipping tests in matrix +* PR #367: Fix CI config to avoid losing logs to nose + Enable 4.8.0 ============ 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/ci/edmtool.py b/ci/edmtool.py index 63297a454..93df32c2c 100644 --- a/ci/edmtool.py +++ b/ci/edmtool.py @@ -182,8 +182,8 @@ def test(runtime, toolkit, pillow, environment): environ = environment_vars.get(toolkit, {}).copy() environ['PYTHONUNBUFFERED'] = "1" commands = [ - "edm run -e {environment} -- coverage run -m nose.core enable -v", - "edm run -e {environment} -- coverage run -a -m nose.core kiva -v", + "edm run -e {environment} -- coverage run -m nose.core enable -v --nologcapture", + "edm run -e {environment} -- coverage run -a -m nose.core kiva -v --nologcapture", ] # We run in a tempdir to avoid accidentally picking up wrong traitsui diff --git a/kiva/fonttools/font.py b/kiva/fonttools/font.py index 42ea53096..4a82b98f2 100644 --- a/kiva/fonttools/font.py +++ b/kiva/fonttools/font.py @@ -10,7 +10,6 @@ import copy from kiva.constants import (DEFAULT, DECORATIVE, ROMAN, SCRIPT, SWISS, MODERN, TELETYPE, NORMAL, ITALIC, BOLD, BOLD_ITALIC) -from .font_manager import FontProperties, fontManager # Various maps used by str_to_font font_families = { @@ -104,6 +103,8 @@ def findfont(self): """ Returns the file name containing the font that most closely matches our font properties. """ + from .font_manager import fontManager + fp = self._make_font_props() return str(fontManager.findfont(fp)) @@ -118,6 +119,8 @@ def _make_font_props(self): """ Returns a font_manager.FontProperties object that encapsulates our font properties """ + from .font_manager import FontProperties + # XXX: change the weight to a numerical value if self.style == BOLD or self.style == BOLD_ITALIC: weight = "bold" 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 f6e54cca8..11008856e 100644 --- a/setup.py +++ b/setup.py @@ -37,9 +37,9 @@ MAJOR = 4 MINOR = 8 -MICRO = 0 +MICRO = 2 -IS_RELEASED = True +IS_RELEASED = False VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO) @@ -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"],