#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include #include #include #include "ft2font.h" #include "_enums.h" #include #include #include namespace py = pybind11; using namespace pybind11::literals; template using double_or_ = std::variant; template static T _double_to_(const char *name, double_or_ &var) { if (auto value = std::get_if(&var)) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.10", "name"_a=name, "obj_type"_a="parameter as float", "alternative"_a="int({})"_s.format(name)); return static_cast(*value); } else if (auto value = std::get_if(&var)) { return *value; } else { // pybind11 will have only allowed types that match the variant, so this `else` // can't happen. We only have this case because older macOS doesn't support // `std::get` and using the conditional `std::get_if` means an `else` to silence // compiler warnings about "unhandled" cases. throw std::runtime_error("Should not happen"); } } /********************************************************************** * Enumerations * */ const char *Kerning__doc__ = R"""( Kerning modes for `.FT2Font.get_kerning`. For more information, see `the FreeType documentation `_. .. versionadded:: 3.10 )"""; P11X_DECLARE_ENUM( "Kerning", "Enum", {"DEFAULT", FT_KERNING_DEFAULT}, {"UNFITTED", FT_KERNING_UNFITTED}, {"UNSCALED", FT_KERNING_UNSCALED}, ); const char *FaceFlags__doc__ = R"""( Flags returned by `FT2Font.face_flags`. For more information, see `the FreeType documentation `_. .. versionadded:: 3.10 )"""; #ifndef FT_FACE_FLAG_VARIATION // backcompat: ft 2.9.0. #define FT_FACE_FLAG_VARIATION (1L << 15) #endif #ifndef FT_FACE_FLAG_SVG // backcompat: ft 2.12.0. #define FT_FACE_FLAG_SVG (1L << 16) #endif #ifndef FT_FACE_FLAG_SBIX // backcompat: ft 2.12.0. #define FT_FACE_FLAG_SBIX (1L << 17) #endif #ifndef FT_FACE_FLAG_SBIX_OVERLAY // backcompat: ft 2.12.0. #define FT_FACE_FLAG_SBIX_OVERLAY (1L << 18) #endif enum class FaceFlags : FT_Long { #define DECLARE_FLAG(name) name = FT_FACE_FLAG_##name DECLARE_FLAG(SCALABLE), DECLARE_FLAG(FIXED_SIZES), DECLARE_FLAG(FIXED_WIDTH), DECLARE_FLAG(SFNT), DECLARE_FLAG(HORIZONTAL), DECLARE_FLAG(VERTICAL), DECLARE_FLAG(KERNING), DECLARE_FLAG(FAST_GLYPHS), DECLARE_FLAG(MULTIPLE_MASTERS), DECLARE_FLAG(GLYPH_NAMES), DECLARE_FLAG(EXTERNAL_STREAM), DECLARE_FLAG(HINTER), DECLARE_FLAG(CID_KEYED), DECLARE_FLAG(TRICKY), DECLARE_FLAG(COLOR), DECLARE_FLAG(VARIATION), DECLARE_FLAG(SVG), DECLARE_FLAG(SBIX), DECLARE_FLAG(SBIX_OVERLAY), #undef DECLARE_FLAG }; P11X_DECLARE_ENUM( "FaceFlags", "Flag", {"SCALABLE", FaceFlags::SCALABLE}, {"FIXED_SIZES", FaceFlags::FIXED_SIZES}, {"FIXED_WIDTH", FaceFlags::FIXED_WIDTH}, {"SFNT", FaceFlags::SFNT}, {"HORIZONTAL", FaceFlags::HORIZONTAL}, {"VERTICAL", FaceFlags::VERTICAL}, {"KERNING", FaceFlags::KERNING}, {"FAST_GLYPHS", FaceFlags::FAST_GLYPHS}, {"MULTIPLE_MASTERS", FaceFlags::MULTIPLE_MASTERS}, {"GLYPH_NAMES", FaceFlags::GLYPH_NAMES}, {"EXTERNAL_STREAM", FaceFlags::EXTERNAL_STREAM}, {"HINTER", FaceFlags::HINTER}, {"CID_KEYED", FaceFlags::CID_KEYED}, {"TRICKY", FaceFlags::TRICKY}, {"COLOR", FaceFlags::COLOR}, {"VARIATION", FaceFlags::VARIATION}, {"SVG", FaceFlags::SVG}, {"SBIX", FaceFlags::SBIX}, {"SBIX_OVERLAY", FaceFlags::SBIX_OVERLAY}, ); const char *LoadFlags__doc__ = R"""( Flags for `FT2Font.load_char`, `FT2Font.load_glyph`, and `FT2Font.set_text`. For more information, see `the FreeType documentation `_. .. versionadded:: 3.10 )"""; #ifndef FT_LOAD_COMPUTE_METRICS // backcompat: ft 2.6.1. #define FT_LOAD_COMPUTE_METRICS (1L << 21) #endif #ifndef FT_LOAD_BITMAP_METRICS_ONLY // backcompat: ft 2.7.1. #define FT_LOAD_BITMAP_METRICS_ONLY (1L << 22) #endif #ifndef FT_LOAD_NO_SVG // backcompat: ft 2.13.1. #define FT_LOAD_NO_SVG (1L << 24) #endif enum class LoadFlags : FT_Int32 { #define DECLARE_FLAG(name) name = FT_LOAD_##name DECLARE_FLAG(DEFAULT), DECLARE_FLAG(NO_SCALE), DECLARE_FLAG(NO_HINTING), DECLARE_FLAG(RENDER), DECLARE_FLAG(NO_BITMAP), DECLARE_FLAG(VERTICAL_LAYOUT), DECLARE_FLAG(FORCE_AUTOHINT), DECLARE_FLAG(CROP_BITMAP), DECLARE_FLAG(PEDANTIC), DECLARE_FLAG(IGNORE_GLOBAL_ADVANCE_WIDTH), DECLARE_FLAG(NO_RECURSE), DECLARE_FLAG(IGNORE_TRANSFORM), DECLARE_FLAG(MONOCHROME), DECLARE_FLAG(LINEAR_DESIGN), DECLARE_FLAG(NO_AUTOHINT), DECLARE_FLAG(COLOR), DECLARE_FLAG(COMPUTE_METRICS), DECLARE_FLAG(BITMAP_METRICS_ONLY), DECLARE_FLAG(NO_SVG), DECLARE_FLAG(TARGET_NORMAL), DECLARE_FLAG(TARGET_LIGHT), DECLARE_FLAG(TARGET_MONO), DECLARE_FLAG(TARGET_LCD), DECLARE_FLAG(TARGET_LCD_V), #undef DECLARE_FLAG }; P11X_DECLARE_ENUM( "LoadFlags", "Flag", {"DEFAULT", LoadFlags::DEFAULT}, {"NO_SCALE", LoadFlags::NO_SCALE}, {"NO_HINTING", LoadFlags::NO_HINTING}, {"RENDER", LoadFlags::RENDER}, {"NO_BITMAP", LoadFlags::NO_BITMAP}, {"VERTICAL_LAYOUT", LoadFlags::VERTICAL_LAYOUT}, {"FORCE_AUTOHINT", LoadFlags::FORCE_AUTOHINT}, {"CROP_BITMAP", LoadFlags::CROP_BITMAP}, {"PEDANTIC", LoadFlags::PEDANTIC}, {"IGNORE_GLOBAL_ADVANCE_WIDTH", LoadFlags::IGNORE_GLOBAL_ADVANCE_WIDTH}, {"NO_RECURSE", LoadFlags::NO_RECURSE}, {"IGNORE_TRANSFORM", LoadFlags::IGNORE_TRANSFORM}, {"MONOCHROME", LoadFlags::MONOCHROME}, {"LINEAR_DESIGN", LoadFlags::LINEAR_DESIGN}, {"NO_AUTOHINT", LoadFlags::NO_AUTOHINT}, {"COLOR", LoadFlags::COLOR}, {"COMPUTE_METRICS", LoadFlags::COMPUTE_METRICS}, {"BITMAP_METRICS_ONLY", LoadFlags::BITMAP_METRICS_ONLY}, {"NO_SVG", LoadFlags::NO_SVG}, // These must be unique, but the others can be OR'd together; I don't know if // there's any way to really enforce that. {"TARGET_NORMAL", LoadFlags::TARGET_NORMAL}, {"TARGET_LIGHT", LoadFlags::TARGET_LIGHT}, {"TARGET_MONO", LoadFlags::TARGET_MONO}, {"TARGET_LCD", LoadFlags::TARGET_LCD}, {"TARGET_LCD_V", LoadFlags::TARGET_LCD_V}, ); const char *RenderMode__doc__ = R"""( Render modes. For more information, see `the FreeType documentation `_. .. versionadded:: 3.10 )"""; P11X_DECLARE_ENUM( "RenderMode", "Enum", {"NORMAL", FT_RENDER_MODE_NORMAL}, {"LIGHT", FT_RENDER_MODE_LIGHT}, {"MONO", FT_RENDER_MODE_MONO}, {"LCD", FT_RENDER_MODE_LCD}, {"LCD_V", FT_RENDER_MODE_LCD_V}, {"SDF", FT_RENDER_MODE_SDF}, ); const char *StyleFlags__doc__ = R"""( Flags returned by `FT2Font.style_flags`. For more information, see `the FreeType documentation `_. .. versionadded:: 3.10 )"""; enum class StyleFlags : FT_Long { #define DECLARE_FLAG(name) name = FT_STYLE_FLAG_##name NORMAL = 0, DECLARE_FLAG(ITALIC), DECLARE_FLAG(BOLD), #undef DECLARE_FLAG }; P11X_DECLARE_ENUM( "StyleFlags", "Flag", {"NORMAL", StyleFlags::NORMAL}, {"ITALIC", StyleFlags::ITALIC}, {"BOLD", StyleFlags::BOLD}, ); /********************************************************************** * FT2Image * */ const char *PyFT2Image__doc__ = R"""( An image buffer for drawing glyphs. )"""; const char *PyFT2Image_init__doc__ = R"""( Parameters ---------- width, height : int The dimensions of the image buffer. )"""; const char *PyFT2Image_draw_rect_filled__doc__ = R"""( Draw a filled rectangle to the image. Parameters ---------- x0, y0, x1, y1 : float The bounds of the rectangle from (x0, y0) to (x1, y1). )"""; static void PyFT2Image_draw_rect_filled(FT2Image *self, double_or_ vx0, double_or_ vy0, double_or_ vx1, double_or_ vy1) { auto x0 = _double_to_("x0", vx0); auto y0 = _double_to_("y0", vy0); auto x1 = _double_to_("x1", vx1); auto y1 = _double_to_("y1", vy1); self->draw_rect_filled(x0, y0, x1, y1); } /********************************************************************** * Positioned Bitmap; owns the FT_Bitmap! * */ struct PyPositionedBitmap { FT_Int left, top; bool owning; FT_Bitmap bitmap; PyPositionedBitmap(FT_GlyphSlot slot) : left{slot->bitmap_left}, top{slot->bitmap_top}, owning{true} { FT_Bitmap_Init(&bitmap); FT_CHECK(FT_Bitmap_Convert, _ft2Library, &slot->bitmap, &bitmap, 1); } PyPositionedBitmap(FT_BitmapGlyph bg) : left{bg->left}, top{bg->top}, owning{true} { FT_Bitmap_Init(&bitmap); FT_CHECK(FT_Bitmap_Convert, _ft2Library, &bg->bitmap, &bitmap, 1); } PyPositionedBitmap(PyPositionedBitmap& other) = delete; // Non-copyable. PyPositionedBitmap(PyPositionedBitmap&& other) : left{other.left}, top{other.top}, owning{true}, bitmap{other.bitmap} { other.owning = false; // Prevent double deletion. } ~PyPositionedBitmap() { if (owning) { FT_Bitmap_Done(_ft2Library, &bitmap); } } }; /********************************************************************** * Glyph * */ typedef struct { size_t glyphInd; long width; long height; long horiBearingX; long horiBearingY; long horiAdvance; long linearHoriAdvance; long vertBearingX; long vertBearingY; long vertAdvance; FT_BBox bbox; } PyGlyph; const char *PyGlyph__doc__ = R"""( Information about a single glyph. You cannot create instances of this object yourself, but must use `.FT2Font.load_char` or `.FT2Font.load_glyph` to generate one. This object may be used in a call to `.FT2Font.draw_glyph_to_bitmap`. For more information on the various metrics, see `the FreeType documentation `_. )"""; static PyGlyph * PyGlyph_from_FT2Font(const FT2Font *font) { const FT_Face &face = font->get_face(); const FT_Glyph &glyph = font->get_last_glyph(); PyGlyph *self = new PyGlyph(); self->glyphInd = font->get_last_glyph_index(); FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_subpixels, &self->bbox); self->width = face->glyph->metrics.width; self->height = face->glyph->metrics.height; self->horiBearingX = face->glyph->metrics.horiBearingX; self->horiBearingY = face->glyph->metrics.horiBearingY; self->horiAdvance = face->glyph->metrics.horiAdvance; self->linearHoriAdvance = face->glyph->linearHoriAdvance; self->vertBearingX = face->glyph->metrics.vertBearingX; self->vertBearingY = face->glyph->metrics.vertBearingY; self->vertAdvance = face->glyph->metrics.vertAdvance; return self; } static py::tuple PyGlyph_get_bbox(PyGlyph *self) { return py::make_tuple(self->bbox.xMin, self->bbox.yMin, self->bbox.xMax, self->bbox.yMax); } /********************************************************************** * FT2Font * */ class PyFT2Font final : public FT2Font { public: using FT2Font::FT2Font; py::object py_file; FT_StreamRec stream; py::list fallbacks; ~PyFT2Font() { // Because destructors are called from subclass up to base class, we need to // explicitly close the font here. Otherwise, the instance attributes here will // be destroyed before the font itself, but those are used in the close callback. close(); } void ft_glyph_warn(FT_ULong charcode, std::set family_names) { std::set::iterator it = family_names.begin(); std::stringstream ss; ss<<*it; while(++it != family_names.end()){ ss<<", "<<*it; } 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__doc__ = R"""( An object representing a single font face. Outside of the font itself and querying its properties, this object provides methods for processing text strings into glyph shapes. Commonly, one will use `FT2Font.set_text` to load some glyph metrics and outlines. Then `FT2Font.draw_glyphs_to_bitmap` and `FT2Font.get_image` may be used to get a rendered form of the loaded string. For single characters, `FT2Font.load_char` or `FT2Font.load_glyph` may be used, either directly for their return values, or to use `FT2Font.draw_glyph_to_bitmap` or `FT2Font.get_path`. Useful metrics may be examined via the `Glyph` return values or `FT2Font.get_kerning`. Most dimensions are given in 26.6 or 16.6 fixed-point integers representing subpixels. Divide these values by 64 to produce floating-point pixels. )"""; static unsigned long read_from_file_callback(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count) { PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; Py_ssize_t n_read = 0; 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. } } return (unsigned long)n_read; } static void close_file_callback(FT_Stream stream) { PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); PyFT2Font *self = (PyFT2Font *)stream->descriptor.pointer; 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); } const char *PyFT2Font_init__doc__ = R"""( Parameters ---------- filename : str, bytes, os.PathLike, or io.BinaryIO The source of the font data in a format (ttf or ttc) that FreeType can read. face_index : int, optional The index of the face in the font file to load. _fallback_list : list of FT2Font, optional A list of FT2Font objects used to find missing glyphs. .. warning:: This API is both private and provisional: do not use it directly. _warn_if_used : bool, optional Used to trigger missing glyph warnings. .. warning:: This API is private: do not use it directly. )"""; static PyFT2Font * PyFT2Font_init(py::object filename, std::optional hinting_factor = std::nullopt, FT_Long face_index = 0, std::optional> fallback_list = std::nullopt, std::optional kerning_factor = std::nullopt, bool warn_if_used = false) { if (hinting_factor) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.11", "name"_a="hinting_factor", "obj_type"_a="parameter"); } if (kerning_factor) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.11", "name"_a="_kerning_factor", "obj_type"_a="parameter"); } else { kerning_factor = 0; } if (face_index < 0 || face_index > 0xffff) { throw std::range_error("face_index must be between 0 and 65535, inclusive"); } std::vector fallback_fonts; if (fallback_list) { // go through fallbacks to add them to our lists std::copy(fallback_list->begin(), fallback_list->end(), std::back_inserter(fallback_fonts)); } auto self = new PyFT2Font(fallback_fonts, warn_if_used); self->set_kerning_factor(*kerning_factor); if (fallback_list) { // go through fallbacks to add them to our lists for (auto item : *fallback_list) { self->fallbacks.append(item); } } memset(&self->stream, 0, sizeof(FT_StreamRec)); self->stream.base = nullptr; 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; auto PathLike = py::module_::import("os").attr("PathLike"); if (py::isinstance(filename) || py::isinstance(filename) || py::isinstance(filename, PathLike)) { self->py_file = py::module_::import("io").attr("open")(filename, "rb"); self->stream.close = &close_file_callback; } 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 = nullptr; } self->open(open_args, face_index); return self; } static py::object PyFT2Font_fname(PyFT2Font *self) { if (self->stream.close) { // User passed a filename to the constructor. return self->py_file.attr("name"); } else { return self->py_file; } } const char *PyFT2Font_clear__doc__ = "Clear all the glyphs, reset for a new call to `.set_text`."; const char *PyFT2Font_set_size__doc__ = R"""( Set the size of the text. Parameters ---------- ptsize : float The size of the text in points. dpi : float The DPI used for rendering the text. )"""; const char *PyFT2Font__set_transform__doc__ = R"""( Set the transform of the text. This is a low-level function, where *matrix* and *delta* are directly in 16.16 and 26.6 formats respectively. Refer to the FreeType docs of FT_Set_Transform for further description. Note, every call to `.font_manager.get_font` will reset the transform to the default to ensure consistency across cache accesses. Parameters ---------- matrix : (2, 2) array of int delta : (2,) array of int )"""; const char *PyFT2Font_set_charmap__doc__ = R"""( Make the i-th charmap current. For more details on character mapping, see the `FreeType documentation `_. Parameters ---------- i : int The charmap number in the range [0, `.num_charmaps`). See Also -------- .num_charmaps .select_charmap .get_charmap )"""; const char *PyFT2Font_select_charmap__doc__ = R"""( Select a charmap by its FT_Encoding number. For more details on character mapping, see the `FreeType documentation `_. Parameters ---------- i : int The charmap in the form defined by FreeType: https://freetype.org/freetype2/docs/reference/ft2-character_mapping.html#ft_encoding See Also -------- .set_charmap .get_charmap )"""; const char *PyFT2Font_get_kerning__doc__ = R"""( Get the kerning between two glyphs. Parameters ---------- left, right : int The glyph indices. Note these are not characters nor character codes. Use `.get_char_index` to convert character codes to glyph indices. mode : Kerning A kerning mode constant: - ``DEFAULT`` - Return scaled and grid-fitted kerning distances. - ``UNFITTED`` - Return scaled but un-grid-fitted kerning distances. - ``UNSCALED`` - Return the kerning vector in original font units. .. versionchanged:: 3.10 This now takes a `.ft2font.Kerning` value instead of an `int`. Returns ------- int The kerning adjustment between the two glyphs. )"""; static int PyFT2Font_get_kerning(PyFT2Font *self, FT_UInt left, FT_UInt right, std::variant mode_or_int) { FT_Kerning_Mode mode; if (auto value = std::get_if(&mode_or_int)) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.10", "name"_a="mode", "obj_type"_a="parameter as int", "alternative"_a="Kerning enum values"); mode = static_cast(*value); } else if (auto value = std::get_if(&mode_or_int)) { mode = *value; } else { // NOTE: this can never happen as pybind11 would have checked the type in the // Python wrapper before calling this function, but we need to keep the // std::get_if instead of std::get for macOS 10.12 compatibility. throw py::type_error("mode must be Kerning or int"); } return self->get_kerning(left, right, mode); } const char *PyFT2Font_set_text__doc__ = R"""( Set the text *string* and *angle*. You must call this before `.draw_glyphs_to_bitmap`. Parameters ---------- string : str The text to prepare rendering information for. angle : float The angle at which to render the supplied text. flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` Any bitwise-OR combination of the `.LoadFlags` flags. .. versionchanged:: 3.10 This now takes an `.ft2font.LoadFlags` instead of an int. features : tuple[str, ...] The font feature tags to use for the font. Available font feature tags may be found at https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist .. versionadded:: 3.11 Returns ------- np.ndarray[double] A sequence of x,y glyph positions in 26.6 subpixels; divide by 64 for pixels. )"""; static py::array_t PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0, std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT, std::optional> features = std::nullopt, std::variant languages_or_str = nullptr) { std::vector xys; LoadFlags flags; if (auto value = std::get_if(&flags_or_int)) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.10", "name"_a="flags", "obj_type"_a="parameter as int", "alternative"_a="LoadFlags enum values"); flags = static_cast(*value); } else if (auto value = std::get_if(&flags_or_int)) { flags = *value; } else { // NOTE: this can never happen as pybind11 would have checked the type in the // Python wrapper before calling this function, but we need to keep the // std::get_if instead of std::get for macOS 10.12 compatibility. throw py::type_error("flags must be LoadFlags or int"); } FT2Font::LanguageType languages; if (auto value = std::get_if(&languages_or_str)) { languages = std::move(*value); } else if (auto value = std::get_if(&languages_or_str)) { languages = std::vector{ FT2Font::LanguageRange{*value, 0, text.size()} }; } else { // NOTE: this can never happen as pybind11 would have checked the type in the // Python wrapper before calling this function, but we need to keep the // std::get_if instead of std::get for macOS 10.12 compatibility. throw py::type_error("languages must be str or list of tuple"); } self->set_text(text, angle, static_cast(flags), features, languages, xys); 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()); } return result; } const char *PyFT2Font_get_num_glyphs__doc__ = "Return the number of loaded glyphs."; const char *PyFT2Font_load_char__doc__ = R"""( Load character in current fontfile and set glyph. Parameters ---------- charcode : int The character code to prepare rendering information for. This code must be in the charmap, or else a ``.notdef`` glyph may be returned instead. flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` Any bitwise-OR combination of the `.LoadFlags` flags. .. versionchanged:: 3.10 This now takes an `.ft2font.LoadFlags` instead of an int. Returns ------- Glyph The glyph information corresponding to the specified character. See Also -------- .load_glyph .select_charmap .set_charmap )"""; static PyGlyph * PyFT2Font_load_char(PyFT2Font *self, long charcode, std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { bool fallback = true; FT2Font *ft_object = nullptr; LoadFlags flags; if (auto value = std::get_if(&flags_or_int)) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.10", "name"_a="flags", "obj_type"_a="parameter as int", "alternative"_a="LoadFlags enum values"); flags = static_cast(*value); } else if (auto value = std::get_if(&flags_or_int)) { flags = *value; } else { // NOTE: this can never happen as pybind11 would have checked the type in the // Python wrapper before calling this function, but we need to keep the // std::get_if instead of std::get for macOS 10.12 compatibility. throw py::type_error("flags must be LoadFlags or int"); } self->load_char(charcode, static_cast(flags), ft_object, fallback); return PyGlyph_from_FT2Font(ft_object); } const char *PyFT2Font_load_glyph__doc__ = R"""( Load glyph index in current fontfile and set glyph. Note that the glyph index is specific to a font, and not universal like a Unicode code point. Parameters ---------- glyph_index : int The glyph index to prepare rendering information for. flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` Any bitwise-OR combination of the `.LoadFlags` flags. .. versionchanged:: 3.10 This now takes an `.ft2font.LoadFlags` instead of an int. Returns ------- Glyph The glyph information corresponding to the specified index. See Also -------- .load_char )"""; static PyGlyph * PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index, std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { LoadFlags flags; if (auto value = std::get_if(&flags_or_int)) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); warn("since"_a="3.10", "name"_a="flags", "obj_type"_a="parameter as int", "alternative"_a="LoadFlags enum values"); flags = static_cast(*value); } else if (auto value = std::get_if(&flags_or_int)) { flags = *value; } else { // NOTE: this can never happen as pybind11 would have checked the type in the // Python wrapper before calling this function, but we need to keep the // std::get_if instead of std::get for macOS 10.12 compatibility. throw py::type_error("flags must be LoadFlags or int"); } self->load_glyph(glyph_index, static_cast(flags)); return PyGlyph_from_FT2Font(self); } const char *PyFT2Font_get_width_height__doc__ = R"""( Get the dimensions of the current string set by `.set_text`. The rotation of the string is accounted for. Returns ------- width, height : float The width and height in 26.6 subpixels of the current string. To get width and height in pixels, divide these values by 64. See Also -------- .get_bitmap_offset .get_descent )"""; const char *PyFT2Font_get_bitmap_offset__doc__ = R"""( Get the (x, y) offset for the bitmap if ink hangs left or below (0, 0). Since Matplotlib only supports left-to-right text, y is always 0. Returns ------- x, y : float The x and y offset in 26.6 subpixels of the bitmap. To get x and y in pixels, divide these values by 64. See Also -------- .get_width_height .get_descent )"""; const char *PyFT2Font_get_descent__doc__ = R"""( Get the descent of the current string set by `.set_text`. The rotation of the string is accounted for. Returns ------- int The descent in 26.6 subpixels of the bitmap. To get the descent in pixels, divide these values by 64. See Also -------- .get_bitmap_offset .get_width_height )"""; const char *PyFT2Font_draw_glyphs_to_bitmap__doc__ = R"""( Draw the glyphs that were loaded by `.set_text` to the bitmap. The bitmap size will be automatically set to include the glyphs. Parameters ---------- antialiased : bool, default: True Whether to render glyphs 8-bit antialiased or in pure black-and-white. See Also -------- .draw_glyph_to_bitmap )"""; const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""( Draw a single glyph to the bitmap at pixel locations x, y. Note it is your responsibility to create the image manually with the correct size before this call is made. If you want automatic layout, use `.set_text` in combinations with `.draw_glyphs_to_bitmap`. This function is instead intended for people who want to render individual glyphs (e.g., returned by `.load_char`) at precise locations. Parameters ---------- image : 2d array of uint8 The image buffer on which to draw the glyph. x, y : int The position of the glyph's top left corner. glyph : Glyph The glyph to draw. antialiased : bool, default: True Whether to render glyphs 8-bit antialiased or in pure black-and-white. See Also -------- .draw_glyphs_to_bitmap )"""; static void PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image, double_or_ vxd, double_or_ vyd, PyGlyph *glyph, bool antialiased = true) { auto xd = _double_to_("x", vxd); auto yd = _double_to_("y", vyd); self->draw_glyph_to_bitmap( py::array_t{image}, xd, yd, glyph->glyphInd, antialiased); } const char *PyFT2Font_get_glyph_name__doc__ = R"""( Retrieve the ASCII name of a given glyph *index* in a face. Due to Matplotlib's internal design, for fonts that do not contain glyph names (per ``FT_FACE_FLAG_GLYPH_NAMES``), this returns a made-up name which does *not* roundtrip through `.get_name_index`. Parameters ---------- index : int The glyph number to query. Returns ------- str The name of the glyph, or if the font does not contain names, a name synthesized by Matplotlib. See Also -------- .get_name_index )"""; const char *PyFT2Font_get_charmap__doc__ = R"""( Return a mapping of character codes to glyph indices in the font. The charmap is Unicode by default, but may be changed by `.set_charmap` or `.select_charmap`. Returns ------- dict[int, int] A dictionary of the selected charmap mapping character codes to their corresponding glyph indices. )"""; static py::dict PyFT2Font_get_charmap(PyFT2Font *self) { py::dict charmap; FT_UInt index; FT_ULong code = FT_Get_First_Char(self->get_face(), &index); while (index != 0) { charmap[py::cast(code)] = py::cast(index); code = FT_Get_Next_Char(self->get_face(), code, &index); } return charmap; } const char *PyFT2Font_get_char_index__doc__ = R"""( Return the glyph index corresponding to a character code point. Parameters ---------- codepoint : int A character code point in the current charmap (which defaults to Unicode.) _fallback : bool Whether to enable fallback fonts while searching for a character. Returns ------- int The corresponding glyph index. See Also -------- .set_charmap .select_charmap .get_glyph_name .get_name_index )"""; const char *PyFT2Font_get_sfnt__doc__ = R"""( Load the entire SFNT names table. Returns ------- dict[tuple[int, int, int, int], bytes] The SFNT names table; the dictionary keys are tuples of: (platform-ID, ISO-encoding-scheme, language-code, description) and the values are the direct information from the font table. )"""; static py::dict PyFT2Font_get_sfnt(PyFT2Font *self) { if (!(self->get_face()->face_flags & FT_FACE_FLAG_SFNT)) { throw py::value_error("No SFNT name table"); } size_t count = FT_Get_Sfnt_Name_Count(self->get_face()); py::dict names; for (FT_UInt j = 0; j < count; ++j) { FT_SfntName sfnt; FT_Error error = FT_Get_Sfnt_Name(self->get_face(), j, &sfnt); if (error) { throw py::value_error("Could not get SFNT name"); } 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__ = R"""( Return the glyph index of a given glyph *name*. Parameters ---------- name : str The name of the glyph to query. Returns ------- int The corresponding glyph index; 0 means 'undefined character code'. See Also -------- .get_char_index .get_glyph_name )"""; const char *PyFT2Font_get_ps_font_info__doc__ = R"""( Return the information in the PS Font Info structure. For more information, see the `FreeType documentation on this structure `_. Returns ------- version : str notice : str full_name : str family_name : str weight : str italic_angle : int is_fixed_pitch : bool underline_position : int underline_thickness : int )"""; static py::tuple PyFT2Font_get_ps_font_info(PyFT2Font *self) { PS_FontInfoRec fontinfo; FT_Error error = FT_Get_PS_Font_Info(self->get_face(), &fontinfo); if (error) { throw py::value_error("Could not get PS font info"); } 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__ = R"""( Return one of the SFNT tables. Parameters ---------- name : {"head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt"} Which table to return. Returns ------- dict[str, Any] The corresponding table; for more information, see `the FreeType documentation `_. )"""; 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}, }; try { tag = names.at(tagname); } catch (const std::out_of_range&) { return std::nullopt; } void *table = FT_Get_Sfnt_Table(self->get_face(), tag); if (!table) { return std::nullopt; } switch (tag) { 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 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 FT_SFNT_OS2: { auto t = static_cast(table); auto version = t->version; auto result = py::dict( "version"_a=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), "ulUnicodeRange"_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, "usFirstCharIndex"_a=t->usFirstCharIndex, "usLastCharIndex"_a=t->usLastCharIndex, "sTypoAscender"_a=t->sTypoAscender, "sTypoDescender"_a=t->sTypoDescender, "sTypoLineGap"_a=t->sTypoLineGap, "usWinAscent"_a=t->usWinAscent, "usWinDescent"_a=t->usWinDescent); if (version >= 1) { result["ulCodePageRange"] = py::make_tuple(t->ulCodePageRange1, t->ulCodePageRange2); } if (version >= 2) { result["sxHeight"] = t->sxHeight; result["sCapHeight"] = t->sCapHeight; result["usDefaultChar"] = t->usDefaultChar; result["usBreakChar"] = t->usBreakChar; result["usMaxContext"] = t->usMaxContext; } if (version >= 5) { result["usLowerOpticalPointSize"] = t->usLowerOpticalPointSize; result["usUpperOpticalPointSize"] = t->usUpperOpticalPointSize; } return result; } 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 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, "minBottomSideBearing"_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 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 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: return std::nullopt; } } const char *PyFT2Font_get_path__doc__ = R"""( Get the path data from the currently loaded glyph. Returns ------- vertices : np.ndarray[double] The (N, 2) array of vertices describing the current glyph. codes : np.ndarray[np.uint8] The (N, ) array of codes corresponding to the vertices. See Also -------- .get_image .load_char .load_glyph .set_text )"""; static py::tuple PyFT2Font_get_path(PyFT2Font *self) { std::vector vertices; std::vector codes; self->get_path(vertices, codes); 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::make_tuple(vertices_arr, codes_arr); } const char *PyFT2Font_get_image__doc__ = R"""( Return the underlying image buffer for this font object. Returns ------- np.ndarray[int] See Also -------- .get_path )"""; const char *PyFT2Font__get_type1_encoding_vector__doc__ = R"""( Return a list mapping CharString indices of a Type 1 font to FreeType glyph indices. Returns ------- list[int] )"""; static std::array PyFT2Font__get_type1_encoding_vector(PyFT2Font *self) { auto face = self->get_face(); auto indices = std::array{}; for (auto i = 0u; i < indices.size(); ++i) { auto len = FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, nullptr, 0); if (len == -1) { // Explicitly ignore missing entries (mapped to glyph 0 = .notdef). continue; } auto buf = std::make_unique(len); FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, buf.get(), len); indices[i] = FT_Get_Name_Index(face, buf.get()); } return indices; } /********************************************************************** * Layout items * */ struct LayoutItem { PyFT2Font *ft_object; std::u32string character; int glyph_index; double x; double y; double prev_kern; LayoutItem(PyFT2Font *f, std::u32string c, int i, double x, double y, double k) : ft_object(f), character(c), glyph_index(i), x(x), y(y), prev_kern(k) {} }; const char *PyFT2Font_layout__doc__ = R"""( Layout a string and yield information about each used glyph. .. warning:: This API uses the fallback list and is both private and provisional: do not use it directly. .. versionadded:: 3.11 Parameters ---------- text : str The characters for which to find fonts. flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT` Any bitwise-OR combination of the `.LoadFlags` flags. features : tuple[str, ...], optional The font feature tags to use for the font. Available font feature tags may be found at https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist language : str, optional The language of the text in a format accepted by libraqm, namely `a BCP47 language code `_. Returns ------- list[LayoutItem] )"""; static auto PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags, std::optional> features = std::nullopt, std::variant languages_or_str = nullptr) { const auto load_flags = static_cast(flags); FT2Font::LanguageType languages; if (auto value = std::get_if(&languages_or_str)) { languages = std::move(*value); } else if (auto value = std::get_if(&languages_or_str)) { languages = std::vector{ FT2Font::LanguageRange{*value, 0, text.size()} }; } else { // NOTE: this can never happen as pybind11 would have checked the type in the // Python wrapper before calling this function, but we need to keep the // std::get_if instead of std::get for macOS 10.12 compatibility. throw py::type_error("languages must be str or list of tuple"); } std::set glyph_seen_fonts; auto glyphs = self->layout(text, load_flags, features, languages, glyph_seen_fonts); std::set clusters; for (auto &glyph : glyphs) { clusters.emplace(glyph.cluster); } std::vector items; double x = 0.0; double y = 0.0; std::optional prev_advance = std::nullopt; double prev_x = 0.0; for (auto &glyph : glyphs) { auto ft_object = static_cast(glyph.ftface->generic.data); ft_object->load_glyph(glyph.index, load_flags); double prev_kern = 0.0; if (prev_advance) { double actual_advance = (x + glyph.x_offset) - prev_x; prev_kern = actual_advance - *prev_advance; } auto next = clusters.upper_bound(glyph.cluster); auto end = (next != clusters.end()) ? *next : text.size(); auto substr = text.substr(glyph.cluster, end - glyph.cluster); items.emplace_back(ft_object, substr, glyph.index, (x + glyph.x_offset) / 64.0, (y + glyph.y_offset) / 64.0, prev_kern / 64.0); prev_x = x + glyph.x_offset; x += glyph.x_advance; y += glyph.y_advance; // Note, linearHoriAdvance is a 16.16 instead of 26.6 fixed-point value. prev_advance = ft_object->get_face()->glyph->linearHoriAdvance / 1024.0; } return items; } /********************************************************************** * Deprecations * */ static py::object ft2font__getattr__(std::string name) { auto api = py::module_::import("matplotlib._api"); auto warn = api.attr("warn_deprecated"); #define DEPRECATE_ATTR_FROM_ENUM(attr_, alternative_, real_value_) \ do { \ if (name == #attr_) { \ warn("since"_a="3.10", "name"_a=#attr_, "obj_type"_a="attribute", \ "alternative"_a=#alternative_); \ return py::cast(static_cast(real_value_)); \ } \ } while(0) DEPRECATE_ATTR_FROM_ENUM(KERNING_DEFAULT, Kerning.DEFAULT, FT_KERNING_DEFAULT); DEPRECATE_ATTR_FROM_ENUM(KERNING_UNFITTED, Kerning.UNFITTED, FT_KERNING_UNFITTED); DEPRECATE_ATTR_FROM_ENUM(KERNING_UNSCALED, Kerning.UNSCALED, FT_KERNING_UNSCALED); #undef DEPRECATE_ATTR_FROM_ENUM #define DEPRECATE_ATTR_FROM_FLAG(attr_, enum_, value_) \ do { \ if (name == #attr_) { \ warn("since"_a="3.10", "name"_a=#attr_, "obj_type"_a="attribute", \ "alternative"_a=#enum_ "." #value_); \ return py::cast(enum_::value_); \ } \ } while(0) DEPRECATE_ATTR_FROM_FLAG(LOAD_DEFAULT, LoadFlags, DEFAULT); DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_SCALE, LoadFlags, NO_SCALE); DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_HINTING, LoadFlags, NO_HINTING); DEPRECATE_ATTR_FROM_FLAG(LOAD_RENDER, LoadFlags, RENDER); DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_BITMAP, LoadFlags, NO_BITMAP); DEPRECATE_ATTR_FROM_FLAG(LOAD_VERTICAL_LAYOUT, LoadFlags, VERTICAL_LAYOUT); DEPRECATE_ATTR_FROM_FLAG(LOAD_FORCE_AUTOHINT, LoadFlags, FORCE_AUTOHINT); DEPRECATE_ATTR_FROM_FLAG(LOAD_CROP_BITMAP, LoadFlags, CROP_BITMAP); DEPRECATE_ATTR_FROM_FLAG(LOAD_PEDANTIC, LoadFlags, PEDANTIC); DEPRECATE_ATTR_FROM_FLAG(LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, LoadFlags, IGNORE_GLOBAL_ADVANCE_WIDTH); DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_RECURSE, LoadFlags, NO_RECURSE); DEPRECATE_ATTR_FROM_FLAG(LOAD_IGNORE_TRANSFORM, LoadFlags, IGNORE_TRANSFORM); DEPRECATE_ATTR_FROM_FLAG(LOAD_MONOCHROME, LoadFlags, MONOCHROME); DEPRECATE_ATTR_FROM_FLAG(LOAD_LINEAR_DESIGN, LoadFlags, LINEAR_DESIGN); DEPRECATE_ATTR_FROM_FLAG(LOAD_NO_AUTOHINT, LoadFlags, NO_AUTOHINT); DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_NORMAL, LoadFlags, TARGET_NORMAL); DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_LIGHT, LoadFlags, TARGET_LIGHT); DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_MONO, LoadFlags, TARGET_MONO); DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_LCD, LoadFlags, TARGET_LCD); DEPRECATE_ATTR_FROM_FLAG(LOAD_TARGET_LCD_V, LoadFlags, TARGET_LCD_V); DEPRECATE_ATTR_FROM_FLAG(SCALABLE, FaceFlags, SCALABLE); DEPRECATE_ATTR_FROM_FLAG(FIXED_SIZES, FaceFlags, FIXED_SIZES); DEPRECATE_ATTR_FROM_FLAG(FIXED_WIDTH, FaceFlags, FIXED_WIDTH); DEPRECATE_ATTR_FROM_FLAG(SFNT, FaceFlags, SFNT); DEPRECATE_ATTR_FROM_FLAG(HORIZONTAL, FaceFlags, HORIZONTAL); DEPRECATE_ATTR_FROM_FLAG(VERTICAL, FaceFlags, VERTICAL); DEPRECATE_ATTR_FROM_FLAG(KERNING, FaceFlags, KERNING); DEPRECATE_ATTR_FROM_FLAG(FAST_GLYPHS, FaceFlags, FAST_GLYPHS); DEPRECATE_ATTR_FROM_FLAG(MULTIPLE_MASTERS, FaceFlags, MULTIPLE_MASTERS); DEPRECATE_ATTR_FROM_FLAG(GLYPH_NAMES, FaceFlags, GLYPH_NAMES); DEPRECATE_ATTR_FROM_FLAG(EXTERNAL_STREAM, FaceFlags, EXTERNAL_STREAM); DEPRECATE_ATTR_FROM_FLAG(ITALIC, StyleFlags, ITALIC); DEPRECATE_ATTR_FROM_FLAG(BOLD, StyleFlags, BOLD); #undef DEPRECATE_ATTR_FROM_FLAG throw py::attribute_error( "module 'matplotlib.ft2font' has no attribute {!r}"_s.format(name)); } PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) { if (FT_Init_FreeType(&_ft2Library)) { // initialize 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); p11x::bind_enums(m); p11x::enums["Kerning"].attr("__doc__") = Kerning__doc__; p11x::enums["LoadFlags"].attr("__doc__") = LoadFlags__doc__; p11x::enums["RenderMode"].attr("__doc__") = RenderMode__doc__; p11x::enums["FaceFlags"].attr("__doc__") = FaceFlags__doc__; p11x::enums["StyleFlags"].attr("__doc__") = StyleFlags__doc__; py::class_(m, "FT2Image", py::is_final(), py::buffer_protocol(), PyFT2Image__doc__) .def(py::init( [](double_or_ width, double_or_ height) { auto warn = py::module_::import("matplotlib._api").attr("warn_deprecated"); warn("since"_a="3.11", "name"_a="FT2Image", "obj_type"_a="class", "alternative"_a="a 2D uint8 ndarray"); return new FT2Image( _double_to_("width", width), _double_to_("height", height) ); }), "width"_a, "height"_a, PyFT2Image_init__doc__) .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, "_PositionedBitmap", py::is_final()) .def_readonly("left", &PyPositionedBitmap::left) .def_readonly("top", &PyPositionedBitmap::top) .def_property_readonly( "buffer", [](PyPositionedBitmap &self) -> py::array { return {{self.bitmap.rows, self.bitmap.width}, {self.bitmap.pitch, 1}, self.bitmap.buffer}; }) ; py::class_(m, "Glyph", py::is_final(), PyGlyph__doc__) .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, "The glyph's width.") .def_readonly("height", &PyGlyph::height, "The glyph's height.") .def_readonly("horiBearingX", &PyGlyph::horiBearingX, "Left side bearing for horizontal layout.") .def_readonly("horiBearingY", &PyGlyph::horiBearingY, "Top side bearing for horizontal layout.") .def_readonly("horiAdvance", &PyGlyph::horiAdvance, "Advance width for horizontal layout.") .def_readonly("linearHoriAdvance", &PyGlyph::linearHoriAdvance, "The advance width of the unhinted glyph.") .def_readonly("vertBearingX", &PyGlyph::vertBearingX, "Left side bearing for vertical layout.") .def_readonly("vertBearingY", &PyGlyph::vertBearingY, "Top side bearing for vertical layout.") .def_readonly("vertAdvance", &PyGlyph::vertAdvance, "Advance height for vertical layout.") .def_property_readonly("bbox", &PyGlyph_get_bbox, "The control box of the glyph."); py::class_(m, "LayoutItem", py::is_final()) .def(py::init<>([]() -> LayoutItem { // LayoutItem is not useful from Python, so mark it as not constructible. throw std::runtime_error("LayoutItem is not constructible"); })) .def_readonly("ft_object", &LayoutItem::ft_object, "The FT_Face of the item.") .def_readonly("char", &LayoutItem::character, "The character code for the item.") .def_readonly("glyph_index", &LayoutItem::glyph_index, "The glyph index for the item.") .def_readonly("x", &LayoutItem::x, "The x position of the item.") .def_readonly("y", &LayoutItem::y, "The y position of the item.") .def_readonly("prev_kern", &LayoutItem::prev_kern, "The kerning between this item and the previous one.") .def("__str__", [](const LayoutItem& item) { return "LayoutItem(ft_object={}, char={!r}, glyph_index={}, "_s "x={}, y={}, prev_kern={})"_s.format( PyFT2Font_fname(item.ft_object), item.character, item.glyph_index, item.x, item.y, item.prev_kern); }); auto cls = py::class_(m, "FT2Font", py::is_final(), py::buffer_protocol(), PyFT2Font__doc__) .def(py::init(&PyFT2Font_init), "filename"_a, "hinting_factor"_a=py::none(), py::kw_only(), "face_index"_a=0, "_fallback_list"_a=py::none(), "_kerning_factor"_a=py::none(), "_warn_if_used"_a=false, 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_transform", &PyFT2Font::_set_transform, "matrix"_a, "delta"_a, PyFT2Font__set_transform__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("_layout", &PyFT2Font_layout, "string"_a, "flags"_a, py::kw_only(), "features"_a=nullptr, "language"_a=nullptr, PyFT2Font_layout__doc__) .def("set_text", &PyFT2Font_set_text, "string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT, py::kw_only(), "features"_a=nullptr, "language"_a=nullptr, PyFT2Font_set_text__doc__) .def("get_num_glyphs", &PyFT2Font::get_num_glyphs, PyFT2Font_get_num_glyphs__doc__) .def("load_char", &PyFT2Font_load_char, "charcode"_a, "flags"_a=LoadFlags::FORCE_AUTOHINT, PyFT2Font_load_char__doc__) .def("load_glyph", &PyFT2Font_load_glyph, "glyph_index"_a, "flags"_a=LoadFlags::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__); // The generated docstring uses an unqualified "Buffer" as type hint, // which causes an error in sphinx. This is fixed as of pybind11 // master (since #5566) which now uses "collections.abc.Buffer"; // restore the signature once that version is released. { py::options options{}; options.disable_function_signatures(); cls .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__); } cls .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, py::kw_only(), "_fallback"_a=true, 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("_get_type1_encoding_vector", &PyFT2Font__get_type1_encoding_vector, PyFT2Font__get_type1_encoding_vector__doc__) .def_property_readonly( "postscript_name", [](PyFT2Font *self) { if (const char *name = FT_Get_Postscript_Name(self->get_face())) { return name; } else { return "UNAVAILABLE"; } }, "PostScript name of the font.") .def_property_readonly( "num_faces", [](PyFT2Font *self) { return self->get_face()->num_faces & 0xffff; }, "Number of faces in file.") .def_property_readonly( "face_index", [](PyFT2Font *self) { return self->get_face()->face_index; }, "The index of the font in the file.") .def_property_readonly( "family_name", [](PyFT2Font *self) { if (const char *name = self->get_face()->family_name) { return name; } else { return "UNAVAILABLE"; } }, "Face family name.") .def_property_readonly( "style_name", [](PyFT2Font *self) { if (const char *name = self->get_face()->style_name) { return name; } else { return "UNAVAILABLE"; } }, "Style name.") .def_property_readonly( "face_flags", [](PyFT2Font *self) { return static_cast(self->get_face()->face_flags); }, "Face flags; see `.FaceFlags`.") .def_property_readonly( "style_flags", [](PyFT2Font *self) { return static_cast(self->get_face()->style_flags & 0xffff); }, "Style flags; see `.StyleFlags`.") .def_property_readonly( "num_named_instances", [](PyFT2Font *self) { return (self->get_face()->style_flags & 0x7fff0000) >> 16; }, "Number of named instances in the face.") .def_property_readonly( "num_glyphs", [](PyFT2Font *self) { return self->get_face()->num_glyphs; }, "Number of glyphs in the face.") .def_property_readonly( "num_fixed_sizes", [](PyFT2Font *self) { return self->get_face()->num_fixed_sizes; }, "Number of bitmap in the face.") .def_property_readonly( "num_charmaps", [](PyFT2Font *self) { return self->get_face()->num_charmaps; }, "Number of charmaps in the face.") .def_property_readonly( "scalable", [](PyFT2Font *self) { return bool(FT_IS_SCALABLE(self->get_face())); }, "Whether face is scalable; attributes after this one " "are only defined for scalable faces.") .def_property_readonly( "units_per_EM", [](PyFT2Font *self) { return self->get_face()->units_per_EM; }, "Number of font units covered by the EM.") .def_property_readonly( "bbox", [](PyFT2Font *self) { FT_BBox bbox = self->get_face()->bbox; return py::make_tuple(bbox.xMin, bbox.yMin, bbox.xMax, bbox.yMax); }, "Face global bounding box (xmin, ymin, xmax, ymax).") .def_property_readonly( "ascender", [](PyFT2Font *self) { return self->get_face()->ascender; }, "Ascender in 26.6 units.") .def_property_readonly( "descender", [](PyFT2Font *self) { return self->get_face()->descender; }, "Descender in 26.6 units.") .def_property_readonly( "height", [](PyFT2Font *self) { return self->get_face()->height; }, "Height in 26.6 units; used to compute a default line spacing " "(baseline-to-baseline distance).") .def_property_readonly( "max_advance_width", [](PyFT2Font *self) { return self->get_face()->max_advance_width; }, "Maximum horizontal cursor advance for all glyphs.") .def_property_readonly( "max_advance_height", [](PyFT2Font *self) { return self->get_face()->max_advance_height; }, "Maximum vertical cursor advance for all glyphs.") .def_property_readonly( "underline_position", [](PyFT2Font *self) { return self->get_face()->underline_position; }, "Vertical position of the underline bar.") .def_property_readonly( "underline_thickness", [](PyFT2Font *self) { return self->get_face()->underline_thickness; }, "Thickness of the underline bar.") .def_property_readonly( "fname", &PyFT2Font_fname, "The original filename for this object.") .def_buffer([](PyFT2Font &self) -> py::buffer_info { return self.get_image().request(); }) .def("_render_glyph", [](PyFT2Font *self, FT_UInt idx, LoadFlags flags, FT_Render_Mode render_mode) { auto face = self->get_face(); FT_CHECK(FT_Load_Glyph, face, idx, static_cast(flags)); FT_CHECK(FT_Render_Glyph, face->glyph, render_mode); return PyPositionedBitmap{face->glyph}; }) ; m.attr("__freetype_version__") = version_string; m.attr("__freetype_build_type__") = FREETYPE_BUILD_TYPE; m.attr("__libraqm_version__") = raqm_version_string(); auto py_int = py::module_::import("builtins").attr("int"); m.attr("CharacterCodeType") = py_int; m.attr("GlyphIndexType") = py_int; m.def("__getattr__", ft2font__getattr__); }