From 1d78d3d160a0dfff2aa316e589cf256825f1f21b Mon Sep 17 00:00:00 2001
From: Michael Droettboom <mdboom@gmail.com>
Date: Mon, 27 Feb 2012 16:32:10 -0500
Subject: [PATCH 1/2] Makes the "hinting factor" tweakable at runtime.

The hinting factor is the amount of softness in hinting is applied in
the horizontal direction.  For years, matplotlib has hinted to 1/8
pixels in the horizontal direction and whole pixels in the vertical
direction.  This results in text that is often more
legible (particularly at smaller sizes) than standard 1:1 hinting.
This is based on idea from this paper from the author of Agg:

    http://www.antigrain.com/research/font_rasterization/

However, sometimes the user may want full-on hinting, so this value is
now tweakable using the `text.hinting_factor` rcParam.
---
 lib/matplotlib/backends/backend_agg.py |  4 +-
 lib/matplotlib/rcsetup.py              |  1 +
 lib/matplotlib/tests/__init__.py       |  1 +
 matplotlibrc.template                  |  3 ++
 src/ft2font.cpp                        | 52 ++++++++++----------------
 src/ft2font.h                          |  3 +-
 6 files changed, 29 insertions(+), 35 deletions(-)

diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py
index db4bdf9e5e51..f0ae4c91f8eb 100644
--- a/lib/matplotlib/backends/backend_agg.py
+++ b/lib/matplotlib/backends/backend_agg.py
@@ -220,7 +220,9 @@ def _get_agg_font(self, prop):
             fname = findfont(prop)
             font = self._fontd.get(fname)
             if font is None:
-                font = FT2Font(str(fname))
+                font = FT2Font(
+                    str(fname),
+                    hinting_factor=rcParams['text.hinting_factor'])
                 self._fontd[fname] = font
             self._fontd[key] = font
 
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index c947b24d5154..6ba5be47a317 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -408,6 +408,7 @@ def __call__(self, s):
     'text.latex.preview' : [False, validate_bool],
     'text.dvipnghack'     : [None, validate_bool_maybe_none],
     'text.hinting'        : [True, validate_bool],
+    'text.hinting_factor' : [8, validate_int],
 
     # The following are deprecated and replaced by, e.g., 'font.style'
     #'text.fontstyle'      : ['normal', str],
diff --git a/lib/matplotlib/tests/__init__.py b/lib/matplotlib/tests/__init__.py
index f2b102b7734c..bd133a4f6645 100644
--- a/lib/matplotlib/tests/__init__.py
+++ b/lib/matplotlib/tests/__init__.py
@@ -12,3 +12,4 @@ def setup():
     rcdefaults() # Start with all defaults
     rcParams['font.family'] = 'Bitstream Vera Sans'
     rcParams['text.hinting'] = False
+    rcParams['text.hinting_factor'] = 8
diff --git a/matplotlibrc.template b/matplotlibrc.template
index ffb659c617d3..f333f1c368ac 100644
--- a/matplotlibrc.template
+++ b/matplotlibrc.template
@@ -172,6 +172,9 @@ backend      : %(backend)s
 
 #text.hinting : True # If True, text will be hinted, otherwise not.  This only
                      # affects the Agg backend.
+#text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
+                         # horizontal direction.  A value of 1 will hint to full
+                         # pixels.  A value of 2 will hint to half pixels etc.
 
 # The following settings allow you to select the fonts in math mode.
 # They map from a TeX font name to a fontconfig font pattern.
diff --git a/src/ft2font.cpp b/src/ft2font.cpp
index 7cf44110bff7..98431d950f6d 100644
--- a/src/ft2font.cpp
+++ b/src/ft2font.cpp
@@ -36,17 +36,7 @@
       at the size at 12 pixels scaled by 2 through a transform,
       because the hints will have been computed differently (except
       you have disabled hints).
-
- This hack is enabled only when VERTICAL_HINTING is defined, and will
- only be effective when load_char and set_text are called with 'flags=
- LOAD_DEFAULT', which is the default.
  */
-#define VERTICAL_HINTING
-#ifdef VERTICAL_HINTING
-#define HORIZ_HINTING 8
-#else
-#define HORIZ_HINTING 1
-#endif
 
 FT_Library _ft2Library;
 
@@ -408,7 +398,7 @@ FT2Image::py_get_height(const Py::Tuple & args)
 PYCXX_VARARGS_METHOD_DECL(FT2Image, py_get_height)
 
 Py::PythonClassObject<Glyph> Glyph::factory(
-        const FT_Face& face, const FT_Glyph& glyph, size_t ind)
+        const FT_Face& face, const FT_Glyph& glyph, size_t ind, long hinting_factor)
 {
     Py::Callable class_type(type());
     Py::PythonClassObject<Glyph> obj = Py::PythonClassObject<Glyph>(
@@ -419,12 +409,12 @@ Py::PythonClassObject<Glyph> Glyph::factory(
     FT_BBox bbox;
     FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &bbox);
 
-    o->setattro("width",        Py::Int(face->glyph->metrics.width / HORIZ_HINTING));
+    o->setattro("width",        Py::Int(face->glyph->metrics.width / hinting_factor));
     o->setattro("height",       Py::Int(face->glyph->metrics.height));
-    o->setattro("horiBearingX", Py::Int(face->glyph->metrics.horiBearingX / HORIZ_HINTING));
+    o->setattro("horiBearingX", Py::Int(face->glyph->metrics.horiBearingX / hinting_factor));
     o->setattro("horiBearingY", Py::Int(face->glyph->metrics.horiBearingY));
     o->setattro("horiAdvance",  Py::Int(face->glyph->metrics.horiAdvance));
-    o->setattro("linearHoriAdvance",  Py::Int(face->glyph->linearHoriAdvance / HORIZ_HINTING));
+    o->setattro("linearHoriAdvance",  Py::Int(face->glyph->linearHoriAdvance / hinting_factor));
     o->setattro("vertBearingX", Py::Int(face->glyph->metrics.vertBearingX));
 
     o->setattro("vertBearingY", Py::Int(face->glyph->metrics.vertBearingY));
@@ -847,14 +837,15 @@ FT2Font::FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds)
     }
 
     // set a default fontsize 12 pt at 72dpi
-#ifdef VERTICAL_HINTING
-    error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * HORIZ_HINTING, 72);
-    static FT_Matrix transform = { 65536 / HORIZ_HINTING, 0, 0, 65536 };
+    hinting_factor = 8;
+    if (kwds.hasKey("hinting_factor"))
+    {
+        hinting_factor = Py::Long(kwds["hinting_factor"]);
+    }
+
+    error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * hinting_factor, 72);
+    static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
     FT_Set_Transform(face, &transform, 0);
-#else
-    error = FT_Set_Char_Size(face, 12 * 64, 0, 72, 72);
-#endif
-    //error = FT_Set_Char_Size( face, 20 * 64, 0, 80, 80 );
     if (error)
     {
         std::ostringstream s;
@@ -993,17 +984,12 @@ FT2Font::set_size(const Py::Tuple & args)
     double ptsize = Py::Float(args[0]);
     double dpi = Py::Float(args[1]);
 
-#ifdef VERTICAL_HINTING
     int error = FT_Set_Char_Size(face, (long)(ptsize * 64), 0,
-                                 (unsigned int)dpi * HORIZ_HINTING,
+                                 (unsigned int)dpi * hinting_factor,
                                  (unsigned int)dpi);
-    static FT_Matrix transform = { 65536 / HORIZ_HINTING, 0, 0, 65536 };
+    static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
     FT_Set_Transform(face, &transform, 0);
-#else
-    int error = FT_Set_Char_Size(face, (long)(ptsize * 64), 0,
-                                 (unsigned int)dpi,
-                                 (unsigned int)dpi);
-#endif
+
     if (error)
     {
         throw Py::RuntimeError("Could not set the fontsize");
@@ -1126,7 +1112,7 @@ FT2Font::get_kerning(const Py::Tuple & args)
 
     if (!FT_Get_Kerning(face, left, right, mode, &delta))
     {
-        return Py::Int(delta.x / HORIZ_HINTING);
+        return Py::Int(delta.x / hinting_factor);
     }
     else
     {
@@ -1214,7 +1200,7 @@ FT2Font::set_text(const Py::Tuple & args, const Py::Dict & kwargs)
             FT_Vector delta;
             FT_Get_Kerning(face, previous, glyph_index,
                            FT_KERNING_DEFAULT, &delta);
-            pen.x += delta.x / HORIZ_HINTING;
+            pen.x += delta.x / hinting_factor;
         }
         error = FT_Load_Glyph(face, glyph_index, flags);
         if (error)
@@ -1319,7 +1305,7 @@ FT2Font::load_char(const Py::Tuple & args, const Py::Dict & kwargs)
 
     size_t num = glyphs.size();  //the index into the glyphs list
     glyphs.push_back(thisGlyph);
-    return Glyph::factory(face, thisGlyph, num);
+    return Glyph::factory(face, thisGlyph, num, hinting_factor);
 }
 PYCXX_KEYWORDS_METHOD_DECL(FT2Font, load_char)
 
@@ -1369,7 +1355,7 @@ FT2Font::load_glyph(const Py::Tuple & args, const Py::Dict & kwargs)
 
     size_t num = glyphs.size();  //the index into the glyphs list
     glyphs.push_back(thisGlyph);
-    return Glyph::factory(face, thisGlyph, num);
+    return Glyph::factory(face, thisGlyph, num, hinting_factor);
 }
 PYCXX_KEYWORDS_METHOD_DECL(FT2Font, load_glyph)
 
diff --git a/src/ft2font.h b/src/ft2font.h
index 5d7739e7518c..502299b3a5f5 100644
--- a/src/ft2font.h
+++ b/src/ft2font.h
@@ -83,7 +83,7 @@ class Glyph : public Py::PythonClass<Glyph>
     Glyph(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds) :
         Py::PythonClass<Glyph>::PythonClass(self, args, kwds) { }
     virtual ~Glyph();
-    static Py::PythonClassObject<Glyph> factory(const FT_Face&, const FT_Glyph&, size_t);
+    static Py::PythonClassObject<Glyph> factory(const FT_Face&, const FT_Glyph&, size_t, long);
     int setattro(const Py::String &name, const Py::Object &value);
     Py::Object getattro(const Py::String &name);
     static void init_type(void);
@@ -137,6 +137,7 @@ class FT2Font : public Py::PythonClass<FT2Font>
     double angle;
     double ptsize;
     double dpi;
+    long hinting_factor;
 
     FT_BBox compute_string_bbox();
     void set_scalable_attributes();

From cf5e358e6a6f525b6cac56e8a6fac68a09ddfe58 Mon Sep 17 00:00:00 2001
From: Michael Droettboom <mdboom@gmail.com>
Date: Tue, 28 Feb 2012 08:26:47 -0500
Subject: [PATCH 2/2] Expose more hinting options to the user.

---
 lib/matplotlib/backends/backend_agg.py | 25 ++++++++++++++++---------
 lib/matplotlib/mathtext.py             |  6 ++----
 lib/matplotlib/rcsetup.py              |  9 ++++++++-
 matplotlibrc.template                  | 14 +++++++++++---
 4 files changed, 37 insertions(+), 17 deletions(-)

diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py
index f0ae4c91f8eb..694eaeccf264 100644
--- a/lib/matplotlib/backends/backend_agg.py
+++ b/lib/matplotlib/backends/backend_agg.py
@@ -30,7 +30,8 @@
 from matplotlib.cbook import is_string_like, maxdict
 from matplotlib.figure import Figure
 from matplotlib.font_manager import findfont
-from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
+from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
+     LOAD_DEFAULT, LOAD_NO_AUTOHINT
 from matplotlib.mathtext import MathTextParser
 from matplotlib.path import Path
 from matplotlib.transforms import Bbox, BboxBase
@@ -40,6 +41,18 @@
 
 backend_version = 'v2.2'
 
+def get_hinting_flag():
+    mapping = {
+        True: LOAD_FORCE_AUTOHINT,
+        False: LOAD_NO_HINTING,
+        'either': LOAD_DEFAULT,
+        'native': LOAD_NO_AUTOHINT,
+        'auto': LOAD_FORCE_AUTOHINT,
+        'none': LOAD_NO_HINTING
+        }
+    return mapping[rcParams['text.hinting']]
+
+
 class RendererAgg(RendererBase):
     """
     The renderer handles all the drawing primitives using a graphics
@@ -69,12 +82,6 @@ def __init__(self, width, height, dpi):
         if __debug__: verbose.report('RendererAgg.__init__ done',
                                      'debug-annoying')
 
-    def _get_hinting_flag(self):
-        if rcParams['text.hinting']:
-            return LOAD_FORCE_AUTOHINT
-        else:
-            return LOAD_NO_HINTING
-
     # for filtering to work with rasterization, methods needs to be wrapped.
     # maybe there is better way to do it.
     def draw_markers(self, *kl, **kw):
@@ -141,7 +148,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath):
         if ismath:
             return self.draw_mathtext(gc, x, y, s, prop, angle)
 
-        flags = self._get_hinting_flag()
+        flags = get_hinting_flag()
         font = self._get_agg_font(prop)
         if font is None: return None
         if len(s) == 1 and ord(s) > 127:
@@ -178,7 +185,7 @@ def get_text_width_height_descent(self, s, prop, ismath):
                 self.mathtext_parser.parse(s, self.dpi, prop)
             return width, height, descent
 
-        flags = self._get_hinting_flag()
+        flags = get_hinting_flag()
         font = self._get_agg_font(prop)
         font.set_text(s, 0.0, flags=flags)  # the width and height of unrotated string
         w, h = font.get_width_height()
diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py
index 9ab50b5dce7b..fb97088346a1 100644
--- a/lib/matplotlib/mathtext.py
+++ b/lib/matplotlib/mathtext.py
@@ -224,10 +224,8 @@ def get_results(self, box, used_characters):
         return result
 
     def get_hinting_type(self):
-        if rcParams['text.hinting']:
-            return LOAD_FORCE_AUTOHINT
-        else:
-            return LOAD_NO_HINTING
+        from matplotlib.backends import backend_agg
+        return backend_agg.get_hinting_flag()
 
 class MathtextBackendBitmap(MathtextBackendAgg):
     def get_results(self, box, used_characters):
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index 6ba5be47a317..658ab2fd71b9 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -313,6 +313,13 @@ def deprecate_svg_embed_char_paths(value):
 
 validate_svg_fonttype = ValidateInStrings('fonttype', ['none', 'path', 'svgfont'])
 
+def validate_hinting(s):
+    if s in (True, False):
+        return s
+    if s.lower() in ('auto', 'native', 'either', 'none'):
+        return s.lower()
+    raise ValueError("hinting should be 'auto', 'native', 'either' or 'none'")
+
 class ValidateInterval:
     """
     Value must be in interval
@@ -407,7 +414,7 @@ def __call__(self, s):
     'text.latex.preamble' : [[''], validate_stringlist],
     'text.latex.preview' : [False, validate_bool],
     'text.dvipnghack'     : [None, validate_bool_maybe_none],
-    'text.hinting'        : [True, validate_bool],
+    'text.hinting'        : [True, validate_hinting],
     'text.hinting_factor' : [8, validate_int],
 
     # The following are deprecated and replaced by, e.g., 'font.style'
diff --git a/matplotlibrc.template b/matplotlibrc.template
index f333f1c368ac..221d533ca9d3 100644
--- a/matplotlibrc.template
+++ b/matplotlibrc.template
@@ -170,9 +170,17 @@ backend      : %(backend)s
                              # correction off.  None will try and
                              # guess based on your dvipng version
 
-#text.hinting : True # If True, text will be hinted, otherwise not.  This only
-                     # affects the Agg backend.
-#text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
+#text.hinting : 'auto' # May be one of the following:
+                       #   'none': Perform no hinting
+                       #   'auto': Use freetype's autohinter
+                       #   'native': Use the hinting information in the
+                       #             font file, if available, and if your
+                       #             freetype library supports it
+                       #   'either': Use the native hinting information,
+                       #             or the autohinter if none is available.
+                       # For backward compatibility, this value may also be
+                       # True === 'auto' or False === 'none'.
+text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
                          # horizontal direction.  A value of 1 will hint to full
                          # pixels.  A value of 2 will hint to half pixels etc.