Skip to content

Commit ae2d73e

Browse files
QuLogicanntzer
andcommitted
ft2font: Convert kerning mode into an enum
This follows some of the idea from #9763, but is based on code from mplcairo. Co-authored-by: Antony Lee <anntzer.lee@gmail.com>
1 parent 8b3c01e commit ae2d73e

File tree

12 files changed

+255
-33
lines changed

12 files changed

+255
-33
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ft2font module-level constants replaced by enums
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The `.ft2font`-level constants have been converted to `enum` classes, and all API using
5+
them now take/return the new types.
6+
7+
The following constants are now part of `.ft2font.Kerning` (without the ``KERNING_``
8+
prefix):
9+
10+
- ``KERNING_DEFAULT``
11+
- ``KERNING_UNFITTED``
12+
- ``KERNING_UNSCALED``

galleries/examples/misc/font_indexing.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
import os
1010

1111
import matplotlib
12-
from matplotlib.ft2font import (KERNING_DEFAULT, KERNING_UNFITTED,
13-
KERNING_UNSCALED, FT2Font)
12+
from matplotlib.ft2font import FT2Font, Kerning
1413

1514
font = FT2Font(
1615
os.path.join(matplotlib.get_data_path(), 'fonts/ttf/DejaVuSans.ttf'))
@@ -31,7 +30,7 @@
3130
glyph = font.load_char(code)
3231
print(glyph.bbox)
3332
print(glyphd['A'], glyphd['V'], coded['A'], coded['V'])
34-
print('AV', font.get_kerning(glyphd['A'], glyphd['V'], KERNING_DEFAULT))
35-
print('AV', font.get_kerning(glyphd['A'], glyphd['V'], KERNING_UNFITTED))
36-
print('AV', font.get_kerning(glyphd['A'], glyphd['V'], KERNING_UNSCALED))
37-
print('AT', font.get_kerning(glyphd['A'], glyphd['T'], KERNING_UNSCALED))
33+
print('AV', font.get_kerning(glyphd['A'], glyphd['V'], Kerning.DEFAULT))
34+
print('AV', font.get_kerning(glyphd['A'], glyphd['V'], Kerning.UNFITTED))
35+
print('AV', font.get_kerning(glyphd['A'], glyphd['V'], Kerning.UNSCALED))
36+
print('AT', font.get_kerning(glyphd['A'], glyphd['T'], Kerning.UNSCALED))

lib/matplotlib/_mathtext.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from ._mathtext_data import (
3030
latex_to_bakoma, stix_glyph_fixes, stix_virtual_fonts, tex2uni)
3131
from .font_manager import FontProperties, findfont, get_font
32-
from .ft2font import FT2Font, FT2Image, KERNING_DEFAULT
32+
from .ft2font import FT2Font, FT2Image, Kerning
3333

3434
from packaging.version import parse as parse_version
3535
from pyparsing import __version__ as pyparsing_version
@@ -426,7 +426,7 @@ def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float,
426426
info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
427427
info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
428428
font = info1.font
429-
return font.get_kerning(info1.num, info2.num, KERNING_DEFAULT) / 64
429+
return font.get_kerning(info1.num, info2.num, Kerning.DEFAULT) / 64
430430
return super().get_kern(font1, fontclass1, sym1, fontsize1,
431431
font2, fontclass2, sym2, fontsize2, dpi)
432432

lib/matplotlib/_text_helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import dataclasses
88

99
from . import _api
10-
from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING, FT2Font
10+
from .ft2font import LOAD_NO_HINTING, FT2Font, Kerning
1111

1212

1313
@dataclasses.dataclass(frozen=True)
@@ -43,7 +43,7 @@ def warn_on_missing_glyph(codepoint, fontnames):
4343
f"Matplotlib currently does not support {block} natively.")
4444

4545

46-
def layout(string, font, *, kern_mode=KERNING_DEFAULT):
46+
def layout(string, font, *, kern_mode=Kerning.DEFAULT):
4747
"""
4848
Render *string* with *font*.
4949
@@ -56,7 +56,7 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT):
5656
The string to be rendered.
5757
font : FT2Font
5858
The font.
59-
kern_mode : int
59+
kern_mode : Kerning
6060
A FreeType kerning mode.
6161
6262
Yields

lib/matplotlib/backends/backend_pdf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from matplotlib.font_manager import get_font, fontManager as _fontManager
3737
from matplotlib._afm import AFM
3838
from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE,
39-
LOAD_NO_HINTING, KERNING_UNFITTED, FT2Font)
39+
LOAD_NO_HINTING, FT2Font, Kerning)
4040
from matplotlib.transforms import Affine2D, BboxBase
4141
from matplotlib.path import Path
4242
from matplotlib.dates import UTC
@@ -2378,7 +2378,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23782378
multibyte_glyphs = []
23792379
prev_was_multibyte = True
23802380
prev_font = font
2381-
for item in _text_helpers.layout(s, font, kern_mode=KERNING_UNFITTED):
2381+
for item in _text_helpers.layout(s, font, kern_mode=Kerning.UNFITTED):
23822382
if _font_supports_glyph(fonttype, ord(item.char)):
23832383
if prev_was_multibyte or item.ft_object != prev_font:
23842384
singlebyte_chunks.append((item.ft_object, item.x, []))

lib/matplotlib/ft2font.pyi

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from enum import Enum
12
import sys
23
from typing import BinaryIO, Literal, TypedDict, final, overload
34
from typing_extensions import Buffer # < Py 3.12
@@ -16,9 +17,6 @@ GLYPH_NAMES: int
1617
HORIZONTAL: int
1718
ITALIC: int
1819
KERNING: int
19-
KERNING_DEFAULT: int
20-
KERNING_UNFITTED: int
21-
KERNING_UNSCALED: int
2220
LOAD_CROP_BITMAP: int
2321
LOAD_DEFAULT: int
2422
LOAD_FORCE_AUTOHINT: int
@@ -44,6 +42,11 @@ SCALABLE: int
4442
SFNT: int
4543
VERTICAL: int
4644

45+
class Kerning(Enum):
46+
DEFAULT: int
47+
UNFITTED: int
48+
UNSCALED: int
49+
4750
class _SfntHeadDict(TypedDict):
4851
version: tuple[int, int]
4952
fontRevision: tuple[int, int]
@@ -184,7 +187,7 @@ class FT2Font(Buffer):
184187
def get_descent(self) -> int: ...
185188
def get_glyph_name(self, index: int) -> str: ...
186189
def get_image(self) -> NDArray[np.uint8]: ...
187-
def get_kerning(self, left: int, right: int, mode: int) -> int: ...
190+
def get_kerning(self, left: int, right: int, mode: Kerning) -> int: ...
188191
def get_name_index(self, name: str) -> int: ...
189192
def get_num_glyphs(self) -> int: ...
190193
def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ...

lib/matplotlib/tests/test_ft2font.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66
import pytest
77

8+
import matplotlib as mpl
89
from matplotlib import ft2font
910
from matplotlib.testing.decorators import check_figures_equal
1011
import matplotlib.font_manager as fm
@@ -714,13 +715,37 @@ def test_ft2font_get_kerning(left, right, unscaled, unfitted, default):
714715
font.set_size(100, 100)
715716
assert font.get_kerning(font.get_char_index(ord(left)),
716717
font.get_char_index(ord(right)),
717-
ft2font.KERNING_UNSCALED) == unscaled
718+
ft2font.Kerning.UNSCALED) == unscaled
718719
assert font.get_kerning(font.get_char_index(ord(left)),
719720
font.get_char_index(ord(right)),
720-
ft2font.KERNING_UNFITTED) == unfitted
721+
ft2font.Kerning.UNFITTED) == unfitted
721722
assert font.get_kerning(font.get_char_index(ord(left)),
722723
font.get_char_index(ord(right)),
723-
ft2font.KERNING_DEFAULT) == default
724+
ft2font.Kerning.DEFAULT) == default
725+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
726+
match='Use Kerning.UNSCALED instead'):
727+
k = ft2font.KERNING_UNSCALED
728+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
729+
match='Use Kerning enum values instead'):
730+
assert font.get_kerning(font.get_char_index(ord(left)),
731+
font.get_char_index(ord(right)),
732+
int(k)) == unscaled
733+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
734+
match='Use Kerning.UNFITTED instead'):
735+
k = ft2font.KERNING_UNFITTED
736+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
737+
match='Use Kerning enum values instead'):
738+
assert font.get_kerning(font.get_char_index(ord(left)),
739+
font.get_char_index(ord(right)),
740+
int(k)) == unfitted
741+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
742+
match='Use Kerning.DEFAULT instead'):
743+
k = ft2font.KERNING_DEFAULT
744+
with pytest.warns(mpl.MatplotlibDeprecationWarning,
745+
match='Use Kerning enum values instead'):
746+
assert font.get_kerning(font.get_char_index(ord(left)),
747+
font.get_char_index(ord(right)),
748+
int(k)) == default
724749

725750

726751
def test_ft2font_set_text():

src/_enums.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#ifndef MPL_ENUMS_H
2+
#define MPL_ENUMS_H
3+
4+
#include <pybind11/pybind11.h>
5+
6+
// Extension for pybind11: Pythonic enums.
7+
// This allows creating classes based on ``enum.*`` types.
8+
// This code was copied from mplcairo, with some slight tweaks.
9+
// The API is:
10+
//
11+
// - P11X_DECLARE_ENUM(py_name: str, py_base_cls: str, ...: {str, enum value}):
12+
// py_name: The name to expose in the module.
13+
// py_base_cls: The name of the enum base class to use.
14+
// ...: The enum name/value pairs to expose.
15+
//
16+
// Use this macro to declare an enum and its values.
17+
//
18+
// - py11x::bind_enums(m: pybind11::module):
19+
// m: The module to use to register the enum classes.
20+
//
21+
// Place this in PYBIND11_MODULE to register the enums declared by P11X_DECLARE_ENUM.
22+
23+
// a1 includes the opening brace and a2 the closing brace.
24+
// This definition is compatible with older compiler versions compared to
25+
// #define P11X_ENUM_TYPE(...) decltype(std::map{std::pair __VA_ARGS__})::mapped_type
26+
#define P11X_ENUM_TYPE(a1, a2, ...) decltype(std::pair a1, a2)::second_type
27+
28+
#define P11X_CAT2(a, b) a##b
29+
#define P11X_CAT(a, b) P11X_CAT2(a, b)
30+
31+
namespace p11x {
32+
namespace {
33+
namespace py = pybind11;
34+
35+
// Holder is (py_base_cls, [(name, value), ...]) before module init;
36+
// converted to the Python class object after init.
37+
auto enums = std::unordered_map<std::string, py::object>{};
38+
39+
auto bind_enums(py::module mod) -> void
40+
{
41+
for (auto& [py_name, spec]: enums) {
42+
auto const& [py_base_cls, pairs] =
43+
spec.cast<std::pair<std::string, py::object>>();
44+
mod.attr(py::cast(py_name)) = spec =
45+
py::module::import("enum").attr(py_base_cls.c_str())(
46+
py_name, pairs, py::arg("module") = mod.attr("__name__"));
47+
}
48+
}
49+
}
50+
}
51+
52+
// Immediately converting the args to a vector outside of the lambda avoids
53+
// name collisions.
54+
#define P11X_DECLARE_ENUM(py_name, py_base_cls, ...) \
55+
namespace p11x { \
56+
namespace { \
57+
[[maybe_unused]] auto const P11X_CAT(enum_placeholder_, __COUNTER__) = \
58+
[](auto args) { \
59+
py::gil_scoped_acquire gil; \
60+
using int_t = std::underlying_type_t<decltype(args[0].second)>; \
61+
auto pairs = std::vector<std::pair<std::string, int_t>>{}; \
62+
for (auto& [k, v]: args) { \
63+
pairs.emplace_back(k, int_t(v)); \
64+
} \
65+
p11x::enums[py_name] = pybind11::cast(std::pair{py_base_cls, pairs}); \
66+
return 0; \
67+
} (std::vector{std::pair __VA_ARGS__}); \
68+
} \
69+
} \
70+
namespace pybind11::detail { \
71+
template<> struct type_caster<P11X_ENUM_TYPE(__VA_ARGS__)> { \
72+
using type = P11X_ENUM_TYPE(__VA_ARGS__); \
73+
static_assert(std::is_enum_v<type>, "Not an enum"); \
74+
PYBIND11_TYPE_CASTER(type, _(py_name)); \
75+
bool load(handle src, bool) { \
76+
auto cls = p11x::enums.at(py_name); \
77+
PyObject* tmp = nullptr; \
78+
if (pybind11::isinstance(src, cls) \
79+
&& (tmp = PyNumber_Index(src.attr("value").ptr()))) { \
80+
auto ival = PyLong_AsLong(tmp); \
81+
value = decltype(value)(ival); \
82+
Py_DECREF(tmp); \
83+
return !(ival == -1 && !PyErr_Occurred()); \
84+
} else { \
85+
return false; \
86+
} \
87+
} \
88+
static handle cast(decltype(value) obj, return_value_policy, handle) { \
89+
auto cls = p11x::enums.at(py_name); \
90+
return cls(std::underlying_type_t<type>(obj)).inc_ref(); \
91+
} \
92+
}; \
93+
}
94+
95+
#endif /* MPL_ENUMS_H */

src/ft2font.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ void FT2Font::select_charmap(unsigned long i)
354354
}
355355
}
356356

357-
int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback = false)
357+
int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode,
358+
bool fallback = false)
358359
{
359360
if (fallback && glyph_to_font.find(left) != glyph_to_font.end() &&
360361
glyph_to_font.find(right) != glyph_to_font.end()) {
@@ -375,7 +376,8 @@ int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallbac
375376
}
376377
}
377378

378-
int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta)
379+
int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode,
380+
FT_Vector &delta)
379381
{
380382
if (!FT_HAS_KERNING(face)) {
381383
return 0;

src/ft2font.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ class FT2Font
7979
void select_charmap(unsigned long i);
8080
void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
8181
std::vector<double> &xys);
82-
int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback);
83-
int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta);
82+
int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, bool fallback);
83+
int get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, FT_Vector &delta);
8484
void set_kerning_factor(int factor);
8585
void load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool fallback);
8686
bool load_char_with_fallback(FT2Font *&ft_object_with_glyph,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy