From fe90050693b940cab249e912b9604da0874939ce Mon Sep 17 00:00:00 2001 From: John Wiggins Date: Tue, 16 Mar 2021 11:34:41 +0100 Subject: [PATCH] Remove default_size and __default_weight from FontManager --- kiva/fonttools/_constants.py | 13 -- .../{_font_properties.py => _query.py} | 39 ++---- kiva/fonttools/_score.py | 7 +- kiva/fonttools/font.py | 21 ++-- kiva/fonttools/font_manager.py | 68 ++++------ kiva/fonttools/tests/test_font_properties.py | 117 ------------------ kiva/fonttools/tests/test_font_query.py | 116 +++++++++++++++++ kiva/fonttools/tests/test_score.py | 20 ++- 8 files changed, 172 insertions(+), 229 deletions(-) rename kiva/fonttools/{_font_properties.py => _query.py} (83%) delete mode 100644 kiva/fonttools/tests/test_font_properties.py create mode 100644 kiva/fonttools/tests/test_font_query.py diff --git a/kiva/fonttools/_constants.py b/kiva/fonttools/_constants.py index 0b437f386..deb28a81b 100644 --- a/kiva/fonttools/_constants.py +++ b/kiva/fonttools/_constants.py @@ -19,19 +19,6 @@ "modern", } -font_scalings = { - "xx-small": 0.579, - "x-small": 0.694, - "small": 0.833, - "medium": 1.0, - "large": 1.200, - "x-large": 1.440, - "xx-large": 1.728, - "larger": 1.2, - "smaller": 0.833, - None: 1.0, -} - preferred_fonts = { "fantasy": [ "Comic Sans MS", diff --git a/kiva/fonttools/_font_properties.py b/kiva/fonttools/_query.py similarity index 83% rename from kiva/fonttools/_font_properties.py rename to kiva/fonttools/_query.py index 44b9a5c6e..59534b2df 100644 --- a/kiva/fonttools/_font_properties.py +++ b/kiva/fonttools/_query.py @@ -10,18 +10,17 @@ from fontTools.afmLib import AFM from fontTools.ttLib import TTFont -from kiva.fonttools._constants import font_scalings, stretch_dict, weight_dict +from kiva.fonttools._constants import stretch_dict, weight_dict from kiva.fonttools._util import get_ttf_prop_dict from kiva.fonttools.font_manager import default_font_manager -class FontProperties(object): - """ A class for storing and manipulating font properties. +class FontQuery(object): + """ A class for storing properties needed to query the font manager. - The font properties are those described in the `W3C Cascading - Style Sheet, Level 1 - `_ font - specification. The six properties are: + The properties are those described in the `W3C Cascading + Style Sheet, Level 1 `_ font + specification. The six properties are: - family: A list of font names in decreasing order of priority. The items may include a generic font family name, either @@ -41,17 +40,10 @@ class FontProperties(object): 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black' - - size: Either an relative value of 'xx-small', 'x-small', - 'small', 'medium', 'large', 'x-large', 'xx-large' or an - absolute font size, e.g. 12 + - size: An absolute font size, e.g. 12 Alternatively, a font may be specified using an absolute path to a .ttf file, by using the *fname* kwarg. - - The preferred usage of font sizes is to use the relative values, - e.g. 'large', instead of absolute font sizes, e.g. 12. This - approach allows all text sizes to be made larger or smaller based - on the font manager's default font size. """ def __init__(self, family=None, style=None, variant=None, weight=None, stretch=None, size=None, fname=None, _init=None): @@ -146,15 +138,6 @@ def get_size(self): """ return self._size - def get_size_in_points(self): - if self._size is not None: - try: - return float(self._size) - except ValueError: - pass - default_size = default_font_manager().get_default_size() - return default_size * font_scalings.get(self._size) - def get_file(self): """ Return the filename of the associated font. """ @@ -238,15 +221,13 @@ def set_stretch(self, stretch): def set_size(self, size): """ Set the font size. - Either an relative value of 'xx-small', 'x-small', 'small', 'medium', - 'large', 'x-large', 'xx-large' or an absolute font size, e.g. 12. + An absolute font size, e.g. 12. """ if size is not None: try: size = float(size) except ValueError: - if size is not None and size not in font_scalings: - raise ValueError("size is invalid") + raise ValueError("size is invalid") self._size = size def set_file(self, file): @@ -259,4 +240,4 @@ def set_file(self, file): def copy(self): """ Return a deep copy of self """ - return FontProperties(_init=self) + return FontQuery(_init=self) diff --git a/kiva/fonttools/_score.py b/kiva/fonttools/_score.py index a715d4695..9749d3020 100644 --- a/kiva/fonttools/_score.py +++ b/kiva/fonttools/_score.py @@ -15,8 +15,7 @@ #################### """ from kiva.fonttools._constants import ( - font_family_aliases, font_scalings, preferred_fonts, stretch_dict, - weight_dict + font_family_aliases, preferred_fonts, stretch_dict, weight_dict ) # Each of the scoring functions below should return a value between @@ -49,7 +48,7 @@ def score_family(families, family2): return 1.0 -def score_size(size1, size2, default): +def score_size(size1, size2): """ Returns a match score between *size1* and *size2*. If *size2* (the size specified in the font file) is 'scalable', this @@ -65,7 +64,7 @@ def score_size(size1, size2, default): try: sizeval1 = float(size1) except ValueError: - sizeval1 = default * font_scalings.get(size1, 1.0) + return 1.0 try: sizeval2 = float(size2) except ValueError: diff --git a/kiva/fonttools/font.py b/kiva/fonttools/font.py index c7b6a2382..f6ef0915b 100644 --- a/kiva/fonttools/font.py +++ b/kiva/fonttools/font.py @@ -16,7 +16,7 @@ BOLD_ITALIC, BOLD, DECORATIVE, DEFAULT, ITALIC, MODERN, NORMAL, ROMAN, SCRIPT, SWISS, TELETYPE, ) -from kiva.fonttools._font_properties import FontProperties +from kiva.fonttools._query import FontQuery from kiva.fonttools.font_manager import default_font_manager # Various maps used by str_to_font @@ -116,19 +116,18 @@ def findfont(self): """ Returns the file name and face index of the font that most closely matches our font properties. """ - fp = self._make_font_props() - return default_font_manager().findfont(fp) + query = self._make_font_query() + return default_font_manager().findfont(query) def findfontname(self): """ Returns the name of the font that most closely matches our font properties """ - fp = self._make_font_props() - return fp.get_name() + query = self._make_font_query() + return query.get_name() - def _make_font_props(self): - """ Returns a font_manager.FontProperties object that encapsulates our - font properties + def _make_font_query(self): + """ Returns a FontQuery object that encapsulates our font properties. """ # XXX: change the weight to a numerical value if self.style == BOLD or self.style == BOLD_ITALIC: @@ -139,15 +138,15 @@ def _make_font_props(self): style = "italic" else: style = "normal" - fp = FontProperties( + query = FontQuery( family=self.familymap[self.family], style=style, weight=weight, size=self.size, ) if self.face_name != "": - fp.set_name(self.face_name) - return fp + query.set_name(self.face_name) + return query def _get_name(self): return self.face_name diff --git a/kiva/fonttools/font_manager.py b/kiva/fonttools/font_manager.py index a65877051..dac49ea6d 100644 --- a/kiva/fonttools/font_manager.py +++ b/kiva/fonttools/font_manager.py @@ -76,13 +76,11 @@ class FontManager: # Increment this version number whenever the font cache data # format or behavior has changed and requires a existing font # cache files to be rebuilt. - __version__ = 10 + __version__ = 11 - def __init__(self, size=None, weight="normal"): + def __init__(self): self._version = self.__version__ - self.__default_weight = weight - self.default_size = size if size is not None else 12.0 self.default_family = "sans-serif" self.default_font = {} @@ -123,21 +121,6 @@ def __init__(self, size=None, weight="normal"): self.ttf_lookup_cache = {} self.afm_lookup_cache = {} - def get_default_weight(self): - """ Return the default font weight. - """ - return self.__default_weight - - def get_default_size(self): - """ Return the default font size. - """ - return self.default_size - - def set_default_weight(self, weight): - """ Set the default font weight. The initial value is 'normal'. - """ - self.__default_weight = weight - def update_fonts(self, paths): """ Update the font lists with new font files. @@ -156,10 +139,10 @@ def update_fonts(self, paths): update_font_database(self.afm_db, afm_paths, fontext="afm") update_font_database(self.ttf_db, ttf_paths, fontext="ttf") - def findfont(self, prop, fontext="ttf", directory=None, + def findfont(self, query, fontext="ttf", directory=None, fallback_to_default=True, rebuild_if_missing=True): """ Search the font list for the font that most closely matches - the :class:`FontProperties` *prop*. + the :class:`FontQuery` *query*. :meth:`findfont` performs a nearest neighbor search. Each font is given a similarity score to the target font @@ -181,7 +164,7 @@ def findfont(self, prop, fontext="ttf", directory=None, `_ documentation for a description of the font finding algorithm. """ - from kiva.fonttools._font_properties import FontProperties + from kiva.fonttools._query import FontQuery class FontSpec(object): """ An object to represent the return value of findfont(). @@ -199,13 +182,13 @@ def __repr__(self): args = f"{self.filename}, face_index={self.face_index}" return f"FontSpec({args})" - if not isinstance(prop, FontProperties): - prop = FontProperties(prop) + if not isinstance(query, FontQuery): + query = FontQuery(query) - fname = prop.get_file() + fname = query.get_file() if fname is not None: logger.debug("findfont returning %s", fname) - # It's not at all clear where a `FontProperties` instance with + # It's not at all clear where a `FontQuery` instance with # `fname` already set would come from. Assume face_index == 0. return FontSpec(fname) @@ -217,7 +200,7 @@ def __repr__(self): font_db = self.ttf_db if directory is None: - cached = font_cache.get(hash(prop)) + cached = font_cache.get(hash(query)) if cached: return cached @@ -232,7 +215,7 @@ def __repr__(self): # both `fonts_for_family` and `score_family` will expand generic # families ("serif", "monospace") into lists of candidate families, # which ensures that all possible matching fonts will be scored. - fontlist = font_db.fonts_for_family(prop.get_family()) + fontlist = font_db.fonts_for_family(query.get_family()) best_score = 20.0 best_font = None @@ -240,12 +223,12 @@ def __repr__(self): # Matching family should have highest priority, so it is multiplied # by 10.0 score = ( - score_family(prop.get_family(), font.family) * 10.0 - + score_style(prop.get_style(), font.style) - + score_variant(prop.get_variant(), font.variant) - + score_weight(prop.get_weight(), font.weight) - + score_stretch(prop.get_stretch(), font.stretch) - + score_size(prop.get_size(), font.size, self.default_size) + score_family(query.get_family(), font.family) * 10.0 + + score_style(query.get_style(), font.style) + + score_variant(query.get_variant(), font.variant) + + score_weight(query.get_weight(), font.weight) + + score_stretch(query.get_stretch(), font.stretch) + + score_size(query.get_size(), font.size) ) # Lowest score wins if score < best_score: @@ -259,12 +242,12 @@ def __repr__(self): if fallback_to_default: warnings.warn( "findfont: Font family %s not found. Falling back to %s" - % (prop.get_family(), self.default_family) + % (query.get_family(), self.default_family) ) - default_prop = prop.copy() - default_prop.set_family(self.default_family) + default_query = query.copy() + default_query.set_family(self.default_family) return self.findfont( - default_prop, fontext, directory, + default_query, fontext, directory, fallback_to_default=False, ) else: @@ -272,7 +255,7 @@ def __repr__(self): # so just return the vera.ttf warnings.warn( "findfont: Could not match %s. Returning %s" - % (prop, self.default_font[fontext]), + % (query, self.default_font[fontext]), UserWarning, ) # Assume this is never a .ttc font, so 0 is ok for face index. @@ -280,7 +263,7 @@ def __repr__(self): else: logger.debug( "findfont: Matching %s to %s (%s[%d]) with score of %f", - prop, + query, best_font.family, best_font.fname, best_font.face_index, @@ -295,7 +278,7 @@ def __repr__(self): ) _rebuild() return default_font_manager().findfont( - prop, fontext, directory, + query, fontext, directory, fallback_to_default=True, rebuild_if_missing=False, ) @@ -303,7 +286,7 @@ def __repr__(self): raise ValueError("No valid font could be found") if directory is None: - font_cache[hash(prop)] = result + font_cache[hash(query)] = result return result @@ -373,7 +356,6 @@ def _load_from_cache_or_rebuild(cache_file): or fontManager._version != FontManager.__version__): fontManager = _new_font_manager(cache_file) else: - fontManager.default_size = None logger.debug("Using fontManager instance from %s", cache_file) except Exception: fontManager = _new_font_manager(cache_file) diff --git a/kiva/fonttools/tests/test_font_properties.py b/kiva/fonttools/tests/test_font_properties.py deleted file mode 100644 index bca1294a0..000000000 --- a/kiva/fonttools/tests/test_font_properties.py +++ /dev/null @@ -1,117 +0,0 @@ -# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only under -# the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# -# Thanks for using Enthought open source! -import unittest - -from kiva.fonttools._font_properties import FontProperties - - -class TestFontProperties(unittest.TestCase): - def setUp(self): - self.fp = FontProperties( - family=["serif"], - style="italic", - variant="small-caps", - weight="bold", - stretch="ultra-condensed", - size=18, - ) - - def test_copying(self): - fp_copy = self.fp.copy() - - for attr in ("_family", "_slant", "_variant", "_weight", - "_stretch", "_size", "_file"): - self.assertEqual(getattr(self.fp, attr), getattr(fp_copy, attr)) - - # Compare the strings too - self.assertEqual(str(self.fp), str(fp_copy)) - # And the hashes - self.assertEqual(hash(self.fp), hash(fp_copy)) - - def test_getters(self): - self.assertListEqual(self.fp.get_family(), ["serif"]) - self.assertIsNone(self.fp.get_file()) - self.assertEqual(self.fp.get_size(), 18) - self.assertEqual(self.fp.get_slant(), "italic") - self.assertEqual(self.fp.get_stretch(), "ultra-condensed") - self.assertEqual(self.fp.get_style(), "italic") - self.assertEqual(self.fp.get_variant(), "small-caps") - self.assertEqual(self.fp.get_weight(), "bold") - - def test_setters(self): - # Family is always converted to a list - self.fp.set_family("cursive") - self.assertListEqual(self.fp.get_family(), ["cursive"]) - # Family can be unset - self.fp.set_family(None) - self.assertIsNone(self.fp.get_family()) - # Family can be a bytestring - self.fp.set_family("Arial".encode("utf8")) - self.assertListEqual(self.fp.get_family(), ["Arial"]) - - filename = "not a real file.ttf" - self.fp.set_file(filename) - self.assertEqual(self.fp.get_file(), filename) - - # set_name is a synonym for set_family - self.fp.set_name("Verdana") - self.assertListEqual(self.fp.get_family(), ["Verdana"]) - - # set_style has requirements - with self.assertRaises(ValueError): - self.fp.set_style("post-modern") - - self.fp.set_style("oblique") - self.assertEqual(self.fp.get_style(), "oblique") - - # set_variant has requirements - with self.assertRaises(ValueError): - self.fp.set_variant("rad") - - self.fp.set_variant("normal") - self.assertEqual(self.fp.get_variant(), "normal") - - # set_weight takes many input types - with self.assertRaises(ValueError): - self.fp.set_weight("superduperdark") - with self.assertRaises(ValueError): - self.fp.set_weight(-42) - with self.assertRaises(ValueError): - self.fp.set_weight(3000) - - self.fp.set_weight(500) - self.assertEqual(self.fp.get_weight(), 500) - self.fp.set_weight("bold") - self.assertEqual(self.fp.get_weight(), "bold") - - # set_stretch has requirements - with self.assertRaises(ValueError): - self.fp.set_stretch("condensed-matter") - with self.assertRaises(ValueError): - self.fp.set_stretch(-42) - with self.assertRaises(ValueError): - self.fp.set_stretch(3000) - - self.fp.set_stretch("semi-condensed") - self.assertEqual(self.fp.get_stretch(), "semi-condensed") - self.fp.set_stretch(300) - self.assertEqual(self.fp.get_stretch(), 300) - # default - self.fp.set_stretch(None) - self.assertEqual(self.fp.get_stretch(), 500) - - # set_size has requirements - with self.assertRaises(ValueError): - self.fp.set_size("itsy-bitsy") - - self.fp.set_size("medium") - self.assertEqual(self.fp.get_size(), "medium") - self.fp.set_size(36) - self.assertEqual(self.fp.get_size(), 36.0) diff --git a/kiva/fonttools/tests/test_font_query.py b/kiva/fonttools/tests/test_font_query.py new file mode 100644 index 000000000..07cce4888 --- /dev/null +++ b/kiva/fonttools/tests/test_font_query.py @@ -0,0 +1,116 @@ +# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +import unittest + +from kiva.fonttools._query import FontQuery + + +class TestFontQuery(unittest.TestCase): + def setUp(self): + self.query = FontQuery( + family=["serif"], + style="italic", + variant="small-caps", + weight="bold", + stretch="ultra-condensed", + size=18, + ) + + def test_copying(self): + query_copy = self.query.copy() + + for attr in ("_family", "_slant", "_variant", "_weight", + "_stretch", "_size", "_file"): + self.assertEqual(getattr(self.query, attr), + getattr(query_copy, attr)) + + # Compare the strings too + self.assertEqual(str(self.query), str(query_copy)) + # And the hashes + self.assertEqual(hash(self.query), hash(query_copy)) + + def test_getters(self): + self.assertListEqual(self.query.get_family(), ["serif"]) + self.assertIsNone(self.query.get_file()) + self.assertEqual(self.query.get_size(), 18) + self.assertEqual(self.query.get_slant(), "italic") + self.assertEqual(self.query.get_stretch(), "ultra-condensed") + self.assertEqual(self.query.get_style(), "italic") + self.assertEqual(self.query.get_variant(), "small-caps") + self.assertEqual(self.query.get_weight(), "bold") + + def test_setters(self): + # Family is always converted to a list + self.query.set_family("cursive") + self.assertListEqual(self.query.get_family(), ["cursive"]) + # Family can be unset + self.query.set_family(None) + self.assertIsNone(self.query.get_family()) + # Family can be a bytestring + self.query.set_family("Arial".encode("utf8")) + self.assertListEqual(self.query.get_family(), ["Arial"]) + + filename = "not a real file.ttf" + self.query.set_file(filename) + self.assertEqual(self.query.get_file(), filename) + + # set_name is a synonym for set_family + self.query.set_name("Verdana") + self.assertListEqual(self.query.get_family(), ["Verdana"]) + + # set_style has requirements + with self.assertRaises(ValueError): + self.query.set_style("post-modern") + + self.query.set_style("oblique") + self.assertEqual(self.query.get_style(), "oblique") + + # set_variant has requirements + with self.assertRaises(ValueError): + self.query.set_variant("rad") + + self.query.set_variant("normal") + self.assertEqual(self.query.get_variant(), "normal") + + # set_weight takes many input types + with self.assertRaises(ValueError): + self.query.set_weight("superduperdark") + with self.assertRaises(ValueError): + self.query.set_weight(-42) + with self.assertRaises(ValueError): + self.query.set_weight(3000) + + self.query.set_weight(500) + self.assertEqual(self.query.get_weight(), 500) + self.query.set_weight("bold") + self.assertEqual(self.query.get_weight(), "bold") + + # set_stretch has requirements + with self.assertRaises(ValueError): + self.query.set_stretch("condensed-matter") + with self.assertRaises(ValueError): + self.query.set_stretch(-42) + with self.assertRaises(ValueError): + self.query.set_stretch(3000) + + self.query.set_stretch("semi-condensed") + self.assertEqual(self.query.get_stretch(), "semi-condensed") + self.query.set_stretch(300) + self.assertEqual(self.query.get_stretch(), 300) + # default + self.query.set_stretch(None) + self.assertEqual(self.query.get_stretch(), 500) + + # set_size has requirements + with self.assertRaises(ValueError): + self.query.set_size("itsy-bitsy") + + self.query.set_size(36) + self.assertEqual(self.query.get_size(), 36.0) diff --git a/kiva/fonttools/tests/test_score.py b/kiva/fonttools/tests/test_score.py index ffcb89977..3b452f471 100644 --- a/kiva/fonttools/tests/test_score.py +++ b/kiva/fonttools/tests/test_score.py @@ -42,24 +42,20 @@ def test_score_family(self): def test_score_size(self): # exact matches - self.assertEqual(score_size(12.0, 12.0, default=24.0), 0.0) - self.assertEqual(score_size("12.0", 12.0, default=24.0), 0.0) - self.assertEqual(score_size(12.0, "12.0", default=24.0), 0.0) + self.assertEqual(score_size(12.0, 12.0), 0.0) + self.assertEqual(score_size("12.0", 12.0), 0.0) + self.assertEqual(score_size(12.0, "12.0"), 0.0) # scaled exact matches - self.assertEqual(score_size(12.0, "scalable", default=24.0), 0.0) - self.assertEqual(score_size("medium", 24.0, default=24.0), 0.0) - self.assertEqual(score_size("larger", 24.0, default=20.0), 0.0) + self.assertEqual(score_size(12.0, "scalable"), 0.0) # fuzzy matches - self.assertAlmostEqual(score_size(12.0, 19.2, default=12.0), 0.1) - self.assertAlmostEqual(score_size(12.0, 48.0, default=12.0), 0.5) - self.assertAlmostEqual(score_size("medium", 48.0, default=12.0), 0.5) - self.assertAlmostEqual(score_size("larger", 60.0, default=20.0), 0.5) + self.assertAlmostEqual(score_size(12.0, 19.2), 0.1) + self.assertAlmostEqual(score_size(12.0, 48.0), 0.5) # misses - self.assertEqual(score_size(8.0, 80.0, default=12.0), 1.0) - self.assertEqual(score_size(24.0, "doesn't matter", default=12.0), 1.0) + self.assertEqual(score_size(8.0, 80.0), 1.0) + self.assertEqual(score_size(24.0, "doesn't matter"), 1.0) def test_score_stretch(self): # exact matches