Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bad font hinting / quality with Agg renderer #719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 29, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -220,7 +227,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

Expand Down
6 changes: 2 additions & 4 deletions lib/matplotlib/mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
10 changes: 9 additions & 1 deletion lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -407,7 +414,8 @@ 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'
#'text.fontstyle' : ['normal', str],
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 13 additions & 2 deletions matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,19 @@ 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 : '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.

# The following settings allow you to select the fonts in math mode.
# They map from a TeX font name to a fontconfig font pattern.
Expand Down
52 changes: 19 additions & 33 deletions src/ft2font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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>(
Expand All @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion src/ft2font.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down