Skip to content

Commit 1d78d3d

Browse files
committed
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.
1 parent b2ca027 commit 1d78d3d

File tree

6 files changed

+29
-35
lines changed

6 files changed

+29
-35
lines changed

Diff for: lib/matplotlib/backends/backend_agg.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,9 @@ def _get_agg_font(self, prop):
220220
fname = findfont(prop)
221221
font = self._fontd.get(fname)
222222
if font is None:
223-
font = FT2Font(str(fname))
223+
font = FT2Font(
224+
str(fname),
225+
hinting_factor=rcParams['text.hinting_factor'])
224226
self._fontd[fname] = font
225227
self._fontd[key] = font
226228

Diff for: lib/matplotlib/rcsetup.py

+1
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ def __call__(self, s):
408408
'text.latex.preview' : [False, validate_bool],
409409
'text.dvipnghack' : [None, validate_bool_maybe_none],
410410
'text.hinting' : [True, validate_bool],
411+
'text.hinting_factor' : [8, validate_int],
411412

412413
# The following are deprecated and replaced by, e.g., 'font.style'
413414
#'text.fontstyle' : ['normal', str],

Diff for: lib/matplotlib/tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ def setup():
1212
rcdefaults() # Start with all defaults
1313
rcParams['font.family'] = 'Bitstream Vera Sans'
1414
rcParams['text.hinting'] = False
15+
rcParams['text.hinting_factor'] = 8

Diff for: matplotlibrc.template

+3
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ backend : %(backend)s
172172

173173
#text.hinting : True # If True, text will be hinted, otherwise not. This only
174174
# affects the Agg backend.
175+
#text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
176+
# horizontal direction. A value of 1 will hint to full
177+
# pixels. A value of 2 will hint to half pixels etc.
175178

176179
# The following settings allow you to select the fonts in math mode.
177180
# They map from a TeX font name to a fontconfig font pattern.

Diff for: src/ft2font.cpp

+19-33
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,7 @@
3636
at the size at 12 pixels scaled by 2 through a transform,
3737
because the hints will have been computed differently (except
3838
you have disabled hints).
39-
40-
This hack is enabled only when VERTICAL_HINTING is defined, and will
41-
only be effective when load_char and set_text are called with 'flags=
42-
LOAD_DEFAULT', which is the default.
4339
*/
44-
#define VERTICAL_HINTING
45-
#ifdef VERTICAL_HINTING
46-
#define HORIZ_HINTING 8
47-
#else
48-
#define HORIZ_HINTING 1
49-
#endif
5040

5141
FT_Library _ft2Library;
5242

@@ -408,7 +398,7 @@ FT2Image::py_get_height(const Py::Tuple & args)
408398
PYCXX_VARARGS_METHOD_DECL(FT2Image, py_get_height)
409399

410400
Py::PythonClassObject<Glyph> Glyph::factory(
411-
const FT_Face& face, const FT_Glyph& glyph, size_t ind)
401+
const FT_Face& face, const FT_Glyph& glyph, size_t ind, long hinting_factor)
412402
{
413403
Py::Callable class_type(type());
414404
Py::PythonClassObject<Glyph> obj = Py::PythonClassObject<Glyph>(
@@ -419,12 +409,12 @@ Py::PythonClassObject<Glyph> Glyph::factory(
419409
FT_BBox bbox;
420410
FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &bbox);
421411

422-
o->setattro("width", Py::Int(face->glyph->metrics.width / HORIZ_HINTING));
412+
o->setattro("width", Py::Int(face->glyph->metrics.width / hinting_factor));
423413
o->setattro("height", Py::Int(face->glyph->metrics.height));
424-
o->setattro("horiBearingX", Py::Int(face->glyph->metrics.horiBearingX / HORIZ_HINTING));
414+
o->setattro("horiBearingX", Py::Int(face->glyph->metrics.horiBearingX / hinting_factor));
425415
o->setattro("horiBearingY", Py::Int(face->glyph->metrics.horiBearingY));
426416
o->setattro("horiAdvance", Py::Int(face->glyph->metrics.horiAdvance));
427-
o->setattro("linearHoriAdvance", Py::Int(face->glyph->linearHoriAdvance / HORIZ_HINTING));
417+
o->setattro("linearHoriAdvance", Py::Int(face->glyph->linearHoriAdvance / hinting_factor));
428418
o->setattro("vertBearingX", Py::Int(face->glyph->metrics.vertBearingX));
429419

430420
o->setattro("vertBearingY", Py::Int(face->glyph->metrics.vertBearingY));
@@ -847,14 +837,15 @@ FT2Font::FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds)
847837
}
848838

849839
// set a default fontsize 12 pt at 72dpi
850-
#ifdef VERTICAL_HINTING
851-
error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * HORIZ_HINTING, 72);
852-
static FT_Matrix transform = { 65536 / HORIZ_HINTING, 0, 0, 65536 };
840+
hinting_factor = 8;
841+
if (kwds.hasKey("hinting_factor"))
842+
{
843+
hinting_factor = Py::Long(kwds["hinting_factor"]);
844+
}
845+
846+
error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * hinting_factor, 72);
847+
static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
853848
FT_Set_Transform(face, &transform, 0);
854-
#else
855-
error = FT_Set_Char_Size(face, 12 * 64, 0, 72, 72);
856-
#endif
857-
//error = FT_Set_Char_Size( face, 20 * 64, 0, 80, 80 );
858849
if (error)
859850
{
860851
std::ostringstream s;
@@ -993,17 +984,12 @@ FT2Font::set_size(const Py::Tuple & args)
993984
double ptsize = Py::Float(args[0]);
994985
double dpi = Py::Float(args[1]);
995986

996-
#ifdef VERTICAL_HINTING
997987
int error = FT_Set_Char_Size(face, (long)(ptsize * 64), 0,
998-
(unsigned int)dpi * HORIZ_HINTING,
988+
(unsigned int)dpi * hinting_factor,
999989
(unsigned int)dpi);
1000-
static FT_Matrix transform = { 65536 / HORIZ_HINTING, 0, 0, 65536 };
990+
static FT_Matrix transform = { 65536 / hinting_factor, 0, 0, 65536 };
1001991
FT_Set_Transform(face, &transform, 0);
1002-
#else
1003-
int error = FT_Set_Char_Size(face, (long)(ptsize * 64), 0,
1004-
(unsigned int)dpi,
1005-
(unsigned int)dpi);
1006-
#endif
992+
1007993
if (error)
1008994
{
1009995
throw Py::RuntimeError("Could not set the fontsize");
@@ -1126,7 +1112,7 @@ FT2Font::get_kerning(const Py::Tuple & args)
11261112

11271113
if (!FT_Get_Kerning(face, left, right, mode, &delta))
11281114
{
1129-
return Py::Int(delta.x / HORIZ_HINTING);
1115+
return Py::Int(delta.x / hinting_factor);
11301116
}
11311117
else
11321118
{
@@ -1214,7 +1200,7 @@ FT2Font::set_text(const Py::Tuple & args, const Py::Dict & kwargs)
12141200
FT_Vector delta;
12151201
FT_Get_Kerning(face, previous, glyph_index,
12161202
FT_KERNING_DEFAULT, &delta);
1217-
pen.x += delta.x / HORIZ_HINTING;
1203+
pen.x += delta.x / hinting_factor;
12181204
}
12191205
error = FT_Load_Glyph(face, glyph_index, flags);
12201206
if (error)
@@ -1319,7 +1305,7 @@ FT2Font::load_char(const Py::Tuple & args, const Py::Dict & kwargs)
13191305
13201306
size_t num = glyphs.size(); //the index into the glyphs list
13211307
glyphs.push_back(thisGlyph);
1322-
return Glyph::factory(face, thisGlyph, num);
1308+
return Glyph::factory(face, thisGlyph, num, hinting_factor);
13231309
}
13241310
PYCXX_KEYWORDS_METHOD_DECL(FT2Font, load_char)
13251311
@@ -1369,7 +1355,7 @@ FT2Font::load_glyph(const Py::Tuple & args, const Py::Dict & kwargs)
13691355
13701356
size_t num = glyphs.size(); //the index into the glyphs list
13711357
glyphs.push_back(thisGlyph);
1372-
return Glyph::factory(face, thisGlyph, num);
1358+
return Glyph::factory(face, thisGlyph, num, hinting_factor);
13731359
}
13741360
PYCXX_KEYWORDS_METHOD_DECL(FT2Font, load_glyph)
13751361

Diff for: src/ft2font.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class Glyph : public Py::PythonClass<Glyph>
8383
Glyph(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds) :
8484
Py::PythonClass<Glyph>::PythonClass(self, args, kwds) { }
8585
virtual ~Glyph();
86-
static Py::PythonClassObject<Glyph> factory(const FT_Face&, const FT_Glyph&, size_t);
86+
static Py::PythonClassObject<Glyph> factory(const FT_Face&, const FT_Glyph&, size_t, long);
8787
int setattro(const Py::String &name, const Py::Object &value);
8888
Py::Object getattro(const Py::String &name);
8989
static void init_type(void);
@@ -137,6 +137,7 @@ class FT2Font : public Py::PythonClass<FT2Font>
137137
double angle;
138138
double ptsize;
139139
double dpi;
140+
long hinting_factor;
140141

141142
FT_BBox compute_string_bbox();
142143
void set_scalable_attributes();

0 commit comments

Comments
 (0)