diff --git a/doc/conf.py b/doc/conf.py index d153aead739c..ea6b1a3fa444 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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, @@ -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) diff --git a/doc/missing-references.json b/doc/missing-references.json index 87c9ce9b716f..089434172e58 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -268,6 +268,9 @@ ":1", "doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32::1" ], + "numpy.float64": [ + "doc/docstring of matplotlib.ft2font.PyCapsule.set_text:1" + ], "numpy.uint8": [ ":1" ] @@ -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" ], diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index 0a27411ff39c..b2eb8cea1cc8 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -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 @@ -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, @@ -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( @@ -232,23 +215,73 @@ class FT2Font: def set_text( self, string: str, angle: float = ..., flags: int = ... ) -> NDArray[np.float64]: ... + @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]: ... diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index f383901b7b31..ace4cea5865e 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -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'), @@ -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) diff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt index 4fec6a8c000f..0b65050b52de 100644 --- a/requirements/testing/mypy.txt +++ b/requirements/testing/mypy.txt @@ -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 diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 34a602562735..dc6bc419d43e 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -1,7 +1,7 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ #include -#include +#include #include #include #include @@ -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 &xys) + std::u32string_view text, double angle, FT_Int32 flags, std::vector &xys) { FT_Matrix matrix; /* transformation matrix */ @@ -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; @@ -429,14 +429,14 @@ void FT2Font::set_text( std::set 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); } @@ -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); + } } } diff --git a/src/ft2font.h b/src/ft2font.h index 2f24bfb01f79..79b0e1ccc518 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -6,12 +6,9 @@ #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H -#define PY_SSIZE_T_CLEAN -#include - -#include #include #include +#include #include #include @@ -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 &xys); + void set_text(std::u32string_view codepoints, double angle, FT_Int32 flags, + std::vector &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); diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 6a05680a474c..9791dc7e2e06 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1,133 +1,29 @@ -#include "mplutils.h" -#include "ft2font.h" -#include "numpy_cpp.h" -#include "py_converters.h" -#include "py_exceptions.h" +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include +#include +#include -// From Python -#include +#include "ft2font.h" +#include "numpy/arrayobject.h" -#include #include +#include +#include -static PyObject *convert_xys_to_array(std::vector &xys) -{ - npy_intp dims[] = {(npy_intp)xys.size() / 2, 2 }; - if (dims[0] > 0) { - auto obj = PyArray_SimpleNew(2, dims, NPY_DOUBLE); - auto array = reinterpret_cast(obj); - memcpy(PyArray_DATA(array), xys.data(), PyArray_NBYTES(array)); - return obj; - } else { - return PyArray_SimpleNew(2, dims, NPY_DOUBLE); - } -} +namespace py = pybind11; +using namespace pybind11::literals; /********************************************************************** * FT2Image * */ -typedef struct -{ - PyObject_HEAD - FT2Image *x; - Py_ssize_t shape[2]; - Py_ssize_t strides[2]; - Py_ssize_t suboffsets[2]; -} PyFT2Image; - -static PyTypeObject PyFT2ImageType; - -static PyObject *PyFT2Image_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyFT2Image *self; - self = (PyFT2Image *)type->tp_alloc(type, 0); - self->x = NULL; - return (PyObject *)self; -} - -static int PyFT2Image_init(PyFT2Image *self, PyObject *args, PyObject *kwds) -{ - double width; - double height; - - if (!PyArg_ParseTuple(args, "dd:FT2Image", &width, &height)) { - return -1; - } - - CALL_CPP_INIT("FT2Image", (self->x = new FT2Image(width, height))); - - return 0; -} - -static void PyFT2Image_dealloc(PyFT2Image *self) -{ - delete self->x; - Py_TYPE(self)->tp_free((PyObject *)self); -} - const char *PyFT2Image_draw_rect_filled__doc__ = - "draw_rect_filled(self, x0, y0, x1, y1)\n" - "--\n\n" - "Draw a filled rectangle to the image.\n"; - -static PyObject *PyFT2Image_draw_rect_filled(PyFT2Image *self, PyObject *args) -{ - double x0, y0, x1, y1; - - if (!PyArg_ParseTuple(args, "dddd:draw_rect_filled", &x0, &y0, &x1, &y1)) { - return NULL; - } - - CALL_CPP("draw_rect_filled", (self->x->draw_rect_filled(x0, y0, x1, y1))); - - Py_RETURN_NONE; -} - -static int PyFT2Image_get_buffer(PyFT2Image *self, Py_buffer *buf, int flags) -{ - FT2Image *im = self->x; - - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = im->get_buffer(); - buf->len = im->get_width() * im->get_height(); - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 2; - self->shape[0] = im->get_height(); - self->shape[1] = im->get_width(); - buf->shape = self->shape; - self->strides[0] = im->get_width(); - self->strides[1] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; -} + "Draw a filled rectangle to the image."; -static PyTypeObject* PyFT2Image_init_type() +static void +PyFT2Image_draw_rect_filled(FT2Image *self, double x0, double y0, double x1, double y1) { - static PyMethodDef methods[] = { - {"draw_rect_filled", (PyCFunction)PyFT2Image_draw_rect_filled, METH_VARARGS, PyFT2Image_draw_rect_filled__doc__}, - {NULL} - }; - - static PyBufferProcs buffer_procs; - buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Image_get_buffer; - - PyFT2ImageType.tp_name = "matplotlib.ft2font.FT2Image"; - PyFT2ImageType.tp_basicsize = sizeof(PyFT2Image); - PyFT2ImageType.tp_dealloc = (destructor)PyFT2Image_dealloc; - PyFT2ImageType.tp_flags = Py_TPFLAGS_DEFAULT; - PyFT2ImageType.tp_methods = methods; - PyFT2ImageType.tp_new = PyFT2Image_new; - PyFT2ImageType.tp_init = (initproc)PyFT2Image_init; - PyFT2ImageType.tp_as_buffer = &buffer_procs; - - return &PyFT2ImageType; + self->draw_rect_filled(x0, y0, x1, y1); } /********************************************************************** @@ -136,7 +32,6 @@ static PyTypeObject* PyFT2Image_init_type() typedef struct { - PyObject_HEAD size_t glyphInd; long width; long height; @@ -150,16 +45,14 @@ typedef struct FT_BBox bbox; } PyGlyph; -static PyTypeObject PyGlyphType; - -static PyObject *PyGlyph_from_FT2Font(const FT2Font *font) +static PyGlyph * +PyGlyph_from_FT2Font(const FT2Font *font) { const FT_Face &face = font->get_face(); const long hinting_factor = font->get_hinting_factor(); const FT_Glyph &glyph = font->get_last_glyph(); - PyGlyph *self; - self = (PyGlyph *)PyGlyphType.tp_alloc(&PyGlyphType, 0); + PyGlyph *self = new PyGlyph(); self->glyphInd = font->get_last_glyph_index(); FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox); @@ -174,48 +67,14 @@ static PyObject *PyGlyph_from_FT2Font(const FT2Font *font) self->vertBearingY = face->glyph->metrics.vertBearingY; self->vertAdvance = face->glyph->metrics.vertAdvance; - return (PyObject *)self; + return self; } -static void PyGlyph_dealloc(PyGlyph *self) +static py::tuple +PyGlyph_get_bbox(PyGlyph *self) { - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject *PyGlyph_get_bbox(PyGlyph *self, void *closure) -{ - return Py_BuildValue( - "llll", self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax); -} - -static PyTypeObject *PyGlyph_init_type() -{ - static PyMemberDef members[] = { - {(char *)"width", T_LONG, offsetof(PyGlyph, width), READONLY, (char *)""}, - {(char *)"height", T_LONG, offsetof(PyGlyph, height), READONLY, (char *)""}, - {(char *)"horiBearingX", T_LONG, offsetof(PyGlyph, horiBearingX), READONLY, (char *)""}, - {(char *)"horiBearingY", T_LONG, offsetof(PyGlyph, horiBearingY), READONLY, (char *)""}, - {(char *)"horiAdvance", T_LONG, offsetof(PyGlyph, horiAdvance), READONLY, (char *)""}, - {(char *)"linearHoriAdvance", T_LONG, offsetof(PyGlyph, linearHoriAdvance), READONLY, (char *)""}, - {(char *)"vertBearingX", T_LONG, offsetof(PyGlyph, vertBearingX), READONLY, (char *)""}, - {(char *)"vertBearingY", T_LONG, offsetof(PyGlyph, vertBearingY), READONLY, (char *)""}, - {(char *)"vertAdvance", T_LONG, offsetof(PyGlyph, vertAdvance), READONLY, (char *)""}, - {NULL} - }; - - static PyGetSetDef getset[] = { - {(char *)"bbox", (getter)PyGlyph_get_bbox, NULL, NULL, NULL}, - {NULL} - }; - - PyGlyphType.tp_name = "matplotlib.ft2font.Glyph"; - PyGlyphType.tp_basicsize = sizeof(PyGlyph); - PyGlyphType.tp_dealloc = (destructor)PyGlyph_dealloc; - PyGlyphType.tp_flags = Py_TPFLAGS_DEFAULT; - PyGlyphType.tp_members = members; - PyGlyphType.tp_getset = getset; - - return &PyGlyphType; + return py::make_tuple(self->bbox.xMin, self->bbox.yMin, + self->bbox.xMax, self->bbox.yMax); } /********************************************************************** @@ -224,40 +83,33 @@ static PyTypeObject *PyGlyph_init_type() struct PyFT2Font { - PyObject_HEAD FT2Font *x; - PyObject *py_file; + py::object py_file; FT_StreamRec stream; - Py_ssize_t shape[2]; - Py_ssize_t strides[2]; - Py_ssize_t suboffsets[2]; - std::vector fallbacks; -}; + py::list fallbacks; -static PyTypeObject PyFT2FontType; + ~PyFT2Font() + { + delete this->x; + } +}; -static unsigned long read_from_file_callback(FT_Stream stream, - unsigned long offset, - unsigned char *buffer, - unsigned long count) +static unsigned long +read_from_file_callback(FT_Stream stream, unsigned long offset, unsigned char *buffer, + unsigned long count) { - PyObject *py_file = ((PyFT2Font *)stream->descriptor.pointer)->py_file; - PyObject *seek_result = NULL, *read_result = NULL; + PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; Py_ssize_t n_read = 0; - if (!(seek_result = PyObject_CallMethod(py_file, "seek", "k", offset)) - || !(read_result = PyObject_CallMethod(py_file, "read", "k", count))) { - goto exit; - } - char *tmpbuf; - if (PyBytes_AsStringAndSize(read_result, &tmpbuf, &n_read) == -1) { - goto exit; - } - memcpy(buffer, tmpbuf, n_read); -exit: - Py_XDECREF(seek_result); - Py_XDECREF(read_result); - if (PyErr_Occurred()) { - PyErr_WriteUnraisable(py_file); + try { + char *tmpbuf; + auto seek_result = self->py_file.attr("seek")(offset); + auto read_result = self->py_file.attr("read")(count); + if (PyBytes_AsStringAndSize(read_result.ptr(), &tmpbuf, &n_read) == -1) { + throw py::error_already_set(); + } + memcpy(buffer, tmpbuf, n_read); + } catch (py::error_already_set &eas) { + eas.discard_as_unraisable(__func__); if (!count) { return 1; // Non-zero signals error, when count == 0. } @@ -265,28 +117,24 @@ static unsigned long read_from_file_callback(FT_Stream stream, return (unsigned long)n_read; } -static void close_file_callback(FT_Stream stream) +static void +close_file_callback(FT_Stream stream) { PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; - PyObject *close_result = NULL; - if (!(close_result = PyObject_CallMethod(self->py_file, "close", ""))) { - goto exit; - } -exit: - Py_XDECREF(close_result); - Py_CLEAR(self->py_file); - if (PyErr_Occurred()) { - PyErr_WriteUnraisable((PyObject*)self); + try { + self->py_file.attr("close")(); + } catch (py::error_already_set &eas) { + eas.discard_as_unraisable(__func__); } + self->py_file = py::object(); PyErr_Restore(type, value, traceback); } static void ft_glyph_warn(FT_ULong charcode, std::set family_names) { - PyObject *text_helpers = NULL, *tmp = NULL; std::set::iterator it = family_names.begin(); std::stringstream ss; ss<<*it; @@ -294,33 +142,12 @@ ft_glyph_warn(FT_ULong charcode, std::set family_names) ss<<", "<<*it; } - if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) || - !(tmp = PyObject_CallMethod(text_helpers, - "warn_on_missing_glyph", "(k, s)", - charcode, ss.str().c_str()))) { - goto exit; - } -exit: - Py_XDECREF(text_helpers); - Py_XDECREF(tmp); - if (PyErr_Occurred()) { - throw mpl::exception(); - } -} - -static PyObject *PyFT2Font_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyFT2Font *self; - self = (PyFT2Font *)type->tp_alloc(type, 0); - self->x = NULL; - self->py_file = NULL; - memset(&self->stream, 0, sizeof(FT_StreamRec)); - return (PyObject *)self; + auto text_helpers = py::module_::import("matplotlib._text_helpers"); + auto warn_on_missing_glyph = text_helpers.attr("warn_on_missing_glyph"); + warn_on_missing_glyph(charcode, ss.str()); } const char *PyFT2Font_init__doc__ = - "FT2Font(filename, hinting_factor=8, *, _fallback_list=None, _kerning_factor=0)\n" - "--\n\n" "Create a new FT2Font object.\n" "\n" "Parameters\n" @@ -341,353 +168,187 @@ const char *PyFT2Font_init__doc__ = "\n" " .. warning::\n" " This API is private: do not use it directly\n" - "\n" - "Attributes\n" - "----------\n" - "num_faces : int\n" - " Number of faces in file.\n" - "face_flags, style_flags : int\n" - " Face and style flags; see the ft2font constants.\n" - "num_glyphs : int\n" - " Number of glyphs in the face.\n" - "family_name, style_name : str\n" - " Face family and style name.\n" - "num_fixed_sizes : int\n" - " Number of bitmap in the face.\n" - "scalable : bool\n" - " Whether face is scalable; attributes after this one are only\n" - " defined for scalable faces.\n" - "bbox : tuple[int, int, int, int]\n" - " Face global bounding box (xmin, ymin, xmax, ymax).\n" - "units_per_EM : int\n" - " Number of font units covered by the EM.\n" - "ascender, descender : int\n" - " Ascender and descender in 26.6 units.\n" - "height : int\n" - " Height in 26.6 units; used to compute a default line spacing\n" - " (baseline-to-baseline distance).\n" - "max_advance_width, max_advance_height : int\n" - " Maximum horizontal and vertical cursor advance for all glyphs.\n" - "underline_position, underline_thickness : int\n" - " Vertical position and thickness of the underline bar.\n" - "postscript_name : str\n" - " PostScript name of the font.\n"; - -static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) +; + +static PyFT2Font * +PyFT2Font_init(py::object filename, long hinting_factor = 8, + std::optional> fallback_list = std::nullopt, + int kerning_factor = 0) { - PyObject *filename = NULL, *open = NULL, *data = NULL, *fallback_list = NULL; - FT_Open_Args open_args; - long hinting_factor = 8; - int kerning_factor = 0; - const char *names[] = { - "filename", "hinting_factor", "_fallback_list", "_kerning_factor", NULL - }; - std::vector fallback_fonts; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O|l$Oi:FT2Font", (char **)names, &filename, - &hinting_factor, &fallback_list, &kerning_factor)) { - return -1; - } if (hinting_factor <= 0) { - PyErr_SetString(PyExc_ValueError, - "hinting_factor must be greater than 0"); - goto exit; + throw py::value_error("hinting_factor must be greater than 0"); } + PyFT2Font *self = new PyFT2Font(); + self->x = NULL; + memset(&self->stream, 0, sizeof(FT_StreamRec)); self->stream.base = NULL; self->stream.size = 0x7fffffff; // Unknown size. self->stream.pos = 0; self->stream.descriptor.pointer = self; self->stream.read = &read_from_file_callback; + FT_Open_Args open_args; memset((void *)&open_args, 0, sizeof(FT_Open_Args)); open_args.flags = FT_OPEN_STREAM; open_args.stream = &self->stream; + std::vector fallback_fonts; if (fallback_list) { - if (!PyList_Check(fallback_list)) { - PyErr_SetString(PyExc_TypeError, "Fallback list must be a list"); - goto exit; - } - Py_ssize_t size = PyList_Size(fallback_list); - - // go through fallbacks once to make sure the types are right - for (Py_ssize_t i = 0; i < size; ++i) { - // this returns a borrowed reference - PyObject* item = PyList_GetItem(fallback_list, i); - if (!PyObject_IsInstance(item, PyObject_Type(reinterpret_cast(self)))) { - PyErr_SetString(PyExc_TypeError, "Fallback fonts must be FT2Font objects."); - goto exit; - } - } - // go through a second time to add them to our lists - for (Py_ssize_t i = 0; i < size; ++i) { - // this returns a borrowed reference - PyObject* item = PyList_GetItem(fallback_list, i); - // Increase the ref count, we will undo this in dealloc this makes - // sure things do not get gc'd under us! - Py_INCREF(item); - self->fallbacks.push_back(item); + // go through fallbacks to add them to our lists + for (auto item : *fallback_list) { + self->fallbacks.append(item); // Also (locally) cache the underlying FT2Font objects. As long as // the Python objects are kept alive, these pointer are good. - FT2Font *fback = reinterpret_cast(item)->x; + FT2Font *fback = item->x; fallback_fonts.push_back(fback); } } - if (PyBytes_Check(filename) || PyUnicode_Check(filename)) { - if (!(open = PyDict_GetItemString(PyEval_GetBuiltins(), "open")) // Borrowed reference. - || !(self->py_file = PyObject_CallFunction(open, "Os", filename, "rb"))) { - goto exit; - } + if (py::isinstance(filename) || py::isinstance(filename)) { + self->py_file = py::module_::import("io").attr("open")(filename, "rb"); self->stream.close = &close_file_callback; - } else if (!PyObject_HasAttrString(filename, "read") - || !(data = PyObject_CallMethod(filename, "read", "i", 0)) - || !PyBytes_Check(data)) { - PyErr_SetString(PyExc_TypeError, - "First argument must be a path to a font file or a binary-mode file object"); - Py_CLEAR(data); - goto exit; } else { + try { + // This will catch various issues: + // 1. `read` not being an attribute. + // 2. `read` raising an error. + // 3. `read` returning something other than `bytes`. + auto data = filename.attr("read")(0).cast(); + } catch (const std::exception&) { + throw py::type_error( + "First argument must be a path to a font file or a binary-mode file object"); + } self->py_file = filename; self->stream.close = NULL; - Py_INCREF(filename); } - Py_CLEAR(data); - - CALL_CPP_FULL( - "FT2Font", - (self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn)), - Py_CLEAR(self->py_file), -1); - - CALL_CPP_INIT("FT2Font->set_kerning_factor", (self->x->set_kerning_factor(kerning_factor))); -exit: - return PyErr_Occurred() ? -1 : 0; -} + self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn); -static void PyFT2Font_dealloc(PyFT2Font *self) -{ - delete self->x; - for (size_t i = 0; i < self->fallbacks.size(); i++) { - Py_DECREF(self->fallbacks[i]); - } + self->x->set_kerning_factor(kerning_factor); - Py_XDECREF(self->py_file); - Py_TYPE(self)->tp_free((PyObject *)self); + return self; } const char *PyFT2Font_clear__doc__ = - "clear(self)\n" - "--\n\n" - "Clear all the glyphs, reset for a new call to `.set_text`.\n"; + "Clear all the glyphs, reset for a new call to `.set_text`."; -static PyObject *PyFT2Font_clear(PyFT2Font *self, PyObject *args) +static void +PyFT2Font_clear(PyFT2Font *self) { - CALL_CPP("clear", (self->x->clear())); - - Py_RETURN_NONE; + self->x->clear(); } const char *PyFT2Font_set_size__doc__ = - "set_size(self, ptsize, dpi)\n" - "--\n\n" - "Set the point size and dpi of the text.\n"; + "Set the point size and dpi of the text."; -static PyObject *PyFT2Font_set_size(PyFT2Font *self, PyObject *args) +static void +PyFT2Font_set_size(PyFT2Font *self, double ptsize, double dpi) { - double ptsize; - double dpi; - - if (!PyArg_ParseTuple(args, "dd:set_size", &ptsize, &dpi)) { - return NULL; - } - - CALL_CPP("set_size", (self->x->set_size(ptsize, dpi))); - - Py_RETURN_NONE; + self->x->set_size(ptsize, dpi); } const char *PyFT2Font_set_charmap__doc__ = - "set_charmap(self, i)\n" - "--\n\n" - "Make the i-th charmap current.\n"; + "Make the i-th charmap current."; -static PyObject *PyFT2Font_set_charmap(PyFT2Font *self, PyObject *args) +static void +PyFT2Font_set_charmap(PyFT2Font *self, int i) { - int i; - - if (!PyArg_ParseTuple(args, "i:set_charmap", &i)) { - return NULL; - } - - CALL_CPP("set_charmap", (self->x->set_charmap(i))); - - Py_RETURN_NONE; + self->x->set_charmap(i); } const char *PyFT2Font_select_charmap__doc__ = - "select_charmap(self, i)\n" - "--\n\n" - "Select a charmap by its FT_Encoding number.\n"; + "Select a charmap by its FT_Encoding number."; -static PyObject *PyFT2Font_select_charmap(PyFT2Font *self, PyObject *args) +static void +PyFT2Font_select_charmap(PyFT2Font *self, unsigned long i) { - unsigned long i; - - if (!PyArg_ParseTuple(args, "k:select_charmap", &i)) { - return NULL; - } - - CALL_CPP("select_charmap", self->x->select_charmap(i)); - - Py_RETURN_NONE; + self->x->select_charmap(i); } const char *PyFT2Font_get_kerning__doc__ = - "get_kerning(self, left, right, mode)\n" - "--\n\n" "Get the kerning between *left* and *right* glyph indices.\n" - "*mode* is a kerning mode constant:\n\n" + "\n" + "*mode* is a kerning mode constant:\n" + "\n" "- KERNING_DEFAULT - Return scaled and grid-fitted kerning distances\n" "- KERNING_UNFITTED - Return scaled but un-grid-fitted kerning distances\n" "- KERNING_UNSCALED - Return the kerning vector in original font units\n"; -static PyObject *PyFT2Font_get_kerning(PyFT2Font *self, PyObject *args) +static int +PyFT2Font_get_kerning(PyFT2Font *self, FT_UInt left, FT_UInt right, FT_UInt mode) { - FT_UInt left, right, mode; - int result; bool fallback = true; - if (!PyArg_ParseTuple(args, "III:get_kerning", &left, &right, &mode)) { - return NULL; - } - - CALL_CPP("get_kerning", (result = self->x->get_kerning(left, right, mode, fallback))); - - return PyLong_FromLong(result); + return self->x->get_kerning(left, right, mode, fallback); } const char *PyFT2Font_get_fontmap__doc__ = - "_get_fontmap(self, string)\n" - "--\n\n" "Get a mapping between characters and the font that includes them.\n" "A dictionary mapping unicode characters to PyFT2Font objects."; -static PyObject *PyFT2Font_get_fontmap(PyFT2Font *self, PyObject *args, PyObject *kwds) -{ - PyObject *textobj; - const char *names[] = { "string", NULL }; - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O:_get_fontmap", (char **)names, &textobj)) { - return NULL; - } +static py::dict +PyFT2Font_get_fontmap(PyFT2Font *self, std::u32string text) +{ std::set codepoints; - size_t size; - if (PyUnicode_Check(textobj)) { - size = PyUnicode_GET_LENGTH(textobj); - for (size_t i = 0; i < size; ++i) { - codepoints.insert(PyUnicode_ReadChar(textobj, i)); - } - } else { - PyErr_SetString(PyExc_TypeError, "string must be str"); - return NULL; - } - PyObject *char_to_font; - if (!(char_to_font = PyDict_New())) { - return NULL; + for (auto code : text) { + codepoints.insert(code); } - for (auto it = codepoints.begin(); it != codepoints.end(); ++it) { - auto x = *it; - PyObject* target_font; + + py::dict char_to_font; + for (auto code : codepoints) { + py::object target_font; int index; - if (self->x->get_char_fallback_index(x, index)) { + if (self->x->get_char_fallback_index(code, index)) { if (index >= 0) { target_font = self->fallbacks[index]; } else { - target_font = (PyObject *)self; + target_font = py::cast(self); } } else { // TODO Handle recursion! - target_font = (PyObject *)self; + target_font = py::cast(self); } - PyObject *key = NULL; - bool error = (!(key = PyUnicode_FromFormat("%c", x)) - || (PyDict_SetItem(char_to_font, key, target_font) == -1)); - Py_XDECREF(key); - if (error) { - Py_DECREF(char_to_font); - PyErr_SetString(PyExc_ValueError, "Something went very wrong"); - return NULL; - } + auto key = py::cast(std::u32string(1, code)); + char_to_font[key] = target_font; } return char_to_font; } - const char *PyFT2Font_set_text__doc__ = - "set_text(self, string, angle=0.0, flags=32)\n" - "--\n\n" "Set the text *string* and *angle*.\n" "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" "the default value is LOAD_FORCE_AUTOHINT.\n" "You must call this before `.draw_glyphs_to_bitmap`.\n" "A sequence of x,y positions in 26.6 subpixels is returned; divide by 64 for pixels.\n"; -static PyObject *PyFT2Font_set_text(PyFT2Font *self, PyObject *args, PyObject *kwds) +static py::array_t +PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0, + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT) { - PyObject *textobj; - double angle = 0.0; - FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; std::vector xys; - const char *names[] = { "string", "angle", "flags", NULL }; - - /* This makes a technically incorrect assumption that FT_Int32 is - int. In theory it can also be long, if the size of int is less - than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O|di:set_text", (char **)names, &textobj, &angle, &flags)) { - return NULL; - } - - std::vector codepoints; - size_t size; - if (PyUnicode_Check(textobj)) { - size = PyUnicode_GET_LENGTH(textobj); - codepoints.resize(size); - for (size_t i = 0; i < size; ++i) { - codepoints[i] = PyUnicode_ReadChar(textobj, i); - } - } else { - PyErr_SetString(PyExc_TypeError, "set_text requires str-input."); - return NULL; - } + self->x->set_text(text, angle, flags, xys); - uint32_t* codepoints_array = NULL; - if (size > 0) { - codepoints_array = &codepoints[0]; + py::ssize_t dims[] = { static_cast(xys.size()) / 2, 2 }; + py::array_t result(dims); + if (xys.size() > 0) { + memcpy(result.mutable_data(), xys.data(), result.nbytes()); } - CALL_CPP("set_text", self->x->set_text(size, codepoints_array, angle, flags, xys)); - - return convert_xys_to_array(xys); + return result; } const char *PyFT2Font_get_num_glyphs__doc__ = - "get_num_glyphs(self)\n" - "--\n\n" - "Return the number of loaded glyphs.\n"; + "Return the number of loaded glyphs."; -static PyObject *PyFT2Font_get_num_glyphs(PyFT2Font *self, PyObject *args) +static size_t +PyFT2Font_get_num_glyphs(PyFT2Font *self) { - return PyLong_FromSize_t(self->x->get_num_glyphs()); + return self->x->get_num_glyphs(); } const char *PyFT2Font_load_char__doc__ = - "load_char(self, charcode, flags=32)\n" - "--\n\n" "Load character with *charcode* in current fontfile and set glyph.\n" "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" "the default value is LOAD_FORCE_AUTOHINT.\n" @@ -702,30 +363,19 @@ const char *PyFT2Font_load_char__doc__ = "- vertBearingY: top side bearing in vertical layouts\n" "- vertAdvance: advance height for vertical layout\n"; -static PyObject *PyFT2Font_load_char(PyFT2Font *self, PyObject *args, PyObject *kwds) +static PyGlyph * +PyFT2Font_load_char(PyFT2Font *self, long charcode, + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT) { - long charcode; bool fallback = true; - FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; - const char *names[] = { "charcode", "flags", NULL }; - - /* This makes a technically incorrect assumption that FT_Int32 is - int. In theory it can also be long, if the size of int is less - than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords(args, kwds, "l|i:load_char", (char **)names, &charcode, - &flags)) { - return NULL; - } - FT2Font *ft_object = NULL; - CALL_CPP("load_char", (self->x->load_char(charcode, flags, ft_object, fallback))); + + self->x->load_char(charcode, flags, ft_object, fallback); return PyGlyph_from_FT2Font(ft_object); } const char *PyFT2Font_load_glyph__doc__ = - "load_glyph(self, glyphindex, flags=32)\n" - "--\n\n" "Load character with *glyphindex* in current fontfile and set glyph.\n" "*flags* can be a bitwise-or of the LOAD_XXX constants;\n" "the default value is LOAD_FORCE_AUTOHINT.\n" @@ -740,99 +390,72 @@ const char *PyFT2Font_load_glyph__doc__ = "- vertBearingY: top side bearing in vertical layouts\n" "- vertAdvance: advance height for vertical layout\n"; -static PyObject *PyFT2Font_load_glyph(PyFT2Font *self, PyObject *args, PyObject *kwds) +static PyGlyph * +PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index, + FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT) { - FT_UInt glyph_index; - FT_Int32 flags = FT_LOAD_FORCE_AUTOHINT; bool fallback = true; - const char *names[] = { "glyph_index", "flags", NULL }; - - /* This makes a technically incorrect assumption that FT_Int32 is - int. In theory it can also be long, if the size of int is less - than 32 bits. This is very unlikely on modern platforms. */ - if (!PyArg_ParseTupleAndKeywords(args, kwds, "I|i:load_glyph", (char **)names, &glyph_index, - &flags)) { - return NULL; - } - FT2Font *ft_object = NULL; - CALL_CPP("load_glyph", (self->x->load_glyph(glyph_index, flags, ft_object, fallback))); + + self->x->load_glyph(glyph_index, flags, ft_object, fallback); return PyGlyph_from_FT2Font(ft_object); } const char *PyFT2Font_get_width_height__doc__ = - "get_width_height(self)\n" - "--\n\n" "Get the width and height in 26.6 subpixels of the current string set by `.set_text`.\n" "The rotation of the string is accounted for. To get width and height\n" "in pixels, divide these values by 64.\n"; -static PyObject *PyFT2Font_get_width_height(PyFT2Font *self, PyObject *args) +static py::tuple +PyFT2Font_get_width_height(PyFT2Font *self) { long width, height; - CALL_CPP("get_width_height", (self->x->get_width_height(&width, &height))); + self->x->get_width_height(&width, &height); - return Py_BuildValue("ll", width, height); + return py::make_tuple(width, height); } const char *PyFT2Font_get_bitmap_offset__doc__ = - "get_bitmap_offset(self)\n" - "--\n\n" "Get the (x, y) offset in 26.6 subpixels for the bitmap if ink hangs left or below (0, 0).\n" "Since Matplotlib only supports left-to-right text, y is always 0.\n"; -static PyObject *PyFT2Font_get_bitmap_offset(PyFT2Font *self, PyObject *args) +static py::tuple +PyFT2Font_get_bitmap_offset(PyFT2Font *self) { long x, y; - CALL_CPP("get_bitmap_offset", (self->x->get_bitmap_offset(&x, &y))); + self->x->get_bitmap_offset(&x, &y); - return Py_BuildValue("ll", x, y); + return py::make_tuple(x, y); } const char *PyFT2Font_get_descent__doc__ = - "get_descent(self)\n" - "--\n\n" "Get the descent in 26.6 subpixels of the current string set by `.set_text`.\n" "The rotation of the string is accounted for. To get the descent\n" "in pixels, divide this value by 64.\n"; -static PyObject *PyFT2Font_get_descent(PyFT2Font *self, PyObject *args) +static long +PyFT2Font_get_descent(PyFT2Font *self) { - long descent; - - CALL_CPP("get_descent", (descent = self->x->get_descent())); - - return PyLong_FromLong(descent); + return self->x->get_descent(); } const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ = - "draw_glyphs_to_bitmap(self, antialiased=True)\n" - "--\n\n" "Draw the glyphs that were loaded by `.set_text` to the bitmap.\n" + "\n" "The bitmap size will be automatically set to include the glyphs.\n"; -static PyObject *PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_draw_glyphs_to_bitmap(PyFT2Font *self, bool antialiased = true) { - bool antialiased = true; - const char *names[] = { "antialiased", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&:draw_glyphs_to_bitmap", - (char **)names, &convert_bool, &antialiased)) { - return NULL; - } - - CALL_CPP("draw_glyphs_to_bitmap", (self->x->draw_glyphs_to_bitmap(antialiased))); - - Py_RETURN_NONE; + self->x->draw_glyphs_to_bitmap(antialiased); } const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = - "draw_glyph_to_bitmap(self, image, x, y, glyph, antialiased=True)\n" - "--\n\n" - "Draw a single glyph to *image* at pixel locations *x*, *y*\n" + "Draw a single glyph to the bitmap at pixel locations x, y.\n" + "\n" "Note it is your responsibility to create the image manually\n" "with the correct size before this call is made.\n" "\n" @@ -841,84 +464,43 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = "who want to render individual glyphs (e.g., returned by `.load_char`)\n" "at precise locations.\n"; -static PyObject *PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, PyObject *args, PyObject *kwds) +static void +PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, FT2Image &image, double xd, double yd, + PyGlyph *glyph, bool antialiased = true) { - PyFT2Image *image; - double xd, yd; - PyGlyph *glyph; - bool antialiased = true; - const char *names[] = { "image", "x", "y", "glyph", "antialiased", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, - kwds, - "O!ddO!|O&:draw_glyph_to_bitmap", - (char **)names, - &PyFT2ImageType, - &image, - &xd, - &yd, - &PyGlyphType, - &glyph, - &convert_bool, - &antialiased)) { - return NULL; - } - - CALL_CPP("draw_glyph_to_bitmap", - self->x->draw_glyph_to_bitmap(*(image->x), xd, yd, glyph->glyphInd, antialiased)); - - Py_RETURN_NONE; + self->x->draw_glyph_to_bitmap(image, xd, yd, glyph->glyphInd, antialiased); } const char *PyFT2Font_get_glyph_name__doc__ = - "get_glyph_name(self, index)\n" - "--\n\n" "Retrieve the ASCII name of a given glyph *index* in a face.\n" "\n" "Due to Matplotlib's internal design, for fonts that do not contain glyph\n" "names (per FT_FACE_FLAG_GLYPH_NAMES), this returns a made-up name which\n" "does *not* roundtrip through `.get_name_index`.\n"; -static PyObject *PyFT2Font_get_glyph_name(PyFT2Font *self, PyObject *args) +static py::str +PyFT2Font_get_glyph_name(PyFT2Font *self, unsigned int glyph_number) { - unsigned int glyph_number; std::string buffer; bool fallback = true; - if (!PyArg_ParseTuple(args, "I:get_glyph_name", &glyph_number)) { - return NULL; - } buffer.resize(128); - CALL_CPP("get_glyph_name", - (self->x->get_glyph_name(glyph_number, buffer, fallback))); - return PyUnicode_FromString(buffer.c_str()); + self->x->get_glyph_name(glyph_number, buffer, fallback); + return buffer; } const char *PyFT2Font_get_charmap__doc__ = - "get_charmap(self)\n" - "--\n\n" "Return a dict that maps the character codes of the selected charmap\n" "(Unicode by default) to their corresponding glyph indices.\n"; -static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args) +static py::dict +PyFT2Font_get_charmap(PyFT2Font *self) { - PyObject *charmap; - if (!(charmap = PyDict_New())) { - return NULL; - } + py::dict charmap; FT_UInt index; FT_ULong code = FT_Get_First_Char(self->x->get_face(), &index); while (index != 0) { - PyObject *key = NULL, *val = NULL; - bool error = (!(key = PyLong_FromLong(code)) - || !(val = PyLong_FromLong(index)) - || (PyDict_SetItem(charmap, key, val) == -1)); - Py_XDECREF(key); - Py_XDECREF(val); - if (error) { - Py_DECREF(charmap); - return NULL; - } + charmap[py::cast(code)] = py::cast(index); code = FT_Get_Next_Char(self->x->get_face(), code, &index); } return charmap; @@ -926,638 +508,632 @@ static PyObject *PyFT2Font_get_charmap(PyFT2Font *self, PyObject *args) const char *PyFT2Font_get_char_index__doc__ = - "get_char_index(self, codepoint)\n" - "--\n\n" - "Return the glyph index corresponding to a character *codepoint*.\n"; + "Return the glyph index corresponding to a character *codepoint*."; -static PyObject *PyFT2Font_get_char_index(PyFT2Font *self, PyObject *args) +static FT_UInt +PyFT2Font_get_char_index(PyFT2Font *self, FT_ULong ccode) { - FT_UInt index; - FT_ULong ccode; bool fallback = true; - if (!PyArg_ParseTuple(args, "k:get_char_index", &ccode)) { - return NULL; - } - - CALL_CPP("get_char_index", index = self->x->get_char_index(ccode, fallback)); - - return PyLong_FromLong(index); + return self->x->get_char_index(ccode, fallback); } const char *PyFT2Font_get_sfnt__doc__ = - "get_sfnt(self)\n" - "--\n\n" "Load the entire SFNT names table, as a dict whose keys are\n" "(platform-ID, ISO-encoding-scheme, language-code, and description)\n" "tuples.\n"; -static PyObject *PyFT2Font_get_sfnt(PyFT2Font *self, PyObject *args) +static py::dict +PyFT2Font_get_sfnt(PyFT2Font *self) { - PyObject *names; - if (!(self->x->get_face()->face_flags & FT_FACE_FLAG_SFNT)) { - PyErr_SetString(PyExc_ValueError, "No SFNT name table"); - return NULL; + throw py::value_error("No SFNT name table"); } size_t count = FT_Get_Sfnt_Name_Count(self->x->get_face()); - names = PyDict_New(); - if (names == NULL) { - return NULL; - } + py::dict names; for (FT_UInt j = 0; j < count; ++j) { FT_SfntName sfnt; FT_Error error = FT_Get_Sfnt_Name(self->x->get_face(), j, &sfnt); if (error) { - Py_DECREF(names); - PyErr_SetString(PyExc_ValueError, "Could not get SFNT name"); - return NULL; - } - - PyObject *key = Py_BuildValue( - "HHHH", sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id); - if (key == NULL) { - Py_DECREF(names); - return NULL; + throw py::value_error("Could not get SFNT name"); } - PyObject *val = PyBytes_FromStringAndSize((const char *)sfnt.string, sfnt.string_len); - if (val == NULL) { - Py_DECREF(key); - Py_DECREF(names); - return NULL; - } - - if (PyDict_SetItem(names, key, val)) { - Py_DECREF(key); - Py_DECREF(val); - Py_DECREF(names); - return NULL; - } - - Py_DECREF(key); - Py_DECREF(val); + auto key = py::make_tuple( + sfnt.platform_id, sfnt.encoding_id, sfnt.language_id, sfnt.name_id); + auto val = py::bytes(reinterpret_cast(sfnt.string), + sfnt.string_len); + names[key] = val; } return names; } const char *PyFT2Font_get_name_index__doc__ = - "get_name_index(self, name)\n" - "--\n\n" "Return the glyph index of a given glyph *name*.\n" "The glyph index 0 means 'undefined character code'.\n"; -static PyObject *PyFT2Font_get_name_index(PyFT2Font *self, PyObject *args) +static long +PyFT2Font_get_name_index(PyFT2Font *self, char *glyphname) { - char *glyphname; - long name_index; - if (!PyArg_ParseTuple(args, "s:get_name_index", &glyphname)) { - return NULL; - } - CALL_CPP("get_name_index", name_index = self->x->get_name_index(glyphname)); - return PyLong_FromLong(name_index); + return self->x->get_name_index(glyphname); } const char *PyFT2Font_get_ps_font_info__doc__ = - "get_ps_font_info(self)\n" - "--\n\n" - "Return the information in the PS Font Info structure.\n"; + "Return the information in the PS Font Info structure."; -static PyObject *PyFT2Font_get_ps_font_info(PyFT2Font *self, PyObject *args) +static py::tuple +PyFT2Font_get_ps_font_info(PyFT2Font *self) { PS_FontInfoRec fontinfo; FT_Error error = FT_Get_PS_Font_Info(self->x->get_face(), &fontinfo); if (error) { - PyErr_SetString(PyExc_ValueError, "Could not get PS font info"); - return NULL; + throw py::value_error("Could not get PS font info"); } - return Py_BuildValue("ssssslbhH", - fontinfo.version ? fontinfo.version : "", - fontinfo.notice ? fontinfo.notice : "", - fontinfo.full_name ? fontinfo.full_name : "", - fontinfo.family_name ? fontinfo.family_name : "", - fontinfo.weight ? fontinfo.weight : "", - fontinfo.italic_angle, - fontinfo.is_fixed_pitch, - fontinfo.underline_position, - fontinfo.underline_thickness); + return py::make_tuple( + fontinfo.version ? fontinfo.version : "", + fontinfo.notice ? fontinfo.notice : "", + fontinfo.full_name ? fontinfo.full_name : "", + fontinfo.family_name ? fontinfo.family_name : "", + fontinfo.weight ? fontinfo.weight : "", + fontinfo.italic_angle, + fontinfo.is_fixed_pitch, + fontinfo.underline_position, + fontinfo.underline_thickness); } const char *PyFT2Font_get_sfnt_table__doc__ = - "get_sfnt_table(self, name)\n" - "--\n\n" "Return one of the following SFNT tables: head, maxp, OS/2, hhea, " - "vhea, post, or pclt.\n"; - -static PyObject *PyFT2Font_get_sfnt_table(PyFT2Font *self, PyObject *args) -{ - char *tagname; - if (!PyArg_ParseTuple(args, "s:get_sfnt_table", &tagname)) { - return NULL; - } - - int tag; - const char *tags[] = { "head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt", NULL }; + "vhea, post, or pclt."; + +static std::optional +PyFT2Font_get_sfnt_table(PyFT2Font *self, std::string tagname) +{ + FT_Sfnt_Tag tag; + const std::unordered_map names = { + {"head", FT_SFNT_HEAD}, + {"maxp", FT_SFNT_MAXP}, + {"OS/2", FT_SFNT_OS2}, + {"hhea", FT_SFNT_HHEA}, + {"vhea", FT_SFNT_VHEA}, + {"post", FT_SFNT_POST}, + {"pclt", FT_SFNT_PCLT}, + }; - for (tag = 0; tags[tag] != NULL; tag++) { - if (strncmp(tagname, tags[tag], 5) == 0) { - break; - } + try { + tag = names.at(tagname); + } catch (const std::out_of_range&) { + return std::nullopt; } - void *table = FT_Get_Sfnt_Table(self->x->get_face(), (FT_Sfnt_Tag)tag); + void *table = FT_Get_Sfnt_Table(self->x->get_face(), tag); if (!table) { - Py_RETURN_NONE; + return std::nullopt; } switch (tag) { - case 0: { - char head_dict[] = - "{s:(h,H), s:(h,H), s:l, s:l, s:H, s:H," - "s:(I,I), s:(I,I), s:h, s:h, s:h, s:h, s:H, s:H, s:h, s:h, s:h}"; - TT_Header *t = (TT_Header *)table; - return Py_BuildValue(head_dict, - "version", FIXED_MAJOR(t->Table_Version), FIXED_MINOR(t->Table_Version), - "fontRevision", FIXED_MAJOR(t->Font_Revision), FIXED_MINOR(t->Font_Revision), - "checkSumAdjustment", t->CheckSum_Adjust, - "magicNumber", t->Magic_Number, - "flags", t->Flags, - "unitsPerEm", t->Units_Per_EM, - // FreeType 2.6.1 defines these two timestamps as FT_Long, - // but they should be unsigned (fixed in 2.10.0): - // https://gitlab.freedesktop.org/freetype/freetype/-/commit/3e8ec291ffcfa03c8ecba1cdbfaa55f5577f5612 - // It's actually read from the file structure as two 32-bit - // values, so we need to cast down in size to prevent sign - // extension from producing huge 64-bit values. - "created", static_cast(t->Created[0]), static_cast(t->Created[1]), - "modified", static_cast(t->Modified[0]), static_cast(t->Modified[1]), - "xMin", t->xMin, - "yMin", t->yMin, - "xMax", t->xMax, - "yMax", t->yMax, - "macStyle", t->Mac_Style, - "lowestRecPPEM", t->Lowest_Rec_PPEM, - "fontDirectionHint", t->Font_Direction, - "indexToLocFormat", t->Index_To_Loc_Format, - "glyphDataFormat", t->Glyph_Data_Format); + case FT_SFNT_HEAD: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Table_Version), + FIXED_MINOR(t->Table_Version)), + "fontRevision"_a=py::make_tuple(FIXED_MAJOR(t->Font_Revision), + FIXED_MINOR(t->Font_Revision)), + "checkSumAdjustment"_a=t->CheckSum_Adjust, + "magicNumber"_a=t->Magic_Number, + "flags"_a=t->Flags, + "unitsPerEm"_a=t->Units_Per_EM, + // FreeType 2.6.1 defines these two timestamps as FT_Long, but they should + // be unsigned (fixed in 2.10.0): + // https://gitlab.freedesktop.org/freetype/freetype/-/commit/3e8ec291ffcfa03c8ecba1cdbfaa55f5577f5612 + // It's actually read from the file structure as two 32-bit values, so we + // need to cast down in size to prevent sign extension from producing huge + // 64-bit values. + "created"_a=py::make_tuple(static_cast(t->Created[0]), + static_cast(t->Created[1])), + "modified"_a=py::make_tuple(static_cast(t->Modified[0]), + static_cast(t->Modified[1])), + "xMin"_a=t->xMin, + "yMin"_a=t->yMin, + "xMax"_a=t->xMax, + "yMax"_a=t->yMax, + "macStyle"_a=t->Mac_Style, + "lowestRecPPEM"_a=t->Lowest_Rec_PPEM, + "fontDirectionHint"_a=t->Font_Direction, + "indexToLocFormat"_a=t->Index_To_Loc_Format, + "glyphDataFormat"_a=t->Glyph_Data_Format); } - case 1: { - char maxp_dict[] = - "{s:(h,H), s:H, s:H, s:H, s:H, s:H, s:H," - "s:H, s:H, s:H, s:H, s:H, s:H, s:H, s:H}"; - TT_MaxProfile *t = (TT_MaxProfile *)table; - return Py_BuildValue(maxp_dict, - "version", FIXED_MAJOR(t->version), FIXED_MINOR(t->version), - "numGlyphs", t->numGlyphs, - "maxPoints", t->maxPoints, - "maxContours", t->maxContours, - "maxComponentPoints", t->maxCompositePoints, - "maxComponentContours", t->maxCompositeContours, - "maxZones", t->maxZones, - "maxTwilightPoints", t->maxTwilightPoints, - "maxStorage", t->maxStorage, - "maxFunctionDefs", t->maxFunctionDefs, - "maxInstructionDefs", t->maxInstructionDefs, - "maxStackElements", t->maxStackElements, - "maxSizeOfInstructions", t->maxSizeOfInstructions, - "maxComponentElements", t->maxComponentElements, - "maxComponentDepth", t->maxComponentDepth); + case FT_SFNT_MAXP: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->version), + FIXED_MINOR(t->version)), + "numGlyphs"_a=t->numGlyphs, + "maxPoints"_a=t->maxPoints, + "maxContours"_a=t->maxContours, + "maxComponentPoints"_a=t->maxCompositePoints, + "maxComponentContours"_a=t->maxCompositeContours, + "maxZones"_a=t->maxZones, + "maxTwilightPoints"_a=t->maxTwilightPoints, + "maxStorage"_a=t->maxStorage, + "maxFunctionDefs"_a=t->maxFunctionDefs, + "maxInstructionDefs"_a=t->maxInstructionDefs, + "maxStackElements"_a=t->maxStackElements, + "maxSizeOfInstructions"_a=t->maxSizeOfInstructions, + "maxComponentElements"_a=t->maxComponentElements, + "maxComponentDepth"_a=t->maxComponentDepth); } - case 2: { - char os_2_dict[] = - "{s:H, s:h, s:H, s:H, s:H, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:h, s:y#, s:(kkkk)," - "s:y#, s:H, s:H, s:H}"; - TT_OS2 *t = (TT_OS2 *)table; - return Py_BuildValue(os_2_dict, - "version", t->version, - "xAvgCharWidth", t->xAvgCharWidth, - "usWeightClass", t->usWeightClass, - "usWidthClass", t->usWidthClass, - "fsType", t->fsType, - "ySubscriptXSize", t->ySubscriptXSize, - "ySubscriptYSize", t->ySubscriptYSize, - "ySubscriptXOffset", t->ySubscriptXOffset, - "ySubscriptYOffset", t->ySubscriptYOffset, - "ySuperscriptXSize", t->ySuperscriptXSize, - "ySuperscriptYSize", t->ySuperscriptYSize, - "ySuperscriptXOffset", t->ySuperscriptXOffset, - "ySuperscriptYOffset", t->ySuperscriptYOffset, - "yStrikeoutSize", t->yStrikeoutSize, - "yStrikeoutPosition", t->yStrikeoutPosition, - "sFamilyClass", t->sFamilyClass, - "panose", t->panose, Py_ssize_t(10), - "ulCharRange", t->ulUnicodeRange1, t->ulUnicodeRange2, t->ulUnicodeRange3, t->ulUnicodeRange4, - "achVendID", t->achVendID, Py_ssize_t(4), - "fsSelection", t->fsSelection, - "fsFirstCharIndex", t->usFirstCharIndex, - "fsLastCharIndex", t->usLastCharIndex); + case FT_SFNT_OS2: { + auto t = static_cast(table); + return py::dict( + "version"_a=t->version, + "xAvgCharWidth"_a=t->xAvgCharWidth, + "usWeightClass"_a=t->usWeightClass, + "usWidthClass"_a=t->usWidthClass, + "fsType"_a=t->fsType, + "ySubscriptXSize"_a=t->ySubscriptXSize, + "ySubscriptYSize"_a=t->ySubscriptYSize, + "ySubscriptXOffset"_a=t->ySubscriptXOffset, + "ySubscriptYOffset"_a=t->ySubscriptYOffset, + "ySuperscriptXSize"_a=t->ySuperscriptXSize, + "ySuperscriptYSize"_a=t->ySuperscriptYSize, + "ySuperscriptXOffset"_a=t->ySuperscriptXOffset, + "ySuperscriptYOffset"_a=t->ySuperscriptYOffset, + "yStrikeoutSize"_a=t->yStrikeoutSize, + "yStrikeoutPosition"_a=t->yStrikeoutPosition, + "sFamilyClass"_a=t->sFamilyClass, + "panose"_a=py::bytes(reinterpret_cast(t->panose), 10), + "ulCharRange"_a=py::make_tuple(t->ulUnicodeRange1, t->ulUnicodeRange2, + t->ulUnicodeRange3, t->ulUnicodeRange4), + "achVendID"_a=py::bytes(reinterpret_cast(t->achVendID), 4), + "fsSelection"_a=t->fsSelection, + "fsFirstCharIndex"_a=t->usFirstCharIndex, + "fsLastCharIndex"_a=t->usLastCharIndex); } - case 3: { - char hhea_dict[] = - "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:H}"; - TT_HoriHeader *t = (TT_HoriHeader *)table; - return Py_BuildValue(hhea_dict, - "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), - "ascent", t->Ascender, - "descent", t->Descender, - "lineGap", t->Line_Gap, - "advanceWidthMax", t->advance_Width_Max, - "minLeftBearing", t->min_Left_Side_Bearing, - "minRightBearing", t->min_Right_Side_Bearing, - "xMaxExtent", t->xMax_Extent, - "caretSlopeRise", t->caret_Slope_Rise, - "caretSlopeRun", t->caret_Slope_Run, - "caretOffset", t->caret_Offset, - "metricDataFormat", t->metric_Data_Format, - "numOfLongHorMetrics", t->number_Of_HMetrics); + case FT_SFNT_HHEA: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version)), + "ascent"_a=t->Ascender, + "descent"_a=t->Descender, + "lineGap"_a=t->Line_Gap, + "advanceWidthMax"_a=t->advance_Width_Max, + "minLeftBearing"_a=t->min_Left_Side_Bearing, + "minRightBearing"_a=t->min_Right_Side_Bearing, + "xMaxExtent"_a=t->xMax_Extent, + "caretSlopeRise"_a=t->caret_Slope_Rise, + "caretSlopeRun"_a=t->caret_Slope_Run, + "caretOffset"_a=t->caret_Offset, + "metricDataFormat"_a=t->metric_Data_Format, + "numOfLongHorMetrics"_a=t->number_Of_HMetrics); } - case 4: { - char vhea_dict[] = - "{s:(h,H), s:h, s:h, s:h, s:H, s:h, s:h, s:h," - "s:h, s:h, s:h, s:h, s:H}"; - TT_VertHeader *t = (TT_VertHeader *)table; - return Py_BuildValue(vhea_dict, - "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), - "vertTypoAscender", t->Ascender, - "vertTypoDescender", t->Descender, - "vertTypoLineGap", t->Line_Gap, - "advanceHeightMax", t->advance_Height_Max, - "minTopSideBearing", t->min_Top_Side_Bearing, - "minBottomSizeBearing", t->min_Bottom_Side_Bearing, - "yMaxExtent", t->yMax_Extent, - "caretSlopeRise", t->caret_Slope_Rise, - "caretSlopeRun", t->caret_Slope_Run, - "caretOffset", t->caret_Offset, - "metricDataFormat", t->metric_Data_Format, - "numOfLongVerMetrics", t->number_Of_VMetrics); + case FT_SFNT_VHEA: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version)), + "vertTypoAscender"_a=t->Ascender, + "vertTypoDescender"_a=t->Descender, + "vertTypoLineGap"_a=t->Line_Gap, + "advanceHeightMax"_a=t->advance_Height_Max, + "minTopSideBearing"_a=t->min_Top_Side_Bearing, + "minBottomSizeBearing"_a=t->min_Bottom_Side_Bearing, + "yMaxExtent"_a=t->yMax_Extent, + "caretSlopeRise"_a=t->caret_Slope_Rise, + "caretSlopeRun"_a=t->caret_Slope_Run, + "caretOffset"_a=t->caret_Offset, + "metricDataFormat"_a=t->metric_Data_Format, + "numOfLongVerMetrics"_a=t->number_Of_VMetrics); } - case 5: { - char post_dict[] = "{s:(h,H), s:(h,H), s:h, s:h, s:k, s:k, s:k, s:k, s:k}"; - TT_Postscript *t = (TT_Postscript *)table; - return Py_BuildValue(post_dict, - "format", FIXED_MAJOR(t->FormatType), FIXED_MINOR(t->FormatType), - "italicAngle", FIXED_MAJOR(t->italicAngle), FIXED_MINOR(t->italicAngle), - "underlinePosition", t->underlinePosition, - "underlineThickness", t->underlineThickness, - "isFixedPitch", t->isFixedPitch, - "minMemType42", t->minMemType42, - "maxMemType42", t->maxMemType42, - "minMemType1", t->minMemType1, - "maxMemType1", t->maxMemType1); + case FT_SFNT_POST: { + auto t = static_cast(table); + return py::dict( + "format"_a=py::make_tuple(FIXED_MAJOR(t->FormatType), + FIXED_MINOR(t->FormatType)), + "italicAngle"_a=py::make_tuple(FIXED_MAJOR(t->italicAngle), + FIXED_MINOR(t->italicAngle)), + "underlinePosition"_a=t->underlinePosition, + "underlineThickness"_a=t->underlineThickness, + "isFixedPitch"_a=t->isFixedPitch, + "minMemType42"_a=t->minMemType42, + "maxMemType42"_a=t->maxMemType42, + "minMemType1"_a=t->minMemType1, + "maxMemType1"_a=t->maxMemType1); } - case 6: { - char pclt_dict[] = - "{s:(h,H), s:k, s:H, s:H, s:H, s:H, s:H, s:H, s:y#, s:y#, s:b, " - "s:b, s:b}"; - TT_PCLT *t = (TT_PCLT *)table; - return Py_BuildValue(pclt_dict, - "version", FIXED_MAJOR(t->Version), FIXED_MINOR(t->Version), - "fontNumber", t->FontNumber, - "pitch", t->Pitch, - "xHeight", t->xHeight, - "style", t->Style, - "typeFamily", t->TypeFamily, - "capHeight", t->CapHeight, - "symbolSet", t->SymbolSet, - "typeFace", t->TypeFace, Py_ssize_t(16), - "characterComplement", t->CharacterComplement, Py_ssize_t(8), - "strokeWeight", t->StrokeWeight, - "widthType", t->WidthType, - "serifStyle", t->SerifStyle); + case FT_SFNT_PCLT: { + auto t = static_cast(table); + return py::dict( + "version"_a=py::make_tuple(FIXED_MAJOR(t->Version), + FIXED_MINOR(t->Version)), + "fontNumber"_a=t->FontNumber, + "pitch"_a=t->Pitch, + "xHeight"_a=t->xHeight, + "style"_a=t->Style, + "typeFamily"_a=t->TypeFamily, + "capHeight"_a=t->CapHeight, + "symbolSet"_a=t->SymbolSet, + "typeFace"_a=py::bytes(reinterpret_cast(t->TypeFace), 16), + "characterComplement"_a=py::bytes( + reinterpret_cast(t->CharacterComplement), 8), + "strokeWeight"_a=t->StrokeWeight, + "widthType"_a=t->WidthType, + "serifStyle"_a=t->SerifStyle); } default: - Py_RETURN_NONE; + return std::nullopt; } } const char *PyFT2Font_get_path__doc__ = - "get_path(self)\n" - "--\n\n" - "Get the path data from the currently loaded glyph as a tuple of vertices, " - "codes.\n"; + "Get the path data from the currently loaded glyph as a tuple of vertices, codes."; -static PyObject *PyFT2Font_get_path(PyFT2Font *self, PyObject *args) +static py::tuple +PyFT2Font_get_path(PyFT2Font *self) { std::vector vertices; std::vector codes; - CALL_CPP("get_path", self->x->get_path(vertices, codes)); + self->x->get_path(vertices, codes); - npy_intp length = codes.size(); - npy_intp vertices_dims[2] = { length, 2 }; - numpy::array_view vertices_arr(vertices_dims); - memcpy(vertices_arr.data(), vertices.data(), sizeof(double) * vertices.size()); - npy_intp codes_dims[1] = { length }; - numpy::array_view codes_arr(codes_dims); - memcpy(codes_arr.data(), codes.data(), codes.size()); + py::ssize_t length = codes.size(); + py::ssize_t vertices_dims[2] = { length, 2 }; + py::array_t vertices_arr(vertices_dims); + if (length > 0) { + memcpy(vertices_arr.mutable_data(), vertices.data(), vertices_arr.nbytes()); + } + py::ssize_t codes_dims[1] = { length }; + py::array_t codes_arr(codes_dims); + if (length > 0) { + memcpy(codes_arr.mutable_data(), codes.data(), codes_arr.nbytes()); + } - return Py_BuildValue("NN", vertices_arr.pyobj(), codes_arr.pyobj()); + return py::make_tuple(vertices_arr, codes_arr); } const char *PyFT2Font_get_image__doc__ = - "get_image(self)\n" - "--\n\n" - "Return the underlying image buffer for this font object.\n"; + "Return the underlying image buffer for this font object."; -static PyObject *PyFT2Font_get_image(PyFT2Font *self, PyObject *args) +static py::array +PyFT2Font_get_image(PyFT2Font *self) { FT2Image &im = self->x->get_image(); - npy_intp dims[] = {(npy_intp)im.get_height(), (npy_intp)im.get_width() }; - return PyArray_SimpleNewFromData(2, dims, NPY_UBYTE, im.get_buffer()); + py::ssize_t dims[] = { + static_cast(im.get_height()), + static_cast(im.get_width()) + }; + return py::array_t(dims, im.get_buffer()); } -static PyObject *PyFT2Font_postscript_name(PyFT2Font *self, void *closure) +static const char * +PyFT2Font_postscript_name(PyFT2Font *self) { const char *ps_name = FT_Get_Postscript_Name(self->x->get_face()); if (ps_name == NULL) { ps_name = "UNAVAILABLE"; } - return PyUnicode_FromString(ps_name); + return ps_name; } -static PyObject *PyFT2Font_num_faces(PyFT2Font *self, void *closure) +static FT_Long +PyFT2Font_num_faces(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->num_faces); + return self->x->get_face()->num_faces; } -static PyObject *PyFT2Font_family_name(PyFT2Font *self, void *closure) +static const char * +PyFT2Font_family_name(PyFT2Font *self) { const char *name = self->x->get_face()->family_name; if (name == NULL) { name = "UNAVAILABLE"; } - return PyUnicode_FromString(name); + return name; } -static PyObject *PyFT2Font_style_name(PyFT2Font *self, void *closure) +static const char * +PyFT2Font_style_name(PyFT2Font *self) { const char *name = self->x->get_face()->style_name; if (name == NULL) { name = "UNAVAILABLE"; } - return PyUnicode_FromString(name); + return name; } -static PyObject *PyFT2Font_face_flags(PyFT2Font *self, void *closure) +static FT_Long +PyFT2Font_face_flags(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->face_flags); + return self->x->get_face()->face_flags; } -static PyObject *PyFT2Font_style_flags(PyFT2Font *self, void *closure) +static FT_Long +PyFT2Font_style_flags(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->style_flags); + return self->x->get_face()->style_flags; } -static PyObject *PyFT2Font_num_glyphs(PyFT2Font *self, void *closure) +static FT_Long +PyFT2Font_num_glyphs(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->num_glyphs); + return self->x->get_face()->num_glyphs; } -static PyObject *PyFT2Font_num_fixed_sizes(PyFT2Font *self, void *closure) +static FT_Int +PyFT2Font_num_fixed_sizes(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->num_fixed_sizes); + return self->x->get_face()->num_fixed_sizes; } -static PyObject *PyFT2Font_num_charmaps(PyFT2Font *self, void *closure) +static FT_Int +PyFT2Font_num_charmaps(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->num_charmaps); + return self->x->get_face()->num_charmaps; } -static PyObject *PyFT2Font_scalable(PyFT2Font *self, void *closure) +static bool +PyFT2Font_scalable(PyFT2Font *self) { if (FT_IS_SCALABLE(self->x->get_face())) { - Py_RETURN_TRUE; + return true; } - Py_RETURN_FALSE; + return false; } -static PyObject *PyFT2Font_units_per_EM(PyFT2Font *self, void *closure) +static FT_UShort +PyFT2Font_units_per_EM(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->units_per_EM); + return self->x->get_face()->units_per_EM; } -static PyObject *PyFT2Font_get_bbox(PyFT2Font *self, void *closure) +static py::tuple +PyFT2Font_get_bbox(PyFT2Font *self) { FT_BBox *bbox = &(self->x->get_face()->bbox); - return Py_BuildValue("llll", - bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax); + return py::make_tuple(bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax); } -static PyObject *PyFT2Font_ascender(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_ascender(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->ascender); + return self->x->get_face()->ascender; } -static PyObject *PyFT2Font_descender(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_descender(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->descender); + return self->x->get_face()->descender; } -static PyObject *PyFT2Font_height(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_height(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->height); + return self->x->get_face()->height; } -static PyObject *PyFT2Font_max_advance_width(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_max_advance_width(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->max_advance_width); + return self->x->get_face()->max_advance_width; } -static PyObject *PyFT2Font_max_advance_height(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_max_advance_height(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->max_advance_height); + return self->x->get_face()->max_advance_height; } -static PyObject *PyFT2Font_underline_position(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_underline_position(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->underline_position); + return self->x->get_face()->underline_position; } -static PyObject *PyFT2Font_underline_thickness(PyFT2Font *self, void *closure) +static FT_Short +PyFT2Font_underline_thickness(PyFT2Font *self) { - return PyLong_FromLong(self->x->get_face()->underline_thickness); + return self->x->get_face()->underline_thickness; } -static PyObject *PyFT2Font_fname(PyFT2Font *self, void *closure) +static py::str +PyFT2Font_fname(PyFT2Font *self) { if (self->stream.close) { // Called passed a filename to the constructor. - return PyObject_GetAttrString(self->py_file, "name"); + return self->py_file.attr("name"); } else { - Py_INCREF(self->py_file); - return self->py_file; + return py::cast(self->py_file); } } -static int PyFT2Font_get_buffer(PyFT2Font *self, Py_buffer *buf, int flags) +PYBIND11_MODULE(ft2font, m) { - FT2Image &im = self->x->get_image(); - - Py_INCREF(self); - buf->obj = (PyObject *)self; - buf->buf = im.get_buffer(); - buf->len = im.get_width() * im.get_height(); - buf->readonly = 0; - buf->format = (char *)"B"; - buf->ndim = 2; - self->shape[0] = im.get_height(); - self->shape[1] = im.get_width(); - buf->shape = self->shape; - self->strides[0] = im.get_width(); - self->strides[1] = 1; - buf->strides = self->strides; - buf->suboffsets = NULL; - buf->itemsize = 1; - buf->internal = NULL; - - return 1; -} - -static PyTypeObject *PyFT2Font_init_type() -{ - static PyGetSetDef getset[] = { - {(char *)"postscript_name", (getter)PyFT2Font_postscript_name, NULL, NULL, NULL}, - {(char *)"num_faces", (getter)PyFT2Font_num_faces, NULL, NULL, NULL}, - {(char *)"family_name", (getter)PyFT2Font_family_name, NULL, NULL, NULL}, - {(char *)"style_name", (getter)PyFT2Font_style_name, NULL, NULL, NULL}, - {(char *)"face_flags", (getter)PyFT2Font_face_flags, NULL, NULL, NULL}, - {(char *)"style_flags", (getter)PyFT2Font_style_flags, NULL, NULL, NULL}, - {(char *)"num_glyphs", (getter)PyFT2Font_num_glyphs, NULL, NULL, NULL}, - {(char *)"num_fixed_sizes", (getter)PyFT2Font_num_fixed_sizes, NULL, NULL, NULL}, - {(char *)"num_charmaps", (getter)PyFT2Font_num_charmaps, NULL, NULL, NULL}, - {(char *)"scalable", (getter)PyFT2Font_scalable, NULL, NULL, NULL}, - {(char *)"units_per_EM", (getter)PyFT2Font_units_per_EM, NULL, NULL, NULL}, - {(char *)"bbox", (getter)PyFT2Font_get_bbox, NULL, NULL, NULL}, - {(char *)"ascender", (getter)PyFT2Font_ascender, NULL, NULL, NULL}, - {(char *)"descender", (getter)PyFT2Font_descender, NULL, NULL, NULL}, - {(char *)"height", (getter)PyFT2Font_height, NULL, NULL, NULL}, - {(char *)"max_advance_width", (getter)PyFT2Font_max_advance_width, NULL, NULL, NULL}, - {(char *)"max_advance_height", (getter)PyFT2Font_max_advance_height, NULL, NULL, NULL}, - {(char *)"underline_position", (getter)PyFT2Font_underline_position, NULL, NULL, NULL}, - {(char *)"underline_thickness", (getter)PyFT2Font_underline_thickness, NULL, NULL, NULL}, - {(char *)"fname", (getter)PyFT2Font_fname, NULL, NULL, NULL}, - {NULL} - }; - - static PyMethodDef methods[] = { - {"clear", (PyCFunction)PyFT2Font_clear, METH_NOARGS, PyFT2Font_clear__doc__}, - {"set_size", (PyCFunction)PyFT2Font_set_size, METH_VARARGS, PyFT2Font_set_size__doc__}, - {"set_charmap", (PyCFunction)PyFT2Font_set_charmap, METH_VARARGS, PyFT2Font_set_charmap__doc__}, - {"select_charmap", (PyCFunction)PyFT2Font_select_charmap, METH_VARARGS, PyFT2Font_select_charmap__doc__}, - {"get_kerning", (PyCFunction)PyFT2Font_get_kerning, METH_VARARGS, PyFT2Font_get_kerning__doc__}, - {"set_text", (PyCFunction)PyFT2Font_set_text, METH_VARARGS|METH_KEYWORDS, PyFT2Font_set_text__doc__}, - {"_get_fontmap", (PyCFunction)PyFT2Font_get_fontmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_get_fontmap__doc__}, - {"get_num_glyphs", (PyCFunction)PyFT2Font_get_num_glyphs, METH_NOARGS, PyFT2Font_get_num_glyphs__doc__}, - {"load_char", (PyCFunction)PyFT2Font_load_char, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_char__doc__}, - {"load_glyph", (PyCFunction)PyFT2Font_load_glyph, METH_VARARGS|METH_KEYWORDS, PyFT2Font_load_glyph__doc__}, - {"get_width_height", (PyCFunction)PyFT2Font_get_width_height, METH_NOARGS, PyFT2Font_get_width_height__doc__}, - {"get_bitmap_offset", (PyCFunction)PyFT2Font_get_bitmap_offset, METH_NOARGS, PyFT2Font_get_bitmap_offset__doc__}, - {"get_descent", (PyCFunction)PyFT2Font_get_descent, METH_NOARGS, PyFT2Font_get_descent__doc__}, - {"draw_glyphs_to_bitmap", (PyCFunction)PyFT2Font_draw_glyphs_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyphs_to_bitmap__doc__}, - {"draw_glyph_to_bitmap", (PyCFunction)PyFT2Font_draw_glyph_to_bitmap, METH_VARARGS|METH_KEYWORDS, PyFT2Font_draw_glyph_to_bitmap__doc__}, - {"get_glyph_name", (PyCFunction)PyFT2Font_get_glyph_name, METH_VARARGS, PyFT2Font_get_glyph_name__doc__}, - {"get_charmap", (PyCFunction)PyFT2Font_get_charmap, METH_NOARGS, PyFT2Font_get_charmap__doc__}, - {"get_char_index", (PyCFunction)PyFT2Font_get_char_index, METH_VARARGS, PyFT2Font_get_char_index__doc__}, - {"get_sfnt", (PyCFunction)PyFT2Font_get_sfnt, METH_NOARGS, PyFT2Font_get_sfnt__doc__}, - {"get_name_index", (PyCFunction)PyFT2Font_get_name_index, METH_VARARGS, PyFT2Font_get_name_index__doc__}, - {"get_ps_font_info", (PyCFunction)PyFT2Font_get_ps_font_info, METH_NOARGS, PyFT2Font_get_ps_font_info__doc__}, - {"get_sfnt_table", (PyCFunction)PyFT2Font_get_sfnt_table, METH_VARARGS, PyFT2Font_get_sfnt_table__doc__}, - {"get_path", (PyCFunction)PyFT2Font_get_path, METH_NOARGS, PyFT2Font_get_path__doc__}, - {"get_image", (PyCFunction)PyFT2Font_get_image, METH_NOARGS, PyFT2Font_get_image__doc__}, - {NULL} + auto ia = [m]() -> const void* { + import_array(); + return &m; }; - - static PyBufferProcs buffer_procs; - buffer_procs.bf_getbuffer = (getbufferproc)PyFT2Font_get_buffer; - - PyFT2FontType.tp_name = "matplotlib.ft2font.FT2Font"; - PyFT2FontType.tp_doc = PyFT2Font_init__doc__; - PyFT2FontType.tp_basicsize = sizeof(PyFT2Font); - PyFT2FontType.tp_dealloc = (destructor)PyFT2Font_dealloc; - PyFT2FontType.tp_flags = Py_TPFLAGS_DEFAULT; - PyFT2FontType.tp_methods = methods; - PyFT2FontType.tp_getset = getset; - PyFT2FontType.tp_new = PyFT2Font_new; - PyFT2FontType.tp_init = (initproc)PyFT2Font_init; - PyFT2FontType.tp_as_buffer = &buffer_procs; - - return &PyFT2FontType; -} - -static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "ft2font" }; - -PyMODINIT_FUNC PyInit_ft2font(void) -{ - import_array(); + if (ia() == NULL) { + throw py::error_already_set(); + } if (FT_Init_FreeType(&_ft2Library)) { // initialize library - return PyErr_Format( - PyExc_RuntimeError, "Could not initialize the freetype2 library"); + throw std::runtime_error("Could not initialize the freetype2 library"); } FT_Int major, minor, patch; char version_string[64]; FT_Library_Version(_ft2Library, &major, &minor, &patch); snprintf(version_string, sizeof(version_string), "%d.%d.%d", major, minor, patch); - PyObject *m; - if (!(m = PyModule_Create(&moduledef)) || - prepare_and_add_type(PyFT2Image_init_type(), m) || - prepare_and_add_type(PyFT2Font_init_type(), m) || - // Glyph is not constructible from Python, thus not added to the module. - PyType_Ready(PyGlyph_init_type()) || - PyModule_AddStringConstant(m, "__freetype_version__", version_string) || - PyModule_AddStringConstant(m, "__freetype_build_type__", FREETYPE_BUILD_TYPE) || - PyModule_AddIntConstant(m, "SCALABLE", FT_FACE_FLAG_SCALABLE) || - PyModule_AddIntConstant(m, "FIXED_SIZES", FT_FACE_FLAG_FIXED_SIZES) || - PyModule_AddIntConstant(m, "FIXED_WIDTH", FT_FACE_FLAG_FIXED_WIDTH) || - PyModule_AddIntConstant(m, "SFNT", FT_FACE_FLAG_SFNT) || - PyModule_AddIntConstant(m, "HORIZONTAL", FT_FACE_FLAG_HORIZONTAL) || - PyModule_AddIntConstant(m, "VERTICAL", FT_FACE_FLAG_VERTICAL) || - PyModule_AddIntConstant(m, "KERNING", FT_FACE_FLAG_KERNING) || - PyModule_AddIntConstant(m, "FAST_GLYPHS", FT_FACE_FLAG_FAST_GLYPHS) || - PyModule_AddIntConstant(m, "MULTIPLE_MASTERS", FT_FACE_FLAG_MULTIPLE_MASTERS) || - PyModule_AddIntConstant(m, "GLYPH_NAMES", FT_FACE_FLAG_GLYPH_NAMES) || - PyModule_AddIntConstant(m, "EXTERNAL_STREAM", FT_FACE_FLAG_EXTERNAL_STREAM) || - PyModule_AddIntConstant(m, "ITALIC", FT_STYLE_FLAG_ITALIC) || - PyModule_AddIntConstant(m, "BOLD", FT_STYLE_FLAG_BOLD) || - PyModule_AddIntConstant(m, "KERNING_DEFAULT", FT_KERNING_DEFAULT) || - PyModule_AddIntConstant(m, "KERNING_UNFITTED", FT_KERNING_UNFITTED) || - PyModule_AddIntConstant(m, "KERNING_UNSCALED", FT_KERNING_UNSCALED) || - PyModule_AddIntConstant(m, "LOAD_DEFAULT", FT_LOAD_DEFAULT) || - PyModule_AddIntConstant(m, "LOAD_NO_SCALE", FT_LOAD_NO_SCALE) || - PyModule_AddIntConstant(m, "LOAD_NO_HINTING", FT_LOAD_NO_HINTING) || - PyModule_AddIntConstant(m, "LOAD_RENDER", FT_LOAD_RENDER) || - PyModule_AddIntConstant(m, "LOAD_NO_BITMAP", FT_LOAD_NO_BITMAP) || - PyModule_AddIntConstant(m, "LOAD_VERTICAL_LAYOUT", FT_LOAD_VERTICAL_LAYOUT) || - PyModule_AddIntConstant(m, "LOAD_FORCE_AUTOHINT", FT_LOAD_FORCE_AUTOHINT) || - PyModule_AddIntConstant(m, "LOAD_CROP_BITMAP", FT_LOAD_CROP_BITMAP) || - PyModule_AddIntConstant(m, "LOAD_PEDANTIC", FT_LOAD_PEDANTIC) || - PyModule_AddIntConstant(m, "LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH", FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH) || - PyModule_AddIntConstant(m, "LOAD_NO_RECURSE", FT_LOAD_NO_RECURSE) || - PyModule_AddIntConstant(m, "LOAD_IGNORE_TRANSFORM", FT_LOAD_IGNORE_TRANSFORM) || - PyModule_AddIntConstant(m, "LOAD_MONOCHROME", FT_LOAD_MONOCHROME) || - PyModule_AddIntConstant(m, "LOAD_LINEAR_DESIGN", FT_LOAD_LINEAR_DESIGN) || - PyModule_AddIntConstant(m, "LOAD_NO_AUTOHINT", (unsigned long)FT_LOAD_NO_AUTOHINT) || - PyModule_AddIntConstant(m, "LOAD_TARGET_NORMAL", (unsigned long)FT_LOAD_TARGET_NORMAL) || - PyModule_AddIntConstant(m, "LOAD_TARGET_LIGHT", (unsigned long)FT_LOAD_TARGET_LIGHT) || - PyModule_AddIntConstant(m, "LOAD_TARGET_MONO", (unsigned long)FT_LOAD_TARGET_MONO) || - PyModule_AddIntConstant(m, "LOAD_TARGET_LCD", (unsigned long)FT_LOAD_TARGET_LCD) || - PyModule_AddIntConstant(m, "LOAD_TARGET_LCD_V", (unsigned long)FT_LOAD_TARGET_LCD_V)) { - FT_Done_FreeType(_ft2Library); - Py_XDECREF(m); - return NULL; - } - - return m; + py::class_(m, "FT2Image", py::is_final(), py::buffer_protocol()) + .def(py::init(), "width"_a, "height"_a) + .def("draw_rect_filled", &PyFT2Image_draw_rect_filled, + "x0"_a, "y0"_a, "x1"_a, "y1"_a, + PyFT2Image_draw_rect_filled__doc__) + .def_buffer([](FT2Image &self) -> py::buffer_info { + std::vector shape { self.get_height(), self.get_width() }; + std::vector strides { self.get_width(), 1 }; + return py::buffer_info(self.get_buffer(), shape, strides); + }); + + py::class_(m, "Glyph", py::is_final()) + .def(py::init<>([]() -> PyGlyph { + // Glyph is not useful from Python, so mark it as not constructible. + throw std::runtime_error("Glyph is not constructible"); + })) + .def_readonly("width", &PyGlyph::width) + .def_readonly("height", &PyGlyph::height) + .def_readonly("horiBearingX", &PyGlyph::horiBearingX) + .def_readonly("horiBearingY", &PyGlyph::horiBearingY) + .def_readonly("horiAdvance", &PyGlyph::horiAdvance) + .def_readonly("linearHoriAdvance", &PyGlyph::linearHoriAdvance) + .def_readonly("vertBearingX", &PyGlyph::vertBearingX) + .def_readonly("vertBearingY", &PyGlyph::vertBearingY) + .def_readonly("vertAdvance", &PyGlyph::vertAdvance) + .def_property_readonly("bbox", &PyGlyph_get_bbox); + + py::class_(m, "FT2Font", py::is_final(), py::buffer_protocol()) + .def(py::init(&PyFT2Font_init), + "filename"_a, "hinting_factor"_a=8, py::kw_only(), + "_fallback_list"_a=py::none(), "_kerning_factor"_a=0, + PyFT2Font_init__doc__) + .def("clear", &PyFT2Font_clear, PyFT2Font_clear__doc__) + .def("set_size", &PyFT2Font_set_size, "ptsize"_a, "dpi"_a, + PyFT2Font_set_size__doc__) + .def("set_charmap", &PyFT2Font_set_charmap, "i"_a, + PyFT2Font_set_charmap__doc__) + .def("select_charmap", &PyFT2Font_select_charmap, "i"_a, + PyFT2Font_select_charmap__doc__) + .def("get_kerning", &PyFT2Font_get_kerning, "left"_a, "right"_a, "mode"_a, + PyFT2Font_get_kerning__doc__) + .def("set_text", &PyFT2Font_set_text, + "string"_a, "angle"_a=0.0, "flags"_a=FT_LOAD_FORCE_AUTOHINT, + PyFT2Font_set_text__doc__) + .def("_get_fontmap", &PyFT2Font_get_fontmap, "string"_a, + PyFT2Font_get_fontmap__doc__) + .def("get_num_glyphs", &PyFT2Font_get_num_glyphs, PyFT2Font_get_num_glyphs__doc__) + .def("load_char", &PyFT2Font_load_char, + "charcode"_a, "flags"_a=FT_LOAD_FORCE_AUTOHINT, + PyFT2Font_load_char__doc__) + .def("load_glyph", &PyFT2Font_load_glyph, + "glyph_index"_a, "flags"_a=FT_LOAD_FORCE_AUTOHINT, + PyFT2Font_load_glyph__doc__) + .def("get_width_height", &PyFT2Font_get_width_height, + PyFT2Font_get_width_height__doc__) + .def("get_bitmap_offset", &PyFT2Font_get_bitmap_offset, + PyFT2Font_get_bitmap_offset__doc__) + .def("get_descent", &PyFT2Font_get_descent, PyFT2Font_get_descent__doc__) + .def("draw_glyphs_to_bitmap", &PyFT2Font_draw_glyphs_to_bitmap, + py::kw_only(), "antialiased"_a=true, + PyFT2Font_draw_glyphs_to_bitmap__doc__) + .def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap, + "image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true, + PyFT2Font_draw_glyph_to_bitmap__doc__) + .def("get_glyph_name", &PyFT2Font_get_glyph_name, "index"_a, + PyFT2Font_get_glyph_name__doc__) + .def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__) + .def("get_char_index", &PyFT2Font_get_char_index, "codepoint"_a, + PyFT2Font_get_char_index__doc__) + .def("get_sfnt", &PyFT2Font_get_sfnt, PyFT2Font_get_sfnt__doc__) + .def("get_name_index", &PyFT2Font_get_name_index, "name"_a, + PyFT2Font_get_name_index__doc__) + .def("get_ps_font_info", &PyFT2Font_get_ps_font_info, + PyFT2Font_get_ps_font_info__doc__) + .def("get_sfnt_table", &PyFT2Font_get_sfnt_table, "name"_a, + PyFT2Font_get_sfnt_table__doc__) + .def("get_path", &PyFT2Font_get_path, PyFT2Font_get_path__doc__) + .def("get_image", &PyFT2Font_get_image, PyFT2Font_get_image__doc__) + + .def_property_readonly("postscript_name", &PyFT2Font_postscript_name, + "PostScript name of the font.") + .def_property_readonly("num_faces", &PyFT2Font_num_faces, + "Number of faces in file.") + .def_property_readonly("family_name", &PyFT2Font_family_name, + "Face family name.") + .def_property_readonly("style_name", &PyFT2Font_style_name, + "Style name.") + .def_property_readonly("face_flags", &PyFT2Font_face_flags, + "Face flags; see the ft2font constants.") + .def_property_readonly("style_flags", &PyFT2Font_style_flags, + "Style flags; see the ft2font constants.") + .def_property_readonly("num_glyphs", &PyFT2Font_num_glyphs, + "Number of glyphs in the face.") + .def_property_readonly("num_fixed_sizes", &PyFT2Font_num_fixed_sizes, + "Number of bitmap in the face.") + .def_property_readonly("num_charmaps", &PyFT2Font_num_charmaps) + .def_property_readonly("scalable", &PyFT2Font_scalable, + "Whether face is scalable; attributes after this one " + "are only defined for scalable faces.") + .def_property_readonly("units_per_EM", &PyFT2Font_units_per_EM, + "Number of font units covered by the EM.") + .def_property_readonly("bbox", &PyFT2Font_get_bbox, + "Face global bounding box (xmin, ymin, xmax, ymax).") + .def_property_readonly("ascender", &PyFT2Font_ascender, + "Ascender in 26.6 units.") + .def_property_readonly("descender", &PyFT2Font_descender, + "Descender in 26.6 units.") + .def_property_readonly("height", &PyFT2Font_height, + "Height in 26.6 units; used to compute a default line " + "spacing (baseline-to-baseline distance).") + .def_property_readonly("max_advance_width", &PyFT2Font_max_advance_width, + "Maximum horizontal cursor advance for all glyphs.") + .def_property_readonly("max_advance_height", &PyFT2Font_max_advance_height, + "Maximum vertical cursor advance for all glyphs.") + .def_property_readonly("underline_position", &PyFT2Font_underline_position, + "Vertical position of the underline bar.") + .def_property_readonly("underline_thickness", &PyFT2Font_underline_thickness, + "Thickness of the underline bar.") + .def_property_readonly("fname", &PyFT2Font_fname) + + .def_buffer([](PyFT2Font &self) -> py::buffer_info { + FT2Image &im = self.x->get_image(); + std::vector shape { im.get_height(), im.get_width() }; + std::vector strides { im.get_width(), 1 }; + return py::buffer_info(im.get_buffer(), shape, strides); + }); + + m.attr("__freetype_version__") = version_string; + m.attr("__freetype_build_type__") = FREETYPE_BUILD_TYPE; + m.attr("SCALABLE") = FT_FACE_FLAG_SCALABLE; + m.attr("FIXED_SIZES") = FT_FACE_FLAG_FIXED_SIZES; + m.attr("FIXED_WIDTH") = FT_FACE_FLAG_FIXED_WIDTH; + m.attr("SFNT") = FT_FACE_FLAG_SFNT; + m.attr("HORIZONTAL") = FT_FACE_FLAG_HORIZONTAL; + m.attr("VERTICAL") = FT_FACE_FLAG_VERTICAL; + m.attr("KERNING") = FT_FACE_FLAG_KERNING; + m.attr("FAST_GLYPHS") = FT_FACE_FLAG_FAST_GLYPHS; + m.attr("MULTIPLE_MASTERS") = FT_FACE_FLAG_MULTIPLE_MASTERS; + m.attr("GLYPH_NAMES") = FT_FACE_FLAG_GLYPH_NAMES; + m.attr("EXTERNAL_STREAM") = FT_FACE_FLAG_EXTERNAL_STREAM; + m.attr("ITALIC") = FT_STYLE_FLAG_ITALIC; + m.attr("BOLD") = FT_STYLE_FLAG_BOLD; + m.attr("KERNING_DEFAULT") = (int)FT_KERNING_DEFAULT; + m.attr("KERNING_UNFITTED") = (int)FT_KERNING_UNFITTED; + m.attr("KERNING_UNSCALED") = (int)FT_KERNING_UNSCALED; + m.attr("LOAD_DEFAULT") = FT_LOAD_DEFAULT; + m.attr("LOAD_NO_SCALE") = FT_LOAD_NO_SCALE; + m.attr("LOAD_NO_HINTING") = FT_LOAD_NO_HINTING; + m.attr("LOAD_RENDER") = FT_LOAD_RENDER; + m.attr("LOAD_NO_BITMAP") = FT_LOAD_NO_BITMAP; + m.attr("LOAD_VERTICAL_LAYOUT") = FT_LOAD_VERTICAL_LAYOUT; + m.attr("LOAD_FORCE_AUTOHINT") = FT_LOAD_FORCE_AUTOHINT; + m.attr("LOAD_CROP_BITMAP") = FT_LOAD_CROP_BITMAP; + m.attr("LOAD_PEDANTIC") = FT_LOAD_PEDANTIC; + m.attr("LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH") = FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; + m.attr("LOAD_NO_RECURSE") = FT_LOAD_NO_RECURSE; + m.attr("LOAD_IGNORE_TRANSFORM") = FT_LOAD_IGNORE_TRANSFORM; + m.attr("LOAD_MONOCHROME") = FT_LOAD_MONOCHROME; + m.attr("LOAD_LINEAR_DESIGN") = FT_LOAD_LINEAR_DESIGN; + m.attr("LOAD_NO_AUTOHINT") = (unsigned long)FT_LOAD_NO_AUTOHINT; + m.attr("LOAD_TARGET_NORMAL") = (unsigned long)FT_LOAD_TARGET_NORMAL; + m.attr("LOAD_TARGET_LIGHT") = (unsigned long)FT_LOAD_TARGET_LIGHT; + m.attr("LOAD_TARGET_MONO") = (unsigned long)FT_LOAD_TARGET_MONO; + m.attr("LOAD_TARGET_LCD") = (unsigned long)FT_LOAD_TARGET_LCD; + m.attr("LOAD_TARGET_LCD_V") = (unsigned long)FT_LOAD_TARGET_LCD_V; } diff --git a/src/meson.build b/src/meson.build index a046b3306ab8..4edd8451aad2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -94,7 +94,7 @@ extension_data = { 'py_converters.cpp', ), 'dependencies': [ - freetype_dep, numpy_dep, agg_dep.partial_dependency(includes: true), + freetype_dep, pybind11_dep, numpy_dep, agg_dep.partial_dependency(includes: true), ], 'cpp_args': [ '-DFREETYPE_BUILD_TYPE="@0@"'.format(