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

Skip to content

Commit 8a26ff9

Browse files
committed
ENH: add c++ code + Python API to get char -> font mapping
1 parent 02e93e2 commit 8a26ff9

File tree

4 files changed

+127
-0
lines changed

4 files changed

+127
-0
lines changed

lib/matplotlib/tests/test_ft2font.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,30 @@ def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name):
7676
):
7777
fig_ref.text(0.05, .85 - 0.15*j, txt, family=ref_font)
7878
fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font)
79+
80+
81+
@pytest.mark.parametrize(
82+
"family_name, file_name",
83+
[
84+
("WenQuanYi Zen Hei", "wqy-zenhei.ttc"),
85+
("Noto Sans CJK JP", "NotoSansCJK-Regular.ttc"),
86+
],
87+
)
88+
def test__get_fontmap(family_name, file_name):
89+
fp = fm.FontProperties(family=[family_name])
90+
if Path(fm.findfont(fp)).name != file_name:
91+
pytest.skip(f"Font {family_name} ({file_name}) is missing")
92+
93+
text = "There are 几个汉字 in between!"
94+
ft = fm.get_font(
95+
fm.fontManager._find_fonts_by_props(
96+
fm.FontProperties(family=["DejaVu Sans", family_name])
97+
)
98+
)
99+
100+
fontmap = ft._get_fontmap(text)
101+
for char, font in fontmap.items():
102+
if ord(char) > 127:
103+
assert Path(font.fname).name == file_name
104+
else:
105+
assert Path(font.fname).name == "DejaVuSans.ttf"

src/ft2font.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,31 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
603603
}
604604
}
605605

606+
607+
bool FT2Font::get_char_fallback_index(FT_ULong charcode, int& index) const
608+
{
609+
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
610+
if (glyph_index) {
611+
// -1 means the host has the char and we do not need to fallback
612+
index = -1;
613+
return true;
614+
} else {
615+
int inner_index = 0;
616+
bool was_found;
617+
618+
for (size_t i = 0; i < fallbacks.size(); ++i) {
619+
// TODO handle recursion somehow!
620+
was_found = fallbacks[i]->get_char_fallback_index(charcode, inner_index);
621+
if (was_found) {
622+
index = i;
623+
return true;
624+
}
625+
}
626+
}
627+
return false;
628+
}
629+
630+
606631
bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
607632
FT_UInt &final_glyph_index,
608633
std::vector<FT_Glyph> &parent_glyphs,

src/ft2font.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class FT2Font
108108
FT_UInt get_char_index(FT_ULong charcode, bool fallback);
109109
void get_cbox(FT_BBox &bbox);
110110
PyObject* get_path();
111+
bool get_char_fallback_index(FT_ULong charcode, int& index) const;
111112

112113
FT_Face const &get_face() const
113114
{

src/ft2font_wrapper.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
// From Python
88
#include <structmember.h>
99

10+
#include <set>
11+
#include <algorithm>
12+
1013
#define STRINGIFY(s) XSTRINGIFY(s)
1114
#define XSTRINGIFY(s) #s
1215

@@ -552,6 +555,76 @@ static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args)
552555
return PyLong_FromLong(result);
553556
}
554557

558+
const char *PyFT2Font_get_fontmap__doc__ =
559+
"_get_fontmap(self, string)\n"
560+
"--\n\n"
561+
"Get a mapping between characters and the font that includes them.\n"
562+
"A dictionary mapping unicode characters to PyFT2Font objects.";
563+
static PyObject *PyFT2Font_get_fontmap(PyFT2Font *self, PyObject *args, PyObject *kwds)
564+
{
565+
PyObject *textobj;
566+
const char *names[] = { "string", NULL };
567+
568+
if (!PyArg_ParseTupleAndKeywords(
569+
args, kwds, "O:_get_fontmap", (char **)names, &textobj)) {
570+
return NULL;
571+
}
572+
573+
std::set<FT_ULong> codepoints;
574+
size_t size;
575+
576+
if (PyUnicode_Check(textobj)) {
577+
size = PyUnicode_GET_LENGTH(textobj);
578+
#if defined(PYPY_VERSION) && (PYPY_VERSION_NUM < 0x07040000)
579+
// PyUnicode_ReadChar is available from PyPy 7.3.2, but wheels do not
580+
// specify the micro-release version, so put the version bound at 7.4
581+
// to prevent generating wheels unusable on PyPy 7.3.{0,1}.
582+
Py_UNICODE *unistr = PyUnicode_AsUnicode(textobj);
583+
for (size_t i = 0; i < size; ++i) {
584+
codepoints.insert(unistr[i]);
585+
}
586+
#else
587+
for (size_t i = 0; i < size; ++i) {
588+
codepoints.insert(PyUnicode_ReadChar(textobj, i));
589+
}
590+
#endif
591+
} else {
592+
PyErr_SetString(PyExc_TypeError, "String must be str");
593+
return NULL;
594+
}
595+
PyObject *char_to_font;
596+
if (!(char_to_font = PyDict_New())) {
597+
return NULL;
598+
}
599+
for (auto it = codepoints.begin(); it != codepoints.end(); ++it) {
600+
auto x = *it;
601+
PyObject* target_font;
602+
int index;
603+
if (self->x->get_char_fallback_index(x, index)) {
604+
if (index >= 0) {
605+
target_font = self->fallbacks[index];
606+
} else {
607+
target_font = (PyObject *)self;
608+
}
609+
} else {
610+
// TODO Handle recursion!
611+
target_font = (PyObject *)self;
612+
}
613+
614+
PyObject *key = NULL;
615+
bool error = (!(key = PyUnicode_FromFormat("%c", x))
616+
|| (PyDict_SetItem(char_to_font, key, target_font) == -1));
617+
Py_XDECREF(key);
618+
if (error) {
619+
Py_DECREF(char_to_font);
620+
PyErr_SetString(PyExc_ValueError, "Something went very wrong");
621+
return NULL;
622+
}
623+
}
624+
return char_to_font;
625+
}
626+
627+
555628
const char *PyFT2Font_set_text__doc__ =
556629
"set_text(self, string, angle, flags=32)\n"
557630
"--\n\n"
@@ -1525,6 +1598,7 @@ static PyTypeObject *PyFT2Font_init_type()
15251598
{"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__},
15261599
{"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__},
15271600
{"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__},
1601+
{"_get_fontmap", (PyCFunction)PyFT2Font_get_fontmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_fontmap__doc__},
15281602
{"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__},
15291603
{"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__},
15301604
{"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__},

0 commit comments

Comments
 (0)