Skip to content

Make PyFT2Font a subclass of FT2Font #30324

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

Draft
wants to merge 3 commits into
base: text-overhaul
Choose a base branch
from
Draft
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
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/deprecations/30322-ES.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Font kerning factor is deprecated
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Due to internal changes to support complex text rendering, the kerning factor on fonts is
no longer used. Setting the ``text.kerning_factor`` rcParam (which existed only for
backwards-compatibility) to any value other than 0 is deprecated, and the rcParam will be
removed in the future.
29 changes: 9 additions & 20 deletions doc/users/prev_whats_new/whats_new_3.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,15 @@ triangle meshes.

Kerning adjustments now use correct values
------------------------------------------
Due to an error in how kerning adjustments were applied, previous versions of
Matplotlib would under-correct kerning. This version will now correctly apply
kerning (for fonts supported by FreeType). To restore the old behavior (e.g.,
for test images), you may set :rc:`text.kerning_factor` to 6 (instead of 0).
Other values have undefined behavior.

.. plot::

import matplotlib.pyplot as plt

# Use old kerning values:
plt.rcParams['text.kerning_factor'] = 6
fig, ax = plt.subplots()
ax.text(0.0, 0.05, 'BRAVO\nAWKWARD\nVAT\nW.Test', fontsize=56)
ax.set_title('Before (text.kerning_factor = 6)')

Note how the spacing between characters is uniform between their bounding boxes
(above). With corrected kerning (below), slanted characters (e.g., AV or VA)
will be spaced closer together, as well as various other character pairs,
depending on font support (e.g., T and e, or the period after the W).
Due to an error in how kerning adjustments were applied, previous versions of Matplotlib
would under-correct kerning. This version will now correctly apply kerning (for fonts
supported by FreeType). To restore the old behavior (e.g., for test images), you may set
the ``text.kerning_factor`` rcParam to 6 (instead of 0). Other values have undefined
behavior.

With corrected kerning (below), slanted characters (e.g., AV or VA) will be spaced closer
together, as well as various other character pairs, depending on font support (e.g., T
and e, or the period after the W).

.. plot::

Expand Down
2 changes: 2 additions & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,8 @@ def __setitem__(self, key, val):
f"a list of valid parameters)") from err
except ValueError as ve:
raise ValueError(f"Key {key}: {ve}") from None
if key == "text.kerning_factor" and cval is not None:
_api.warn_deprecated("3.11", name="text.kerning_factor", obj_type="rcParam")
self._set(key, cval)

def __getitem__(self, key):
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/ft2font.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class FT2Font(Buffer):
hinting_factor: int = ...,
*,
_fallback_list: list[FT2Font] | None = ...,
_kerning_factor: int = ...
_kerning_factor: int | None = ...
) -> None: ...
if sys.version_info[:2] >= (3, 12):
def __buffer__(self, flags: int) -> memoryview: ...
Expand Down
7 changes: 3 additions & 4 deletions lib/matplotlib/mpl-data/matplotlibrc
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,9 @@
#text.hinting_factor: 1 # 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.
#text.kerning_factor: 0 # Specifies the scaling factor for kerning values. This
# is provided solely to allow old test images to remain
# unchanged. Set to 6 to obtain previous behavior.
# Values other than 0 or 6 have no defined meaning.
#text.kerning_factor: None # Specifies the scaling factor for kerning values. Values
# other than 0, 6, or None have no defined meaning.
# This setting is deprecated.
#text.antialiased: True # If True (default), the text will be antialiased.
# This only affects raster outputs.
#text.parse_math: True # Use mathtext if there is an even number of unescaped
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,7 @@ def _convert_validator_spec(key, conv):
"text.hinting": ["default", "no_autohint", "force_autohint",
"no_hinting", "auto", "native", "either", "none"],
"text.hinting_factor": validate_int,
"text.kerning_factor": validate_int,
"text.kerning_factor": validate_int_or_None,
"text.antialiased": validate_bool,
"text.parse_math": validate_bool,

Expand Down
20 changes: 13 additions & 7 deletions lib/matplotlib/tests/test_ft2font.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ def test_ft2font_invalid_args(tmp_path):
# kerning_factor argument.
with pytest.raises(TypeError, match='incompatible constructor arguments'):
ft2font.FT2Font(file, _kerning_factor=1.3)
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='text.kerning_factor rcParam was deprecated .+ 3.11'):
mpl.rcParams['text.kerning_factor'] = 0
with pytest.warns(mpl.MatplotlibDeprecationWarning,
match='_kerning_factor parameter was deprecated .+ 3.11'):
ft2font.FT2Font(file, _kerning_factor=123)


def test_ft2font_clear():
Expand All @@ -188,7 +194,7 @@ def test_ft2font_clear():

def test_ft2font_set_size():
file = fm.findfont('DejaVu Sans')
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=1)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
font.set_text('ABabCDcd')
orig = font.get_width_height()
Expand Down Expand Up @@ -717,7 +723,7 @@ def test_ft2font_get_sfnt_table(font_name, header):
def test_ft2font_get_kerning(left, right, unscaled, unfitted, default):
file = fm.findfont('DejaVu Sans')
# With unscaled, these settings should produce exact values found in FontForge.
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(100, 100)
assert font.get_kerning(font.get_char_index(ord(left)),
font.get_char_index(ord(right)),
Expand Down Expand Up @@ -756,7 +762,7 @@ def test_ft2font_get_kerning(left, right, unscaled, unfitted, default):

def test_ft2font_set_text():
file = fm.findfont('DejaVu Sans')
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
xys = font.set_text('')
np.testing.assert_array_equal(xys, np.empty((0, 2)))
Expand All @@ -778,7 +784,7 @@ def test_ft2font_set_text():

def test_ft2font_loading():
file = fm.findfont('DejaVu Sans')
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
for glyph in [font.load_char(ord('M')),
font.load_glyph(font.get_char_index(ord('M')))]:
Expand Down Expand Up @@ -819,13 +825,13 @@ def test_ft2font_drawing():
])
expected *= 255
file = fm.findfont('DejaVu Sans')
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
font.set_text('M')
font.draw_glyphs_to_bitmap(antialiased=False)
image = font.get_image()
np.testing.assert_array_equal(image, expected)
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
glyph = font.load_char(ord('M'))
image = np.zeros(expected.shape, np.uint8)
Expand All @@ -835,7 +841,7 @@ def test_ft2font_drawing():

def test_ft2font_get_path():
file = fm.findfont('DejaVu Sans')
font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
font = ft2font.FT2Font(file, hinting_factor=1)
font.set_size(12, 72)
vertices, codes = font.get_path()
assert vertices.shape == (0, 2)
Expand Down
37 changes: 22 additions & 15 deletions src/ft2font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <cstdio>
#include <iterator>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
Expand Down Expand Up @@ -207,32 +206,42 @@ FT2Font::get_path(std::vector<double> &vertices, std::vector<unsigned char> &cod
codes.push_back(CLOSEPOLY);
}

FT2Font::FT2Font(FT_Open_Args &open_args,
long hinting_factor_,
std::vector<FT2Font *> &fallback_list,
FT2Font::WarnFunc warn, bool warn_if_used)
: ft_glyph_warn(warn), warn_if_used(warn_if_used), image({1, 1}), face(nullptr),
FT2Font::FT2Font(long hinting_factor_, std::vector<FT2Font *> &fallback_list,
bool warn_if_used)
: warn_if_used(warn_if_used), image({1, 1}), face(nullptr), fallbacks(fallback_list),
hinting_factor(hinting_factor_),
// set default kerning factor to 0, i.e., no kerning manipulation
kerning_factor(0)
{
clear();
}

FT2Font::~FT2Font()
{
close();
}

void FT2Font::open(FT_Open_Args &open_args)
{
FT_CHECK(FT_Open_Face, _ft2Library, &open_args, 0, &face);
if (open_args.stream != nullptr) {
face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM;
}
// Set fallbacks
std::copy(fallback_list.begin(), fallback_list.end(), std::back_inserter(fallbacks));
}

FT2Font::~FT2Font()
void FT2Font::close()
{
// This should be idempotent, in case a user manually calls close before the
// destructor does.

for (auto & glyph : glyphs) {
FT_Done_Glyph(glyph);
}
glyphs.clear();

if (face) {
FT_Done_Face(face);
face = nullptr;
}
}

Expand Down Expand Up @@ -533,21 +542,19 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false)
return FT_Get_Char_Index(ft_object->get_face(), charcode);
}

void FT2Font::get_width_height(long *width, long *height)
std::tuple<long, long> FT2Font::get_width_height()
{
*width = advance;
*height = bbox.yMax - bbox.yMin;
return {advance, bbox.yMax - bbox.yMin};
}

long FT2Font::get_descent()
{
return -bbox.yMin;
}

void FT2Font::get_bitmap_offset(long *x, long *y)
std::tuple<long, long> FT2Font::get_bitmap_offset()
{
*x = bbox.xMin;
*y = 0;
return {bbox.xMin, 0};
}

void FT2Font::draw_glyphs_to_bitmap(bool antialiased)
Expand Down
17 changes: 9 additions & 8 deletions src/ft2font.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <vector>

Expand Down Expand Up @@ -96,13 +97,12 @@ extern FT_Library _ft2Library;

class FT2Font
{
typedef void (*WarnFunc)(FT_ULong charcode, std::set<FT_String*> family_names);

public:
FT2Font(FT_Open_Args &open_args, long hinting_factor,
std::vector<FT2Font *> &fallback_list,
WarnFunc warn, bool warn_if_used);
FT2Font(long hinting_factor, std::vector<FT2Font *> &fallback_list,
bool warn_if_used);
virtual ~FT2Font();
void open(FT_Open_Args &open_args);
void close();
void clear();
void set_size(double ptsize, double dpi);
void set_charmap(int i);
Expand All @@ -123,8 +123,8 @@ class FT2Font
std::set<FT_String*> &glyph_seen_fonts,
bool override);
void load_glyph(FT_UInt glyph_index, FT_Int32 flags);
void get_width_height(long *width, long *height);
void get_bitmap_offset(long *x, long *y);
std::tuple<long, long> get_width_height();
std::tuple<long, long> get_bitmap_offset();
long get_descent();
void draw_glyphs_to_bitmap(bool antialiased);
void draw_glyph_to_bitmap(
Expand Down Expand Up @@ -166,8 +166,9 @@ class FT2Font
return FT_HAS_KERNING(face);
}

protected:
virtual void ft_glyph_warn(FT_ULong charcode, std::set<FT_String*> family_names) = 0;
private:
WarnFunc ft_glyph_warn;
bool warn_if_used;
py::array_t<uint8_t, py::array::c_style> image;
FT_Face face;
Expand Down
Loading
Loading