8000 Merge pull request #719 from mdboom/hinting · matplotlib/matplotlib@d9186fe · GitHub
[go: up one dir, main page]

Skip to content

Commit d9186fe

Browse files
committed
Merge pull request #719 from mdboom/hinting
Bad font hinting / quality with Agg renderer
2 parents 54177a4 + cf5e358 commit d9186fe

File tree

7 files changed

+65
-51
lines changed

7 files changed

+65
-51
lines changed

lib/matplotlib/backends/backend_agg.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from matplotlib.cbook import is_string_like, maxdict
3131
from matplotlib.figure import Figure
3232
from matplotlib.font_manager import findfont
33-
from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING
33+
from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
34+
LOAD_DEFAULT, LOAD_NO_AUTOHINT
3435
from matplotlib.mathtext import MathTextParser
3536
from matplotlib.path import Path
3637
from matplotlib.transforms import Bbox, BboxBase
@@ -40,6 +41,18 @@
4041

4142
backend_version = 'v2.2'
4243

44+
def get_hinting_flag():
45+
mapping = {
46+
True: LOAD_FORCE_AUTOHINT,
47+
False: LOAD_NO_HINTING,
48+
'either': LOAD_DEFAULT,
49+
'native': LOAD_NO_AUTOHINT,
50+
'auto': LOAD_FORCE_AUTOHINT,
51+
'none': LOAD_NO_HINTING
52+
}
53+
return mapping[rcParams['text.hinting']]
54+
55+
4356
class RendererAgg(RendererBase):
4457
"""
4558
The renderer handles all the drawing primitives using a graphics
@@ -69,12 +82,6 @@ def __init__(self, width, height, dpi):
6982
if __debug__: verbose.report('RendererAgg.__init__ done',
7083
'debug-annoying')
7184

72-
def _get_hinting_flag(self):
73-
if rcParams['text.hinting']:
74-
return LOAD_FORCE_AUTOHINT
75-
else:
76 8000 -
return LOAD_NO_HINTING
77-
7885
# for filtering to work with rasterization, methods needs to be wrapped.
7986
# maybe there is better way to do it.
8087
def draw_markers(self, *kl, **kw):
@@ -141,7 +148,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath):
141148
if ismath:
142149
return self.draw_mathtext(gc, x, y, s, prop, angle)
143150

144-
flags = self._get_hinting_flag()
151+
flags = get_hinting_flag()
145152
font = self._get_agg_font(prop)
146153
if font is None: return None
147154
if len(s) == 1 and ord(s) > 127:
@@ -178,7 +185,7 @@ def get_text_width_height_descent(self, s, prop, ismath):
178185
self.mathtext_parser.parse(s, self.dpi, prop)
179186
return width, height, descent
180187

181-
flags = self._get_hinting_flag()
188+
flags = get_hinting_flag()
182189
font = self._get_agg_font(prop)
183190
font.set_text(s, 0.0, flags=flags) # the width and height of unrotated string
184191
w, h = font.get_width_height()
@@ -220,7 +227,9 @@ def _get_agg_font(self, prop):
220227
fname = findfont(prop)
221228
font = self._fontd.get(fname)
222229
if font is None:
223-
font = FT2Font(str(fname))
230+
font = FT2Font(
231+
str(fname),
232+
hinting_factor=rcParams['text.hinting_factor'])
224233
self._fontd[fname] = font
225234
self._fontd[key] = font
226235

lib/matplotlib/mathtext.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,8 @@ def get_results(self, box, used_characters):
224224
return result
225225

226226
def get_hinting_type(self):
227-
if rcParams['text.hinting']:
228-
return LOAD_FORCE_AUTOHINT
229-
else:
230-
return LOAD_NO_HINTING
227+
from matplotlib.backends import backend_agg
228+
return backend_agg.get_hinting_flag()
231229

232230
class MathtextBackendBitmap(MathtextBackendAgg):
233231
def get_results(self, box, used_characters):

lib/matplotlib/rcsetup.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ def deprecate_svg_embed_char_paths(value):
313313

314314
validate_svg_fonttype = ValidateInStrings('fonttype', ['none', 'path', 'svgfont'])
315315

316+
def validate_hinting(s):
317+
if s in (True, False):
318+
return s
319+
if s.lower() in ('auto', 'native', 'either', 'none'):
320+
return s.lower()
321+
raise ValueError("hinting should be 'auto', 'native', 'either' or 'none'")
322+
316323
class ValidateInterval:
317324
"""
318325
Value must be in interval
@@ -407,7 +414,8 @@ def __call__(self, s):
407414
'text.latex.preamble' : [[''], validate_stringlist],
408415
'text.latex.preview' : [False, validate_bool],
409416
'text.dvipnghack' : [None, validate_bool_maybe_none],
410-
'text.hinting' : [True, validate_bool],
417+
'text.hinting' : [True, validate_hinting],
418+
'text.hinting_factor' : [8, validate_int],
411419

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

lib/matplotlib/tests/__init__.py

Lines changed: 1 addition & 0 deletions
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

matplotlibrc.template

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,19 @@ backend : %(backend)s
170170
# correction off. None will try and
171171
# guess based on your dvipng version
172172

173-
#text.hinting : True # If True, text will be hinted, otherwise not. This only
174-
# affects the Agg backend.
173+
#text.hinting : 'auto' # May be one of the following:
174+
# 'none': Perform no hinting
175+
# 'auto': Use freetype's autohinter
176+
# 'native': Use the hinting information in the
177+
# font file, if available, and if your
178+
# freetype library supports it
179+
# 'either': Use the native hinting information,
180+
# or the autohinter if none is available.
181+
# For backward compatibility, this value may also be
182+
# True === 'auto' or False === 'none'.
183+
text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
184+
# horizontal direction. A value of 1 will hint to full
185+
# pixels. A value of 2 will hint to half pixels etc.
175186

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

src/ft2font.cpp

Lines changed: 19 additions & 33 deletions
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 trans 179B form = { 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

src/ft2font.h

Lines changed: 2 additions & 1 deletion
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)
0