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

Skip to content

Convert ft2font extension to pybind11 #28785

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 5 commits into from
Sep 18, 2024
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
15 changes: 15 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,20 @@ def tutorials_download_error(record):
autodoc_docstring_signature = True
autodoc_default_options = {'members': None, 'undoc-members': None}


def autodoc_process_bases(app, name, obj, options, bases):
"""
Hide pybind11 base object from inheritance tree.

Note, *bases* must be modified in place.
"""
for cls in bases[:]:
if not isinstance(cls, type):
continue
if cls.__module__ == 'pybind11_builtins' and cls.__name__ == 'pybind11_object':
bases.remove(cls)


# make sure to ignore warnings that stem from simply inspecting deprecated
# class-level attributes
warnings.filterwarnings('ignore', category=DeprecationWarning,
Expand Down Expand Up @@ -847,5 +861,6 @@ def setup(app):
bld_type = 'rel'
app.add_config_value('skip_sub_dirs', 0, '')
app.add_config_value('releaselevel', bld_type, 'env')
app.connect('autodoc-process-bases', autodoc_process_bases)
if sphinx.version_info[:2] < (7, 1):
app.connect('html-page-context', add_html_cache_busting, priority=1000)
6 changes: 3 additions & 3 deletions doc/missing-references.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@
"<unknown>:1",
"doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32:<autosummary>:1"
],
"numpy.float64": [
"doc/docstring of matplotlib.ft2font.PyCapsule.set_text:1"
],
"numpy.uint8": [
"<unknown>:1"
]
Expand Down Expand Up @@ -349,9 +352,6 @@
"Figure.stale_callback": [
"doc/users/explain/figure/interactive_guide.rst:333"
],
"Glyph": [
"doc/gallery/misc/ftface_props.rst:25"
],
"Image": [
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.gci:4"
],
Expand Down
99 changes: 66 additions & 33 deletions lib/matplotlib/ft2font.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import sys
from typing import BinaryIO, Literal, TypedDict, final, overload
from typing_extensions import Buffer # < Py 3.12

import numpy as np
from numpy.typing import NDArray
Expand Down Expand Up @@ -159,28 +161,7 @@ class _SfntPcltDict(TypedDict):
serifStyle: int

@final
class FT2Font:
ascender: int
bbox: tuple[int, int, int, int]
descender: int
face_flags: int
family_name: str
fname: str
height: int
max_advance_height: int
max_advance_width: int
num_charmaps: int
num_faces: int
num_fixed_sizes: int
num_glyphs: int
postscript_name: str
scalable: bool
style_flags: int
style_name: str
underline_position: int
underline_thickness: int
units_per_EM: int

class FT2Font(Buffer):
def __init__(
self,
filename: str | BinaryIO,
Expand All @@ -189,6 +170,8 @@ class FT2Font:
_fallback_list: list[FT2Font] | None = ...,
_kerning_factor: int = ...
) -> None: ...
if sys.version_info[:2] >= (3, 12):
def __buffer__(self, flags: int) -> memoryview: ...
def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
def clear(self) -> None: ...
def draw_glyph_to_bitmap(
Expand Down Expand Up @@ -232,23 +215,73 @@ class FT2Font:
def set_text(
self, string: str, angle: float = ..., flags: int = ...
) -> NDArray[np.float64]: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if np.double is the correct return type?

https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.double

From reading the documentation it seems like float64 is an alias for double on some (probably most though) platforms.

(Not added in the PR, but the missing reference is...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thought, but unfortunately it doesn't fix the missing reference. It appears that Sphinx doesn't read any .pyi files (which is why most other pages don't have type hint issues), but in this case is seeing something that pybind11 is generating in the runtime itself.

@property
def ascender(self) -> int: ...
@property
def bbox(self) -> tuple[int, int, int, int]: ...
@property
def descender(self) -> int: ...
@property
def face_flags(self) -> int: ...
@property
def family_name(self) -> str: ...
@property
def fname(self) -> str: ...
@property
def height(self) -> int: ...
@property
def max_advance_height(self) -> int: ...
@property
def max_advance_width(self) -> int: ...
@property
def num_charmaps(self) -> int: ...
@property
def num_faces(self) -> int: ...
@property
def num_fixed_sizes(self) -> int: ...
@property
def num_glyphs(self) -> int: ...
@property
def postscript_name(self) -> str: ...
@property
def scalable(self) -> bool: ...
@property
def style_flags(self) -> int: ...
@property
def style_name(self) -> str: ...
@property
def underline_position(self) -> int: ...
@property
def underline_thickness(self) -> int: ...
@property
def units_per_EM(self) -> int: ...

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

@final
class Glyph:
width: int
height: int
horiBearingX: int
horiBearingY: int
horiAdvance: int
linearHoriAdvance: int
vertBearingX: int
vertBearingY: int
vertAdvance: int

@property
def width(self) -> int: ...
@property
def height(self) -> int: ...
@property
def horiBearingX(self) -> int: ...
@property
def horiBearingY(self) -> int: ...
@property
def horiAdvance(self) -> int: ...
@property
def linearHoriAdvance(self) -> int: ...
@property
def vertBearingX(self) -> int: ...
@property
def vertBearingY(self) -> int: ...
@property
def vertAdvance(self) -> int: ...
@property
def bbox(self) -> tuple[int, int, int, int]: ...
10 changes: 6 additions & 4 deletions lib/matplotlib/tests/test_ft2font.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ def test_ft2font_invalid_args(tmp_path):
# filename argument.
with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):
ft2font.FT2Font(None)
with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):
ft2font.FT2Font(object()) # Not bytes or string, and has no read() method.
file = tmp_path / 'invalid-font.ttf'
file.write_text('This is not a valid font file.')
with (pytest.raises(TypeError, match='to a font file or a binary-mode file object'),
Expand All @@ -145,19 +147,19 @@ def test_ft2font_invalid_args(tmp_path):
file = fm.findfont('DejaVu Sans')

# hinting_factor argument.
with pytest.raises(TypeError, match='cannot be interpreted as an integer'):
with pytest.raises(TypeError, match='incompatible constructor arguments'):
ft2font.FT2Font(file, 1.3)
with pytest.raises(ValueError, match='hinting_factor must be greater than 0'):
ft2font.FT2Font(file, 0)

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

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


Expand Down
2 changes: 1 addition & 1 deletion requirements/testing/mypy.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Extra pip requirements for the GitHub Actions mypy build

mypy>=1.9
typing-extensions>=4.1
typing-extensions>=4.6

# Extra stubs distributed separately from the main pypi package
pandas-stubs
Expand Down
25 changes: 16 additions & 9 deletions src/ft2font.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* -*- mode: c++; c-basic-offset: 4 -*- */

#include <algorithm>
#include <charconv>
#include <cstdio>
#include <iterator>
#include <set>
#include <sstream>
Expand Down Expand Up @@ -397,7 +397,7 @@ void FT2Font::set_kerning_factor(int factor)
}

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

Expand All @@ -420,7 +420,7 @@ void FT2Font::set_text(
FT_UInt previous = 0;
FT2Font *previous_ft_object = NULL;

for (size_t n = 0; n < N; n++) {
for (auto codepoint : text) {
FT_UInt glyph_index = 0;
FT_BBox glyph_bbox;
FT_Pos last_advance;
Expand All @@ -429,14 +429,14 @@ void FT2Font::set_text(
std::set<FT_String*> glyph_seen_fonts;
FT2Font *ft_object_with_glyph = this;
bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs,
char_to_font, glyph_to_font, codepoints[n], flags,
char_to_font, glyph_to_font, codepoint, flags,
charcode_error, glyph_error, glyph_seen_fonts, false);
if (!was_found) {
ft_glyph_warn((FT_ULong)codepoints[n], glyph_seen_fonts);
ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts);
// render missing glyph tofu
// come back to top-most font
ft_object_with_glyph = this;
char_to_font[codepoints[n]] = ft_object_with_glyph;
char_to_font[codepoint] = ft_object_with_glyph;
glyph_to_font[glyph_index] = ft_object_with_glyph;
ft_object_with_glyph->load_glyph(glyph_index, flags, ft_object_with_glyph, false);
}
Expand Down Expand Up @@ -727,13 +727,20 @@ void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer,
if (!FT_HAS_GLYPH_NAMES(face)) {
/* Note that this generated name must match the name that
is generated by ttconv in ttfont_CharStrings_getname. */
buffer.replace(0, 3, "uni");
std::to_chars(buffer.data() + 3, buffer.data() + buffer.size(),
glyph_number, 16);
auto len = snprintf(buffer.data(), buffer.size(), "uni%08x", glyph_number);
if (len >= 0) {
buffer.resize(len);
} else {
throw std::runtime_error("Failed to convert glyph to standard name");
}
} else {
if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer.data(), buffer.size())) {
throw_ft_error("Could not get glyph names", error);
}
auto len = buffer.find('\0');
if (len != buffer.npos) {
buffer.resize(len);
}
}
}

Expand Down
9 changes: 3 additions & 6 deletions src/ft2font.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
#ifndef MPL_FT2FONT_H
#define MPL_FT2FONT_H

#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <cstdint>
#include <set>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

Expand Down Expand Up @@ -80,8 +77,8 @@ class FT2Font
void set_size(double ptsize, double dpi);
void set_charmap(int i);
void select_charmap(unsigned long i);
void set_text(
size_t N, uint32_t *codepoints, double angle, FT_Int32 flags, std::vector<double> &xys);
void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags,
std::vector<double> &xys);
int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, bool fallback);
int get_kerning(FT_UInt left, FT_UInt right, FT_UInt mode, FT_Vector &delta);
void set_kerning_factor(int factor);
Expand Down
Loading
Loading