Skip to content

Commit 9674b53

Browse files
authored
Merge pull request #28785 from QuLogic/ft2font-pybind11
Convert ft2font extension to pybind11
2 parents c56c404 + a0649e7 commit 9674b53

File tree

9 files changed

+736
-1106
lines changed

9 files changed

+736
-1106
lines changed

doc/conf.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,20 @@ def tutorials_download_error(record):
230230
autodoc_docstring_signature = True
231231
autodoc_default_options = {'members': None, 'undoc-members': None}
232232

233+
234+
def autodoc_process_bases(app, name, obj, options, bases):
235+
"""
236+
Hide pybind11 base object from inheritance tree.
237+
238+
Note, *bases* must be modified in place.
239+
"""
240+
for cls in bases[:]:
241+
if not isinstance(cls, type):
242+
continue
243+
if cls.__module__ == 'pybind11_builtins' and cls.__name__ == 'pybind11_object':
244+
bases.remove(cls)
245+
246+
233247
# make sure to ignore warnings that stem from simply inspecting deprecated
234248
# class-level attributes
235249
warnings.filterwarnings('ignore', category=DeprecationWarning,
@@ -847,5 +861,6 @@ def setup(app):
847861
bld_type = 'rel'
848862
app.add_config_value('skip_sub_dirs', 0, '')
849863
app.add_config_value('releaselevel', bld_type, 'env')
864+
app.connect('autodoc-process-bases', autodoc_process_bases)
850865
if sphinx.version_info[:2] < (7, 1):
851866
app.connect('html-page-context', add_html_cache_busting, priority=1000)

doc/missing-references.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@
268268
"<unknown>:1",
269269
"doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32:<autosummary>:1"
270270
],
271+
"numpy.float64": [
272+
"doc/docstring of matplotlib.ft2font.PyCapsule.set_text:1"
273+
],
271274
"numpy.uint8": [
272275
"<unknown>:1"
273276
]
@@ -349,9 +352,6 @@
349352
"Figure.stale_callback": [
350353
"doc/users/explain/figure/interactive_guide.rst:333"
351354
],
352-
"Glyph": [
353-
"doc/gallery/misc/ftface_props.rst:25"
354-
],
355355
"Image": [
356356
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.gci:4"
357357
],

lib/matplotlib/ft2font.pyi

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import sys
12
from typing import BinaryIO, Literal, TypedDict, final, overload
3+
from typing_extensions import Buffer # < Py 3.12
24

35
import numpy as np
46
from numpy.typing import NDArray
@@ -159,28 +161,7 @@ class _SfntPcltDict(TypedDict):
159161
serifStyle: int
160162

161163
@final
162-
class FT2Font:
163-
ascender: int
164-
bbox: tuple[int, int, int, int]
165-
descender: int
166-
face_flags: int
167-
family_name: str
168-
fname: str
169-
height: int
170-
max_advance_height: int
171-
max_advance_width: int
172-
num_charmaps: int
173-
num_faces: int
174-
num_fixed_sizes: int
175-
num_glyphs: int
176-
postscript_name: str
177-
scalable: bool
178-
style_flags: int
179-
style_name: str
180-
underline_position: int
181-
underline_thickness: int
182-
units_per_EM: int
183-
164+
class FT2Font(Buffer):
184165
def __init__(
185166
self,
186167
filename: str | BinaryIO,
@@ -189,6 +170,8 @@ class FT2Font:
189170
_fallback_list: list[FT2Font] | None = ...,
190171
_kerning_factor: int = ...
191172
) -> None: ...
173+
if sys.version_info[:2] >= (3, 12):
174+
def __buffer__(self, flags: int) -> memoryview: ...
192175
def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
193176
def clear(self) -> None: ...
194177
def draw_glyph_to_bitmap(
@@ -232,23 +215,73 @@ class FT2Font:
232215
def set_text(
233216
self, string: str, angle: float = ..., flags: int = ...
234217
) -> NDArray[np.float64]: ...
218+
@property
219+
def ascender(self) -> int: ...
220+
@property
221+
def bbox(self) -> tuple[int, int, int, int]: ...
222+
@property
223+
def descender(self) -> int: ...
224+
@property
225+
def face_flags(self) -> int: ...
226+
@property
227+
def family_name(self) -> str: ...
228+
@property
229+
def fname(self) -> str: ...
230+
@property
231+
def height(self) -> int: ...
232+
@property
233+
def max_advance_height(self) -> int: ...
234+
@property
235+
def max_advance_width(self) -> int: ...
236+
@property
237+
def num_charmaps(self) -> int: ...
238+
@property
239+
def num_faces(self) -> int: ...
240+
@property
241+
def num_fixed_sizes(self) -> int: ...
242+
@property
243+
def num_glyphs(self) -> int: ...
244+
@property
245+
def postscript_name(self) -> str: ...
246+
@property
247+
def scalable(self) -> bool: ...
248+
@property
249+
def style_flags(self) -> int: ...
250+
@property
251+
def style_name(self) -> str: ...
252+
@property
253+
def underline_position(self) -> int: ...
254+
@property
255+
def underline_thickness(self) -> int: ...
256+
@property
257+
def units_per_EM(self) -> int: ...
235258

236259
@final
237-
class FT2Image: # TODO: When updating mypy>=1.4, subclass from Buffer.
260+
class FT2Image(Buffer):
238261
def __init__(self, width: float, height: float) -> None: ...
239262
def draw_rect_filled(self, x0: float, y0: float, x1: float, y1: float) -> None: ...
263+
if sys.version_info[:2] >= (3, 12):
264+
def __buffer__(self, flags: int) -> memoryview: ...
240265

241266
@final
242267
class Glyph:
243-
width: int
244-
height: int
245-
horiBearingX: int
246-
horiBearingY: int
247-
horiAdvance: int
248-
linearHoriAdvance: int
249-
vertBearingX: int
250-
vertBearingY: int
251-
vertAdvance: int
252-
268+
@property
269+
def width(self) -> int: ...
270+
@property
271+
def height(self) -> int: ...
272+
@property
273+
def horiBearingX(self) -> int: ...
274+
@property
275+
def horiBearingY(self) -> int: ...
276+
@property
277+
def horiAdvance(self) -> int: ...
278+
@property
279+
def linearHoriAdvance(self) -> int: ...
280+
@property
281+
def vertBearingX(self) -> int: ...
282+
@property
283+
def vertBearingY(self) -> int: ...
284+
@property
285+
def vertAdvance(self) -> int: ...
253286
@property
254287
def bbox(self) -> tuple[int, int, int, int]: ...

lib/matplotlib/tests/test_ft2font.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ def test_ft2font_invalid_args(tmp_path):
130130
# filename argument.
131131
with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):
132132
ft2font.FT2Font(None)
133+
with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):
134+
ft2font.FT2Font(object()) # Not bytes or string, and has no read() method.
133135
file = tmp_path / 'invalid-font.ttf'
134136
file.write_text('This is not a valid font file.')
135137
with (pytest.raises(TypeError, match='to a font file or a binary-mode file object'),
@@ -145,19 +147,19 @@ def test_ft2font_invalid_args(tmp_path):
145147
file = fm.findfont('DejaVu Sans')
146148

147149
# hinting_factor argument.
148-
with pytest.raises(TypeError, match='cannot be interpreted as an integer'):
150+
with pytest.raises(TypeError, match='incompatible constructor arguments'):
149151
ft2font.FT2Font(file, 1.3)
150152
with pytest.raises(ValueError, match='hinting_factor must be greater than 0'):
151153
ft2font.FT2Font(file, 0)
152154

153-
with pytest.raises(TypeError, match='Fallback list must be a list'):
155+
with pytest.raises(TypeError, match='incompatible constructor arguments'):
154156
# failing to be a list will fail before the 0
155157
ft2font.FT2Font(file, _fallback_list=(0,)) # type: ignore[arg-type]
156-
with pytest.raises(TypeError, match='Fallback fonts must be FT2Font objects.'):
158+
with pytest.raises(TypeError, match='incompatible constructor arguments'):
157159
ft2font.FT2Font(file, _fallback_list=[0]) # type: ignore[list-item]
158160

159161
# kerning_factor argument.
160-
with pytest.raises(TypeError, match='cannot be interpreted as an integer'):
162+
with pytest.raises(TypeError, match='incompatible constructor arguments'):
161163
ft2font.FT2Font(file, _kerning_factor=1.3)
162164

163165

requirements/testing/mypy.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Extra pip requirements for the GitHub Actions mypy build
22

33
mypy>=1.9
4-
typing-extensions>=4.1
4+
typing-extensions>=4.6
55

66
# Extra stubs distributed separately from the main pypi package
77
pandas-stubs

src/ft2font.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* -*- mode: c++; c-basic-offset: 4 -*- */
22

33
#include <algorithm>
4-
#include <charconv>
4+
#include <cstdio>
55
#include <iterator>
66
#include <set>
77
#include <sstream>
@@ -397,7 +397,7 @@ void FT2Font::set_kerning_factor(int factor)
397397
}
398398

399399
void FT2Font::set_text(
400-
size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys)
400+
std::u32string_view text, double angle, FT_Int32 flags, std::vector<double> &xys)
401401
{
402402
FT_Matrix matrix; /* transformation matrix */
403403

@@ -420,7 +420,7 @@ void FT2Font::set_text(
420420
FT_UInt previous = 0;
421421
FT2Font *previous_ft_object = NULL;
422422

423-
for (size_t n = 0; n < N; n++) {
423+
for (auto codepoint : text) {
424424
FT_UInt glyph_index = 0;
425425
FT_BBox glyph_bbox;
426426
FT_Pos last_advance;
@@ -429,14 +429,14 @@ void FT2Font::set_text(
429429
std::set<FT_String*> glyph_seen_fonts;
430430
FT2Font *ft_object_with_glyph = this;
431431
bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs,
432-
char_to_font, glyph_to_font, codepoints[n], flags,
432+
char_to_font, glyph_to_font, codepoint, flags,
433433
charcode_error, glyph_error, glyph_seen_fonts, false);
434434
if (!was_found) {
435-
ft_glyph_warn((FT_ULong)codepoints[n], glyph_seen_fonts);
435+
ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts);
436436
// render missing glyph tofu
437437
// come back to top-most font
438438
ft_object_with_glyph = this;
439-
char_to_font[codepoints[n]] = ft_object_with_glyph;
439+
char_to_font[codepoint] = ft_object_with_glyph;
440440
glyph_to_font[glyph_index] = ft_object_with_glyph;
441441
ft_object_with_glyph->load_glyph(glyph_index, flags, ft_object_with_glyph, false);
442442
}
@@ -727,13 +727,20 @@ void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer,
727727
if (!FT_HAS_GLYPH_NAMES(face)) {
728728
/* Note that this generated name must match the name that
729729
is generated by ttconv in ttfont_CharStrings_getname. */
730-
buffer.replace(0, 3, "uni");
731-
std::to_chars(buffer.data() + 3, buffer.data() + buffer.size(),
732-
glyph_number, 16);
730+
auto len = snprintf(buffer.data(), buffer.size(), "uni%08x", glyph_number);
731+
if (len >= 0) {
732+
buffer.resize(len);
733+
} else {
734+
throw std::runtime_error("Failed to convert glyph to standard name");
735+
}
733736
} else {
734737
if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer.data(), buffer.size())) {
735738
throw_ft_error("Could not get glyph names", error);
736739
}
740+
auto len = buffer.find('\0');
741+
if (len != buffer.npos) {
742+
buffer.resize(len);
743+
}
737744
}
738745
}
739746

src/ft2font.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66
#ifndef MPL_FT2FONT_H
77
#define MPL_FT2FONT_H
88

9-
#define PY_SSIZE_T_CLEAN
10-
#include <Python.h>
11-
12-
#include <cstdint>
139
#include <set>
1410
#include <string>
11+
#include <string_view>
1512
#include <unordered_map>
1613
#include <vector>
1714

@@ -80,8 +77,8 @@ class FT2Font
8077
void set_size(double ptsize, double dpi);
8178
void set_charmap(int i);
8279
void select_charmap(unsigned long i);
83-
void set_text(
84-
size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys);
80+
void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
81+
std::vector<double> &xys);
8582
int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback);
8683
int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta);
8784
void set_kerning_factor(int factor);

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